diff --git a/src/internal/data/collection_test.go b/src/internal/data/collection_test.go index 033e9b6e1..3c1b6cb2b 100644 --- a/src/internal/data/collection_test.go +++ b/src/internal/data/collection_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/suite" "github.com/alcionai/corso/src/internal/tester" + "github.com/alcionai/corso/src/pkg/control" "github.com/alcionai/corso/src/pkg/path" ) @@ -70,3 +71,84 @@ func (suite *CollectionSuite) TestStateOf() { }) } } + +func (suite *CollectionSuite) TestNewBaseCollection() { + fooP, err := path.Build("t", "u", path.ExchangeService, path.EmailCategory, false, "foo") + require.NoError(suite.T(), err, clues.ToCore(err)) + barP, err := path.Build("t", "u", path.ExchangeService, path.EmailCategory, false, "bar") + require.NoError(suite.T(), err, clues.ToCore(err)) + preP, err := path.Build("_t", "_u", path.ExchangeService, path.EmailCategory, false, "foo") + require.NoError(suite.T(), err, clues.ToCore(err)) + + loc := path.Builder{}.Append("foo") + + table := []struct { + name string + current path.Path + previous path.Path + doNotMerge bool + + expectCurrent path.Path + expectPrev path.Path + expectState CollectionState + expectDoNotMerge bool + }{ + { + name: "NotMoved DoNotMerge", + current: fooP, + previous: fooP, + doNotMerge: true, + expectCurrent: fooP, + expectPrev: fooP, + expectState: NotMovedState, + expectDoNotMerge: true, + }, + { + name: "Moved", + current: fooP, + previous: barP, + expectCurrent: fooP, + expectPrev: barP, + expectState: MovedState, + }, + { + name: "PrefixMoved", + current: fooP, + previous: preP, + expectCurrent: fooP, + expectPrev: preP, + expectState: MovedState, + }, + { + name: "New", + current: fooP, + expectCurrent: fooP, + expectState: NewState, + }, + { + name: "Deleted", + previous: fooP, + expectPrev: fooP, + expectState: DeletedState, + }, + } + + for _, test := range table { + suite.Run(test.name, func() { + t := suite.T() + + b := NewBaseCollection( + test.current, + test.previous, + loc, + control.Options{}, + test.doNotMerge) + + assert.Equal(t, test.expectCurrent, b.FullPath(), "full path") + assert.Equal(t, test.expectPrev, b.PreviousPath(), "previous path") + assert.Equal(t, loc, b.LocationPath(), "location path") + assert.Equal(t, test.expectState, b.State(), "state") + assert.Equal(t, test.expectDoNotMerge, b.DoNotMergeItems(), "do not merge") + }) + } +} diff --git a/src/internal/data/implementations.go b/src/internal/data/implementations.go index d75bd93b6..d5a94ec3d 100644 --- a/src/internal/data/implementations.go +++ b/src/internal/data/implementations.go @@ -5,6 +5,7 @@ import ( "github.com/alcionai/clues" + "github.com/alcionai/corso/src/pkg/control" "github.com/alcionai/corso/src/pkg/path" ) @@ -51,3 +52,76 @@ func StateOf(prev, curr path.Path) CollectionState { return NotMovedState } + +// ----------------------------------------------------------------------------- +// BaseCollection +// ----------------------------------------------------------------------------- + +func NewBaseCollection( + curr, prev path.Path, + location *path.Builder, + ctrlOpts control.Options, + doNotMergeItems bool, +) BaseCollection { + return BaseCollection{ + opts: ctrlOpts, + doNotMergeItems: doNotMergeItems, + fullPath: curr, + locationPath: location, + prevPath: prev, + state: StateOf(prev, curr), + } +} + +// BaseCollection contains basic functionality like returning path, location, +// and state information. It can be embedded in other implementations to provide +// this functionality. +// +// Functionality like how items are fetched is left to the embedding struct. +type BaseCollection struct { + opts control.Options + + // FullPath is the current hierarchical path used by this collection. + fullPath path.Path + + // PrevPath is the previous hierarchical path used by this collection. + // It may be the same as fullPath, if the folder was not renamed or + // moved. It will be empty on its first retrieval. + prevPath path.Path + + // LocationPath contains the path with human-readable display names. + // IE: "/Inbox/Important" instead of "/abcdxyz123/algha=lgkhal=t" + locationPath *path.Builder + + state CollectionState + + // doNotMergeItems should only be true if the old delta token expired. + doNotMergeItems bool +} + +// FullPath returns the BaseCollection's fullPath []string +func (col *BaseCollection) FullPath() path.Path { + return col.fullPath +} + +// LocationPath produces the BaseCollection's full path, but with display names +// instead of IDs in the folders. Only populated for Calendars. +func (col *BaseCollection) LocationPath() *path.Builder { + return col.locationPath +} + +func (col BaseCollection) PreviousPath() path.Path { + return col.prevPath +} + +func (col BaseCollection) State() CollectionState { + return col.state +} + +func (col BaseCollection) DoNotMergeItems() bool { + return col.doNotMergeItems +} + +func (col BaseCollection) Opts() control.Options { + return col.opts +} diff --git a/src/internal/m365/collection/exchange/backup.go b/src/internal/m365/collection/exchange/backup.go index f78efd95a..8ec4818f3 100644 --- a/src/internal/m365/collection/exchange/backup.go +++ b/src/internal/m365/collection/exchange/backup.go @@ -189,7 +189,7 @@ func populateCollections( } edc := NewCollection( - NewBaseCollection( + data.NewBaseCollection( currPath, prevPath, locPath, @@ -242,7 +242,7 @@ func populateCollections( } edc := NewCollection( - NewBaseCollection( + data.NewBaseCollection( nil, // marks the collection as deleted prevPath, nil, // tombstones don't need a location diff --git a/src/internal/m365/collection/exchange/collection.go b/src/internal/m365/collection/exchange/collection.go index 0868a0299..a7278029d 100644 --- a/src/internal/m365/collection/exchange/collection.go +++ b/src/internal/m365/collection/exchange/collection.go @@ -20,7 +20,6 @@ import ( "github.com/alcionai/corso/src/internal/m365/support" "github.com/alcionai/corso/src/internal/observe" "github.com/alcionai/corso/src/pkg/backup/details" - "github.com/alcionai/corso/src/pkg/control" "github.com/alcionai/corso/src/pkg/fault" "github.com/alcionai/corso/src/pkg/logger" "github.com/alcionai/corso/src/pkg/path" @@ -38,71 +37,6 @@ const ( numberOfRetries = 4 ) -func NewBaseCollection( - curr, prev path.Path, - location *path.Builder, - ctrlOpts control.Options, - doNotMergeItems bool, -) baseCollection { - return baseCollection{ - ctrl: ctrlOpts, - doNotMergeItems: doNotMergeItems, - fullPath: curr, - locationPath: location, - prevPath: prev, - state: data.StateOf(prev, curr), - } -} - -// baseCollection contains basic functionality like returning path, location, -// and state information. It can be embedded in other implementations to provide -// this functionality. -// -// Functionality like how items are fetched is left to the embedding struct. -type baseCollection struct { - ctrl control.Options - - // FullPath is the current hierarchical path used by this collection. - fullPath path.Path - - // PrevPath is the previous hierarchical path used by this collection. - // It may be the same as fullPath, if the folder was not renamed or - // moved. It will be empty on its first retrieval. - prevPath path.Path - - // LocationPath contains the path with human-readable display names. - // IE: "/Inbox/Important" instead of "/abcdxyz123/algha=lgkhal=t" - locationPath *path.Builder - - state data.CollectionState - - // doNotMergeItems should only be true if the old delta token expired. - doNotMergeItems bool -} - -// FullPath returns the baseCollection's fullPath []string -func (col *baseCollection) FullPath() path.Path { - return col.fullPath -} - -// LocationPath produces the baseCollection's full path, but with display names -// instead of IDs in the folders. Only populated for Calendars. -func (col *baseCollection) LocationPath() *path.Builder { - return col.locationPath -} - -func (col baseCollection) PreviousPath() path.Path { - return col.prevPath -} - -func (col baseCollection) State() data.CollectionState { - return col.state -} - -func (col baseCollection) DoNotMergeItems() bool { - return col.doNotMergeItems -} - // updateStatus is a utility function used to send the status update through // the channel. func updateStatus( @@ -173,7 +107,7 @@ func getItemAndInfo( // If both are populated, then state is either moved (if they differ), // or notMoved (if they match). func NewCollection( - bc baseCollection, + bc data.BaseCollection, user string, items itemGetterSerializer, origAdded map[string]time.Time, @@ -199,7 +133,7 @@ func NewCollection( if !validModTimes { return &prefetchCollection{ - baseCollection: bc, + BaseCollection: bc, user: user, added: added, removed: removed, @@ -209,7 +143,7 @@ func NewCollection( } return &lazyFetchCollection{ - baseCollection: bc, + BaseCollection: bc, user: user, added: added, removed: removed, @@ -221,7 +155,7 @@ func NewCollection( // prefetchCollection implements the interface from data.BackupCollection // Structure holds data for an Exchange application for a single user type prefetchCollection struct { - baseCollection + data.BaseCollection user string @@ -283,7 +217,7 @@ func (col *prefetchCollection) streamItems( defer close(colProgress) } - semaphoreCh := make(chan struct{}, col.ctrl.Parallelism.ItemFetch) + semaphoreCh := make(chan struct{}, col.Opts().Parallelism.ItemFetch) defer close(semaphoreCh) // delete all removed items @@ -331,7 +265,7 @@ func (col *prefetchCollection) streamItems( col.getter, user, id, - col.ctrl.ToggleFeatures.ExchangeImmutableIDs, + col.Opts().ToggleFeatures.ExchangeImmutableIDs, parentPath) if err != nil { // Don't report errors for deleted items as there's no way for us to @@ -380,7 +314,7 @@ func (col *prefetchCollection) streamItems( // information (path and mod time) is handed to kopia. Total bytes across all // items is not tracked. type lazyFetchCollection struct { - baseCollection + data.BaseCollection user string @@ -474,7 +408,7 @@ func (col *lazyFetchCollection) streamItems( id: id, getter: col.getter, modTime: modTime, - immutableIDs: col.ctrl.ToggleFeatures.ExchangeImmutableIDs, + immutableIDs: col.Opts().ToggleFeatures.ExchangeImmutableIDs, parentPath: parentPath, errs: errs, } diff --git a/src/internal/m365/collection/exchange/collection_test.go b/src/internal/m365/collection/exchange/collection_test.go index 6bac61d4a..39a7e2fc5 100644 --- a/src/internal/m365/collection/exchange/collection_test.go +++ b/src/internal/m365/collection/exchange/collection_test.go @@ -63,32 +63,6 @@ func (suite *CollectionUnitSuite) TestReader_Empty() { assert.NoError(t, err, clues.ToCore(err)) } -func (suite *CollectionUnitSuite) TestCollection_NewCollection() { - t := suite.T() - tenant := "a-tenant" - user := "a-user" - folder := "a-folder" - name := "User" - - fullPath, err := path.Build( - tenant, - user, - path.ExchangeService, - path.EmailCategory, - false, - folder) - require.NoError(t, err, clues.ToCore(err)) - - edc := prefetchCollection{ - baseCollection: baseCollection{ - fullPath: fullPath, - }, - user: name, - } - assert.Equal(t, name, edc.user) - assert.Equal(t, fullPath, edc.FullPath()) -} - func (suite *CollectionUnitSuite) TestNewCollection_state() { type collectionTypes struct { name string @@ -153,7 +127,7 @@ func (suite *CollectionUnitSuite) TestNewCollection_state() { t := suite.T() c := NewCollection( - NewBaseCollection( + data.NewBaseCollection( test.curr, test.prev, test.loc, @@ -296,7 +270,7 @@ func (suite *CollectionUnitSuite) TestPrefetchCollection_Items() { defer flush() col := NewCollection( - NewBaseCollection( + data.NewBaseCollection( fullPath, nil, locPath.ToBuilder(), @@ -434,7 +408,7 @@ func (suite *CollectionUnitSuite) TestLazyFetchCollection_Items_LazyFetch() { defer mlg.check(t, test.expectReads) col := NewCollection( - NewBaseCollection( + data.NewBaseCollection( fullPath, nil, locPath.ToBuilder(),