1
0
Fork 0
mirror of https://github.com/anyproto/any-sync.git synced 2025-06-10 18:10:54 +09:00

Moving parts, refactoring for deletion

This commit is contained in:
mcrakhman 2023-08-16 21:16:15 +02:00
parent e02abc153c
commit 17a6f7cb55
No known key found for this signature in database
GPG key ID: DED12CFEF5B8396B
26 changed files with 412 additions and 526 deletions

View file

@ -1,61 +0,0 @@
package settings
import (
"context"
"time"
)
const deleteLoopInterval = time.Second * 20
type deleteLoop struct {
deleteCtx context.Context
deleteCancel context.CancelFunc
deleteChan chan struct{}
deleteFunc func()
loopDone chan struct{}
}
func newDeleteLoop(deleteFunc func()) *deleteLoop {
ctx, cancel := context.WithCancel(context.Background())
return &deleteLoop{
deleteCtx: ctx,
deleteCancel: cancel,
deleteChan: make(chan struct{}, 1),
deleteFunc: deleteFunc,
loopDone: make(chan struct{}),
}
}
func (dl *deleteLoop) Run() {
go dl.loop()
}
func (dl *deleteLoop) loop() {
defer close(dl.loopDone)
dl.deleteFunc()
ticker := time.NewTicker(deleteLoopInterval)
defer ticker.Stop()
for {
select {
case <-dl.deleteCtx.Done():
return
case <-dl.deleteChan:
dl.deleteFunc()
ticker.Reset(deleteLoopInterval)
case <-ticker.C:
dl.deleteFunc()
}
}
}
func (dl *deleteLoop) notify() {
select {
case dl.deleteChan <- struct{}{}:
default:
}
}
func (dl *deleteLoop) Close() {
dl.deleteCancel()
<-dl.loopDone
}

View file

@ -1,63 +0,0 @@
package settings
import (
"context"
"github.com/anyproto/any-sync/commonspace/deletionstate"
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
"github.com/anyproto/any-sync/commonspace/object/treemanager"
"github.com/anyproto/any-sync/commonspace/spacestorage"
"go.uber.org/zap"
)
type Deleter interface {
Delete()
}
type deleter struct {
st spacestorage.SpaceStorage
state deletionstate.ObjectDeletionState
getter treemanager.TreeManager
}
func newDeleter(st spacestorage.SpaceStorage, state deletionstate.ObjectDeletionState, getter treemanager.TreeManager) Deleter {
return &deleter{st, state, getter}
}
func (d *deleter) Delete() {
var (
allQueued = d.state.GetQueued()
spaceId = d.st.Id()
)
for _, id := range allQueued {
log := log.With(zap.String("treeId", id), zap.String("spaceId", spaceId))
shouldDelete, err := d.tryMarkDeleted(spaceId, id)
if !shouldDelete {
if err != nil {
log.Error("failed to mark object as deleted", zap.Error(err))
continue
}
} else {
err = d.getter.DeleteTree(context.Background(), spaceId, id)
if err != nil && err != spacestorage.ErrTreeStorageAlreadyDeleted {
log.Error("failed to delete object", zap.Error(err))
continue
}
}
err = d.state.Delete(id)
if err != nil {
log.Error("failed to mark object as deleted", zap.Error(err))
}
log.Debug("object successfully deleted", zap.Error(err))
}
}
func (d *deleter) tryMarkDeleted(spaceId, treeId string) (bool, error) {
_, err := d.st.TreeStorage(treeId)
if err == nil {
return true, nil
}
if err != treestorage.ErrUnknownTreeId {
return false, err
}
return false, d.getter.MarkTreeDeleted(context.Background(), spaceId, treeId)
}

View file

@ -1,76 +0,0 @@
package settings
import (
"fmt"
"github.com/anyproto/any-sync/commonspace/deletionstate/mock_deletionstate"
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
"github.com/anyproto/any-sync/commonspace/object/treemanager/mock_treemanager"
"github.com/anyproto/any-sync/commonspace/spacestorage/mock_spacestorage"
"go.uber.org/mock/gomock"
"testing"
)
func TestDeleter_Delete(t *testing.T) {
ctrl := gomock.NewController(t)
treeManager := mock_treemanager.NewMockTreeManager(ctrl)
st := mock_spacestorage.NewMockSpaceStorage(ctrl)
delState := mock_deletionstate.NewMockObjectDeletionState(ctrl)
deleter := newDeleter(st, delState, treeManager)
t.Run("deleter delete mark deleted success", func(t *testing.T) {
id := "id"
spaceId := "spaceId"
delState.EXPECT().GetQueued().Return([]string{id})
st.EXPECT().Id().Return(spaceId)
st.EXPECT().TreeStorage(id).Return(nil, treestorage.ErrUnknownTreeId)
treeManager.EXPECT().MarkTreeDeleted(gomock.Any(), spaceId, id).Return(nil)
delState.EXPECT().Delete(id).Return(nil)
deleter.Delete()
})
t.Run("deleter delete mark deleted other error", func(t *testing.T) {
id := "id"
spaceId := "spaceId"
delState.EXPECT().GetQueued().Return([]string{id})
st.EXPECT().Id().Return(spaceId)
st.EXPECT().TreeStorage(id).Return(nil, fmt.Errorf("unknown error"))
deleter.Delete()
})
t.Run("deleter delete mark deleted fail", func(t *testing.T) {
id := "id"
spaceId := "spaceId"
delState.EXPECT().GetQueued().Return([]string{id})
st.EXPECT().Id().Return(spaceId)
st.EXPECT().TreeStorage(id).Return(nil, treestorage.ErrUnknownTreeId)
treeManager.EXPECT().MarkTreeDeleted(gomock.Any(), spaceId, id).Return(fmt.Errorf("mark error"))
deleter.Delete()
})
//treeManager.EXPECT().DeleteTree(gomock.Any(), spaceId, id).Return(spacestorage.ErrTreeStorageAlreadyDeleted)
t.Run("deleter delete success", func(t *testing.T) {
id := "id"
spaceId := "spaceId"
delState.EXPECT().GetQueued().Return([]string{id})
st.EXPECT().Id().Return(spaceId)
st.EXPECT().TreeStorage(id).Return(nil, nil)
treeManager.EXPECT().DeleteTree(gomock.Any(), spaceId, id).Return(nil)
delState.EXPECT().Delete(id).Return(nil)
deleter.Delete()
})
t.Run("deleter delete error", func(t *testing.T) {
id := "id"
spaceId := "spaceId"
delState.EXPECT().GetQueued().Return([]string{id})
st.EXPECT().Id().Return(spaceId)
st.EXPECT().TreeStorage(id).Return(nil, nil)
treeManager.EXPECT().DeleteTree(gomock.Any(), spaceId, id).Return(fmt.Errorf("some error"))
deleter.Delete()
})
}

View file

@ -1,67 +0,0 @@
package settings
import (
"context"
"github.com/anyproto/any-sync/commonspace/deletionstate"
"github.com/anyproto/any-sync/commonspace/object/treemanager"
"github.com/anyproto/any-sync/commonspace/settings/settingsstate"
"go.uber.org/zap"
)
type SpaceIdsProvider interface {
AllIds() []string
}
type DeletionManager interface {
UpdateState(ctx context.Context, state *settingsstate.State) (err error)
}
func newDeletionManager(
spaceId string,
settingsId string,
isResponsible bool,
treeManager treemanager.TreeManager,
deletionState deletionstate.ObjectDeletionState,
provider SpaceIdsProvider,
onSpaceDelete func()) DeletionManager {
return &deletionManager{
treeManager: treeManager,
isResponsible: isResponsible,
spaceId: spaceId,
settingsId: settingsId,
deletionState: deletionState,
provider: provider,
onSpaceDelete: onSpaceDelete,
}
}
type deletionManager struct {
deletionState deletionstate.ObjectDeletionState
provider SpaceIdsProvider
treeManager treemanager.TreeManager
spaceId string
settingsId string
isResponsible bool
onSpaceDelete func()
}
func (d *deletionManager) UpdateState(ctx context.Context, state *settingsstate.State) error {
log := log.With(zap.String("spaceId", d.spaceId))
d.deletionState.Add(state.DeletedIds)
if state.DeleterId == "" {
return nil
}
// we should delete space
log.Debug("deleting space")
if d.isResponsible {
mapIds := map[string]struct{}{}
for _, id := range d.provider.AllIds() {
if id != d.settingsId {
mapIds[id] = struct{}{}
}
}
d.deletionState.Add(mapIds)
}
d.onSpaceDelete()
return nil
}

View file

@ -1,79 +0,0 @@
package settings
import (
"context"
"github.com/anyproto/any-sync/commonspace/deletionstate/mock_deletionstate"
"github.com/anyproto/any-sync/commonspace/object/treemanager/mock_treemanager"
"github.com/anyproto/any-sync/commonspace/settings/mock_settings"
"github.com/anyproto/any-sync/commonspace/settings/settingsstate"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
"testing"
)
func TestDeletionManager_UpdateState_NotResponsible(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.Background()
spaceId := "spaceId"
settingsId := "settingsId"
state := &settingsstate.State{
DeletedIds: map[string]struct{}{"id": {}},
DeleterId: "deleterId",
}
deleted := false
onDeleted := func() {
deleted = true
}
delState := mock_deletionstate.NewMockObjectDeletionState(ctrl)
treeManager := mock_treemanager.NewMockTreeManager(ctrl)
delState.EXPECT().Add(state.DeletedIds)
delManager := newDeletionManager(spaceId,
settingsId,
false,
treeManager,
delState,
nil,
onDeleted)
err := delManager.UpdateState(ctx, state)
require.NoError(t, err)
require.True(t, deleted)
}
func TestDeletionManager_UpdateState_Responsible(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.Background()
spaceId := "spaceId"
settingsId := "settingsId"
state := &settingsstate.State{
DeletedIds: map[string]struct{}{"id": struct{}{}},
DeleterId: "deleterId",
}
deleted := false
onDeleted := func() {
deleted = true
}
delState := mock_deletionstate.NewMockObjectDeletionState(ctrl)
treeManager := mock_treemanager.NewMockTreeManager(ctrl)
provider := mock_settings.NewMockSpaceIdsProvider(ctrl)
delState.EXPECT().Add(state.DeletedIds)
provider.EXPECT().AllIds().Return([]string{"id", "otherId", settingsId})
delState.EXPECT().Add(map[string]struct{}{"id": {}, "otherId": {}})
delManager := newDeletionManager(spaceId,
settingsId,
true,
treeManager,
delState,
provider,
onDeleted)
err := delManager.UpdateState(ctx, state)
require.NoError(t, err)
require.True(t, deleted)
}

View file

@ -1,122 +0,0 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/anyproto/any-sync/commonspace/settings (interfaces: DeletionManager,Deleter,SpaceIdsProvider)
// Package mock_settings is a generated GoMock package.
package mock_settings
import (
context "context"
reflect "reflect"
settingsstate "github.com/anyproto/any-sync/commonspace/settings/settingsstate"
gomock "go.uber.org/mock/gomock"
)
// MockDeletionManager is a mock of DeletionManager interface.
type MockDeletionManager struct {
ctrl *gomock.Controller
recorder *MockDeletionManagerMockRecorder
}
// MockDeletionManagerMockRecorder is the mock recorder for MockDeletionManager.
type MockDeletionManagerMockRecorder struct {
mock *MockDeletionManager
}
// NewMockDeletionManager creates a new mock instance.
func NewMockDeletionManager(ctrl *gomock.Controller) *MockDeletionManager {
mock := &MockDeletionManager{ctrl: ctrl}
mock.recorder = &MockDeletionManagerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockDeletionManager) EXPECT() *MockDeletionManagerMockRecorder {
return m.recorder
}
// UpdateState mocks base method.
func (m *MockDeletionManager) UpdateState(arg0 context.Context, arg1 *settingsstate.State) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateState", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// UpdateState indicates an expected call of UpdateState.
func (mr *MockDeletionManagerMockRecorder) UpdateState(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateState", reflect.TypeOf((*MockDeletionManager)(nil).UpdateState), arg0, arg1)
}
// MockDeleter is a mock of Deleter interface.
type MockDeleter struct {
ctrl *gomock.Controller
recorder *MockDeleterMockRecorder
}
// MockDeleterMockRecorder is the mock recorder for MockDeleter.
type MockDeleterMockRecorder struct {
mock *MockDeleter
}
// NewMockDeleter creates a new mock instance.
func NewMockDeleter(ctrl *gomock.Controller) *MockDeleter {
mock := &MockDeleter{ctrl: ctrl}
mock.recorder = &MockDeleterMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockDeleter) EXPECT() *MockDeleterMockRecorder {
return m.recorder
}
// Delete mocks base method.
func (m *MockDeleter) Delete() {
m.ctrl.T.Helper()
m.ctrl.Call(m, "Delete")
}
// Delete indicates an expected call of Delete.
func (mr *MockDeleterMockRecorder) Delete() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockDeleter)(nil).Delete))
}
// MockSpaceIdsProvider is a mock of SpaceIdsProvider interface.
type MockSpaceIdsProvider struct {
ctrl *gomock.Controller
recorder *MockSpaceIdsProviderMockRecorder
}
// MockSpaceIdsProviderMockRecorder is the mock recorder for MockSpaceIdsProvider.
type MockSpaceIdsProviderMockRecorder struct {
mock *MockSpaceIdsProvider
}
// NewMockSpaceIdsProvider creates a new mock instance.
func NewMockSpaceIdsProvider(ctrl *gomock.Controller) *MockSpaceIdsProvider {
mock := &MockSpaceIdsProvider{ctrl: ctrl}
mock.recorder = &MockSpaceIdsProviderMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockSpaceIdsProvider) EXPECT() *MockSpaceIdsProviderMockRecorder {
return m.recorder
}
// AllIds mocks base method.
func (m *MockSpaceIdsProvider) AllIds() []string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AllIds")
ret0, _ := ret[0].([]string)
return ret0
}
// AllIds indicates an expected call of AllIds.
func (mr *MockSpaceIdsProviderMockRecorder) AllIds() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AllIds", reflect.TypeOf((*MockSpaceIdsProvider)(nil).AllIds))
}

View file

@ -2,17 +2,14 @@ package settings
import (
"context"
"github.com/anyproto/any-sync/commonspace/deletionmanager"
"sync/atomic"
"github.com/anyproto/any-sync/accountservice"
"github.com/anyproto/any-sync/app"
"github.com/anyproto/any-sync/commonspace/deletionstate"
"github.com/anyproto/any-sync/commonspace/headsync"
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
"github.com/anyproto/any-sync/commonspace/object/tree/synctree"
"github.com/anyproto/any-sync/commonspace/object/tree/synctree/updatelistener"
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
"github.com/anyproto/any-sync/commonspace/object/treemanager"
"github.com/anyproto/any-sync/commonspace/objecttreebuilder"
"github.com/anyproto/any-sync/commonspace/spacestate"
"github.com/anyproto/any-sync/commonspace/spacestorage"
@ -24,8 +21,6 @@ const CName = "common.commonspace.settings"
type Settings interface {
DeleteTree(ctx context.Context, id string) (err error)
SpaceDeleteRawChange(ctx context.Context) (raw *treechangeproto.RawTreeChangeWithId, err error)
DeleteSpace(ctx context.Context, deleteChange *treechangeproto.RawTreeChangeWithId) (err error)
SettingsObject() SettingsObject
app.ComponentRunnable
}
@ -36,11 +31,8 @@ func New() Settings {
type settings struct {
account accountservice.Service
treeManager treemanager.TreeManager
storage spacestorage.SpaceStorage
configuration nodeconf.NodeConf
deletionState deletionstate.ObjectDeletionState
headsync headsync.HeadSync
treeBuilder objecttreebuilder.TreeBuilderComponent
spaceIsDeleted *atomic.Bool
@ -49,12 +41,8 @@ type settings struct {
func (s *settings) Init(a *app.App) (err error) {
s.account = a.MustComponent(accountservice.CName).(accountservice.Service)
s.treeManager = app.MustComponent[treemanager.TreeManager](a)
s.headsync = a.MustComponent(headsync.CName).(headsync.HeadSync)
s.configuration = a.MustComponent(nodeconf.CName).(nodeconf.NodeConf)
s.deletionState = a.MustComponent(deletionstate.CName).(deletionstate.ObjectDeletionState)
s.treeBuilder = a.MustComponent(objecttreebuilder.CName).(objecttreebuilder.TreeBuilderComponent)
sharedState := a.MustComponent(spacestate.CName).(*spacestate.SpaceState)
s.storage = a.MustComponent(spacestorage.CName).(spacestorage.SpaceStorage)
s.spaceIsDeleted = sharedState.SpaceIsDeleted
@ -74,12 +62,9 @@ func (s *settings) Init(a *app.App) (err error) {
return
},
Account: s.account,
TreeManager: s.treeManager,
Store: s.storage,
Configuration: s.configuration,
DeletionState: s.deletionState,
Provider: s.headsync,
OnSpaceDelete: s.onSpaceDelete,
DelManager: a.MustComponent(deletionmanager.CName).(deletionmanager.DeletionManager),
}
s.settingsObject = NewSettingsObject(deps, sharedState.SpaceId)
return nil
@ -101,22 +86,6 @@ func (s *settings) DeleteTree(ctx context.Context, id string) (err error) {
return s.settingsObject.DeleteObject(id)
}
func (s *settings) SpaceDeleteRawChange(ctx context.Context) (raw *treechangeproto.RawTreeChangeWithId, err error) {
return s.settingsObject.SpaceDeleteRawChange()
}
func (s *settings) DeleteSpace(ctx context.Context, deleteChange *treechangeproto.RawTreeChangeWithId) (err error) {
return s.settingsObject.DeleteSpace(ctx, deleteChange)
}
func (s *settings) onSpaceDelete() {
err := s.storage.SetSpaceDeleted()
if err != nil {
log.Warn("failed to set space deleted")
}
s.spaceIsDeleted.Swap(true)
}
func (s *settings) SettingsObject() SettingsObject {
return s.settingsObject
}

View file

@ -1,11 +1,10 @@
//go:generate mockgen -destination mock_settings/mock_settings.go github.com/anyproto/any-sync/commonspace/settings DeletionManager,Deleter,SpaceIdsProvider
package settings
import (
"context"
"errors"
"fmt"
"github.com/anyproto/any-sync/commonspace/deletionstate"
"github.com/anyproto/any-sync/commonspace/deletionmanager"
"github.com/anyproto/any-sync/util/crypto"
"github.com/anyproto/any-sync/accountservice"
@ -21,7 +20,6 @@ import (
"github.com/anyproto/any-sync/nodeconf"
"github.com/gogo/protobuf/proto"
"go.uber.org/zap"
"golang.org/x/exp/slices"
)
var log = logger.NewNamed("common.commonspace.settings")
@ -30,8 +28,6 @@ type SettingsObject interface {
synctree.SyncTree
Init(ctx context.Context) (err error)
DeleteObject(id string) (err error)
DeleteSpace(ctx context.Context, raw *treechangeproto.RawTreeChangeWithId) (err error)
SpaceDeleteRawChange() (raw *treechangeproto.RawTreeChangeWithId, err error)
}
var (
@ -60,56 +56,30 @@ type Deps struct {
TreeManager treemanager.TreeManager
Store spacestorage.SpaceStorage
Configuration nodeconf.NodeConf
DeletionState deletionstate.ObjectDeletionState
Provider SpaceIdsProvider
OnSpaceDelete func()
DelManager deletionmanager.DeletionManager
// testing dependencies
builder settingsstate.StateBuilder
del Deleter
delManager DeletionManager
changeFactory settingsstate.ChangeFactory
}
type settingsObject struct {
synctree.SyncTree
account accountservice.Service
spaceId string
treeManager treemanager.TreeManager
store spacestorage.SpaceStorage
builder settingsstate.StateBuilder
buildFunc BuildTreeFunc
loop *deleteLoop
account accountservice.Service
spaceId string
store spacestorage.SpaceStorage
builder settingsstate.StateBuilder
buildFunc BuildTreeFunc
state *settingsstate.State
deletionState deletionstate.ObjectDeletionState
deletionManager DeletionManager
deletionManager deletionmanager.DeletionManager
changeFactory settingsstate.ChangeFactory
}
func NewSettingsObject(deps Deps, spaceId string) (obj SettingsObject) {
var (
deleter Deleter
deletionManager DeletionManager
builder settingsstate.StateBuilder
changeFactory settingsstate.ChangeFactory
builder settingsstate.StateBuilder
changeFactory settingsstate.ChangeFactory
)
if deps.del == nil {
deleter = newDeleter(deps.Store, deps.DeletionState, deps.TreeManager)
} else {
deleter = deps.del
}
if deps.delManager == nil {
deletionManager = newDeletionManager(
spaceId,
deps.Store.SpaceSettingsId(),
deps.Configuration.IsResponsible(spaceId),
deps.TreeManager,
deps.DeletionState,
deps.Provider,
deps.OnSpaceDelete)
} else {
deletionManager = deps.delManager
}
if deps.builder == nil {
builder = settingsstate.NewStateBuilder()
} else {
@ -121,23 +91,13 @@ func NewSettingsObject(deps Deps, spaceId string) (obj SettingsObject) {
changeFactory = deps.changeFactory
}
loop := newDeleteLoop(func() {
deleter.Delete()
})
deps.DeletionState.AddObserver(func(ids []string) {
loop.notify()
})
s := &settingsObject{
loop: loop,
spaceId: spaceId,
account: deps.Account,
deletionState: deps.DeletionState,
treeManager: deps.TreeManager,
store: deps.Store,
buildFunc: deps.BuildFunc,
builder: builder,
deletionManager: deletionManager,
deletionManager: deps.DelManager,
changeFactory: changeFactory,
}
obj = s
@ -151,7 +111,6 @@ func (s *settingsObject) updateIds(tr objecttree.ObjectTree) {
log.Error("failed to build state", zap.Error(err))
return
}
log.Debug("updating object state", zap.String("deleted by", s.state.DeleterId))
if err = s.deletionManager.UpdateState(context.Background(), s.state); err != nil {
log.Error("failed to update state", zap.Error(err))
}
@ -180,7 +139,6 @@ func (s *settingsObject) Init(ctx context.Context) (err error) {
if err = s.checkHistoryState(ctx); err != nil {
return
}
s.loop.Run()
return
}
@ -207,48 +165,9 @@ func (s *settingsObject) checkHistoryState(ctx context.Context) (err error) {
}
func (s *settingsObject) Close() error {
s.loop.Close()
return s.SyncTree.Close()
}
func (s *settingsObject) DeleteSpace(ctx context.Context, raw *treechangeproto.RawTreeChangeWithId) (err error) {
s.Lock()
defer s.Unlock()
defer func() {
log.Debug("finished adding delete change", zap.Error(err))
}()
err = s.verifyDeleteSpace(raw)
if err != nil {
return
}
res, err := s.AddRawChanges(ctx, objecttree.RawChangesPayload{
NewHeads: []string{raw.Id},
RawChanges: []*treechangeproto.RawTreeChangeWithId{raw},
})
if err != nil {
return
}
if !slices.Contains(res.Heads, raw.Id) {
err = ErrCantDeleteSpace
return
}
return
}
func (s *settingsObject) SpaceDeleteRawChange() (raw *treechangeproto.RawTreeChangeWithId, err error) {
accountData := s.account.Account()
data, err := s.changeFactory.CreateSpaceDeleteChange(accountData.PeerId, s.state, false)
if err != nil {
return
}
return s.PrepareChange(objecttree.SignableChangeContent{
Data: data,
Key: accountData.SignKey,
IsSnapshot: false,
IsEncrypted: false,
})
}
func (s *settingsObject) DeleteObject(id string) (err error) {
s.Lock()
defer s.Unlock()

View file

@ -3,16 +3,14 @@ package settings
import (
"context"
"github.com/anyproto/any-sync/accountservice/mock_accountservice"
"github.com/anyproto/any-sync/commonspace/deletionstate/mock_deletionstate"
"github.com/anyproto/any-sync/commonspace/deletionmanager/mock_deletionmanager"
"github.com/anyproto/any-sync/commonspace/object/accountdata"
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree/mock_objecttree"
"github.com/anyproto/any-sync/commonspace/object/tree/synctree"
"github.com/anyproto/any-sync/commonspace/object/tree/synctree/mock_synctree"
"github.com/anyproto/any-sync/commonspace/object/tree/synctree/updatelistener"
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
"github.com/anyproto/any-sync/commonspace/object/treemanager/mock_treemanager"
"github.com/anyproto/any-sync/commonspace/settings/mock_settings"
"github.com/anyproto/any-sync/commonspace/settings/settingsstate"
"github.com/anyproto/any-sync/commonspace/settings/settingsstate/mock_settingsstate"
"github.com/anyproto/any-sync/commonspace/spacestorage/mock_spacestorage"
@ -50,12 +48,10 @@ type settingsFixture struct {
treeManager *mock_treemanager.MockTreeManager
spaceStorage *mock_spacestorage.MockSpaceStorage
stateBuilder *mock_settingsstate.MockStateBuilder
deletionManager *mock_settings.MockDeletionManager
deletionManager *mock_deletionmanager.MockDeletionManager
changeFactory *mock_settingsstate.MockChangeFactory
deleter *mock_settings.MockDeleter
syncTree *mock_synctree.MockSyncTree
historyTree *mock_objecttree.MockObjectTree
delState *mock_deletionstate.MockObjectDeletionState
account *mock_accountservice.MockService
}
@ -67,15 +63,11 @@ func newSettingsFixture(t *testing.T) *settingsFixture {
acc := mock_accountservice.NewMockService(ctrl)
treeManager := mock_treemanager.NewMockTreeManager(ctrl)
st := mock_spacestorage.NewMockSpaceStorage(ctrl)
delState := mock_deletionstate.NewMockObjectDeletionState(ctrl)
delManager := mock_settings.NewMockDeletionManager(ctrl)
delManager := mock_deletionmanager.NewMockDeletionManager(ctrl)
stateBuilder := mock_settingsstate.NewMockStateBuilder(ctrl)
changeFactory := mock_settingsstate.NewMockChangeFactory(ctrl)
syncTree := mock_synctree.NewMockSyncTree(ctrl)
historyTree := mock_objecttree.NewMockObjectTree(ctrl)
del := mock_settings.NewMockDeleter(ctrl)
delState.EXPECT().AddObserver(gomock.Any())
buildFunc := BuildTreeFunc(func(ctx context.Context, id string, listener updatelistener.UpdateListener) (synctree.SyncTree, error) {
require.Equal(t, objectId, id)
@ -90,11 +82,9 @@ func newSettingsFixture(t *testing.T) *settingsFixture {
Account: acc,
TreeManager: treeManager,
Store: st,
DeletionState: delState,
delManager: delManager,
DelManager: delManager,
changeFactory: changeFactory,
builder: stateBuilder,
del: del,
}
doc := NewSettingsObject(deps, spaceId).(*settingsObject)
return &settingsFixture{
@ -107,17 +97,14 @@ func newSettingsFixture(t *testing.T) *settingsFixture {
stateBuilder: stateBuilder,
changeFactory: changeFactory,
deletionManager: delManager,
deleter: del,
syncTree: syncTree,
account: acc,
delState: delState,
historyTree: historyTree,
}
}
func (fx *settingsFixture) init(t *testing.T) {
fx.spaceStorage.EXPECT().SpaceSettingsId().Return(fx.docId)
fx.deleter.EXPECT().Delete()
fx.stateBuilder.EXPECT().Build(fx.historyTree, nil).Return(&settingsstate.State{}, nil)
fx.doc.state = &settingsstate.State{}
@ -235,64 +222,3 @@ func TestSettingsObject_Update(t *testing.T) {
fx.doc.Update(fx.doc)
}
func TestSettingsObject_DeleteSpace(t *testing.T) {
fx := newSettingsFixture(t)
defer fx.stop(t)
fx.init(t)
time.Sleep(100 * time.Millisecond)
deleterId := "delId"
rawCh := &treechangeproto.RawTreeChangeWithId{
RawChange: []byte{1},
Id: "id",
}
changeFactory := settingsstate.NewChangeFactory()
delChange, _ := changeFactory.CreateSpaceDeleteChange(deleterId, &settingsstate.State{}, false)
fx.syncTree.EXPECT().UnpackChange(rawCh).Return(delChange, nil)
fx.syncTree.EXPECT().AddRawChanges(gomock.Any(), objecttree.RawChangesPayload{
NewHeads: []string{rawCh.Id},
RawChanges: []*treechangeproto.RawTreeChangeWithId{rawCh},
}).Return(objecttree.AddResult{
Heads: []string{rawCh.Id},
}, nil)
err := fx.doc.DeleteSpace(context.Background(), rawCh)
require.NoError(t, err)
}
func TestSettingsObject_DeleteSpaceIncorrectChange(t *testing.T) {
fx := newSettingsFixture(t)
defer fx.stop(t)
fx.init(t)
time.Sleep(100 * time.Millisecond)
t.Run("incorrect change type", func(t *testing.T) {
rawCh := &treechangeproto.RawTreeChangeWithId{
RawChange: []byte{1},
Id: "id",
}
changeFactory := settingsstate.NewChangeFactory()
delChange, _ := changeFactory.CreateObjectDeleteChange("otherId", &settingsstate.State{}, false)
fx.syncTree.EXPECT().UnpackChange(rawCh).Return(delChange, nil)
err := fx.doc.DeleteSpace(context.Background(), rawCh)
require.NotNil(t, err)
})
t.Run("empty peer", func(t *testing.T) {
rawCh := &treechangeproto.RawTreeChangeWithId{
RawChange: []byte{1},
Id: "id",
}
changeFactory := settingsstate.NewChangeFactory()
delChange, _ := changeFactory.CreateSpaceDeleteChange("", &settingsstate.State{}, false)
fx.syncTree.EXPECT().UnpackChange(rawCh).Return(delChange, nil)
err := fx.doc.DeleteSpace(context.Background(), rawCh)
require.NotNil(t, err)
})
}

View file

@ -6,7 +6,6 @@ import (
type ChangeFactory interface {
CreateObjectDeleteChange(id string, state *State, isSnapshot bool) (res []byte, err error)
CreateSpaceDeleteChange(peerId string, state *State, isSnapshot bool) (res []byte, err error)
}
func NewChangeFactory() ChangeFactory {
@ -26,44 +25,23 @@ func (c *changeFactory) CreateObjectDeleteChange(id string, state *State, isSnap
},
}
if isSnapshot {
change.Snapshot = c.makeSnapshot(state, id, "")
change.Snapshot = c.makeSnapshot(state, id)
}
res, err = change.Marshal()
return
}
func (c *changeFactory) CreateSpaceDeleteChange(peerId string, state *State, isSnapshot bool) (res []byte, err error) {
content := &spacesyncproto.SpaceSettingsContent_SpaceDelete{
SpaceDelete: &spacesyncproto.SpaceDelete{DeleterPeerId: peerId},
}
change := &spacesyncproto.SettingsData{
Content: []*spacesyncproto.SpaceSettingsContent{
{Value: content},
},
}
if isSnapshot {
change.Snapshot = c.makeSnapshot(state, "", peerId)
}
res, err = change.Marshal()
return
}
func (c *changeFactory) makeSnapshot(state *State, objectId, deleterPeer string) *spacesyncproto.SpaceSettingsSnapshot {
func (c *changeFactory) makeSnapshot(state *State, objectId string) *spacesyncproto.SpaceSettingsSnapshot {
var (
deletedIds = make([]string, 0, len(state.DeletedIds)+1)
deleterId = state.DeleterId
)
if objectId != "" {
deletedIds = append(deletedIds, objectId)
}
if deleterPeer != "" {
deleterId = deleterPeer
}
for id := range state.DeletedIds {
deletedIds = append(deletedIds, id)
}
return &spacesyncproto.SpaceSettingsSnapshot{
DeletedIds: deletedIds,
DeleterPeerId: deleterId,
DeletedIds: deletedIds,
}
}

View file

@ -12,7 +12,6 @@ func TestChangeFactory_CreateObjectDeleteChange(t *testing.T) {
factory := NewChangeFactory()
state := &State{
DeletedIds: map[string]struct{}{"1": {}, "2": {}},
DeleterId: "del",
}
marshalled, err := factory.CreateObjectDeleteChange("3", state, false)
require.NoError(t, err)
@ -29,34 +28,7 @@ func TestChangeFactory_CreateObjectDeleteChange(t *testing.T) {
require.NoError(t, err)
slices.Sort(data.Snapshot.DeletedIds)
require.Equal(t, &spacesyncproto.SpaceSettingsSnapshot{
DeletedIds: []string{"1", "2", "3"},
DeleterPeerId: "del",
DeletedIds: []string{"1", "2", "3"},
}, data.Snapshot)
require.Equal(t, "3", data.Content[0].Value.(*spacesyncproto.SpaceSettingsContent_ObjectDelete).ObjectDelete.Id)
}
func TestChangeFactory_CreateSpaceDeleteChange(t *testing.T) {
factory := NewChangeFactory()
state := &State{
DeletedIds: map[string]struct{}{"1": {}, "2": {}},
}
marshalled, err := factory.CreateSpaceDeleteChange("del", state, false)
require.NoError(t, err)
data := &spacesyncproto.SettingsData{}
err = proto.Unmarshal(marshalled, data)
require.NoError(t, err)
require.Nil(t, data.Snapshot)
require.Equal(t, "del", data.Content[0].Value.(*spacesyncproto.SpaceSettingsContent_SpaceDelete).SpaceDelete.DeleterPeerId)
marshalled, err = factory.CreateSpaceDeleteChange("del", state, true)
require.NoError(t, err)
data = &spacesyncproto.SettingsData{}
err = proto.Unmarshal(marshalled, data)
require.NoError(t, err)
slices.Sort(data.Snapshot.DeletedIds)
require.Equal(t, &spacesyncproto.SpaceSettingsSnapshot{
DeletedIds: []string{"1", "2"},
DeleterPeerId: "del",
}, data.Snapshot)
require.Equal(t, "del", data.Content[0].Value.(*spacesyncproto.SpaceSettingsContent_SpaceDelete).SpaceDelete.DeleterPeerId)
}

View file

@ -87,18 +87,3 @@ func (mr *MockChangeFactoryMockRecorder) CreateObjectDeleteChange(arg0, arg1, ar
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateObjectDeleteChange", reflect.TypeOf((*MockChangeFactory)(nil).CreateObjectDeleteChange), arg0, arg1, arg2)
}
// CreateSpaceDeleteChange mocks base method.
func (m *MockChangeFactory) CreateSpaceDeleteChange(arg0 string, arg1 *settingsstate.State, arg2 bool) ([]byte, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateSpaceDeleteChange", arg0, arg1, arg2)
ret0, _ := ret[0].([]byte)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CreateSpaceDeleteChange indicates an expected call of CreateSpaceDeleteChange.
func (mr *MockChangeFactoryMockRecorder) CreateSpaceDeleteChange(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSpaceDeleteChange", reflect.TypeOf((*MockChangeFactory)(nil).CreateSpaceDeleteChange), arg0, arg1, arg2)
}

View file

@ -5,7 +5,6 @@ import "github.com/anyproto/any-sync/commonspace/spacesyncproto"
type State struct {
DeletedIds map[string]struct{}
DeleterId string
LastIteratedId string
}
@ -18,7 +17,6 @@ func NewStateFromSnapshot(snapshot *spacesyncproto.SpaceSettingsSnapshot, lastIt
for _, id := range snapshot.DeletedIds {
st.DeletedIds[id] = struct{}{}
}
st.DeleterId = snapshot.DeleterPeerId
st.LastIteratedId = lastIteratedId
return st
}

View file

@ -64,8 +64,6 @@ func (s *stateBuilder) processChange(change *objecttree.Change, rootId string, s
switch {
case cnt.GetObjectDelete() != nil:
state.DeletedIds[cnt.GetObjectDelete().GetId()] = struct{}{}
case cnt.GetSpaceDelete() != nil:
state.DeleterId = cnt.GetSpaceDelete().GetDeleterPeerId()
}
}
return state

View file

@ -1,6 +1,8 @@
package settingsstate
import (
"fmt"
"github.com/anyproto/any-sync/commonspace/object/accountdata"
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree/mock_objecttree"
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
@ -22,6 +24,26 @@ func TestStateBuilder_ProcessChange(t *testing.T) {
require.Equal(t, map[string]struct{}{deletedId: struct{}{}}, newSt.DeletedIds)
})
t.Run("correct space deleted", func(t *testing.T) {
keys, _ := accountdata.NewRandom()
ch := &objecttree.Change{
Identity: keys.SignKey.GetPublic(),
}
ch.PreviousIds = []string{"someId"}
ch.Model = &spacesyncproto.SettingsData{
Content: []*spacesyncproto.SpaceSettingsContent{
{Value: &spacesyncproto.SpaceSettingsContent_SpaceDelete{
SpaceDelete: &spacesyncproto.SpaceDelete{DeleterPeerId: "peerId"},
}},
},
}
ch.Id = "someId"
ctrl := gomock.NewController(t)
defer ctrl.Finish()
newSt := sb.processChange(ch, rootId, NewState())
fmt.Println(newSt)
})
t.Run("changeId is equal to startId, LastIteratedId is equal to startId", func(t *testing.T) {
ch := &objecttree.Change{}
ch.Model = &spacesyncproto.SettingsData{
@ -45,14 +67,12 @@ func TestStateBuilder_ProcessChange(t *testing.T) {
ch.PreviousIds = []string{"someId"}
ch.Model = &spacesyncproto.SettingsData{
Snapshot: &spacesyncproto.SpaceSettingsSnapshot{
DeletedIds: []string{"id1", "id2"},
DeleterPeerId: "peerId",
DeletedIds: []string{"id1", "id2"},
},
}
ch.Id = "rootId"
newSt := sb.processChange(ch, rootId, NewState())
require.Equal(t, map[string]struct{}{"id1": struct{}{}, "id2": struct{}{}}, newSt.DeletedIds)
require.Equal(t, "peerId", newSt.DeleterId)
})
t.Run("changeId is not equal to lastIteratedId or rootId", func(t *testing.T) {