1
0
Fork 0
mirror of https://github.com/anyproto/any-sync.git synced 2025-06-08 05:57:03 +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,4 +1,4 @@
package settings
package deletionmanager
import (
"context"

View file

@ -1,7 +1,8 @@
package settings
package deletionmanager
import (
"context"
"github.com/anyproto/any-sync/app/logger"
"github.com/anyproto/any-sync/commonspace/deletionstate"
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
"github.com/anyproto/any-sync/commonspace/object/treemanager"
@ -17,10 +18,11 @@ type deleter struct {
st spacestorage.SpaceStorage
state deletionstate.ObjectDeletionState
getter treemanager.TreeManager
log logger.CtxLogger
}
func newDeleter(st spacestorage.SpaceStorage, state deletionstate.ObjectDeletionState, getter treemanager.TreeManager) Deleter {
return &deleter{st, state, getter}
func newDeleter(st spacestorage.SpaceStorage, state deletionstate.ObjectDeletionState, getter treemanager.TreeManager, log logger.CtxLogger) Deleter {
return &deleter{st, state, getter, log}
}
func (d *deleter) Delete() {
@ -29,7 +31,7 @@ func (d *deleter) Delete() {
spaceId = d.st.Id()
)
for _, id := range allQueued {
log := log.With(zap.String("treeId", id), zap.String("spaceId", spaceId))
log := d.log.With(zap.String("treeId", id))
shouldDelete, err := d.tryMarkDeleted(spaceId, id)
if !shouldDelete {
if err != nil {

View file

@ -1,4 +1,4 @@
package settings
package deletionmanager
import (
"fmt"
@ -16,7 +16,7 @@ func TestDeleter_Delete(t *testing.T) {
st := mock_spacestorage.NewMockSpaceStorage(ctrl)
delState := mock_deletionstate.NewMockObjectDeletionState(ctrl)
deleter := newDeleter(st, delState, treeManager)
deleter := newDeleter(st, delState, treeManager, log)
t.Run("deleter delete mark deleted success", func(t *testing.T) {
id := "id"
@ -50,7 +50,7 @@ func TestDeleter_Delete(t *testing.T) {
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"

View file

@ -0,0 +1,74 @@
//go:generate mockgen -destination mock_deletionmanager/mock_deletionmanager.go github.com/anyproto/any-sync/commonspace/deletionmanager DeletionManager,Deleter,SpaceIdsProvider
package deletionmanager
import (
"context"
"github.com/anyproto/any-sync/app"
"github.com/anyproto/any-sync/app/logger"
"github.com/anyproto/any-sync/commonspace/deletionstate"
"github.com/anyproto/any-sync/commonspace/object/treemanager"
"github.com/anyproto/any-sync/commonspace/settings/settingsstate"
"github.com/anyproto/any-sync/commonspace/spacestate"
"github.com/anyproto/any-sync/commonspace/spacestorage"
"go.uber.org/zap"
)
type SpaceIdsProvider interface {
app.Component
AllIds() []string
}
type DeletionManager interface {
app.ComponentRunnable
UpdateState(ctx context.Context, state *settingsstate.State) (err error)
}
func New() DeletionManager {
return &deletionManager{}
}
const CName = "common.commonspace.deletionmanager"
var log = logger.NewNamed(CName)
type deletionManager struct {
deletionState deletionstate.ObjectDeletionState
deleter Deleter
loop *deleteLoop
log logger.CtxLogger
spaceId string
}
func (d *deletionManager) Init(a *app.App) (err error) {
state := a.MustComponent(spacestate.CName).(*spacestate.SpaceState)
storage := a.MustComponent(spacestorage.CName).(spacestorage.SpaceStorage)
d.log = log.With(zap.String("spaceId", state.SpaceId), zap.String("settingsId", storage.SpaceSettingsId()))
d.deletionState = a.MustComponent(deletionstate.CName).(deletionstate.ObjectDeletionState)
treeManager := a.MustComponent(treemanager.CName).(treemanager.TreeManager)
d.deleter = newDeleter(storage, d.deletionState, treeManager, d.log)
d.loop = newDeleteLoop(d.deleter.Delete)
d.deletionState.AddObserver(func(ids []string) {
d.loop.notify()
})
return
}
func (d *deletionManager) Name() (name string) {
return CName
}
func (d *deletionManager) Run(ctx context.Context) (err error) {
d.loop.Run()
return
}
func (d *deletionManager) Close(ctx context.Context) (err error) {
d.loop.Close()
return
}
func (d *deletionManager) UpdateState(ctx context.Context, state *settingsstate.State) error {
d.deletionState.Add(state.DeletedIds)
return nil
}

View file

@ -0,0 +1,86 @@
package deletionmanager
import (
"context"
"github.com/anyproto/any-sync/app"
"github.com/anyproto/any-sync/commonspace/deletionmanager/mock_deletionmanager"
"github.com/anyproto/any-sync/commonspace/deletionstate"
"github.com/anyproto/any-sync/commonspace/deletionstate/mock_deletionstate"
"github.com/anyproto/any-sync/commonspace/object/treemanager"
"github.com/anyproto/any-sync/commonspace/object/treemanager/mock_treemanager"
"github.com/anyproto/any-sync/commonspace/settings/settingsstate"
"github.com/anyproto/any-sync/commonspace/spacestate"
"github.com/anyproto/any-sync/commonspace/spacestorage"
"github.com/anyproto/any-sync/commonspace/spacestorage/mock_spacestorage"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
"testing"
)
type deletionManagerFixture struct {
delState *mock_deletionstate.MockObjectDeletionState
treeManager *mock_treemanager.MockTreeManager
storage *mock_spacestorage.MockSpaceStorage
deleter *mock_deletionmanager.MockDeleter
spaceState *spacestate.SpaceState
settingsId string
app *app.App
delManager *deletionManager
ctrl *gomock.Controller
}
func newDelManagerFixture(t *testing.T) *deletionManagerFixture {
ctrl := gomock.NewController(t)
spaceState := &spacestate.SpaceState{
SpaceId: "spaceId",
}
fx := &deletionManagerFixture{
delState: mock_deletionstate.NewMockObjectDeletionState(ctrl),
treeManager: mock_treemanager.NewMockTreeManager(ctrl),
storage: mock_spacestorage.NewMockSpaceStorage(ctrl),
deleter: mock_deletionmanager.NewMockDeleter(ctrl),
spaceState: spaceState,
settingsId: "settingsId",
delManager: New().(*deletionManager),
ctrl: ctrl,
app: &app.App{},
}
return fx
}
func (fx *deletionManagerFixture) init(t *testing.T) {
fx.delState.EXPECT().Name().AnyTimes().Return(deletionstate.CName)
fx.treeManager.EXPECT().Name().AnyTimes().Return(treemanager.CName)
fx.storage.EXPECT().Name().AnyTimes().Return(spacestorage.CName)
fx.storage.EXPECT().SpaceSettingsId().AnyTimes().Return(fx.settingsId)
fx.delState.EXPECT().AddObserver(gomock.Any())
fx.app.Register(fx.spaceState).
Register(fx.storage).
Register(fx.delState).
Register(fx.treeManager).
Register(fx.delManager)
err := fx.delManager.Init(fx.app)
require.NoError(t, err)
fx.delManager.deleter = fx.deleter
}
func (fx *deletionManagerFixture) stop() {
fx.ctrl.Finish()
}
func TestDeletionManager_UpdateState(t *testing.T) {
fx := newDelManagerFixture(t)
fx.init(t)
defer fx.stop()
ctx := context.Background()
state := &settingsstate.State{
DeletedIds: map[string]struct{}{"id": {}},
}
fx.delState.EXPECT().Add(state.DeletedIds)
err := fx.delManager.UpdateState(ctx, state)
require.NoError(t, err)
}

View file

@ -1,13 +1,14 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/anyproto/any-sync/commonspace/settings (interfaces: DeletionManager,Deleter,SpaceIdsProvider)
// Source: github.com/anyproto/any-sync/commonspace/deletionmanager (interfaces: DeletionManager,Deleter,SpaceIdsProvider)
// Package mock_settings is a generated GoMock package.
package mock_settings
// Package mock_deletionmanager is a generated GoMock package.
package mock_deletionmanager
import (
context "context"
reflect "reflect"
app "github.com/anyproto/any-sync/app"
settingsstate "github.com/anyproto/any-sync/commonspace/settings/settingsstate"
gomock "go.uber.org/mock/gomock"
)
@ -35,6 +36,62 @@ func (m *MockDeletionManager) EXPECT() *MockDeletionManagerMockRecorder {
return m.recorder
}
// Close mocks base method.
func (m *MockDeletionManager) Close(arg0 context.Context) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Close", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// Close indicates an expected call of Close.
func (mr *MockDeletionManagerMockRecorder) Close(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockDeletionManager)(nil).Close), arg0)
}
// Init mocks base method.
func (m *MockDeletionManager) Init(arg0 *app.App) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Init", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// Init indicates an expected call of Init.
func (mr *MockDeletionManagerMockRecorder) Init(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockDeletionManager)(nil).Init), arg0)
}
// Name mocks base method.
func (m *MockDeletionManager) Name() string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Name")
ret0, _ := ret[0].(string)
return ret0
}
// Name indicates an expected call of Name.
func (mr *MockDeletionManagerMockRecorder) Name() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockDeletionManager)(nil).Name))
}
// Run mocks base method.
func (m *MockDeletionManager) Run(arg0 context.Context) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Run", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// Run indicates an expected call of Run.
func (mr *MockDeletionManagerMockRecorder) Run(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Run", reflect.TypeOf((*MockDeletionManager)(nil).Run), arg0)
}
// UpdateState mocks base method.
func (m *MockDeletionManager) UpdateState(arg0 context.Context, arg1 *settingsstate.State) error {
m.ctrl.T.Helper()
@ -120,3 +177,31 @@ func (mr *MockSpaceIdsProviderMockRecorder) AllIds() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AllIds", reflect.TypeOf((*MockSpaceIdsProvider)(nil).AllIds))
}
// Init mocks base method.
func (m *MockSpaceIdsProvider) Init(arg0 *app.App) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Init", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// Init indicates an expected call of Init.
func (mr *MockSpaceIdsProviderMockRecorder) Init(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockSpaceIdsProvider)(nil).Init), arg0)
}
// Name mocks base method.
func (m *MockSpaceIdsProvider) Name() string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Name")
ret0, _ := ret[0].(string)
return ret0
}
// Name indicates an expected call of Name.
func (mr *MockSpaceIdsProviderMockRecorder) Name() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockSpaceIdsProvider)(nil).Name))
}

View file

@ -407,12 +407,13 @@ func (a *aclRecordBuilder) Unmarshall(rawRecord *consensusproto.RawRecord) (rec
return
}
rec = &AclRecord{
PrevId: aclRecord.PrevId,
Timestamp: aclRecord.Timestamp,
Data: aclRecord.Data,
Signature: rawRecord.Signature,
Identity: pubKey,
Model: aclData,
PrevId: aclRecord.PrevId,
Timestamp: aclRecord.Timestamp,
AcceptorTimestamp: rawRecord.AcceptorTimestamp,
Data: aclRecord.Data,
Signature: rawRecord.Signature,
Identity: pubKey,
Model: aclData,
}
res, err := pubKey.Verify(rawRecord.Payload, rawRecord.Signature)
if err != nil {

View file

@ -264,45 +264,45 @@ func (st *AclState) saveKeysFromRoot(id string, root *aclrecordproto.AclRoot) (e
func (st *AclState) applyChangeData(record *AclRecord) (err error) {
model := record.Model.(*aclrecordproto.AclData)
for _, ch := range model.GetAclContent() {
if err = st.applyChangeContent(ch, record.Id, record.Identity); err != nil {
log.Info("error while applying changes: %v; ignore", zap.Error(err))
if err = st.applyChangeContent(ch, record); err != nil {
log.Info("error while applying changes; ignore", zap.Error(err))
return err
}
}
return nil
}
func (st *AclState) applyChangeContent(ch *aclrecordproto.AclContentValue, recordId string, authorIdentity crypto.PubKey) error {
func (st *AclState) applyChangeContent(ch *aclrecordproto.AclContentValue, record *AclRecord) error {
switch {
case ch.GetPermissionChange() != nil:
return st.applyPermissionChange(ch.GetPermissionChange(), recordId, authorIdentity)
return st.applyPermissionChange(ch.GetPermissionChange(), record)
case ch.GetInvite() != nil:
return st.applyInvite(ch.GetInvite(), recordId, authorIdentity)
return st.applyInvite(ch.GetInvite(), record)
case ch.GetInviteRevoke() != nil:
return st.applyInviteRevoke(ch.GetInviteRevoke(), recordId, authorIdentity)
return st.applyInviteRevoke(ch.GetInviteRevoke(), record)
case ch.GetRequestJoin() != nil:
return st.applyRequestJoin(ch.GetRequestJoin(), recordId, authorIdentity)
return st.applyRequestJoin(ch.GetRequestJoin(), record)
case ch.GetRequestAccept() != nil:
return st.applyRequestAccept(ch.GetRequestAccept(), recordId, authorIdentity)
return st.applyRequestAccept(ch.GetRequestAccept(), record)
case ch.GetRequestDecline() != nil:
return st.applyRequestDecline(ch.GetRequestDecline(), recordId, authorIdentity)
return st.applyRequestDecline(ch.GetRequestDecline(), record)
case ch.GetAccountRemove() != nil:
return st.applyAccountRemove(ch.GetAccountRemove(), recordId, authorIdentity)
return st.applyAccountRemove(ch.GetAccountRemove(), record)
case ch.GetReadKeyChange() != nil:
return st.applyReadKeyChange(ch.GetReadKeyChange(), recordId, authorIdentity, true)
return st.applyReadKeyChange(ch.GetReadKeyChange(), record, true)
case ch.GetAccountRequestRemove() != nil:
return st.applyRequestRemove(ch.GetAccountRequestRemove(), recordId, authorIdentity)
return st.applyRequestRemove(ch.GetAccountRequestRemove(), record)
default:
return ErrUnexpectedContentType
}
}
func (st *AclState) applyPermissionChange(ch *aclrecordproto.AclAccountPermissionChange, recordId string, authorIdentity crypto.PubKey) error {
func (st *AclState) applyPermissionChange(ch *aclrecordproto.AclAccountPermissionChange, record *AclRecord) error {
chIdentity, err := st.keyStore.PubKeyFromProto(ch.Identity)
if err != nil {
return err
}
err = st.contentValidator.ValidatePermissionChange(ch, authorIdentity)
err = st.contentValidator.ValidatePermissionChange(ch, record.Identity)
if err != nil {
return err
}
@ -313,21 +313,21 @@ func (st *AclState) applyPermissionChange(ch *aclrecordproto.AclAccountPermissio
return nil
}
func (st *AclState) applyInvite(ch *aclrecordproto.AclAccountInvite, recordId string, authorIdentity crypto.PubKey) error {
func (st *AclState) applyInvite(ch *aclrecordproto.AclAccountInvite, record *AclRecord) error {
inviteKey, err := st.keyStore.PubKeyFromProto(ch.InviteKey)
if err != nil {
return err
}
err = st.contentValidator.ValidateInvite(ch, authorIdentity)
err = st.contentValidator.ValidateInvite(ch, record.Identity)
if err != nil {
return err
}
st.inviteKeys[recordId] = inviteKey
st.inviteKeys[record.Id] = inviteKey
return nil
}
func (st *AclState) applyInviteRevoke(ch *aclrecordproto.AclAccountInviteRevoke, recordId string, authorIdentity crypto.PubKey) error {
err := st.contentValidator.ValidateInviteRevoke(ch, authorIdentity)
func (st *AclState) applyInviteRevoke(ch *aclrecordproto.AclAccountInviteRevoke, record *AclRecord) error {
err := st.contentValidator.ValidateInviteRevoke(ch, record.Identity)
if err != nil {
return err
}
@ -335,22 +335,22 @@ func (st *AclState) applyInviteRevoke(ch *aclrecordproto.AclAccountInviteRevoke,
return nil
}
func (st *AclState) applyRequestJoin(ch *aclrecordproto.AclAccountRequestJoin, recordId string, authorIdentity crypto.PubKey) error {
err := st.contentValidator.ValidateRequestJoin(ch, authorIdentity)
func (st *AclState) applyRequestJoin(ch *aclrecordproto.AclAccountRequestJoin, record *AclRecord) error {
err := st.contentValidator.ValidateRequestJoin(ch, record.Identity)
if err != nil {
return err
}
st.pendingRequests[mapKeyFromPubKey(authorIdentity)] = recordId
st.requestRecords[recordId] = RequestRecord{
RequestIdentity: authorIdentity,
st.pendingRequests[mapKeyFromPubKey(record.Identity)] = record.Id
st.requestRecords[record.Id] = RequestRecord{
RequestIdentity: record.Identity,
RequestMetadata: ch.Metadata,
Type: RequestTypeJoin,
}
return nil
}
func (st *AclState) applyRequestAccept(ch *aclrecordproto.AclAccountRequestAccept, recordId string, authorIdentity crypto.PubKey) error {
err := st.contentValidator.ValidateRequestAccept(ch, authorIdentity)
func (st *AclState) applyRequestAccept(ch *aclrecordproto.AclAccountRequestAccept, record *AclRecord) error {
err := st.contentValidator.ValidateRequestAccept(ch, record.Identity)
if err != nil {
return err
}
@ -358,11 +358,11 @@ func (st *AclState) applyRequestAccept(ch *aclrecordproto.AclAccountRequestAccep
if err != nil {
return err
}
record, _ := st.requestRecords[ch.RequestRecordId]
requestRecord, _ := st.requestRecords[ch.RequestRecordId]
st.accountStates[mapKeyFromPubKey(acceptIdentity)] = AclAccountState{
PubKey: acceptIdentity,
Permissions: AclPermissions(ch.Permissions),
RequestMetadata: record.RequestMetadata,
RequestMetadata: requestRecord.RequestMetadata,
KeyRecordId: st.CurrentReadKeyId(),
}
delete(st.pendingRequests, mapKeyFromPubKey(st.requestRecords[ch.RequestRecordId].RequestIdentity))
@ -421,8 +421,8 @@ func (st *AclState) applyRequestAccept(ch *aclrecordproto.AclAccountRequestAccep
return nil
}
func (st *AclState) applyRequestDecline(ch *aclrecordproto.AclAccountRequestDecline, recordId string, authorIdentity crypto.PubKey) error {
err := st.contentValidator.ValidateRequestDecline(ch, authorIdentity)
func (st *AclState) applyRequestDecline(ch *aclrecordproto.AclAccountRequestDecline, record *AclRecord) error {
err := st.contentValidator.ValidateRequestDecline(ch, record.Identity)
if err != nil {
return err
}
@ -431,21 +431,21 @@ func (st *AclState) applyRequestDecline(ch *aclrecordproto.AclAccountRequestDecl
return nil
}
func (st *AclState) applyRequestRemove(ch *aclrecordproto.AclAccountRequestRemove, recordId string, authorIdentity crypto.PubKey) error {
err := st.contentValidator.ValidateRequestRemove(ch, authorIdentity)
func (st *AclState) applyRequestRemove(ch *aclrecordproto.AclAccountRequestRemove, record *AclRecord) error {
err := st.contentValidator.ValidateRequestRemove(ch, record.Identity)
if err != nil {
return err
}
st.requestRecords[recordId] = RequestRecord{
RequestIdentity: authorIdentity,
st.requestRecords[record.Id] = RequestRecord{
RequestIdentity: record.Identity,
Type: RequestTypeRemove,
}
st.pendingRequests[mapKeyFromPubKey(authorIdentity)] = recordId
st.pendingRequests[mapKeyFromPubKey(record.Identity)] = record.Id
return nil
}
func (st *AclState) applyAccountRemove(ch *aclrecordproto.AclAccountRemove, recordId string, authorIdentity crypto.PubKey) error {
err := st.contentValidator.ValidateAccountRemove(ch, authorIdentity)
func (st *AclState) applyAccountRemove(ch *aclrecordproto.AclAccountRemove, record *AclRecord) error {
err := st.contentValidator.ValidateAccountRemove(ch, record.Identity)
if err != nil {
return err
}
@ -458,17 +458,17 @@ func (st *AclState) applyAccountRemove(ch *aclrecordproto.AclAccountRemove, reco
delete(st.accountStates, idKey)
delete(st.pendingRequests, idKey)
}
return st.applyReadKeyChange(ch.ReadKeyChange, recordId, authorIdentity, false)
return st.applyReadKeyChange(ch.ReadKeyChange, record, false)
}
func (st *AclState) applyReadKeyChange(ch *aclrecordproto.AclReadKeyChange, recordId string, authorIdentity crypto.PubKey, validate bool) error {
func (st *AclState) applyReadKeyChange(ch *aclrecordproto.AclReadKeyChange, record *AclRecord, validate bool) error {
if validate {
err := st.contentValidator.ValidateReadKeyChange(ch, authorIdentity)
err := st.contentValidator.ValidateReadKeyChange(ch, record.Identity)
if err != nil {
return err
}
}
st.readKeyChanges = append(st.readKeyChanges, recordId)
st.readKeyChanges = append(st.readKeyChanges, record.Id)
mkPubKey, err := st.keyStore.PubKeyFromProto(ch.MetadataPubKey)
if err != nil {
return err
@ -493,7 +493,7 @@ func (st *AclState) applyReadKeyChange(ch *aclrecordproto.AclReadKeyChange, reco
}
aclKeys.MetadataPrivKey = metadataKey
}
st.keys[recordId] = aclKeys
st.keys[record.Id] = aclKeys
return nil
}

View file

@ -86,12 +86,11 @@ func (fx *aclFixture) inviteAccount(t *testing.T, perms AclPermissions) {
fx.addRec(t, requestAcceptRec)
// checking acl state
require.True(t, ownerState.Permissions(ownerState.pubKey).IsOwner())
require.True(t, ownerState.Permissions(accountState.pubKey).CanWrite())
require.Equal(t, 0, len(ownerState.pendingRequests))
require.Equal(t, 0, len(accountState.pendingRequests))
require.True(t, accountState.Permissions(ownerState.pubKey).IsOwner())
require.True(t, accountState.Permissions(accountState.pubKey).CanWrite())
for _, acl := range []*aclList{ownerAcl, accountAcl} {
require.True(t, acl.AclState().Permissions(ownerAcl.AclState().pubKey).IsOwner())
require.True(t, acl.AclState().Permissions(acl.AclState().pubKey).CanWrite())
require.Equal(t, 0, len(acl.AclState().pendingRequests))
}
_, err = ownerState.StateAtRecord(requestJoinRec.Id, accountState.pubKey)
require.Equal(t, ErrNoSuchAccount, err)
@ -295,14 +294,12 @@ func TestAclList_PermissionChange(t *testing.T) {
fx.addRec(t, permissionChangeRec)
// checking acl state
require.True(t, ownerState.Permissions(ownerState.pubKey).IsOwner())
require.True(t, ownerState.Permissions(accountState.pubKey) == AclPermissions(aclrecordproto.AclUserPermissions_Writer))
require.True(t, accountState.Permissions(ownerState.pubKey).IsOwner())
require.True(t, accountState.Permissions(accountState.pubKey) == AclPermissions(aclrecordproto.AclUserPermissions_Writer))
require.NotEmpty(t, ownerState.keys[fx.ownerAcl.Id()])
require.NotEmpty(t, accountState.keys[fx.ownerAcl.Id()])
require.Equal(t, 0, len(ownerState.pendingRequests))
require.Equal(t, 0, len(accountState.pendingRequests))
for _, acl := range []*aclList{fx.ownerAcl, fx.accountAcl} {
require.True(t, acl.AclState().Permissions(ownerState.pubKey).IsOwner())
require.True(t, acl.AclState().Permissions(accountState.pubKey).CanWrite())
require.Equal(t, 0, len(acl.AclState().pendingRequests))
require.NotEmpty(t, acl.AclState().keys[fx.ownerAcl.Id()])
}
}
func TestAclList_RequestRemove(t *testing.T) {
@ -337,14 +334,13 @@ func TestAclList_RequestRemove(t *testing.T) {
fx.addRec(t, removeRec)
// checking acl state
require.True(t, ownerState.Permissions(ownerState.pubKey).IsOwner())
require.True(t, ownerState.Permissions(accountState.pubKey).NoPermissions())
for _, acl := range []*aclList{fx.ownerAcl, fx.accountAcl} {
require.True(t, acl.AclState().Permissions(ownerState.pubKey).IsOwner())
require.True(t, acl.AclState().Permissions(accountState.pubKey).NoPermissions())
require.Equal(t, 0, len(acl.AclState().pendingRequests))
}
require.True(t, ownerState.keys[removeRec.Id].ReadKey.Equals(newReadKey))
require.NotEmpty(t, ownerState.keys[fx.ownerAcl.Id()])
require.Equal(t, 0, len(ownerState.pendingRequests))
require.Equal(t, 0, len(accountState.pendingRequests))
require.True(t, accountState.Permissions(ownerState.pubKey).IsOwner())
require.True(t, accountState.Permissions(accountState.pubKey).NoPermissions())
require.Nil(t, accountState.keys[removeRec.Id].MetadataPrivKey)
require.NotNil(t, accountState.keys[removeRec.Id].MetadataPubKey)
require.Nil(t, accountState.keys[removeRec.Id].ReadKey)

View file

@ -6,13 +6,14 @@ import (
)
type AclRecord struct {
Id string
PrevId string
Timestamp int64
Data []byte
Identity crypto.PubKey
Model interface{}
Signature []byte
Id string
PrevId string
Timestamp int64
AcceptorTimestamp int64
Data []byte
Identity crypto.PubKey
Model interface{}
Signature []byte
}
type RequestRecord struct {

View file

@ -1,5 +1,11 @@
package headupdater
import "github.com/anyproto/any-sync/commonspace/object/acl/list"
type HeadUpdater interface {
UpdateHeads(id string, heads []string)
}
type AclUpdater interface {
UpdateAcl(aclList list.AclList)
}

View file

@ -400,6 +400,18 @@ func (mr *MockSyncAclMockRecorder) Run(arg0 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Run", reflect.TypeOf((*MockSyncAcl)(nil).Run), arg0)
}
// SetAclUpdater mocks base method.
func (m *MockSyncAcl) SetAclUpdater(arg0 headupdater.AclUpdater) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "SetAclUpdater", arg0)
}
// SetAclUpdater indicates an expected call of SetAclUpdater.
func (mr *MockSyncAclMockRecorder) SetAclUpdater(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetAclUpdater", reflect.TypeOf((*MockSyncAcl)(nil).SetAclUpdater), arg0)
}
// SetHeadUpdater mocks base method.
func (m *MockSyncAcl) SetHeadUpdater(arg0 headupdater.HeadUpdater) {
m.ctrl.T.Helper()

View file

@ -33,6 +33,7 @@ type SyncAcl interface {
syncobjectgetter.SyncObject
SetHeadUpdater(updater headupdater.HeadUpdater)
SyncWithPeer(ctx context.Context, peerId string) (err error)
SetAclUpdater(updater headupdater.AclUpdater)
}
func New() SyncAcl {
@ -45,6 +46,13 @@ type syncAcl struct {
syncHandler synchandler.SyncHandler
headUpdater headupdater.HeadUpdater
isClosed bool
aclUpdater headupdater.AclUpdater
}
func (s *syncAcl) SetAclUpdater(updater headupdater.AclUpdater) {
s.Lock()
defer s.Unlock()
s.aclUpdater = updater
}
func (s *syncAcl) Run(ctx context.Context) (err error) {
@ -97,6 +105,9 @@ func (s *syncAcl) AddRawRecord(rawRec *consensusproto.RawRecordWithId) (err erro
headUpdate := s.syncClient.CreateHeadUpdate(s, []*consensusproto.RawRecordWithId{rawRec})
s.headUpdater.UpdateHeads(s.Id(), []string{rawRec.Id})
s.syncClient.Broadcast(headUpdate)
if s.aclUpdater != nil {
s.aclUpdater.UpdateAcl(s)
}
return
}
@ -111,6 +122,9 @@ func (s *syncAcl) AddRawRecords(rawRecords []*consensusproto.RawRecordWithId) (e
headUpdate := s.syncClient.CreateHeadUpdate(s, rawRecords)
s.headUpdater.UpdateHeads(s.Id(), []string{rawRecords[len(rawRecords)-1].Id})
s.syncClient.Broadcast(headUpdate)
if s.aclUpdater != nil {
s.aclUpdater.UpdateAcl(s)
}
return
}

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

@ -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) {

View file

@ -6,7 +6,6 @@ import (
"github.com/anyproto/any-sync/commonspace/headsync"
"github.com/anyproto/any-sync/commonspace/object/acl/list"
"github.com/anyproto/any-sync/commonspace/object/acl/syncacl"
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
"github.com/anyproto/any-sync/commonspace/objectsync"
"github.com/anyproto/any-sync/commonspace/objecttreebuilder"
"github.com/anyproto/any-sync/commonspace/peermanager"
@ -63,7 +62,7 @@ func NewSpaceId(id string, repKey uint64) string {
type Space interface {
Id() string
Init(ctx context.Context) error
Acl() list.AclList
Acl() syncacl.SyncAcl
StoredIds() []string
DebugAllHeads() []headsync.TreeHeads
@ -74,9 +73,6 @@ type Space interface {
Storage() spacestorage.SpaceStorage
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)
GetNodePeers(ctx context.Context) (peer []peer.Peer, err error)
HandleMessage(ctx context.Context, msg objectsync.HandleMessage) (err error)
@ -137,14 +133,6 @@ func (s *space) DeleteTree(ctx context.Context, id string) (err error) {
return s.settings.DeleteTree(ctx, id)
}
func (s *space) SpaceDeleteRawChange(ctx context.Context) (raw *treechangeproto.RawTreeChangeWithId, err error) {
return s.settings.SpaceDeleteRawChange(ctx)
}
func (s *space) DeleteSpace(ctx context.Context, deleteChange *treechangeproto.RawTreeChangeWithId) (err error) {
return s.settings.DeleteSpace(ctx, deleteChange)
}
func (s *space) HandleMessage(ctx context.Context, msg objectsync.HandleMessage) (err error) {
return s.objectSync.HandleMessage(ctx, msg)
}
@ -165,8 +153,8 @@ func (s *space) GetNodePeers(ctx context.Context) (peer []peer.Peer, err error)
return s.peerManager.GetNodePeers(ctx)
}
func (s *space) Acl() list.AclList {
return s.aclList
func (s *space) Acl() syncacl.SyncAcl {
return s.aclList.(syncacl.SyncAcl)
}
func (s *space) Id() string {

View file

@ -2,6 +2,7 @@ package commonspace
import (
"context"
"github.com/anyproto/any-sync/commonspace/deletionmanager"
"sync/atomic"
"github.com/anyproto/any-sync/accountservice"
@ -179,6 +180,7 @@ func (s *spaceService) NewSpace(ctx context.Context, id string) (Space, error) {
Register(syncacl.New()).
Register(requestmanager.New()).
Register(deletionstate.New()).
Register(deletionmanager.New()).
Register(settings.New()).
Register(objectmanager.New(s.treeManager)).
Register(objecttreebuilder.New()).