From db9b96f93348c5caf7b419d9f4e3b1ed32b1b0f3 Mon Sep 17 00:00:00 2001 From: Sergey Date: Mon, 5 Feb 2024 14:01:24 +0100 Subject: [PATCH] GO-2801: Do not migrate files that belong to another space as objects; add tests --- .mockery.yaml | 4 + core/files/fileobject/service.go | 33 + core/files/fileobject/service_test.go | 285 +++++++++ core/files/mock_files/mock_File.go | 307 ++++++++++ core/files/mock_files/mock_Service.go | 633 ++++++++++++++++++++ core/filestorage/filesync/filestore_mock.go | 30 + pkg/lib/localstore/filestore/files.go | 13 + tests/blockbuilder/constructors.go | 39 +- 8 files changed, 1340 insertions(+), 4 deletions(-) create mode 100644 core/files/fileobject/service_test.go create mode 100644 core/files/mock_files/mock_File.go create mode 100644 core/files/mock_files/mock_Service.go diff --git a/.mockery.yaml b/.mockery.yaml index c05e49d46..6decbfcb9 100644 --- a/.mockery.yaml +++ b/.mockery.yaml @@ -78,3 +78,7 @@ packages: github.com/anyproto/anytype-heart/core/files/fileobject: interfaces: Service: + github.com/anyproto/anytype-heart/core/files: + interfaces: + Service: + File: diff --git a/core/files/fileobject/service.go b/core/files/fileobject/service.go index f62074680..c8b102724 100644 --- a/core/files/fileobject/service.go +++ b/core/files/fileobject/service.go @@ -361,6 +361,18 @@ func (s *service) migrate(space clientspace.Space, objectId string, keys []*pb.C return fileObjectId } + // If due to some reason fileId is a file object id from another space, we should not migrate it + // This is definitely a bug, but we should not break things further. + exists, err := s.isFileExistInAnotherSpace(space.Id(), fileId) + if err != nil { + log.Errorf("checking that file exist in another space: %v", err) + return fileId + } + if exists { + log.With("fileObjectId", fileId).Error("found file object in another space") + return fileId + } + if len(fileKeys) == 0 { log.Warnf("no encryption keys for fileId %s", fileId) } @@ -390,6 +402,27 @@ func (s *service) migrate(space clientspace.Space, objectId string, keys []*pb.C return fileObjectId } +func (s *service) isFileExistInAnotherSpace(spaceId string, fileObjectId string) (bool, error) { + recs, _, err := s.objectStore.Query(database.Query{ + Filters: []*model.BlockContentDataviewFilter{ + { + RelationKey: bundle.RelationKeyId.String(), + Condition: model.BlockContentDataviewFilter_Equal, + Value: pbtypes.String(fileObjectId), + }, + { + RelationKey: bundle.RelationKeySpaceId.String(), + Condition: model.BlockContentDataviewFilter_NotEqual, + Value: pbtypes.String(spaceId), + }, + }, + }) + if err != nil { + return false, fmt.Errorf("query objects by file hash: %w", err) + } + return len(recs) > 0, nil +} + func (s *service) MigrateBlocks(st *state.State, spc source.Space, keys []*pb.ChangeFileKeys) { origin := objectorigin.FromDetails(st.Details()) st.Iterate(func(b simple.Block) (isContinue bool) { diff --git a/core/files/fileobject/service_test.go b/core/files/fileobject/service_test.go new file mode 100644 index 000000000..71fc27d69 --- /dev/null +++ b/core/files/fileobject/service_test.go @@ -0,0 +1,285 @@ +package fileobject + +import ( + "context" + "testing" + + "github.com/anyproto/any-sync/app" + "github.com/anyproto/any-sync/app/ocache" + "github.com/gogo/protobuf/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + "github.com/anyproto/anytype-heart/core/block/editor/state" + "github.com/anyproto/anytype-heart/core/domain" + "github.com/anyproto/anytype-heart/core/domain/objectorigin" + "github.com/anyproto/anytype-heart/core/files/mock_files" + "github.com/anyproto/anytype-heart/pb" + "github.com/anyproto/anytype-heart/pkg/lib/bundle" + "github.com/anyproto/anytype-heart/pkg/lib/datastore" + "github.com/anyproto/anytype-heart/pkg/lib/localstore/filestore" + "github.com/anyproto/anytype-heart/pkg/lib/localstore/objectstore" + "github.com/anyproto/anytype-heart/pkg/lib/pb/model" + "github.com/anyproto/anytype-heart/pkg/lib/pb/storage" + "github.com/anyproto/anytype-heart/space/clientspace" + "github.com/anyproto/anytype-heart/space/clientspace/mock_clientspace" + bb "github.com/anyproto/anytype-heart/tests/blockbuilder" + "github.com/anyproto/anytype-heart/tests/testutil" + "github.com/anyproto/anytype-heart/util/pbtypes" +) + +type fixture struct { + objectStore *objectstore.StoreFixture + fileService *mock_files.MockService + objectCreator *objectCreatorStub + + *service +} + +func newFixture(t *testing.T) *fixture { + fileStore := filestore.New() + objectStore := objectstore.NewStoreFixture(t) + fileService := mock_files.NewMockService(t) + objectCreator := &objectCreatorStub{} + + ctx := context.Background() + a := new(app.App) + a.Register(datastore.NewInMemory()) + a.Register(fileStore) + a.Register(objectStore) + a.Register(testutil.PrepareMock(ctx, a, fileService)) + + err := a.Start(ctx) + require.NoError(t, err) + + svc := &service{ + objectStore: objectStore, + fileStore: fileStore, + fileService: fileService, + objectCreator: objectCreator, + } + + fx := &fixture{ + objectStore: objectStore, + fileService: fileService, + objectCreator: objectCreator, + + service: svc, + } + return fx +} + +type objectCreatorStub struct { + objectId string + creationState *state.State + details *types.Struct +} + +func (c *objectCreatorStub) CreateSmartBlockFromStateInSpace(ctx context.Context, space clientspace.Space, objectTypeKeys []domain.TypeKey, createState *state.State) (id string, newDetails *types.Struct, err error) { + c.creationState = createState + return c.objectId, c.details, nil +} + +func TestMigration(t *testing.T) { + t.Run("do not migrate empty file ids", func(t *testing.T) { + fx := newFixture(t) + + fileId := "" + st := testutil.BuildStateFromAST( + bb.Root( + bb.ID("root"), + bb.Children(bb.File("", bb.FileHash(fileId))), + ), + ) + + space := mock_clientspace.NewMockSpace(t) + + fx.MigrateBlocks(st, space, nil) + }) + + t.Run("do not migrate object itself", func(t *testing.T) { + fx := newFixture(t) + + objectId := "objectId" + fileId := objectId + st := testutil.BuildStateFromAST( + bb.Root( + bb.ID(objectId), + bb.Children(bb.File("", bb.FileHash(fileId))), + ), + ) + + space := mock_clientspace.NewMockSpace(t) + + fx.MigrateBlocks(st, space, nil) + }) + + t.Run("do not migrate already migrated file: fileId equals to objectId", func(t *testing.T) { + fx := newFixture(t) + + objectId := "objectId" + fileId := domain.FileId("fileId") + st := testutil.BuildStateFromAST( + bb.Root( + bb.ID(objectId), + bb.Children(bb.File("", bb.FileHash(fileId.String()))), + ), + ) + + space := mock_clientspace.NewMockSpace(t) + space.EXPECT().Do(fileId.String(), mock.Anything).Return(nil) + + fx.MigrateBlocks(st, space, nil) + }) + + t.Run("do not migrate already migrated file: objectId is found by fileId in current space", func(t *testing.T) { + fx := newFixture(t) + + spaceId := "spaceId" + objectId := "objectId" + fileId := domain.FileId("fileId") + expectedFileObjectId := "fileObjectId" + st := testutil.BuildStateFromAST( + bb.Root( + bb.ID(objectId), + ), + ) + st.SetDetailAndBundledRelation(bundle.RelationKeyAttachments, pbtypes.StringList([]string{fileId.String()})) + + space := mock_clientspace.NewMockSpace(t) + space.EXPECT().Do(fileId.String(), mock.Anything).Return(ocache.ErrNotExists) + space.EXPECT().Id().Return(spaceId) + + fx.objectStore.AddObjects(t, []objectstore.TestObject{ + { + bundle.RelationKeyId: pbtypes.String(expectedFileObjectId), + bundle.RelationKeyFileId: pbtypes.String(fileId.String()), + bundle.RelationKeySpaceId: pbtypes.String(spaceId), + }, + }) + + fx.MigrateBlocks(st, space, nil) + fx.MigrateDetails(st, space, nil) + + wantState := testutil.BuildStateFromAST( + bb.Root( + bb.ID(objectId), + ), + ) + wantState.SetDetailAndBundledRelation(bundle.RelationKeyAttachments, pbtypes.StringList([]string{expectedFileObjectId})) + + bb.AssertTreesEqual(t, wantState.Blocks(), st.Blocks()) + assert.Equal(t, wantState.Details(), st.Details()) + }) + + t.Run("do not migrate already migrated file: objectId is found by fileId in another space", func(t *testing.T) { + fx := newFixture(t) + + spaceId := "spaceId" + objectId := "objectId" + fileId := domain.FileId("fileObjectIdFromAnotherSpace") + st := testutil.BuildStateFromAST( + bb.Root( + bb.ID(objectId), + ), + ) + st.SetDetailAndBundledRelation(bundle.RelationKeyAttachments, pbtypes.StringList([]string{fileId.String()})) + + space := mock_clientspace.NewMockSpace(t) + space.EXPECT().Do(fileId.String(), mock.Anything).Return(ocache.ErrNotExists) + space.EXPECT().Id().Return(spaceId) + + fx.objectStore.AddObjects(t, []objectstore.TestObject{ + { + bundle.RelationKeyId: pbtypes.String(fileId.String()), + bundle.RelationKeyFileId: pbtypes.String("fileId"), + bundle.RelationKeySpaceId: pbtypes.String("spaceId2"), + }, + }) + + fx.MigrateBlocks(st, space, nil) + fx.MigrateDetails(st, space, nil) + + wantState := testutil.BuildStateFromAST( + bb.Root( + bb.ID(objectId), + ), + ) + wantState.SetDetailAndBundledRelation(bundle.RelationKeyAttachments, pbtypes.StringList([]string{fileId.String()})) + + bb.AssertTreesEqual(t, wantState.Blocks(), st.Blocks()) + assert.Equal(t, wantState.Details(), st.Details()) + }) + + t.Run("when file is not migrated yet: derive new object", func(t *testing.T) { + fx := newFixture(t) + + spaceId := "spaceId" + objectId := "objectId" + fileId := domain.FileId("fileId") + expectedFileObjectId := "fileObjectId" + st := testutil.BuildStateFromAST( + bb.Root( + bb.ID(objectId), + bb.Children( + bb.File("", bb.FileHash(fileId.String())), + bb.Text("sample text", bb.TextIconImage(fileId.String())), + ), + ), + ) + st.SetDetailAndBundledRelation(bundle.RelationKeyIconImage, pbtypes.StringList([]string{fileId.String()})) + + space := mock_clientspace.NewMockSpace(t) + space.EXPECT().Do(fileId.String(), mock.Anything).Return(ocache.ErrNotExists) + space.EXPECT().Id().Return(spaceId) + space.EXPECT().DeriveObjectIdWithAccountSignature(mock.Anything, mock.Anything).Return(expectedFileObjectId, nil) + + origin := objectorigin.Import(model.Import_Html) + err := fx.fileStore.SetFileOrigin(fileId, origin) + require.NoError(t, err) + + file := mock_files.NewMockFile(t) + file.EXPECT().Info().Return(&storage.FileInfo{ + Media: "text/html", + }) + file.EXPECT().Details(mock.Anything).Return(&types.Struct{Fields: map[string]*types.Value{}}, bundle.TypeKeyFile, nil) + + fx.fileService.EXPECT().FileByHash(mock.Anything, domain.FullFileId{SpaceId: spaceId, FileId: fileId}).Return(file, nil) + + fx.objectCreator.objectId = expectedFileObjectId + + keys := map[string]string{ + "filepath": "key", + } + keysChanges := []*pb.ChangeFileKeys{ + { + Hash: fileId.String(), + Keys: keys, + }, + } + fx.MigrateBlocks(st, space, keysChanges) + fx.MigrateDetails(st, space, keysChanges) + + assert.Equal(t, pbtypes.GetInt64(fx.objectCreator.creationState.Details(), bundle.RelationKeyOrigin.String()), int64(origin.Origin)) + assert.Equal(t, pbtypes.GetInt64(fx.objectCreator.creationState.Details(), bundle.RelationKeyImportType.String()), int64(origin.ImportType)) + assert.Equal(t, state.FileInfo{ + FileId: fileId, + EncryptionKeys: keys, + }, fx.objectCreator.creationState.GetFileInfo()) + + wantState := testutil.BuildStateFromAST( + bb.Root( + bb.ID(objectId), + bb.Children( + bb.File(expectedFileObjectId, bb.FileHash(fileId.String())), + bb.Text("sample text", bb.TextIconImage(expectedFileObjectId)), + ), + ), + ) + wantState.SetDetailAndBundledRelation(bundle.RelationKeyIconImage, pbtypes.StringList([]string{expectedFileObjectId})) + + bb.AssertTreesEqual(t, wantState.Blocks(), st.Blocks()) + assert.Equal(t, wantState.Details(), st.Details()) + }) +} diff --git a/core/files/mock_files/mock_File.go b/core/files/mock_files/mock_File.go new file mode 100644 index 000000000..c2bf31b1d --- /dev/null +++ b/core/files/mock_files/mock_File.go @@ -0,0 +1,307 @@ +// Code generated by mockery. DO NOT EDIT. + +package mock_files + +import ( + context "context" + + domain "github.com/anyproto/anytype-heart/core/domain" + files "github.com/anyproto/anytype-heart/core/files" + + io "io" + + mock "github.com/stretchr/testify/mock" + + storage "github.com/anyproto/anytype-heart/pkg/lib/pb/storage" + + types "github.com/gogo/protobuf/types" +) + +// MockFile is an autogenerated mock type for the File type +type MockFile struct { + mock.Mock +} + +type MockFile_Expecter struct { + mock *mock.Mock +} + +func (_m *MockFile) EXPECT() *MockFile_Expecter { + return &MockFile_Expecter{mock: &_m.Mock} +} + +// Details provides a mock function with given fields: ctx +func (_m *MockFile) Details(ctx context.Context) (*types.Struct, domain.TypeKey, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for Details") + } + + var r0 *types.Struct + var r1 domain.TypeKey + var r2 error + if rf, ok := ret.Get(0).(func(context.Context) (*types.Struct, domain.TypeKey, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) *types.Struct); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.Struct) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) domain.TypeKey); ok { + r1 = rf(ctx) + } else { + r1 = ret.Get(1).(domain.TypeKey) + } + + if rf, ok := ret.Get(2).(func(context.Context) error); ok { + r2 = rf(ctx) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// MockFile_Details_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Details' +type MockFile_Details_Call struct { + *mock.Call +} + +// Details is a helper method to define mock.On call +// - ctx context.Context +func (_e *MockFile_Expecter) Details(ctx interface{}) *MockFile_Details_Call { + return &MockFile_Details_Call{Call: _e.mock.On("Details", ctx)} +} + +func (_c *MockFile_Details_Call) Run(run func(ctx context.Context)) *MockFile_Details_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *MockFile_Details_Call) Return(_a0 *types.Struct, _a1 domain.TypeKey, _a2 error) *MockFile_Details_Call { + _c.Call.Return(_a0, _a1, _a2) + return _c +} + +func (_c *MockFile_Details_Call) RunAndReturn(run func(context.Context) (*types.Struct, domain.TypeKey, error)) *MockFile_Details_Call { + _c.Call.Return(run) + return _c +} + +// FileId provides a mock function with given fields: +func (_m *MockFile) FileId() domain.FileId { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for FileId") + } + + var r0 domain.FileId + if rf, ok := ret.Get(0).(func() domain.FileId); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(domain.FileId) + } + + return r0 +} + +// MockFile_FileId_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FileId' +type MockFile_FileId_Call struct { + *mock.Call +} + +// FileId is a helper method to define mock.On call +func (_e *MockFile_Expecter) FileId() *MockFile_FileId_Call { + return &MockFile_FileId_Call{Call: _e.mock.On("FileId")} +} + +func (_c *MockFile_FileId_Call) Run(run func()) *MockFile_FileId_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MockFile_FileId_Call) Return(_a0 domain.FileId) *MockFile_FileId_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockFile_FileId_Call) RunAndReturn(run func() domain.FileId) *MockFile_FileId_Call { + _c.Call.Return(run) + return _c +} + +// Info provides a mock function with given fields: +func (_m *MockFile) Info() *storage.FileInfo { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Info") + } + + var r0 *storage.FileInfo + if rf, ok := ret.Get(0).(func() *storage.FileInfo); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*storage.FileInfo) + } + } + + return r0 +} + +// MockFile_Info_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Info' +type MockFile_Info_Call struct { + *mock.Call +} + +// Info is a helper method to define mock.On call +func (_e *MockFile_Expecter) Info() *MockFile_Info_Call { + return &MockFile_Info_Call{Call: _e.mock.On("Info")} +} + +func (_c *MockFile_Info_Call) Run(run func()) *MockFile_Info_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MockFile_Info_Call) Return(_a0 *storage.FileInfo) *MockFile_Info_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockFile_Info_Call) RunAndReturn(run func() *storage.FileInfo) *MockFile_Info_Call { + _c.Call.Return(run) + return _c +} + +// Meta provides a mock function with given fields: +func (_m *MockFile) Meta() *files.FileMeta { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Meta") + } + + var r0 *files.FileMeta + if rf, ok := ret.Get(0).(func() *files.FileMeta); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*files.FileMeta) + } + } + + return r0 +} + +// MockFile_Meta_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Meta' +type MockFile_Meta_Call struct { + *mock.Call +} + +// Meta is a helper method to define mock.On call +func (_e *MockFile_Expecter) Meta() *MockFile_Meta_Call { + return &MockFile_Meta_Call{Call: _e.mock.On("Meta")} +} + +func (_c *MockFile_Meta_Call) Run(run func()) *MockFile_Meta_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MockFile_Meta_Call) Return(_a0 *files.FileMeta) *MockFile_Meta_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockFile_Meta_Call) RunAndReturn(run func() *files.FileMeta) *MockFile_Meta_Call { + _c.Call.Return(run) + return _c +} + +// Reader provides a mock function with given fields: ctx +func (_m *MockFile) Reader(ctx context.Context) (io.ReadSeeker, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for Reader") + } + + var r0 io.ReadSeeker + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (io.ReadSeeker, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) io.ReadSeeker); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(io.ReadSeeker) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockFile_Reader_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Reader' +type MockFile_Reader_Call struct { + *mock.Call +} + +// Reader is a helper method to define mock.On call +// - ctx context.Context +func (_e *MockFile_Expecter) Reader(ctx interface{}) *MockFile_Reader_Call { + return &MockFile_Reader_Call{Call: _e.mock.On("Reader", ctx)} +} + +func (_c *MockFile_Reader_Call) Run(run func(ctx context.Context)) *MockFile_Reader_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *MockFile_Reader_Call) Return(_a0 io.ReadSeeker, _a1 error) *MockFile_Reader_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockFile_Reader_Call) RunAndReturn(run func(context.Context) (io.ReadSeeker, error)) *MockFile_Reader_Call { + _c.Call.Return(run) + return _c +} + +// NewMockFile creates a new instance of MockFile. 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 NewMockFile(t interface { + mock.TestingT + Cleanup(func()) +}) *MockFile { + mock := &MockFile{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/core/files/mock_files/mock_Service.go b/core/files/mock_files/mock_Service.go new file mode 100644 index 000000000..dd60b5509 --- /dev/null +++ b/core/files/mock_files/mock_Service.go @@ -0,0 +1,633 @@ +// Code generated by mockery. DO NOT EDIT. + +package mock_files + +import ( + context "context" + + app "github.com/anyproto/any-sync/app" + + domain "github.com/anyproto/anytype-heart/core/domain" + + files "github.com/anyproto/anytype-heart/core/files" + + mock "github.com/stretchr/testify/mock" + + pb "github.com/anyproto/anytype-heart/pb" +) + +// 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} +} + +// FileAdd provides a mock function with given fields: ctx, spaceID, options +func (_m *MockService) FileAdd(ctx context.Context, spaceID string, options ...files.AddOption) (*files.FileAddResult, error) { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, spaceID) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for FileAdd") + } + + var r0 *files.FileAddResult + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, ...files.AddOption) (*files.FileAddResult, error)); ok { + return rf(ctx, spaceID, options...) + } + if rf, ok := ret.Get(0).(func(context.Context, string, ...files.AddOption) *files.FileAddResult); ok { + r0 = rf(ctx, spaceID, options...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*files.FileAddResult) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, ...files.AddOption) error); ok { + r1 = rf(ctx, spaceID, options...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockService_FileAdd_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FileAdd' +type MockService_FileAdd_Call struct { + *mock.Call +} + +// FileAdd is a helper method to define mock.On call +// - ctx context.Context +// - spaceID string +// - options ...files.AddOption +func (_e *MockService_Expecter) FileAdd(ctx interface{}, spaceID interface{}, options ...interface{}) *MockService_FileAdd_Call { + return &MockService_FileAdd_Call{Call: _e.mock.On("FileAdd", + append([]interface{}{ctx, spaceID}, options...)...)} +} + +func (_c *MockService_FileAdd_Call) Run(run func(ctx context.Context, spaceID string, options ...files.AddOption)) *MockService_FileAdd_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]files.AddOption, len(args)-2) + for i, a := range args[2:] { + if a != nil { + variadicArgs[i] = a.(files.AddOption) + } + } + run(args[0].(context.Context), args[1].(string), variadicArgs...) + }) + return _c +} + +func (_c *MockService_FileAdd_Call) Return(_a0 *files.FileAddResult, _a1 error) *MockService_FileAdd_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockService_FileAdd_Call) RunAndReturn(run func(context.Context, string, ...files.AddOption) (*files.FileAddResult, error)) *MockService_FileAdd_Call { + _c.Call.Return(run) + return _c +} + +// FileByHash provides a mock function with given fields: ctx, id +func (_m *MockService) FileByHash(ctx context.Context, id domain.FullFileId) (files.File, error) { + ret := _m.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for FileByHash") + } + + var r0 files.File + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, domain.FullFileId) (files.File, error)); ok { + return rf(ctx, id) + } + if rf, ok := ret.Get(0).(func(context.Context, domain.FullFileId) files.File); ok { + r0 = rf(ctx, id) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(files.File) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, domain.FullFileId) error); ok { + r1 = rf(ctx, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockService_FileByHash_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FileByHash' +type MockService_FileByHash_Call struct { + *mock.Call +} + +// FileByHash is a helper method to define mock.On call +// - ctx context.Context +// - id domain.FullFileId +func (_e *MockService_Expecter) FileByHash(ctx interface{}, id interface{}) *MockService_FileByHash_Call { + return &MockService_FileByHash_Call{Call: _e.mock.On("FileByHash", ctx, id)} +} + +func (_c *MockService_FileByHash_Call) Run(run func(ctx context.Context, id domain.FullFileId)) *MockService_FileByHash_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(domain.FullFileId)) + }) + return _c +} + +func (_c *MockService_FileByHash_Call) Return(_a0 files.File, _a1 error) *MockService_FileByHash_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockService_FileByHash_Call) RunAndReturn(run func(context.Context, domain.FullFileId) (files.File, error)) *MockService_FileByHash_Call { + _c.Call.Return(run) + return _c +} + +// FileGetKeys provides a mock function with given fields: id +func (_m *MockService) FileGetKeys(id domain.FullFileId) (*domain.FileEncryptionKeys, error) { + ret := _m.Called(id) + + if len(ret) == 0 { + panic("no return value specified for FileGetKeys") + } + + var r0 *domain.FileEncryptionKeys + var r1 error + if rf, ok := ret.Get(0).(func(domain.FullFileId) (*domain.FileEncryptionKeys, error)); ok { + return rf(id) + } + if rf, ok := ret.Get(0).(func(domain.FullFileId) *domain.FileEncryptionKeys); ok { + r0 = rf(id) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*domain.FileEncryptionKeys) + } + } + + if rf, ok := ret.Get(1).(func(domain.FullFileId) error); ok { + r1 = rf(id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockService_FileGetKeys_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FileGetKeys' +type MockService_FileGetKeys_Call struct { + *mock.Call +} + +// FileGetKeys is a helper method to define mock.On call +// - id domain.FullFileId +func (_e *MockService_Expecter) FileGetKeys(id interface{}) *MockService_FileGetKeys_Call { + return &MockService_FileGetKeys_Call{Call: _e.mock.On("FileGetKeys", id)} +} + +func (_c *MockService_FileGetKeys_Call) Run(run func(id domain.FullFileId)) *MockService_FileGetKeys_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(domain.FullFileId)) + }) + return _c +} + +func (_c *MockService_FileGetKeys_Call) Return(_a0 *domain.FileEncryptionKeys, _a1 error) *MockService_FileGetKeys_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockService_FileGetKeys_Call) RunAndReturn(run func(domain.FullFileId) (*domain.FileEncryptionKeys, error)) *MockService_FileGetKeys_Call { + _c.Call.Return(run) + return _c +} + +// FileOffload provides a mock function with given fields: ctx, id +func (_m *MockService) FileOffload(ctx context.Context, id domain.FullFileId) (uint64, error) { + ret := _m.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for FileOffload") + } + + var r0 uint64 + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, domain.FullFileId) (uint64, error)); ok { + return rf(ctx, id) + } + if rf, ok := ret.Get(0).(func(context.Context, domain.FullFileId) uint64); ok { + r0 = rf(ctx, id) + } else { + r0 = ret.Get(0).(uint64) + } + + if rf, ok := ret.Get(1).(func(context.Context, domain.FullFileId) error); ok { + r1 = rf(ctx, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockService_FileOffload_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FileOffload' +type MockService_FileOffload_Call struct { + *mock.Call +} + +// FileOffload is a helper method to define mock.On call +// - ctx context.Context +// - id domain.FullFileId +func (_e *MockService_Expecter) FileOffload(ctx interface{}, id interface{}) *MockService_FileOffload_Call { + return &MockService_FileOffload_Call{Call: _e.mock.On("FileOffload", ctx, id)} +} + +func (_c *MockService_FileOffload_Call) Run(run func(ctx context.Context, id domain.FullFileId)) *MockService_FileOffload_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(domain.FullFileId)) + }) + return _c +} + +func (_c *MockService_FileOffload_Call) Return(totalSize uint64, err error) *MockService_FileOffload_Call { + _c.Call.Return(totalSize, err) + return _c +} + +func (_c *MockService_FileOffload_Call) RunAndReturn(run func(context.Context, domain.FullFileId) (uint64, error)) *MockService_FileOffload_Call { + _c.Call.Return(run) + return _c +} + +// GetNodeUsage provides a mock function with given fields: ctx +func (_m *MockService) GetNodeUsage(ctx context.Context) (*files.NodeUsageResponse, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for GetNodeUsage") + } + + var r0 *files.NodeUsageResponse + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (*files.NodeUsageResponse, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) *files.NodeUsageResponse); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*files.NodeUsageResponse) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockService_GetNodeUsage_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetNodeUsage' +type MockService_GetNodeUsage_Call struct { + *mock.Call +} + +// GetNodeUsage is a helper method to define mock.On call +// - ctx context.Context +func (_e *MockService_Expecter) GetNodeUsage(ctx interface{}) *MockService_GetNodeUsage_Call { + return &MockService_GetNodeUsage_Call{Call: _e.mock.On("GetNodeUsage", ctx)} +} + +func (_c *MockService_GetNodeUsage_Call) Run(run func(ctx context.Context)) *MockService_GetNodeUsage_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *MockService_GetNodeUsage_Call) Return(_a0 *files.NodeUsageResponse, _a1 error) *MockService_GetNodeUsage_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockService_GetNodeUsage_Call) RunAndReturn(run func(context.Context) (*files.NodeUsageResponse, error)) *MockService_GetNodeUsage_Call { + _c.Call.Return(run) + return _c +} + +// GetSpaceUsage provides a mock function with given fields: ctx, spaceID +func (_m *MockService) GetSpaceUsage(ctx context.Context, spaceID string) (*pb.RpcFileSpaceUsageResponseUsage, error) { + ret := _m.Called(ctx, spaceID) + + if len(ret) == 0 { + panic("no return value specified for GetSpaceUsage") + } + + var r0 *pb.RpcFileSpaceUsageResponseUsage + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (*pb.RpcFileSpaceUsageResponseUsage, error)); ok { + return rf(ctx, spaceID) + } + if rf, ok := ret.Get(0).(func(context.Context, string) *pb.RpcFileSpaceUsageResponseUsage); ok { + r0 = rf(ctx, spaceID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*pb.RpcFileSpaceUsageResponseUsage) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, spaceID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockService_GetSpaceUsage_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetSpaceUsage' +type MockService_GetSpaceUsage_Call struct { + *mock.Call +} + +// GetSpaceUsage is a helper method to define mock.On call +// - ctx context.Context +// - spaceID string +func (_e *MockService_Expecter) GetSpaceUsage(ctx interface{}, spaceID interface{}) *MockService_GetSpaceUsage_Call { + return &MockService_GetSpaceUsage_Call{Call: _e.mock.On("GetSpaceUsage", ctx, spaceID)} +} + +func (_c *MockService_GetSpaceUsage_Call) Run(run func(ctx context.Context, spaceID string)) *MockService_GetSpaceUsage_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *MockService_GetSpaceUsage_Call) Return(_a0 *pb.RpcFileSpaceUsageResponseUsage, _a1 error) *MockService_GetSpaceUsage_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockService_GetSpaceUsage_Call) RunAndReturn(run func(context.Context, string) (*pb.RpcFileSpaceUsageResponseUsage, error)) *MockService_GetSpaceUsage_Call { + _c.Call.Return(run) + return _c +} + +// ImageAdd provides a mock function with given fields: ctx, spaceID, options +func (_m *MockService) ImageAdd(ctx context.Context, spaceID string, options ...files.AddOption) (*files.ImageAddResult, error) { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, spaceID) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for ImageAdd") + } + + var r0 *files.ImageAddResult + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, ...files.AddOption) (*files.ImageAddResult, error)); ok { + return rf(ctx, spaceID, options...) + } + if rf, ok := ret.Get(0).(func(context.Context, string, ...files.AddOption) *files.ImageAddResult); ok { + r0 = rf(ctx, spaceID, options...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*files.ImageAddResult) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, ...files.AddOption) error); ok { + r1 = rf(ctx, spaceID, options...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockService_ImageAdd_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ImageAdd' +type MockService_ImageAdd_Call struct { + *mock.Call +} + +// ImageAdd is a helper method to define mock.On call +// - ctx context.Context +// - spaceID string +// - options ...files.AddOption +func (_e *MockService_Expecter) ImageAdd(ctx interface{}, spaceID interface{}, options ...interface{}) *MockService_ImageAdd_Call { + return &MockService_ImageAdd_Call{Call: _e.mock.On("ImageAdd", + append([]interface{}{ctx, spaceID}, options...)...)} +} + +func (_c *MockService_ImageAdd_Call) Run(run func(ctx context.Context, spaceID string, options ...files.AddOption)) *MockService_ImageAdd_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]files.AddOption, len(args)-2) + for i, a := range args[2:] { + if a != nil { + variadicArgs[i] = a.(files.AddOption) + } + } + run(args[0].(context.Context), args[1].(string), variadicArgs...) + }) + return _c +} + +func (_c *MockService_ImageAdd_Call) Return(_a0 *files.ImageAddResult, _a1 error) *MockService_ImageAdd_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockService_ImageAdd_Call) RunAndReturn(run func(context.Context, string, ...files.AddOption) (*files.ImageAddResult, error)) *MockService_ImageAdd_Call { + _c.Call.Return(run) + return _c +} + +// ImageByHash provides a mock function with given fields: ctx, id +func (_m *MockService) ImageByHash(ctx context.Context, id domain.FullFileId) (files.Image, error) { + ret := _m.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for ImageByHash") + } + + var r0 files.Image + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, domain.FullFileId) (files.Image, error)); ok { + return rf(ctx, id) + } + if rf, ok := ret.Get(0).(func(context.Context, domain.FullFileId) files.Image); ok { + r0 = rf(ctx, id) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(files.Image) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, domain.FullFileId) error); ok { + r1 = rf(ctx, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockService_ImageByHash_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ImageByHash' +type MockService_ImageByHash_Call struct { + *mock.Call +} + +// ImageByHash is a helper method to define mock.On call +// - ctx context.Context +// - id domain.FullFileId +func (_e *MockService_Expecter) ImageByHash(ctx interface{}, id interface{}) *MockService_ImageByHash_Call { + return &MockService_ImageByHash_Call{Call: _e.mock.On("ImageByHash", ctx, id)} +} + +func (_c *MockService_ImageByHash_Call) Run(run func(ctx context.Context, id domain.FullFileId)) *MockService_ImageByHash_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(domain.FullFileId)) + }) + return _c +} + +func (_c *MockService_ImageByHash_Call) Return(_a0 files.Image, _a1 error) *MockService_ImageByHash_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockService_ImageByHash_Call) RunAndReturn(run func(context.Context, domain.FullFileId) (files.Image, error)) *MockService_ImageByHash_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/filestorage/filesync/filestore_mock.go b/core/filestorage/filesync/filestore_mock.go index 82bda8439..87b43a2e5 100644 --- a/core/filestorage/filesync/filestore_mock.go +++ b/core/filestorage/filesync/filestore_mock.go @@ -15,6 +15,7 @@ import ( app "github.com/anyproto/any-sync/app" domain "github.com/anyproto/anytype-heart/core/domain" + objectorigin "github.com/anyproto/anytype-heart/core/domain/objectorigin" localstore "github.com/anyproto/anytype-heart/pkg/lib/localstore" storage "github.com/anyproto/anytype-heart/pkg/lib/pb/storage" gomock "go.uber.org/mock/gomock" @@ -152,6 +153,21 @@ func (mr *MockFileStoreMockRecorder) GetFileKeys(arg0 any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFileKeys", reflect.TypeOf((*MockFileStore)(nil).GetFileKeys), arg0) } +// GetFileOrigin mocks base method. +func (m *MockFileStore) GetFileOrigin(arg0 domain.FileId) (objectorigin.ObjectOrigin, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetFileOrigin", arg0) + ret0, _ := ret[0].(objectorigin.ObjectOrigin) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetFileOrigin indicates an expected call of GetFileOrigin. +func (mr *MockFileStoreMockRecorder) GetFileOrigin(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFileOrigin", reflect.TypeOf((*MockFileStore)(nil).GetFileOrigin), arg0) +} + // GetFileSize mocks base method. func (m *MockFileStore) GetFileSize(arg0 domain.FileId) (int, error) { m.ctrl.T.Helper() @@ -370,6 +386,20 @@ func (mr *MockFileStoreMockRecorder) SetChunksCount(arg0, arg1 any) *gomock.Call return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetChunksCount", reflect.TypeOf((*MockFileStore)(nil).SetChunksCount), arg0, arg1) } +// SetFileOrigin mocks base method. +func (m *MockFileStore) SetFileOrigin(arg0 domain.FileId, arg1 objectorigin.ObjectOrigin) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetFileOrigin", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetFileOrigin indicates an expected call of SetFileOrigin. +func (mr *MockFileStoreMockRecorder) SetFileOrigin(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetFileOrigin", reflect.TypeOf((*MockFileStore)(nil).SetFileOrigin), arg0, arg1) +} + // SetFileSize mocks base method. func (m *MockFileStore) SetFileSize(arg0 domain.FileId, arg1 int) error { m.ctrl.T.Helper() diff --git a/pkg/lib/localstore/filestore/files.go b/pkg/lib/localstore/filestore/files.go index 52f99fe74..1eb525e28 100644 --- a/pkg/lib/localstore/filestore/files.go +++ b/pkg/lib/localstore/filestore/files.go @@ -113,6 +113,7 @@ type FileStore interface { SetFileSize(fileId domain.FileId, size int) error GetFileSize(fileId domain.FileId) (int, error) GetFileOrigin(fileId domain.FileId) (objectorigin.ObjectOrigin, error) + SetFileOrigin(fileId domain.FileId, origin objectorigin.ObjectOrigin) error } func New() FileStore { @@ -550,6 +551,18 @@ func (s *dsFileStore) GetFileOrigin(fileId domain.FileId) (objectorigin.ObjectOr }, nil } +func (s *dsFileStore) SetFileOrigin(fileId domain.FileId, origin objectorigin.ObjectOrigin) error { + err := s.setInt(fileOrigin.ChildString(fileId.String()), int(origin.Origin)) + if err != nil { + return fmt.Errorf("failed to set file origin: %w", err) + } + err = s.setInt(fileImportType.ChildString(fileId.String()), int(origin.ImportType)) + if err != nil { + return fmt.Errorf("failed to set file import type: %w", err) + } + return nil +} + func (s *dsFileStore) Close(ctx context.Context) (err error) { return nil } diff --git a/tests/blockbuilder/constructors.go b/tests/blockbuilder/constructors.go index 875b93710..c7d8dbc6b 100644 --- a/tests/blockbuilder/constructors.go +++ b/tests/blockbuilder/constructors.go @@ -77,10 +77,12 @@ type options struct { color string restrictions *model.BlockRestrictions textStyle model.BlockContentTextStyle + textIconImage string marks *model.BlockContentTextMarks fields *types.Struct id string backgroundColor string + fileHash string } type Option func(*options) @@ -127,6 +129,12 @@ func TextStyle(s model.BlockContentTextStyle) Option { } } +func TextIconImage(id string) Option { + return func(o *options) { + o.textIconImage = id + } +} + func TextMarks(m model.BlockContentTextMarks) Option { return func(o *options) { o.marks = &m @@ -182,10 +190,11 @@ func Text(s string, opts ...Option) *Block { return mkBlock(&model.Block{ Content: &model.BlockContentOfText{ Text: &model.BlockContentText{ - Text: s, - Style: o.textStyle, - Color: o.color, - Marks: o.marks, + Text: s, + Style: o.textStyle, + Color: o.color, + Marks: o.marks, + IconImage: o.textIconImage, }, }, }, opts...) @@ -198,3 +207,25 @@ func Row(opts ...Option) *Block { func Column(opts ...Option) *Block { return Layout(model.BlockContentLayout_Column, opts...) } + +func FileHash(hash string) Option { + return func(o *options) { + o.fileHash = hash + } +} + +func File(targetObjectId string, opts ...Option) *Block { + var o options + for _, apply := range opts { + apply(&o) + } + + return mkBlock(&model.Block{ + Content: &model.BlockContentOfFile{ + File: &model.BlockContentFile{ + Hash: o.fileHash, + TargetObjectId: targetObjectId, + }, + }, + }, opts...) +}