diff --git a/.mockery.yaml b/.mockery.yaml index 9b9a26ff5..095b9b0a3 100644 --- a/.mockery.yaml +++ b/.mockery.yaml @@ -51,9 +51,6 @@ packages: github.com/anyproto/anytype-heart/core/block/import/web/parsers: interfaces: Parser: - github.com/anyproto/anytype-heart/core/block/restriction: - interfaces: - Service: github.com/anyproto/anytype-heart/core/filestorage/filesync: interfaces: FileSync: diff --git a/core/anytype/bootstrap.go b/core/anytype/bootstrap.go index 2785feddd..0ec5d9456 100644 --- a/core/anytype/bootstrap.go +++ b/core/anytype/bootstrap.go @@ -54,7 +54,6 @@ import ( "github.com/anyproto/anytype-heart/core/block/object/objectgraph" "github.com/anyproto/anytype-heart/core/block/object/treemanager" "github.com/anyproto/anytype-heart/core/block/process" - "github.com/anyproto/anytype-heart/core/block/restriction" "github.com/anyproto/anytype-heart/core/block/source/sourceimpl" "github.com/anyproto/anytype-heart/core/block/template/templateimpl" "github.com/anyproto/anytype-heart/core/configfetcher" @@ -299,7 +298,6 @@ func Bootstrap(a *app.App, components ...app.Component) { Register(export.New()). Register(linkpreview.New()). Register(unsplash.New()). - Register(restriction.New()). Register(debug.New()). Register(syncsubscriptions.New()). Register(builtinobjects.New()). diff --git a/core/application/application.go b/core/application/application.go index 293ceef8b..96aa7572e 100644 --- a/core/application/application.go +++ b/core/application/application.go @@ -8,6 +8,7 @@ import ( "github.com/anyproto/any-sync/app" + "github.com/anyproto/anytype-heart/core/domain" "github.com/anyproto/anytype-heart/core/event" "github.com/anyproto/anytype-heart/core/session" "github.com/anyproto/anytype-heart/pkg/lib/logging" @@ -70,6 +71,8 @@ func (s *Service) stop() error { defer task.End() if s != nil && s.app != nil { + log.Warnf("stopping app") + s.app.SetDeviceState(int(domain.CompStateAppClosingInitiated)) err := s.app.Close(ctx) if err != nil { log.Warnf("error while stop anytype: %v", err) diff --git a/core/block/detailservice/service.go b/core/block/detailservice/service.go index 5fb872ee1..4fecf96ce 100644 --- a/core/block/detailservice/service.go +++ b/core/block/detailservice/service.go @@ -13,13 +13,11 @@ import ( "github.com/anyproto/anytype-heart/core/block/cache" "github.com/anyproto/anytype-heart/core/block/editor/basic" "github.com/anyproto/anytype-heart/core/block/object/idresolver" - "github.com/anyproto/anytype-heart/core/block/restriction" "github.com/anyproto/anytype-heart/core/domain" "github.com/anyproto/anytype-heart/core/session" "github.com/anyproto/anytype-heart/pb" "github.com/anyproto/anytype-heart/pkg/lib/localstore/objectstore" "github.com/anyproto/anytype-heart/space" - "github.com/anyproto/anytype-heart/util/pbtypes" "github.com/anyproto/anytype-heart/util/slice" ) @@ -61,7 +59,6 @@ type service struct { resolver idresolver.Resolver spaceService space.Service store objectstore.ObjectStore - restriction restriction.Service } func (s *service) Init(a *app.App) error { @@ -69,7 +66,6 @@ func (s *service) Init(a *app.App) error { s.resolver = app.MustComponent[idresolver.Resolver](a) s.spaceService = app.MustComponent[space.Service](a) s.store = app.MustComponent[objectstore.ObjectStore](a) - s.restriction = app.MustComponent[restriction.Service](a) return nil } @@ -114,7 +110,7 @@ func (s *service) ModifyDetailsList(req *pb.RpcObjectListModifyDetailValuesReque for _, objectId := range req.ObjectIds { err := s.ModifyDetails(nil, objectId, func(current *domain.Details) (*domain.Details, error) { for _, op := range req.Operations { - if !pbtypes.IsNullValue(op.Set) { + if op.Set != nil { // Set operation has higher priority than Add and Remove, because it modifies full value current.Set(domain.RelationKey(op.RelationKey), domain.ValueFromProto(op.Set)) continue diff --git a/core/block/detailservice/service_test.go b/core/block/detailservice/service_test.go index a16a49854..231fd8627 100644 --- a/core/block/detailservice/service_test.go +++ b/core/block/detailservice/service_test.go @@ -16,7 +16,6 @@ import ( "github.com/anyproto/anytype-heart/core/block/editor/smartblock/smarttest" "github.com/anyproto/anytype-heart/core/block/object/idresolver/mock_idresolver" "github.com/anyproto/anytype-heart/core/block/restriction" - "github.com/anyproto/anytype-heart/core/block/restriction/mock_restriction" "github.com/anyproto/anytype-heart/core/block/simple" "github.com/anyproto/anytype-heart/core/domain" "github.com/anyproto/anytype-heart/pb" @@ -38,7 +37,6 @@ type fixture struct { resolver *mock_idresolver.MockResolver spaceService *mock_space.MockService store *objectstore.StoreFixture - restriction *mock_restriction.MockService space *mock_clientspace.MockSpace } @@ -47,7 +45,6 @@ func newFixture(t *testing.T) *fixture { resolver := mock_idresolver.NewMockResolver(t) spaceService := mock_space.NewMockService(t) store := objectstore.NewStoreFixture(t) - restriction := mock_restriction.NewMockService(t) spc := mock_clientspace.NewMockSpace(t) resolver.EXPECT().ResolveSpaceID(mock.Anything).Return(spaceId, nil).Maybe() @@ -58,7 +55,6 @@ func newFixture(t *testing.T) *fixture { resolver: resolver, spaceService: spaceService, store: store, - restriction: restriction, } return &fixture{ @@ -67,7 +63,6 @@ func newFixture(t *testing.T) *fixture { resolver, spaceService, store, - restriction, spc, } } @@ -472,7 +467,6 @@ func TestService_SetIsArchived(t *testing.T) { } return smarttest.New(objectId), nil }) - fx.restriction.EXPECT().CheckRestrictions(mock.Anything, mock.Anything).Return(nil) // when err := fx.SetIsArchived("obj1", true) @@ -493,9 +487,10 @@ func TestService_SetIsArchived(t *testing.T) { if objectId == binId { return editor.NewArchive(sb, fx.store.SpaceIndex(spaceId)), nil } - return smarttest.New(objectId), nil + obj := smarttest.New(objectId) + obj.SetType(coresb.SmartBlockTypeProfilePage) + return obj, nil }) - fx.restriction.EXPECT().CheckRestrictions(mock.Anything, mock.Anything).Return(restriction.ErrRestricted) // when err := fx.SetIsArchived("obj1", true) @@ -529,7 +524,6 @@ func TestService_SetListIsArchived(t *testing.T) { } return smarttest.New(objectId), nil }) - fx.restriction.EXPECT().CheckRestrictions(mock.Anything, mock.Anything).Return(nil) // when err := fx.SetListIsArchived([]string{"obj1", "obj2", "obj3"}, true) @@ -582,7 +576,6 @@ func TestService_SetListIsArchived(t *testing.T) { } return smarttest.New(objectId), nil }) - fx.restriction.EXPECT().CheckRestrictions(mock.Anything, mock.Anything).Return(nil) // when err := fx.SetListIsArchived([]string{"obj1", "obj2", "obj3"}, true) diff --git a/core/block/detailservice/set_details.go b/core/block/detailservice/set_details.go index eaa1e75ec..f8feee24c 100644 --- a/core/block/detailservice/set_details.go +++ b/core/block/detailservice/set_details.go @@ -14,6 +14,7 @@ import ( "github.com/anyproto/anytype-heart/core/block/editor/smartblock" "github.com/anyproto/anytype-heart/core/block/editor/state" "github.com/anyproto/anytype-heart/core/block/editor/widget" + "github.com/anyproto/anytype-heart/core/block/restriction" "github.com/anyproto/anytype-heart/core/domain" "github.com/anyproto/anytype-heart/core/session" "github.com/anyproto/anytype-heart/pkg/lib/bundle" @@ -170,7 +171,7 @@ func (s *service) checkArchivedRestriction(isArchived bool, objectId string) err return nil } return cache.Do(s.objectGetter, objectId, func(sb smartblock.SmartBlock) error { - return s.restriction.CheckRestrictions(sb, model.Restrictions_Delete) + return restriction.CheckRestrictions(sb, model.Restrictions_Delete) }) } diff --git a/core/block/editor/basic/basic_test.go b/core/block/editor/basic/basic_test.go index 866e5abce..9a36eed01 100644 --- a/core/block/editor/basic/basic_test.go +++ b/core/block/editor/basic/basic_test.go @@ -75,7 +75,7 @@ func TestBasic_Create(t *testing.T) { sb := smarttest.New("test") sb.TestRestrictions = restriction.Restrictions{ Object: restriction.ObjectRestrictions{ - model.Restrictions_Blocks, + model.Restrictions_Blocks: {}, }, } sb.AddBlock(simple.New(&model.Block{Id: "test"})) diff --git a/core/block/editor/basic/details.go b/core/block/editor/basic/details.go index 37c6e0399..a7e170e64 100644 --- a/core/block/editor/basic/details.go +++ b/core/block/editor/basic/details.go @@ -373,6 +373,9 @@ func (bs *basic) getLayoutForType(objectTypeKey domain.TypeKey) (model.ObjectTyp func (bs *basic) SetLayoutInState(s *state.State, toLayout model.ObjectTypeLayout, ignoreRestriction bool) (err error) { fromLayout, _ := s.Layout() + if fromLayout == toLayout { + return nil + } if !ignoreRestriction { if err = bs.Restrictions().Object.Check(model.Restrictions_LayoutChange); errors.Is(err, restriction.ErrRestricted) { diff --git a/core/block/editor/basic/details_test.go b/core/block/editor/basic/details_test.go index 517315fd8..573be5079 100644 --- a/core/block/editor/basic/details_test.go +++ b/core/block/editor/basic/details_test.go @@ -165,7 +165,7 @@ func TestBasic_SetObjectTypesInState(t *testing.T) { t.Run("type change is restricted", func(t *testing.T) { // given f := newBasicFixture(t) - f.sb.TestRestrictions = restriction.Restrictions{Object: []model.RestrictionsObjectRestriction{model.Restrictions_TypeChange}} + f.sb.TestRestrictions = restriction.Restrictions{Object: restriction.ObjectRestrictions{model.Restrictions_TypeChange: {}}} s := f.sb.NewState() // when diff --git a/core/block/editor/clipboard/clipboard_test.go b/core/block/editor/clipboard/clipboard_test.go index aaf5742a7..fe1ae8af7 100644 --- a/core/block/editor/clipboard/clipboard_test.go +++ b/core/block/editor/clipboard/clipboard_test.go @@ -1087,7 +1087,7 @@ func TestClipboard_TitleOps(t *testing.T) { t.Run("do not paste if Blocks restriction is set to smartblock", func(t *testing.T) { // given sb := smarttest.New("test") - sb.TestRestrictions = restriction.Restrictions{Object: restriction.ObjectRestrictions{model.Restrictions_Blocks}} + sb.TestRestrictions = restriction.Restrictions{Object: restriction.ObjectRestrictions{model.Restrictions_Blocks: {}}} cb := newFixture(t, sb) // when diff --git a/core/block/editor/factory.go b/core/block/editor/factory.go index 2748f16df..a65c898c3 100644 --- a/core/block/editor/factory.go +++ b/core/block/editor/factory.go @@ -19,7 +19,6 @@ import ( "github.com/anyproto/anytype-heart/core/block/migration" "github.com/anyproto/anytype-heart/core/block/object/idresolver" "github.com/anyproto/anytype-heart/core/block/process" - "github.com/anyproto/anytype-heart/core/block/restriction" "github.com/anyproto/anytype-heart/core/block/source" "github.com/anyproto/anytype-heart/core/domain" "github.com/anyproto/anytype-heart/core/event" @@ -64,7 +63,6 @@ type ObjectFactory struct { config *config.Config picker cache.ObjectGetter eventSender event.Sender - restrictionService restriction.Service indexer smartblock.Indexer spaceService spaceService accountService accountService @@ -101,7 +99,6 @@ func (f *ObjectFactory) Init(a *app.App) (err error) { f.layoutConverter = app.MustComponent[converter.LayoutConverter](a) f.fileBlockService = app.MustComponent[file.BlockService](a) f.fileObjectService = app.MustComponent[fileobject.Service](a) - f.restrictionService = app.MustComponent[restriction.Service](a) f.fileUploaderService = app.MustComponent[fileuploader.Service](a) f.objectDeleter = app.MustComponent[ObjectDeleter](a) f.fileReconciler = app.MustComponent[reconciler.Reconciler](a) @@ -170,7 +167,6 @@ func (f *ObjectFactory) produceSmartblock(space smartblock.Space) (smartblock.Sm space, f.accountService.MyParticipantId(space.Id()), f.fileStore, - f.restrictionService, store, f.objectStore, f.indexer, diff --git a/core/block/editor/file/file_test.go b/core/block/editor/file/file_test.go index 8d3fbe1f2..ba679f3e3 100644 --- a/core/block/editor/file/file_test.go +++ b/core/block/editor/file/file_test.go @@ -143,7 +143,7 @@ func TestDropFiles(t *testing.T) { t.Run("do not drop files to object with Blocks restriction", func(t *testing.T) { // given fx := newFixture(t) - fx.sb.TestRestrictions = restriction.Restrictions{Object: restriction.ObjectRestrictions{model.Restrictions_Blocks}} + fx.sb.TestRestrictions = restriction.Restrictions{Object: restriction.ObjectRestrictions{model.Restrictions_Blocks: {}}} // when err := fx.sfile.DropFiles(pb.RpcFileDropRequest{}) diff --git a/core/block/editor/smartblock/smartblock.go b/core/block/editor/smartblock/smartblock.go index 63669b7c2..099bece0b 100644 --- a/core/block/editor/smartblock/smartblock.go +++ b/core/block/editor/smartblock/smartblock.go @@ -99,7 +99,6 @@ func New( space Space, currentParticipantId string, fileStore filestore.FileStore, - restrictionService restriction.Service, spaceIndex spaceindex.Store, objectStore objectstore.ObjectStore, indexer Indexer, @@ -114,14 +113,13 @@ func New( Locker: &sync.Mutex{}, sessions: map[string]session.Context{}, - fileStore: fileStore, - restrictionService: restrictionService, - spaceIndex: spaceIndex, - indexer: indexer, - eventSender: eventSender, - objectStore: objectStore, - spaceIdResolver: spaceIdResolver, - lastDepDetails: map[string]*domain.Details{}, + fileStore: fileStore, + spaceIndex: spaceIndex, + indexer: indexer, + eventSender: eventSender, + objectStore: objectStore, + spaceIdResolver: spaceIdResolver, + lastDepDetails: map[string]*domain.Details{}, } return s } @@ -205,7 +203,6 @@ type InitContext struct { RequiredInternalRelationKeys []domain.RelationKey // bundled relations that MUST be present in the state State *state.State Relations []*model.Relation - Restriction restriction.Service ObjectStore objectstore.ObjectStore SpaceID string BuildOpts source.BuildOptions @@ -252,13 +249,12 @@ type smartBlock struct { space Space // Deps - fileStore filestore.FileStore - restrictionService restriction.Service - spaceIndex spaceindex.Store - objectStore objectstore.ObjectStore - indexer Indexer - eventSender event.Sender - spaceIdResolver idresolver.Resolver + fileStore filestore.FileStore + spaceIndex spaceindex.Store + objectStore objectstore.ObjectStore + indexer Indexer + eventSender event.Sender + spaceIdResolver idresolver.Resolver } func (sb *smartBlock) SetLocker(locker Locker) { @@ -329,7 +325,7 @@ func (sb *smartBlock) Init(ctx *InitContext) (err error) { sb.ObjectTree = provider.Tree() } sb.undo = undo.NewHistory(0) - sb.restrictions = sb.restrictionService.GetRestrictions(sb) + sb.restrictions = restriction.GetRestrictions(sb) if ctx.State != nil { // need to store file keys in case we have some new files in the state sb.storeFileKeys(ctx.State) @@ -389,7 +385,7 @@ func (sb *smartBlock) sendObjectCloseEvent(_ ApplyInfo) error { // updateRestrictions refetch restrictions from restriction service and update them in the smartblock func (sb *smartBlock) updateRestrictions() { - r := sb.restrictionService.GetRestrictions(sb) + r := restriction.GetRestrictions(sb) if sb.restrictions.Equal(r) { return } diff --git a/core/block/editor/smartblock/smartblock_test.go b/core/block/editor/smartblock/smartblock_test.go index a7893aa46..d2d5e6cf2 100644 --- a/core/block/editor/smartblock/smartblock_test.go +++ b/core/block/editor/smartblock/smartblock_test.go @@ -10,8 +10,6 @@ import ( "github.com/anyproto/anytype-heart/core/block/editor/state" "github.com/anyproto/anytype-heart/core/block/object/idresolver/mock_idresolver" - "github.com/anyproto/anytype-heart/core/block/restriction" - "github.com/anyproto/anytype-heart/core/block/restriction/mock_restriction" "github.com/anyproto/anytype-heart/core/block/simple" _ "github.com/anyproto/anytype-heart/core/block/simple/base" _ "github.com/anyproto/anytype-heart/core/block/simple/link" @@ -52,8 +50,6 @@ func TestSmartBlock_Apply(t *testing.T) { // given fx := newFixture("", t) - fx.restrictionService.EXPECT().GetRestrictions(mock.Anything).Return(restriction.Restrictions{}) - fx.init(t, []*model.Block{{Id: "1"}}) s := fx.NewState() s.Add(simple.New(&model.Block{Id: "2"})) @@ -82,7 +78,6 @@ func TestBasic_SetAlign(t *testing.T) { // given fx := newFixture("", t) - fx.restrictionService.EXPECT().GetRestrictions(mock.Anything).Return(restriction.Restrictions{}) fx.init(t, []*model.Block{ {Id: "test", ChildrenIds: []string{"title", "2"}}, {Id: "title"}, @@ -102,7 +97,6 @@ func TestBasic_SetAlign(t *testing.T) { // given fx := newFixture("", t) - fx.restrictionService.EXPECT().GetRestrictions(mock.Anything).Return(restriction.Restrictions{}) fx.init(t, []*model.Block{ {Id: "test", ChildrenIds: []string{"title", "2"}}, {Id: "title"}, @@ -180,14 +174,13 @@ func Test_removeInternalFlags(t *testing.T) { } type fixture struct { - objectStore *objectstore.StoreFixture - store spaceindex.Store - restrictionService *mock_restriction.MockService - indexer *MockIndexer - eventSender *mock_event.MockSender - source *sourceStub - spaceIdResolver *mock_idresolver.MockResolver - space *MockSpace + objectStore *objectstore.StoreFixture + store spaceindex.Store + indexer *MockIndexer + eventSender *mock_event.MockSender + source *sourceStub + spaceIdResolver *mock_idresolver.MockResolver + space *MockSpace *smartBlock } @@ -204,12 +197,9 @@ func newFixture(id string, t *testing.T) *fixture { indexer := NewMockIndexer(t) - restrictionService := mock_restriction.NewMockService(t) - restrictionService.EXPECT().GetRestrictions(mock.Anything).Return(restriction.Restrictions{}).Maybe() - sender := mock_event.NewMockSender(t) - sb := New(space, "", nil, restrictionService, spaceIndex, objectStore, indexer, sender, spaceIdResolver).(*smartBlock) + sb := New(space, "", nil, spaceIndex, objectStore, indexer, sender, spaceIdResolver).(*smartBlock) source := &sourceStub{ id: id, spaceId: "space1", @@ -218,15 +208,14 @@ func newFixture(id string, t *testing.T) *fixture { sb.source = source return &fixture{ - source: source, - smartBlock: sb, - store: spaceIndex, - restrictionService: restrictionService, - indexer: indexer, - eventSender: sender, - spaceIdResolver: spaceIdResolver, - objectStore: objectStore, - space: space, + source: source, + smartBlock: sb, + store: spaceIndex, + indexer: indexer, + eventSender: sender, + spaceIdResolver: spaceIdResolver, + objectStore: objectStore, + space: space, } } diff --git a/core/block/editor/table/editor_test.go b/core/block/editor/table/editor_test.go index 4b937016b..398d77e49 100644 --- a/core/block/editor/table/editor_test.go +++ b/core/block/editor/table/editor_test.go @@ -56,7 +56,7 @@ func TestEditor_TableCreate(t *testing.T) { // given sb := smarttest.New("root") sb.AddBlock(simple.New(&model.Block{Id: "root"})) - sb.TestRestrictions = restriction.Restrictions{Object: restriction.ObjectRestrictions{model.Restrictions_Blocks}} + sb.TestRestrictions = restriction.Restrictions{Object: restriction.ObjectRestrictions{model.Restrictions_Blocks: {}}} e := NewEditor(sb) s := sb.NewState() diff --git a/core/block/restriction/dataview_test.go b/core/block/restriction/dataview_test.go index 0cfde41fe..ffe2a78b9 100644 --- a/core/block/restriction/dataview_test.go +++ b/core/block/restriction/dataview_test.go @@ -11,11 +11,9 @@ import ( ) func TestService_DataviewRestrictions(t *testing.T) { - s := service{} - t.Run("internal types have restrictions", func(t *testing.T) { for _, typeKey := range bundle.InternalTypes { - restrictions := s.GetRestrictions(givenObjectType(typeKey)) + restrictions := GetRestrictions(givenObjectType(typeKey)) assert.Equal(t, DataviewRestrictions{ model.RestrictionsDataviewRestrictions{ @@ -28,17 +26,17 @@ func TestService_DataviewRestrictions(t *testing.T) { }) t.Run("non-internal types have no restrictions", func(t *testing.T) { - restrictions := s.GetRestrictions(givenObjectType(bundle.TypeKeyContact)) + restrictions := GetRestrictions(givenObjectType(bundle.TypeKeyContact)) assert.Nil(t, restrictions.Dataview) }) t.Run("relations don't have restrictions", func(t *testing.T) { - restrictions := s.GetRestrictions(givenRelation(bundle.RelationKeyId)) + restrictions := GetRestrictions(givenRelation(bundle.RelationKeyId)) assert.Nil(t, restrictions.Dataview) }) t.Run("ordinary objects don't have restrictions", func(t *testing.T) { - restrictions := s.GetRestrictions( + restrictions := GetRestrictions( &restrictionHolder{ sbType: smartblock.SmartBlockTypePage, uniqueKey: nil, diff --git a/core/block/restriction/mock_restriction/mock_RestrictionHolder.go b/core/block/restriction/mock_restriction/mock_RestrictionHolder.go deleted file mode 100644 index e78947f02..000000000 --- a/core/block/restriction/mock_restriction/mock_RestrictionHolder.go +++ /dev/null @@ -1,186 +0,0 @@ -// Code generated by mockery. DO NOT EDIT. - -package mock_restriction - -import ( - domain "github.com/anyproto/anytype-heart/core/domain" - mock "github.com/stretchr/testify/mock" - - model "github.com/anyproto/anytype-heart/pkg/lib/pb/model" - - smartblock "github.com/anyproto/anytype-heart/pkg/lib/core/smartblock" -) - -// MockRestrictionHolder is an autogenerated mock type for the RestrictionHolder type -type MockRestrictionHolder struct { - mock.Mock -} - -type MockRestrictionHolder_Expecter struct { - mock *mock.Mock -} - -func (_m *MockRestrictionHolder) EXPECT() *MockRestrictionHolder_Expecter { - return &MockRestrictionHolder_Expecter{mock: &_m.Mock} -} - -// Layout provides a mock function with given fields: -func (_m *MockRestrictionHolder) Layout() (model.ObjectTypeLayout, bool) { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for Layout") - } - - var r0 model.ObjectTypeLayout - var r1 bool - if rf, ok := ret.Get(0).(func() (model.ObjectTypeLayout, bool)); ok { - return rf() - } - if rf, ok := ret.Get(0).(func() model.ObjectTypeLayout); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(model.ObjectTypeLayout) - } - - if rf, ok := ret.Get(1).(func() bool); ok { - r1 = rf() - } else { - r1 = ret.Get(1).(bool) - } - - return r0, r1 -} - -// MockRestrictionHolder_Layout_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Layout' -type MockRestrictionHolder_Layout_Call struct { - *mock.Call -} - -// Layout is a helper method to define mock.On call -func (_e *MockRestrictionHolder_Expecter) Layout() *MockRestrictionHolder_Layout_Call { - return &MockRestrictionHolder_Layout_Call{Call: _e.mock.On("Layout")} -} - -func (_c *MockRestrictionHolder_Layout_Call) Run(run func()) *MockRestrictionHolder_Layout_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *MockRestrictionHolder_Layout_Call) Return(_a0 model.ObjectTypeLayout, _a1 bool) *MockRestrictionHolder_Layout_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *MockRestrictionHolder_Layout_Call) RunAndReturn(run func() (model.ObjectTypeLayout, bool)) *MockRestrictionHolder_Layout_Call { - _c.Call.Return(run) - return _c -} - -// Type provides a mock function with given fields: -func (_m *MockRestrictionHolder) Type() smartblock.SmartBlockType { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for Type") - } - - var r0 smartblock.SmartBlockType - if rf, ok := ret.Get(0).(func() smartblock.SmartBlockType); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(smartblock.SmartBlockType) - } - - return r0 -} - -// MockRestrictionHolder_Type_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Type' -type MockRestrictionHolder_Type_Call struct { - *mock.Call -} - -// Type is a helper method to define mock.On call -func (_e *MockRestrictionHolder_Expecter) Type() *MockRestrictionHolder_Type_Call { - return &MockRestrictionHolder_Type_Call{Call: _e.mock.On("Type")} -} - -func (_c *MockRestrictionHolder_Type_Call) Run(run func()) *MockRestrictionHolder_Type_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *MockRestrictionHolder_Type_Call) Return(_a0 smartblock.SmartBlockType) *MockRestrictionHolder_Type_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockRestrictionHolder_Type_Call) RunAndReturn(run func() smartblock.SmartBlockType) *MockRestrictionHolder_Type_Call { - _c.Call.Return(run) - return _c -} - -// UniqueKey provides a mock function with given fields: -func (_m *MockRestrictionHolder) UniqueKey() domain.UniqueKey { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for UniqueKey") - } - - var r0 domain.UniqueKey - if rf, ok := ret.Get(0).(func() domain.UniqueKey); ok { - r0 = rf() - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(domain.UniqueKey) - } - } - - return r0 -} - -// MockRestrictionHolder_UniqueKey_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UniqueKey' -type MockRestrictionHolder_UniqueKey_Call struct { - *mock.Call -} - -// UniqueKey is a helper method to define mock.On call -func (_e *MockRestrictionHolder_Expecter) UniqueKey() *MockRestrictionHolder_UniqueKey_Call { - return &MockRestrictionHolder_UniqueKey_Call{Call: _e.mock.On("UniqueKey")} -} - -func (_c *MockRestrictionHolder_UniqueKey_Call) Run(run func()) *MockRestrictionHolder_UniqueKey_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *MockRestrictionHolder_UniqueKey_Call) Return(_a0 domain.UniqueKey) *MockRestrictionHolder_UniqueKey_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockRestrictionHolder_UniqueKey_Call) RunAndReturn(run func() domain.UniqueKey) *MockRestrictionHolder_UniqueKey_Call { - _c.Call.Return(run) - return _c -} - -// NewMockRestrictionHolder creates a new instance of MockRestrictionHolder. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewMockRestrictionHolder(t interface { - mock.TestingT - Cleanup(func()) -}) *MockRestrictionHolder { - mock := &MockRestrictionHolder{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/core/block/restriction/mock_restriction/mock_Service.go b/core/block/restriction/mock_restriction/mock_Service.go deleted file mode 100644 index 3be69ad79..000000000 --- a/core/block/restriction/mock_restriction/mock_Service.go +++ /dev/null @@ -1,237 +0,0 @@ -// Code generated by mockery. DO NOT EDIT. - -package mock_restriction - -import ( - app "github.com/anyproto/any-sync/app" - mock "github.com/stretchr/testify/mock" - - model "github.com/anyproto/anytype-heart/pkg/lib/pb/model" - - restriction "github.com/anyproto/anytype-heart/core/block/restriction" -) - -// MockService is an autogenerated mock type for the Service type -type MockService struct { - mock.Mock -} - -type MockService_Expecter struct { - mock *mock.Mock -} - -func (_m *MockService) EXPECT() *MockService_Expecter { - return &MockService_Expecter{mock: &_m.Mock} -} - -// CheckRestrictions provides a mock function with given fields: rh, cr -func (_m *MockService) CheckRestrictions(rh restriction.RestrictionHolder, cr ...model.RestrictionsObjectRestriction) error { - _va := make([]interface{}, len(cr)) - for _i := range cr { - _va[_i] = cr[_i] - } - var _ca []interface{} - _ca = append(_ca, rh) - _ca = append(_ca, _va...) - ret := _m.Called(_ca...) - - if len(ret) == 0 { - panic("no return value specified for CheckRestrictions") - } - - var r0 error - if rf, ok := ret.Get(0).(func(restriction.RestrictionHolder, ...model.RestrictionsObjectRestriction) error); ok { - r0 = rf(rh, cr...) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// MockService_CheckRestrictions_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CheckRestrictions' -type MockService_CheckRestrictions_Call struct { - *mock.Call -} - -// CheckRestrictions is a helper method to define mock.On call -// - rh restriction.RestrictionHolder -// - cr ...model.RestrictionsObjectRestriction -func (_e *MockService_Expecter) CheckRestrictions(rh interface{}, cr ...interface{}) *MockService_CheckRestrictions_Call { - return &MockService_CheckRestrictions_Call{Call: _e.mock.On("CheckRestrictions", - append([]interface{}{rh}, cr...)...)} -} - -func (_c *MockService_CheckRestrictions_Call) Run(run func(rh restriction.RestrictionHolder, cr ...model.RestrictionsObjectRestriction)) *MockService_CheckRestrictions_Call { - _c.Call.Run(func(args mock.Arguments) { - variadicArgs := make([]model.RestrictionsObjectRestriction, len(args)-1) - for i, a := range args[1:] { - if a != nil { - variadicArgs[i] = a.(model.RestrictionsObjectRestriction) - } - } - run(args[0].(restriction.RestrictionHolder), variadicArgs...) - }) - return _c -} - -func (_c *MockService_CheckRestrictions_Call) Return(_a0 error) *MockService_CheckRestrictions_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockService_CheckRestrictions_Call) RunAndReturn(run func(restriction.RestrictionHolder, ...model.RestrictionsObjectRestriction) error) *MockService_CheckRestrictions_Call { - _c.Call.Return(run) - return _c -} - -// GetRestrictions provides a mock function with given fields: _a0 -func (_m *MockService) GetRestrictions(_a0 restriction.RestrictionHolder) restriction.Restrictions { - ret := _m.Called(_a0) - - if len(ret) == 0 { - panic("no return value specified for GetRestrictions") - } - - var r0 restriction.Restrictions - if rf, ok := ret.Get(0).(func(restriction.RestrictionHolder) restriction.Restrictions); ok { - r0 = rf(_a0) - } else { - r0 = ret.Get(0).(restriction.Restrictions) - } - - return r0 -} - -// MockService_GetRestrictions_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetRestrictions' -type MockService_GetRestrictions_Call struct { - *mock.Call -} - -// GetRestrictions is a helper method to define mock.On call -// - _a0 restriction.RestrictionHolder -func (_e *MockService_Expecter) GetRestrictions(_a0 interface{}) *MockService_GetRestrictions_Call { - return &MockService_GetRestrictions_Call{Call: _e.mock.On("GetRestrictions", _a0)} -} - -func (_c *MockService_GetRestrictions_Call) Run(run func(_a0 restriction.RestrictionHolder)) *MockService_GetRestrictions_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(restriction.RestrictionHolder)) - }) - return _c -} - -func (_c *MockService_GetRestrictions_Call) Return(_a0 restriction.Restrictions) *MockService_GetRestrictions_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockService_GetRestrictions_Call) RunAndReturn(run func(restriction.RestrictionHolder) restriction.Restrictions) *MockService_GetRestrictions_Call { - _c.Call.Return(run) - return _c -} - -// Init provides a mock function with given fields: a -func (_m *MockService) Init(a *app.App) error { - ret := _m.Called(a) - - if len(ret) == 0 { - panic("no return value specified for Init") - } - - var r0 error - if rf, ok := ret.Get(0).(func(*app.App) error); ok { - r0 = rf(a) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// MockService_Init_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Init' -type MockService_Init_Call struct { - *mock.Call -} - -// Init is a helper method to define mock.On call -// - a *app.App -func (_e *MockService_Expecter) Init(a interface{}) *MockService_Init_Call { - return &MockService_Init_Call{Call: _e.mock.On("Init", a)} -} - -func (_c *MockService_Init_Call) Run(run func(a *app.App)) *MockService_Init_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(*app.App)) - }) - return _c -} - -func (_c *MockService_Init_Call) Return(err error) *MockService_Init_Call { - _c.Call.Return(err) - return _c -} - -func (_c *MockService_Init_Call) RunAndReturn(run func(*app.App) error) *MockService_Init_Call { - _c.Call.Return(run) - return _c -} - -// Name provides a mock function with given fields: -func (_m *MockService) Name() string { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for Name") - } - - var r0 string - if rf, ok := ret.Get(0).(func() string); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(string) - } - - return r0 -} - -// MockService_Name_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Name' -type MockService_Name_Call struct { - *mock.Call -} - -// Name is a helper method to define mock.On call -func (_e *MockService_Expecter) Name() *MockService_Name_Call { - return &MockService_Name_Call{Call: _e.mock.On("Name")} -} - -func (_c *MockService_Name_Call) Run(run func()) *MockService_Name_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *MockService_Name_Call) Return(name string) *MockService_Name_Call { - _c.Call.Return(name) - return _c -} - -func (_c *MockService_Name_Call) RunAndReturn(run func() string) *MockService_Name_Call { - _c.Call.Return(run) - return _c -} - -// NewMockService creates a new instance of MockService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewMockService(t interface { - mock.TestingT - Cleanup(func()) -}) *MockService { - mock := &MockService{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/core/block/restriction/object.go b/core/block/restriction/object.go index c9a5e82f4..48fc3fd9b 100644 --- a/core/block/restriction/object.go +++ b/core/block/restriction/object.go @@ -2,8 +2,11 @@ package restriction import ( "fmt" + "maps" "slices" + "github.com/samber/lo" + "github.com/anyproto/anytype-heart/core/domain" "github.com/anyproto/anytype-heart/pkg/lib/bundle" "github.com/anyproto/anytype-heart/pkg/lib/core/smartblock" @@ -12,198 +15,106 @@ import ( var ( objRestrictAll = ObjectRestrictions{ - model.Restrictions_Blocks, - model.Restrictions_Relations, - model.Restrictions_Details, - model.Restrictions_Delete, - model.Restrictions_LayoutChange, - model.Restrictions_TypeChange, - model.Restrictions_Template, - model.Restrictions_Duplicate, - model.Restrictions_Publish, - } - objRestrictEditAndDuplicate = ObjectRestrictions{ - model.Restrictions_Blocks, - model.Restrictions_LayoutChange, - model.Restrictions_TypeChange, - model.Restrictions_Template, - model.Restrictions_Duplicate, - model.Restrictions_Publish, + model.Restrictions_Blocks: {}, + model.Restrictions_Relations: {}, + model.Restrictions_Details: {}, + model.Restrictions_Delete: {}, + model.Restrictions_LayoutChange: {}, + model.Restrictions_TypeChange: {}, + model.Restrictions_Template: {}, + model.Restrictions_Duplicate: {}, + model.Restrictions_Publish: {}, } objRestrictEdit = ObjectRestrictions{ - model.Restrictions_Blocks, - model.Restrictions_LayoutChange, - model.Restrictions_TypeChange, - model.Restrictions_Publish, - } - objRestrictEditAndTemplate = ObjectRestrictions{ - model.Restrictions_Blocks, - model.Restrictions_LayoutChange, - model.Restrictions_TypeChange, - model.Restrictions_Template, - model.Restrictions_Publish, - } - sysTypesRestrictionsEdit = ObjectRestrictions{ - model.Restrictions_Blocks, - model.Restrictions_LayoutChange, - model.Restrictions_TypeChange, - model.Restrictions_Template, - model.Restrictions_Details, - model.Restrictions_Delete, - model.Restrictions_Publish, - } - sysTypesRestrictions = ObjectRestrictions{ - model.Restrictions_Blocks, - model.Restrictions_LayoutChange, - model.Restrictions_TypeChange, - model.Restrictions_Template, - model.Restrictions_Delete, - model.Restrictions_Publish, - } - sysRelationsRestrictions = ObjectRestrictions{ - model.Restrictions_Blocks, - model.Restrictions_LayoutChange, - model.Restrictions_TypeChange, - model.Restrictions_Template, - model.Restrictions_Delete, - model.Restrictions_Relations, - model.Restrictions_Details, - model.Restrictions_Publish, + model.Restrictions_Blocks: {}, + model.Restrictions_LayoutChange: {}, + model.Restrictions_TypeChange: {}, + model.Restrictions_Publish: {}, } + objRestrictEditAndTemplate = objRestrictEdit.Copy().Add(model.Restrictions_Template) + objRestrictEditAndDuplicate = objRestrictEditAndTemplate.Copy().Add(model.Restrictions_Duplicate) objectRestrictionsByLayout = map[model.ObjectTypeLayout]ObjectRestrictions{ model.ObjectType_basic: {}, model.ObjectType_profile: {}, model.ObjectType_todo: {}, + model.ObjectType_note: {}, + model.ObjectType_bookmark: {}, model.ObjectType_set: objRestrictEdit, - model.ObjectType_collection: objRestrictEdit, - model.ObjectType_objectType: objRestrictEditAndTemplate, - model.ObjectType_relation: objRestrictEditAndTemplate, - model.ObjectType_file: objRestrictEditAndDuplicate, - model.ObjectType_dashboard: { - model.Restrictions_Details, - model.Restrictions_Relations, - model.Restrictions_Delete, - model.Restrictions_LayoutChange, - model.Restrictions_TypeChange, - model.Restrictions_Template, - model.Restrictions_Duplicate, - }, - model.ObjectType_image: objRestrictAll, - model.ObjectType_note: {}, - model.ObjectType_space: { - model.Restrictions_Template, - }, - - model.ObjectType_bookmark: {}, - model.ObjectType_relationOption: objRestrictEditAndTemplate, + model.ObjectType_collection: objRestrictEdit.Copy().Remove(model.Restrictions_TypeChange), model.ObjectType_relationOptionsList: { - model.Restrictions_Template, + model.Restrictions_Template: {}, + }, + model.ObjectType_space: { + model.Restrictions_Template: {}, }, - model.ObjectType_participant: objRestrictAll, - model.ObjectType_chat: objRestrictEditAndDuplicate, - model.ObjectType_chatDerived: objRestrictEditAndDuplicate, - model.ObjectType_tag: objRestrictEditAndTemplate, } objectRestrictionsBySBType = map[smartblock.SmartBlockType]ObjectRestrictions{ - smartblock.SmartBlockTypeIdentity: objRestrictAll, + smartblock.SmartBlockTypeIdentity: objRestrictAll, + smartblock.SmartBlockTypeAnytypeProfile: objRestrictAll, + smartblock.SmartBlockTypeArchive: objRestrictAll, + smartblock.SmartBlockTypeBundledRelation: objRestrictAll, + smartblock.SmartBlockTypeBundledObjectType: objRestrictAll, + smartblock.SmartBlockTypeBundledTemplate: objRestrictAll, + smartblock.SmartBlockTypeMissingObject: objRestrictAll, + smartblock.SmartBlockTypeDate: objRestrictAll, + smartblock.SmartBlockTypeParticipant: objRestrictAll, + smartblock.SmartBlockTypeAccountOld: objRestrictAll, // new value + smartblock.SmartBlockTypeAccountObject: objRestrictAll, // new + smartblock.SmartBlockTypeSpaceView: objRestrictAll, // new + smartblock.SmartBlockTypeNotificationObject: objRestrictAll, // new + smartblock.SmartBlockTypeDevicesObject: objRestrictAll, // new + smartblock.SmartBlockTypeChatDerivedObject: objRestrictEditAndDuplicate, + smartblock.SmartBlockTypeChatObject: objRestrictEditAndDuplicate, + smartblock.SmartBlockTypeFileObject: objRestrictEditAndDuplicate, + smartblock.SmartBlockTypeSubObject: objRestrictEditAndTemplate, + smartblock.SmartBlockTypeObjectType: objRestrictEditAndTemplate, + smartblock.SmartBlockTypeRelation: objRestrictEditAndTemplate, + smartblock.SmartBlockTypeHome: objRestrictAll.Copy().Remove(model.Restrictions_Blocks), + smartblock.SmartBlockTypeWidget: objRestrictAll.Copy().Remove(model.Restrictions_Blocks), + smartblock.SmartBlockTypeWorkspace: objRestrictAll.Copy().Remove(model.Restrictions_Details), smartblock.SmartBlockTypeProfilePage: { - model.Restrictions_LayoutChange, - model.Restrictions_TypeChange, - model.Restrictions_Delete, - model.Restrictions_Duplicate, + model.Restrictions_LayoutChange: {}, + model.Restrictions_TypeChange: {}, + model.Restrictions_Delete: {}, + model.Restrictions_Duplicate: {}, }, - smartblock.SmartBlockTypeAnytypeProfile: objRestrictAll, - smartblock.SmartBlockTypeHome: { - model.Restrictions_Details, - model.Restrictions_Relations, - model.Restrictions_Delete, - model.Restrictions_LayoutChange, - model.Restrictions_TypeChange, - model.Restrictions_Template, - model.Restrictions_Duplicate, - model.Restrictions_Publish, - }, - smartblock.SmartBlockTypeWorkspace: { - model.Restrictions_Blocks, - model.Restrictions_Relations, - model.Restrictions_Delete, - model.Restrictions_LayoutChange, - model.Restrictions_TypeChange, - model.Restrictions_Template, - model.Restrictions_Duplicate, - model.Restrictions_Publish, - }, - smartblock.SmartBlockTypeFileObject: objRestrictEditAndDuplicate, - smartblock.SmartBlockTypeArchive: objRestrictAll, - smartblock.SmartBlockTypeBundledRelation: objRestrictAll, - smartblock.SmartBlockTypeSubObject: objRestrictEditAndTemplate, - smartblock.SmartBlockTypeObjectType: objRestrictEditAndTemplate, - smartblock.SmartBlockTypeRelation: objRestrictEditAndTemplate, - smartblock.SmartBlockTypeBundledObjectType: objRestrictAll, - smartblock.SmartBlockTypeBundledTemplate: objRestrictAll, smartblock.SmartBlockTypeTemplate: { - model.Restrictions_TypeChange, - model.Restrictions_Template, - model.Restrictions_Publish, + model.Restrictions_TypeChange: {}, + model.Restrictions_Template: {}, + model.Restrictions_Publish: {}, }, - smartblock.SmartBlockTypeWidget: { - model.Restrictions_Relations, - model.Restrictions_Details, - model.Restrictions_Delete, - model.Restrictions_LayoutChange, - model.Restrictions_TypeChange, - model.Restrictions_Template, - model.Restrictions_Duplicate, - model.Restrictions_Publish, - }, - smartblock.SmartBlockTypeMissingObject: objRestrictAll, - smartblock.SmartBlockTypeDate: objRestrictAll, - smartblock.SmartBlockTypeAccountOld: { - model.Restrictions_Template, - }, - smartblock.SmartBlockTypeParticipant: objRestrictAll, - smartblock.SmartBlockTypeChatObject: objRestrictEditAndDuplicate, - smartblock.SmartBlockTypeChatDerivedObject: objRestrictEditAndDuplicate, } ) -var ( - editableSystemTypes = []domain.TypeKey{ - bundle.TypeKeyPage, bundle.TypeKeyBookmark, // pages - bundle.TypeKeySet, bundle.TypeKeyCollection, // lists - bundle.TypeKeyFile, bundle.TypeKeyAudio, bundle.TypeKeyVideo, bundle.TypeKeyImage, // files - } -) - -func GetRestrictionsBySBType(sbType smartblock.SmartBlockType) []int { - restrictions := objectRestrictionsBySBType[sbType] - result := make([]int, len(restrictions)) - for i, restriction := range restrictions { - result[i] = int(restriction) - } - return result +var editableSystemTypes = map[domain.TypeKey]struct{}{ + bundle.TypeKeyPage: {}, bundle.TypeKeyBookmark: {}, // pages + bundle.TypeKeySet: {}, bundle.TypeKeyCollection: {}, // lists + bundle.TypeKeyFile: {}, bundle.TypeKeyAudio: {}, bundle.TypeKeyVideo: {}, bundle.TypeKeyImage: {}, // files } -type ObjectRestrictions []model.RestrictionsObjectRestriction +func GetRestrictionsBySBType(sbType smartblock.SmartBlockType) domain.Value { + restrictions := objectRestrictionsBySBType[sbType] + return restrictions.ToValue() +} + +type ObjectRestrictions map[model.RestrictionsObjectRestriction]struct{} func NewObjectRestrictionsFromValue(v domain.Value) ObjectRestrictions { raw := v.Int64List() restrictions := make(ObjectRestrictions, len(raw)) - for i, restriction := range raw { + for _, restriction := range raw { // nolint:gosec - restrictions[i] = model.RestrictionsObjectRestriction(restriction) + restrictions[model.RestrictionsObjectRestriction(restriction)] = struct{}{} } return restrictions } func (or ObjectRestrictions) Check(cr ...model.RestrictionsObjectRestriction) (err error) { for _, r := range cr { - for _, er := range or { - if er == r { - return fmt.Errorf("%w: %s", ErrRestricted, r.String()) - } + if _, found := or[r]; found { + return fmt.Errorf("%w: %s", ErrRestricted, r.String()) } } return @@ -213,7 +124,7 @@ func (or ObjectRestrictions) Equal(or2 ObjectRestrictions) bool { if len(or) != len(or2) { return false } - for _, r := range or { + for r := range or { if or2.Check(r) == nil { return false } @@ -223,18 +134,38 @@ func (or ObjectRestrictions) Equal(or2 ObjectRestrictions) bool { func (or ObjectRestrictions) Copy() ObjectRestrictions { obj := make(ObjectRestrictions, len(or)) - copy(obj, or) + maps.Copy(obj, or) return obj } func (or ObjectRestrictions) ToValue() domain.Value { var ints = make([]int64, len(or)) - for i, v := range or { + var i int + for v := range or { ints[i] = int64(v) + i++ } return domain.Int64List(ints) } +func (or ObjectRestrictions) ToProto() []model.RestrictionsObjectRestriction { + return lo.Keys(or) +} + +func (or ObjectRestrictions) Add(restrictions ...model.RestrictionsObjectRestriction) ObjectRestrictions { + for _, r := range restrictions { + or[r] = struct{}{} + } + return or +} + +func (or ObjectRestrictions) Remove(restrictions ...model.RestrictionsObjectRestriction) ObjectRestrictions { + for _, r := range restrictions { + delete(or, r) + } + return or +} + func getObjectRestrictions(rh RestrictionHolder) (r ObjectRestrictions) { uk := rh.UniqueKey() if uk != nil && uk.InternalKey() != "" { @@ -255,25 +186,24 @@ func getObjectRestrictions(rh RestrictionHolder) (r ObjectRestrictions) { } func getRestrictionsForUniqueKey(uk domain.UniqueKey) (r ObjectRestrictions) { - r = objectRestrictionsBySBType[uk.SmartblockType()] + r = objectRestrictionsBySBType[uk.SmartblockType()].Copy() switch uk.SmartblockType() { case smartblock.SmartBlockTypeObjectType: key := uk.InternalKey() if slices.Contains(bundle.SystemTypes, domain.TypeKey(key)) { - if slices.Contains(editableSystemTypes, domain.TypeKey(key)) { - r = sysTypesRestrictions - } else { - r = sysTypesRestrictionsEdit + r.Add(model.Restrictions_Delete) + if _, isEditable := editableSystemTypes[domain.TypeKey(key)]; !isEditable { + r.Add(model.Restrictions_Details) } } if t, _ := bundle.GetType(domain.TypeKey(key)); t != nil && t.RestrictObjectCreation { - r = append(r, model.Restrictions_CreateObjectOfThisType) + r.Add(model.Restrictions_CreateObjectOfThisType) } return r case smartblock.SmartBlockTypeRelation: key := uk.InternalKey() if slices.Contains(bundle.SystemRelations, domain.RelationKey(key)) { - r = sysRelationsRestrictions + r = objRestrictAll.Copy().Remove(model.Restrictions_Duplicate) } } // we assume that all sb types exist in objectRestrictionsBySBType diff --git a/core/block/restriction/object_test.go b/core/block/restriction/object_test.go index 4a4c95b23..9ae7d69e7 100644 --- a/core/block/restriction/object_test.go +++ b/core/block/restriction/object_test.go @@ -3,51 +3,54 @@ package restriction import ( "testing" + "github.com/samber/lo" "github.com/stretchr/testify/assert" "github.com/anyproto/anytype-heart/pkg/lib/bundle" - coresb "github.com/anyproto/anytype-heart/pkg/lib/core/smartblock" + "github.com/anyproto/anytype-heart/pkg/lib/core/smartblock" "github.com/anyproto/anytype-heart/pkg/lib/pb/model" ) func TestService_ObjectRestrictionsById(t *testing.T) { - rs := service{} + all := lo.Keys(objRestrictAll) + edit := lo.Keys(objRestrictEdit) + dup := lo.Keys(objRestrictEditAndDuplicate) t.Run("anytype profile should have all restrictions", func(t *testing.T) { - assert.ErrorIs(t, rs.GetRestrictions(givenRestrictionHolder(coresb.SmartBlockTypeAnytypeProfile, bundle.TypeKeyProfile)).Object.Check( - objRestrictAll..., - ), ErrRestricted) + assert.ErrorIs(t, GetRestrictions( + givenRestrictionHolder(smartblock.SmartBlockTypeAnytypeProfile, bundle.TypeKeyProfile), + ).Object.Check(all...), ErrRestricted) }) t.Run("sets and collections should have edit restrictions", func(t *testing.T) { - collection := givenRestrictionHolder(coresb.SmartBlockTypePage, bundle.TypeKeyCollection) - assert.ErrorIs(t, rs.GetRestrictions(collection).Object.Check(objRestrictEdit...), ErrRestricted) - set := givenRestrictionHolder(coresb.SmartBlockTypePage, bundle.TypeKeySet) - assert.ErrorIs(t, rs.GetRestrictions(set).Object.Check(objRestrictEdit...), ErrRestricted) + collection := givenRestrictionHolder(smartblock.SmartBlockTypePage, bundle.TypeKeyCollection) + assert.ErrorIs(t, GetRestrictions(collection).Object.Check(edit...), ErrRestricted) + set := givenRestrictionHolder(smartblock.SmartBlockTypePage, bundle.TypeKeySet) + assert.ErrorIs(t, GetRestrictions(set).Object.Check(edit...), ErrRestricted) }) t.Run("plain pages should not have any restrictions", func(t *testing.T) { - page := givenRestrictionHolder(coresb.SmartBlockTypePage, bundle.TypeKeyPage) - for _, restriction := range objRestrictAll { - assert.NoError(t, rs.GetRestrictions(page).Object.Check(restriction)) + page := givenRestrictionHolder(smartblock.SmartBlockTypePage, bundle.TypeKeyPage) + for restriction := range objRestrictAll { + assert.NoError(t, GetRestrictions(page).Object.Check(restriction)) } }) t.Run("system type", func(t *testing.T) { - assert.ErrorIs(t, rs.GetRestrictions(givenObjectType(bundle.TypeKeyObjectType)).Object.Check( + assert.ErrorIs(t, GetRestrictions(givenObjectType(bundle.TypeKeyObjectType)).Object.Check( model.Restrictions_Details, model.Restrictions_Delete, ), ErrRestricted) }) t.Run("system type restricted creation", func(t *testing.T) { - assert.ErrorIs(t, rs.GetRestrictions(givenObjectType(bundle.TypeKeyParticipant)).Object.Check( + assert.ErrorIs(t, GetRestrictions(givenObjectType(bundle.TypeKeyParticipant)).Object.Check( model.Restrictions_CreateObjectOfThisType, ), ErrRestricted) }) t.Run("ordinary type", func(t *testing.T) { - assert.NoError(t, rs.GetRestrictions(givenObjectType(bundle.TypeKeyDiaryEntry)).Object.Check( + assert.NoError(t, GetRestrictions(givenObjectType(bundle.TypeKeyDiaryEntry)).Object.Check( model.Restrictions_Details, model.Restrictions_Delete, model.Restrictions_CreateObjectOfThisType, @@ -55,25 +58,25 @@ func TestService_ObjectRestrictionsById(t *testing.T) { }) t.Run("ordinary type has basic restrictions", func(t *testing.T) { - assert.ErrorIs(t, rs.GetRestrictions(givenObjectType(bundle.TypeKeyDiaryEntry)).Object.Check( + assert.ErrorIs(t, GetRestrictions(givenObjectType(bundle.TypeKeyDiaryEntry)).Object.Check( model.Restrictions_Blocks, model.Restrictions_LayoutChange, ), ErrRestricted) }) t.Run("ordinary relation has basic restrictions", func(t *testing.T) { - assert.ErrorIs(t, rs.GetRestrictions(givenObjectType(bundle.TypeKeyDiaryEntry)).Object.Check( + assert.ErrorIs(t, GetRestrictions(givenObjectType(bundle.TypeKeyDiaryEntry)).Object.Check( model.Restrictions_TypeChange, ), ErrRestricted) }) t.Run("bundled object types should have all restrictions", func(t *testing.T) { - bundledType := givenRestrictionHolder(coresb.SmartBlockTypeBundledObjectType, bundle.TypeKeyObjectType) - assert.ErrorIs(t, rs.GetRestrictions(bundledType).Object.Check(objRestrictAll...), ErrRestricted) + bundledType := givenRestrictionHolder(smartblock.SmartBlockTypeBundledObjectType, bundle.TypeKeyObjectType) + assert.ErrorIs(t, GetRestrictions(bundledType).Object.Check(all...), ErrRestricted) }) t.Run("ordinary relation", func(t *testing.T) { - assert.NoError(t, rs.GetRestrictions(givenRelation(bundle.RelationKeyAudioLyrics)).Object.Check( + assert.NoError(t, GetRestrictions(givenRelation(bundle.RelationKeyAudioLyrics)).Object.Check( model.Restrictions_Delete, model.Restrictions_Relations, model.Restrictions_Details, @@ -81,7 +84,7 @@ func TestService_ObjectRestrictionsById(t *testing.T) { }) t.Run("system relation", func(t *testing.T) { - assert.ErrorIs(t, rs.GetRestrictions(givenRelation(bundle.RelationKeyId)).Object.Check( + assert.ErrorIs(t, GetRestrictions(givenRelation(bundle.RelationKeyId)).Object.Check( model.Restrictions_Delete, model.Restrictions_Relations, model.Restrictions_Details, @@ -89,39 +92,37 @@ func TestService_ObjectRestrictionsById(t *testing.T) { }) t.Run("bundled object types should have all restrictions", func(t *testing.T) { - bundledRelation := givenRestrictionHolder(coresb.SmartBlockTypeBundledRelation, bundle.TypeKeyRelation) - assert.ErrorIs(t, rs.GetRestrictions(bundledRelation).Object.Check(objRestrictAll...), ErrRestricted) + bundledRelation := givenRestrictionHolder(smartblock.SmartBlockTypeBundledRelation, bundle.TypeKeyRelation) + assert.ErrorIs(t, GetRestrictions(bundledRelation).Object.Check(all...), ErrRestricted) }) t.Run("chat should have edit and duplication restrictions", func(t *testing.T) { - assert.ErrorIs(t, rs.GetRestrictions(givenRestrictionHolder(coresb.SmartBlockTypeChatObject, bundle.TypeKeyChat)).Object.Check( - objRestrictEditAndDuplicate..., + assert.ErrorIs(t, GetRestrictions(givenRestrictionHolder(smartblock.SmartBlockTypeChatObject, bundle.TypeKeyChat)).Object.Check( + dup..., ), ErrRestricted) }) } func TestTemplateRestriction(t *testing.T) { - rs := service{} - t.Run("cannot make template from Template smartblock type", func(t *testing.T) { - template := givenRestrictionHolder(coresb.SmartBlockTypeTemplate, bundle.TypeKeyTemplate) - assert.ErrorIs(t, rs.GetRestrictions(template).Object.Check(model.Restrictions_Template), ErrRestricted) + template := givenRestrictionHolder(smartblock.SmartBlockTypeTemplate, bundle.TypeKeyTemplate) + assert.ErrorIs(t, GetRestrictions(template).Object.Check(model.Restrictions_Template), ErrRestricted) }) t.Run("we CAN make template from set or collection layout", func(t *testing.T) { - collection := givenRestrictionHolder(coresb.SmartBlockTypePage, bundle.TypeKeyCollection) - set := givenRestrictionHolder(coresb.SmartBlockTypePage, bundle.TypeKeySet) - assert.NoError(t, rs.GetRestrictions(collection).Object.Check(model.Restrictions_Template)) - assert.NoError(t, rs.GetRestrictions(set).Object.Check(model.Restrictions_Template)) + collection := givenRestrictionHolder(smartblock.SmartBlockTypePage, bundle.TypeKeyCollection) + set := givenRestrictionHolder(smartblock.SmartBlockTypePage, bundle.TypeKeySet) + assert.NoError(t, GetRestrictions(collection).Object.Check(model.Restrictions_Template)) + assert.NoError(t, GetRestrictions(set).Object.Check(model.Restrictions_Template)) }) t.Run("cannot make template from space layout", func(t *testing.T) { - space := givenRestrictionHolder(coresb.SmartBlockTypePage, bundle.TypeKeySpace) - assert.ErrorIs(t, rs.GetRestrictions(space).Object.Check(model.Restrictions_Template), ErrRestricted) + space := givenRestrictionHolder(smartblock.SmartBlockTypePage, bundle.TypeKeySpace) + assert.ErrorIs(t, GetRestrictions(space).Object.Check(model.Restrictions_Template), ErrRestricted) }) t.Run("make template from object with objectType added to space", func(t *testing.T) { - book := givenRestrictionHolder(coresb.SmartBlockTypePage, bundle.TypeKeyBook) - assert.NoError(t, rs.GetRestrictions(book).Object.Check(model.Restrictions_Template)) + book := givenRestrictionHolder(smartblock.SmartBlockTypePage, bundle.TypeKeyBook) + assert.NoError(t, GetRestrictions(book).Object.Check(model.Restrictions_Template)) }) } diff --git a/core/block/restriction/restrictions.go b/core/block/restriction/restrictions.go index 52607d5e7..d5e181a3e 100644 --- a/core/block/restriction/restrictions.go +++ b/core/block/restriction/restrictions.go @@ -1,6 +1,35 @@ package restriction -import "github.com/anyproto/anytype-heart/pkg/lib/pb/model" +import ( + "errors" + + "github.com/anyproto/anytype-heart/core/domain" + "github.com/anyproto/anytype-heart/pkg/lib/core/smartblock" + "github.com/anyproto/anytype-heart/pkg/lib/pb/model" +) + +var ErrRestricted = errors.New("restricted") + +type RestrictionHolder interface { + Type() smartblock.SmartBlockType + Layout() (model.ObjectTypeLayout, bool) + UniqueKey() domain.UniqueKey +} + +func GetRestrictions(rh RestrictionHolder) (r Restrictions) { + return Restrictions{ + Object: getObjectRestrictions(rh), + Dataview: getDataviewRestrictions(rh), + } +} + +func CheckRestrictions(rh RestrictionHolder, cr ...model.RestrictionsObjectRestriction) error { + r := getObjectRestrictions(rh) + if err := r.Check(cr...); err != nil { + return err + } + return nil +} type Restrictions struct { Object ObjectRestrictions @@ -9,7 +38,7 @@ type Restrictions struct { func (r Restrictions) Proto() *model.Restrictions { res := &model.Restrictions{ - Object: r.Object, + Object: r.Object.ToProto(), } if len(r.Dataview) > 0 { res.Dataview = make([]*model.RestrictionsDataviewRestrictions, 0, len(r.Dataview)) diff --git a/core/block/restriction/fixture_test.go b/core/block/restriction/restrictions_test.go similarity index 81% rename from core/block/restriction/fixture_test.go rename to core/block/restriction/restrictions_test.go index cb2cd0cbe..0f96017c6 100644 --- a/core/block/restriction/fixture_test.go +++ b/core/block/restriction/restrictions_test.go @@ -1,6 +1,10 @@ package restriction import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/anyproto/anytype-heart/core/domain" "github.com/anyproto/anytype-heart/pkg/lib/bundle" "github.com/anyproto/anytype-heart/pkg/lib/core/smartblock" @@ -49,10 +53,18 @@ func givenRestrictionHolder(sbType smartblock.SmartBlockType, typeKey domain.Typ if err == nil { layout = t.Layout } - uk, _ := domain.NewUniqueKey(sbType, "") + var uk domain.UniqueKey + if sbType != smartblock.SmartBlockTypePage { + uk, _ = domain.NewUniqueKey(sbType, "") + } return &restrictionHolder{ sbType: sbType, layout: layout, uniqueKey: uk, } } + +func TestService_GetRestrictions(t *testing.T) { + res := GetRestrictions(&restrictionHolder{sbType: smartblock.SmartBlockTypeBundledObjectType}) + assert.NotEmpty(t, res.Object) +} diff --git a/core/block/restriction/service.go b/core/block/restriction/service.go deleted file mode 100644 index dee8c09c0..000000000 --- a/core/block/restriction/service.go +++ /dev/null @@ -1,56 +0,0 @@ -package restriction - -import ( - "errors" - - "github.com/anyproto/any-sync/app" - - "github.com/anyproto/anytype-heart/core/domain" - "github.com/anyproto/anytype-heart/pkg/lib/core/smartblock" - "github.com/anyproto/anytype-heart/pkg/lib/pb/model" -) - -const CName = "restriction" - -var ErrRestricted = errors.New("restricted") - -type RestrictionHolder interface { - Type() smartblock.SmartBlockType - Layout() (model.ObjectTypeLayout, bool) - UniqueKey() domain.UniqueKey -} - -type Service interface { - GetRestrictions(RestrictionHolder) Restrictions - CheckRestrictions(rh RestrictionHolder, cr ...model.RestrictionsObjectRestriction) error - app.Component -} - -type service struct{} - -func New() Service { - return &service{} -} - -func (s *service) Init(*app.App) (err error) { - return -} - -func (s *service) Name() (name string) { - return CName -} - -func (s *service) GetRestrictions(rh RestrictionHolder) (r Restrictions) { - return Restrictions{ - Object: getObjectRestrictions(rh), - Dataview: getDataviewRestrictions(rh), - } -} - -func (s *service) CheckRestrictions(rh RestrictionHolder, cr ...model.RestrictionsObjectRestriction) error { - r := getObjectRestrictions(rh) - if err := r.Check(cr...); err != nil { - return err - } - return nil -} diff --git a/core/block/restriction/service_test.go b/core/block/restriction/service_test.go deleted file mode 100644 index 31a205c3a..000000000 --- a/core/block/restriction/service_test.go +++ /dev/null @@ -1,15 +0,0 @@ -package restriction - -import ( - "testing" - - "github.com/stretchr/testify/assert" - - coresb "github.com/anyproto/anytype-heart/pkg/lib/core/smartblock" -) - -func TestService_GetRestrictions(t *testing.T) { - s := New() - res := s.GetRestrictions(&restrictionHolder{sbType: coresb.SmartBlockTypeBundledObjectType}) - assert.NotEmpty(t, res.Object) -} diff --git a/core/block/service.go b/core/block/service.go index 9bc91fd32..f23343f8b 100644 --- a/core/block/service.go +++ b/core/block/service.go @@ -27,7 +27,6 @@ import ( "github.com/anyproto/anytype-heart/core/block/object/idresolver" "github.com/anyproto/anytype-heart/core/block/object/objectcreator" "github.com/anyproto/anytype-heart/core/block/process" - "github.com/anyproto/anytype-heart/core/block/restriction" "github.com/anyproto/anytype-heart/core/block/simple/bookmark" "github.com/anyproto/anytype-heart/core/block/template" "github.com/anyproto/anytype-heart/core/domain" @@ -91,7 +90,6 @@ type Service struct { eventSender event.Sender process process.Service objectStore objectstore.ObjectStore - restriction restriction.Service bookmark bookmarksvc.Service objectCreator objectcreator.Service templateService template.Service @@ -131,7 +129,6 @@ func (s *Service) Init(a *app.App) (err error) { s.process = a.MustComponent(process.CName).(process.Service) s.eventSender = a.MustComponent(event.CName).(event.Sender) s.objectStore = a.MustComponent(objectstore.CName).(objectstore.ObjectStore) - s.restriction = a.MustComponent(restriction.CName).(restriction.Service) s.bookmark = a.MustComponent("bookmark-importer").(bookmarksvc.Service) s.objectCreator = app.MustComponent[objectcreator.Service](a) s.templateService = app.MustComponent[template.Service](a) diff --git a/core/block/source/sourceimpl/date.go b/core/block/source/sourceimpl/date.go index a626bea83..242ec20d4 100644 --- a/core/block/source/sourceimpl/date.go +++ b/core/block/source/sourceimpl/date.go @@ -73,7 +73,7 @@ func (d *date) getDetails() (*domain.Details, error) { bundle.RelationKeyIconEmoji: domain.String("📅"), bundle.RelationKeySpaceId: domain.String(d.SpaceID()), bundle.RelationKeyTimestamp: domain.Int64(dateObject.Time().Unix()), - bundle.RelationKeyRestrictions: domain.Int64List(restrictions), + bundle.RelationKeyRestrictions: restrictions, }), nil } diff --git a/core/domain/app.go b/core/domain/app.go new file mode 100644 index 000000000..676e46875 --- /dev/null +++ b/core/domain/app.go @@ -0,0 +1,11 @@ +package domain + +import "github.com/anyproto/anytype-heart/pb" + +type CompState int + +const ( + CompStateAppWentBackground CompState = CompState(pb.RpcAppSetDeviceStateRequest_BACKGROUND) // 0 + CompStateAppWentForeground CompState = CompState(pb.RpcAppSetDeviceStateRequest_FOREGROUND) // 1 + CompStateAppClosingInitiated CompState = 2 // triggered by app +) diff --git a/core/indexer/fulltext.go b/core/indexer/fulltext.go index d5ab72bd9..8ccb0cc80 100644 --- a/core/indexer/fulltext.go +++ b/core/indexer/fulltext.go @@ -45,10 +45,10 @@ func (i *indexer) ForceFTIndex() { // ftLoop runs full-text indexer // MUST NOT be called more than once -func (i *indexer) ftLoopRoutine() { +func (i *indexer) ftLoopRoutine(ctx context.Context) { tickerDuration := ftIndexInterval ticker := time.NewTicker(tickerDuration) - ctx := i.runCtx + prevError := i.runFullTextIndexer(ctx) defer close(i.ftQueueFinished) var lastForceIndex time.Time diff --git a/core/indexer/indexer.go b/core/indexer/indexer.go index 16e9015e5..615532b97 100644 --- a/core/indexer/indexer.go +++ b/core/indexer/indexer.go @@ -32,7 +32,7 @@ const ( var log = logging.Logger("anytype-doc-indexer") func New() Indexer { - return &indexer{} + return new(indexer) } type Indexer interface { @@ -59,6 +59,7 @@ type indexer struct { runCtx context.Context runCtxCancel context.CancelFunc + ftQueueStop context.CancelFunc ftQueueFinished chan struct{} config *config.Config @@ -98,12 +99,20 @@ func (i *indexer) Run(context.Context) (err error) { return i.StartFullTextIndex() } +func (f *indexer) StateChange(state int) { + if state == int(domain.CompStateAppClosingInitiated) && f.ftQueueStop != nil { + f.ftQueueStop() + } +} + func (i *indexer) StartFullTextIndex() (err error) { if ftErr := i.ftInit(); ftErr != nil { log.Errorf("can't init ft: %v", ftErr) } i.ftQueueFinished = make(chan struct{}) - go i.ftLoopRoutine() + var ftCtx context.Context + ftCtx, i.ftQueueStop = context.WithCancel(i.runCtx) + go i.ftLoopRoutine(ftCtx) return } diff --git a/go.mod b/go.mod index 0cf8a13b2..9e216ae1f 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/anyproto/go-slip10 v1.0.0 github.com/anyproto/lexid v0.0.4 github.com/anyproto/protobuf v1.3.3-0.20240814124528-72b8c7e0e0f5 - github.com/anyproto/tantivy-go v1.0.1 + github.com/anyproto/tantivy-go v1.0.2 github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de github.com/avast/retry-go/v4 v4.6.1 github.com/chai2010/webp v1.1.2-0.20240612091223-aa1b379218b7 diff --git a/go.sum b/go.sum index 8d3ddca25..40df49670 100644 --- a/go.sum +++ b/go.sum @@ -112,8 +112,8 @@ github.com/anyproto/protobuf v1.3.3-0.20240814124528-72b8c7e0e0f5 h1:aY7tBzQ+z8H github.com/anyproto/protobuf v1.3.3-0.20240814124528-72b8c7e0e0f5/go.mod h1:5+PHE01DgsDPkralb8MYmGg2sPQahsqEJ9ue7ciDHKg= github.com/anyproto/ristretto v0.1.2-0.20240221153107-2b23839cc50c h1:GicoaTUyB2mtCIl3YMrO0OzysqRT5GA4vuvDsqEkhSM= github.com/anyproto/ristretto v0.1.2-0.20240221153107-2b23839cc50c/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= -github.com/anyproto/tantivy-go v1.0.1 h1:Uc9WqwGEDsVUEwRgSg4nmhoW20GjMUBKRz5FYw4r+ns= -github.com/anyproto/tantivy-go v1.0.1/go.mod h1:LtipOpRjGtcYMGcop6gQN7rVl1Pc6BlIs9BTMqeWMsk= +github.com/anyproto/tantivy-go v1.0.2 h1:PVr4Tt4QH0JAZDs2Z9T4KJfLL+0mgXizj2Y5LSvEQdU= +github.com/anyproto/tantivy-go v1.0.2/go.mod h1:LtipOpRjGtcYMGcop6gQN7rVl1Pc6BlIs9BTMqeWMsk= github.com/anyproto/zeroconf/v2 v2.2.1-0.20240228113933-f90a5cc4439d h1:5bj7nX/AS8sxGpTIrapE7PC4oPlhkHMwMqXlJbUHBlg= github.com/anyproto/zeroconf/v2 v2.2.1-0.20240228113933-f90a5cc4439d/go.mod h1:fuJqLnUwZTshS3U/bMRJ3+ow/v9oid1n0DmyYyNO1Xs= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= diff --git a/pkg/lib/gateway/gateway.go b/pkg/lib/gateway/gateway.go index b1ba13762..daf47cc35 100644 --- a/pkg/lib/gateway/gateway.go +++ b/pkg/lib/gateway/gateway.go @@ -21,7 +21,6 @@ import ( "github.com/anyproto/anytype-heart/core/domain" "github.com/anyproto/anytype-heart/core/files" "github.com/anyproto/anytype-heart/core/files/fileobject" - "github.com/anyproto/anytype-heart/pb" "github.com/anyproto/anytype-heart/pkg/lib/logging" "github.com/anyproto/anytype-heart/util/constant" "github.com/anyproto/anytype-heart/util/svg" @@ -130,10 +129,10 @@ func (g *gateway) Addr() string { } func (g *gateway) StateChange(state int) { - switch pb.RpcAppSetDeviceStateRequestDeviceState(state) { - case pb.RpcAppSetDeviceStateRequest_FOREGROUND: + switch domain.CompState(state) { + case domain.CompStateAppWentForeground: g.startServer() - case pb.RpcAppSetDeviceStateRequest_BACKGROUND: + case domain.CompStateAppWentBackground, domain.CompStateAppClosingInitiated: if err := g.stopServer(); err != nil { log.Errorf("err gateway close: %+v", err) } diff --git a/pkg/lib/localstore/ftsearch/ftsearch.go b/pkg/lib/localstore/ftsearch/ftsearch.go index c2e86d67d..39d7d82f0 100644 --- a/pkg/lib/localstore/ftsearch/ftsearch.go +++ b/pkg/lib/localstore/ftsearch/ftsearch.go @@ -16,11 +16,13 @@ package ftsearch import "C" import ( "context" + "errors" "fmt" "os" "path/filepath" "strings" "sync" + "sync/atomic" "time" "unicode" @@ -29,9 +31,11 @@ import ( tantivy "github.com/anyproto/tantivy-go" "github.com/valyala/fastjson" + "github.com/anyproto/anytype-heart/core/domain" "github.com/anyproto/anytype-heart/core/wallet" "github.com/anyproto/anytype-heart/metrics" "github.com/anyproto/anytype-heart/pkg/lib/bundle" + "github.com/anyproto/anytype-heart/pkg/lib/localstore/ftsearch/tantivycheck" "github.com/anyproto/anytype-heart/pkg/lib/logging" "github.com/anyproto/anytype-heart/util/text" ) @@ -40,7 +44,7 @@ const ( CName = "fts" ftsDir = "fts" ftsDir2 = "fts_tantivy" - ftsVer = "13" + ftsVer = "14" docLimit = 10000 fieldTitle = "Title" @@ -57,7 +61,10 @@ const ( tokenizerId = "SimpleIdTokenizer" ) -var log = logging.Logger("ftsearch") +var ( + log = logging.Logger("ftsearch") + ErrAppClosingInitiated = errors.New("app closing initiated") +) type FTSearch interface { app.ComponentRunnable @@ -93,14 +100,15 @@ type DocumentMatch struct { } type ftSearch struct { - rootPath string - ftsPath string - builderId string - index *tantivy.TantivyContext - parserPool *fastjson.ParserPool - mu sync.Mutex - blevePath string - lang tantivy.Language + rootPath string + ftsPath string + builderId string + index *tantivy.TantivyContext + parserPool *fastjson.ParserPool + mu sync.Mutex + blevePath string + lang tantivy.Language + appClosingInitiated atomic.Bool } func (f *ftSearch) ProvideStat() any { @@ -126,6 +134,9 @@ func (f *ftSearch) BatchDeleteObjects(ids []string) error { } f.mu.Lock() defer f.mu.Unlock() + if f.appClosingInitiated.Load() { + return ErrAppClosingInitiated + } start := time.Now() defer func() { spentMs := time.Since(start).Milliseconds() @@ -147,6 +158,9 @@ func (f *ftSearch) BatchDeleteObjects(ids []string) error { func (f *ftSearch) DeleteObject(objectId string) error { f.mu.Lock() defer f.mu.Unlock() + if f.appClosingInitiated.Load() { + return ErrAppClosingInitiated + } return f.index.DeleteDocuments(fieldIdRaw, objectId) } @@ -182,6 +196,24 @@ func (f *ftSearch) Name() (name string) { } func (f *ftSearch) Run(context.Context) error { + report, err := tantivycheck.Check(f.ftsPath) + if err != nil { + if !errors.Is(err, os.ErrNotExist) { + log.Warnf("tantivy index checking failed: %v", err) + } + } + if !report.IsOk() { + log.With("missingSegments", len(report.MissingSegments)). + With("missingDelFiles", len(report.MissingDelFiles)). + With("extraSegments", len(report.ExtraSegments)). + With("extraDelFiles", len(report.ExtraDelFiles)). + With("writerLockPresent", report.WriterLockPresent). + With("metaLockPresent", report.MetaLockPresent). + With("totalSegmentsInMeta", report.TotalSegmentsInMeta). + With("uniqueSegmentPrefixesOnDisk", report.UniqueSegmentPrefixesOnDisk). + Warnf("tantivy index is inconsistent state, dry run") + } + builder, err := tantivy.NewSchemaBuilder() if err != nil { return err @@ -328,6 +360,9 @@ func (f *ftSearch) tryToBuildSchema(schema *tantivy.Schema) (*tantivy.TantivyCon func (f *ftSearch) Index(doc SearchDoc) error { f.mu.Lock() defer f.mu.Unlock() + if f.appClosingInitiated.Load() { + return ErrAppClosingInitiated + } metrics.ObjectFTUpdatedCounter.Inc() tantivyDoc, err := f.convertDoc(doc) if err != nil { @@ -375,6 +410,9 @@ func (f *ftSearch) BatchIndex(ctx context.Context, docs []SearchDoc, deletedDocs } }() f.mu.Lock() + if f.appClosingInitiated.Load() { + return ErrAppClosingInitiated + } err = f.index.DeleteDocuments(fieldIdRaw, deletedDocs...) f.mu.Unlock() if err != nil { @@ -390,6 +428,9 @@ func (f *ftSearch) BatchIndex(ctx context.Context, docs []SearchDoc, deletedDocs } f.mu.Lock() defer f.mu.Unlock() + if f.appClosingInitiated.Load() { + return ErrAppClosingInitiated + } return f.index.AddAndConsumeDocuments(tantivyDocs...) } @@ -572,8 +613,10 @@ func (f *ftSearch) DocCount() (uint64, error) { func (f *ftSearch) Close(ctx context.Context) error { if f.index != nil { - f.index.Free() - f.index = nil + err := f.index.Close() + if err != nil { + log.Errorf("failed to close tantivy index: %v", err) + } } return nil } @@ -582,6 +625,12 @@ func (f *ftSearch) cleanupBleve() { _ = os.RemoveAll(f.blevePath) } +func (f *ftSearch) StateChange(state int) { + if state == int(domain.CompStateAppClosingInitiated) { + f.appClosingInitiated.Store(true) + } +} + func prepareQuery(query string) string { query = text.Truncate(query, 100, "") query = strings.ToLower(query) diff --git a/pkg/lib/localstore/ftsearch/tantivycheck/tantivycheck.go b/pkg/lib/localstore/ftsearch/tantivycheck/tantivycheck.go new file mode 100644 index 000000000..97fa3825d --- /dev/null +++ b/pkg/lib/localstore/ftsearch/tantivycheck/tantivycheck.go @@ -0,0 +1,230 @@ +// Package tantivycheck provides a DRY-RUN consistency check for Tantivy index +// directories. +// +// It verifies that +// +// - every segment listed in meta.json has files on disk +// - every expected ..del file exists +// - there are no extra segment prefixes on disk +// - there are no extra .del files on disk +// - the special lock files INDEX_WRITER_LOCK and META_LOCK are noted +// +// Nothing is modified on disk; you simply get a ConsistencyReport back. +package tantivycheck + +import ( + "encoding/json" + "fmt" + "io/fs" + "os" + "path/filepath" + "regexp" + "strconv" +) + +// ----------------------------------------------------------------------------- +// Package-level helpers (compiled once) +// ----------------------------------------------------------------------------- + +var ( + segPrefixRe = regexp.MustCompile(`^([0-9a-f]{32})(?:\..+)?$`) + delFileRe = regexp.MustCompile(`^([0-9a-f]{32})\.(\d+)\.del$`) +) + +// ----------------------------------------------------------------------------- +// Public API +// ----------------------------------------------------------------------------- + +// ConsistencyReport gathers all findings of the dry-run. +type ConsistencyReport struct { + // Segments present in meta.json but with no files on disk. + MissingSegments []string + // ..del files that meta.json expects but are absent. + MissingDelFiles []string + // Segment prefixes that exist on disk but are not referenced in meta.json. + ExtraSegments []string + // .del files on disk that are not referenced (wrong opstamp or orphan). + ExtraDelFiles []string + + // Lock-file information + WriterLockPresent bool // true if INDEX_WRITER_LOCK exists + MetaLockPresent bool // true if META_LOCK exists + + // Informational counters + TotalSegmentsInMeta int + UniqueSegmentPrefixesOnDisk int +} + +// Check runs the consistency test against dir and returns a report. +// +// It fails with an error if meta.json is absent or can’t be decoded. +func Check(dir string) (ConsistencyReport, error) { + // --------------------------------------------------------------------- + // 1) Parse meta.json + // --------------------------------------------------------------------- + meta, err := readMeta(filepath.Join(dir, "meta.json")) + if err != nil { + return ConsistencyReport{}, err + } + + // Build metaSegments: 32-hex-id (no dashes) → expected opstamp (nil if none) + metaSegments := make(map[string]*uint64, len(meta.Segments)) + for _, s := range meta.Segments { + id := stripDashes(s.SegmentID) + if s.Deletes != nil { + metaSegments[id] = &s.Deletes.Opstamp + } else { + metaSegments[id] = nil + } + } + + // --------------------------------------------------------------------- + // 2) Walk directory once + // --------------------------------------------------------------------- + segmentPrefixesDisk := map[string]struct{}{} + delFilesDisk := map[[2]string]struct{}{} // key = [segPrefix, opstamp] + + var writerLockPresent, metaLockPresent bool + + err = filepath.WalkDir(dir, func(path string, d fs.DirEntry, walkErr error) error { + if walkErr != nil { + return walkErr + } + if d.IsDir() { + return nil + } + + name := d.Name() + + switch name { + case "INDEX_WRITER_LOCK": + writerLockPresent = true + case "META_LOCK": + metaLockPresent = true + } + + if m := segPrefixRe.FindStringSubmatch(name); m != nil { + segmentPrefixesDisk[m[1]] = struct{}{} + } + if m := delFileRe.FindStringSubmatch(name); m != nil { + delFilesDisk[[2]string{m[1], m[2]}] = struct{}{} + } + return nil + }) + if err != nil { + return ConsistencyReport{}, fmt.Errorf("scanning directory: %w", err) + } + + // --------------------------------------------------------------------- + // 3) Compare sets + // --------------------------------------------------------------------- + var ( + missingSegments []string + extraSegments []string + missingDelFiles []string + extraDelFiles []string + ) + + // missing segments + for id := range metaSegments { + if _, ok := segmentPrefixesDisk[id]; !ok { + missingSegments = append(missingSegments, id) + } + } + + // extra segments + for id := range segmentPrefixesDisk { + if _, ok := metaSegments[id]; !ok { + extraSegments = append(extraSegments, id) + } + } + + // missing del files + for id, opPtr := range metaSegments { + if opPtr == nil { + continue // no deletes expected + } + opStr := strconv.FormatUint(*opPtr, 10) + if _, ok := delFilesDisk[[2]string{id, opStr}]; !ok { + missingDelFiles = append(missingDelFiles, fmt.Sprintf("%s.%s.del", id, opStr)) + } + } + + // extra del files + for key := range delFilesDisk { + id, opStr := key[0], key[1] + expectedOpPtr, segKnown := metaSegments[id] + if !segKnown || expectedOpPtr == nil || strconv.FormatUint(*expectedOpPtr, 10) != opStr { + extraDelFiles = append(extraDelFiles, fmt.Sprintf("%s.%s.del", id, opStr)) + } + } + + // --------------------------------------------------------------------- + // 4) Return aggregated report + // --------------------------------------------------------------------- + return ConsistencyReport{ + MissingSegments: missingSegments, + MissingDelFiles: missingDelFiles, + ExtraSegments: extraSegments, + ExtraDelFiles: extraDelFiles, + WriterLockPresent: writerLockPresent, + MetaLockPresent: metaLockPresent, + TotalSegmentsInMeta: len(metaSegments), + UniqueSegmentPrefixesOnDisk: len(segmentPrefixesDisk), + }, nil +} + +// IsOk returns true when the report is free of inconsistencies: +// +// - no segments are missing +// - no .del files are missing +// - no extra segments are present +// - no extra .del files are present +// +// The presence of INDEX_WRITER_LOCK or META_LOCK is *not* considered +// an inconsistency—these files are expected during normal operation and +// merely reported for information. +func (r *ConsistencyReport) IsOk() bool { + return len(r.MissingSegments) == 0 && + len(r.MissingDelFiles) == 0 && + len(r.ExtraSegments) == 0 && + len(r.ExtraDelFiles) == 0 && + !r.WriterLockPresent && + !r.MetaLockPresent +} + +// ----------------------------------------------------------------------------- +// Internal helpers +// ----------------------------------------------------------------------------- + +// metaFile mirrors only the parts of meta.json we need. +type metaFile struct { + Segments []struct { + SegmentID string `json:"segment_id"` + Deletes *struct { + Opstamp uint64 `json:"opstamp"` + } `json:"deletes"` + } `json:"segments"` +} + +func readMeta(path string) (*metaFile, error) { + b, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("reading %s: %w", path, err) + } + var m metaFile + if err := json.Unmarshal(b, &m); err != nil { + return nil, fmt.Errorf("decoding meta.json: %w", err) + } + return &m, nil +} + +func stripDashes(s string) string { + out := make([]byte, 0, len(s)) + for i := 0; i < len(s); i++ { + if s[i] != '-' { + out = append(out, s[i]) + } + } + return string(out) +} diff --git a/space/internal/components/migration/systemobjectreviser/systemobjectreviser.go b/space/internal/components/migration/systemobjectreviser/systemobjectreviser.go index fff61b49e..f7f766471 100644 --- a/space/internal/components/migration/systemobjectreviser/systemobjectreviser.go +++ b/space/internal/components/migration/systemobjectreviser/systemobjectreviser.go @@ -42,6 +42,7 @@ var ( } customObjectFilterKeys = []domain.RelationKey{ + bundle.RelationKeyRevision, bundle.RelationKeyIconOption, bundle.RelationKeyIconName, bundle.RelationKeyPluralName, diff --git a/tantivity_sha256.txt b/tantivity_sha256.txt index 2456d1167..c969cec02 100644 --- a/tantivity_sha256.txt +++ b/tantivity_sha256.txt @@ -1,12 +1,12 @@ -565dd299ef61edff535b20ec166f34f2399529b8254eed5d137a771a7b2b7c04 deps/libs/android-386.tar.gz -34d9c7d4a400653ddbcf28e64a5c8e055c08ab675df6b921cebedb4c5e667254 deps/libs/android-amd64.tar.gz -d90b501033763687f1ae136d69e284033594870c7143e50624799ff474e12f41 deps/libs/android-arm.tar.gz -3939f7ad6a1d08a0a559566d0ae5195739317a0023381bcb719603a7e2cac10d deps/libs/android-arm64.tar.gz -b7f7c3e82b1054a4c0755e7cd32f98d1dbe2ec55dab49f07e6620c8e2cd3ff83 deps/libs/darwin-amd64.tar.gz -40cdb051d2e063eb374e1a911ca7926beb4586c41a0347e430dc18fe5c216790 deps/libs/darwin-arm64.tar.gz -2dc93d6cb9356df9b5c330a6c566e1da1a4dfb32a17f1262080f2c1a875a5775 deps/libs/ios-amd64.tar.gz -2a949a364f29c5801c3c18607dbc14b5edefdcd002746ce94c89f297bb4f620c deps/libs/ios-arm64.tar.gz -b09afc40d37a37377579ec4096511fd88cc22cdd387518225a77f205c226130a deps/libs/ios-arm64-sim.tar.gz -122010a6f287a3d3b169a64c26918bd5745a1190c99638e7e91a664172781cf7 deps/libs/linux-amd64-musl.tar.gz -2b86930fcd938fe41413f4f915c90a2093d703179c61153ec4a007cdd2ed4a9c deps/libs/linux-arm64-musl.tar.gz -2cdefb8975651849f6c5f41c5a934035d959e797c6fdef9a5a946ec9052e3bee deps/libs/windows-amd64.tar.gz +bbde99ff75d9c20a7eefeea4ca6c5c77a75c1d1114ee7d53d77b78a49741f2e3 deps/libs/android-386.tar.gz +5c4599d29230f689ed2cfb21185548cb2fc3e8e61fd31e856c969fd4bdd7abc3 deps/libs/android-amd64.tar.gz +e4d16d8fce08b5de22d475c8e9be02473c07786b298601d6b96768f0fc7ecbd6 deps/libs/android-arm.tar.gz +8c67e2b0e8cf701ea6fecbe074f19ee48fb4321d02bc8a8b3fb11055397eb853 deps/libs/android-arm64.tar.gz +1278ecd5b7d73e36163e48c6b98b88a6e7c96a663ddba31eb168224a6921fd92 deps/libs/darwin-amd64.tar.gz +209429e1ffb2cdd55cedb90c74617d87aced121bc905802430f71b48b0ea6a43 deps/libs/darwin-arm64.tar.gz +101f9afde0a06339d81060f1bb77be63055d9674878cea5f7b142fcc3ca9658e deps/libs/ios-amd64.tar.gz +8b0f568bbf09e0ada76c9b331eab59ad9ed409aa37dec3b3d0538480babfeb5e deps/libs/ios-arm64.tar.gz +f3a90aff6eab8aa0e3e89a2866f0dbbd9c5ffb0d8a47d5aa4ad3d28afbd3416b deps/libs/ios-arm64-sim.tar.gz +74c3585022a962794c49f8f15606d3093684cad5543868e90d91d3ca876c45a8 deps/libs/linux-amd64-musl.tar.gz +2e1a052e7c12b3a0fa845c53c151228243ab6483c9fa3d83b97f34f40550fa6b deps/libs/linux-arm64-musl.tar.gz +5357ae4522bd826a8c53cb39d8c63b77e4cbc1683154c54b6f71542a25b1dcd1 deps/libs/windows-amd64.tar.gz diff --git a/util/builtinobjects/data/empty.zip b/util/builtinobjects/data/empty.zip index 40a3fa6c9..da2d1aa7f 100644 Binary files a/util/builtinobjects/data/empty.zip and b/util/builtinobjects/data/empty.zip differ