Use generic item in Drive backup (#4357)

Use generic lazy item implementation for drive
backups. This is a bit of a stop-gap
implementation since the drive logic is more
complex than exchange logic

Refactoring the drive logic to streamline it
would help reduce the number of functions
called to download a single item etc.

---

#### Does this PR need a docs update or release note?

- [ ]  Yes, it's included
- [ ] 🕐 Yes, but in a later PR
- [x]  No

#### Type of change

- [ ] 🌻 Feature
- [ ] 🐛 Bugfix
- [ ] 🗺️ Documentation
- [ ] 🤖 Supportability/Tests
- [ ] 💻 CI/Deployment
- [x] 🧹 Tech Debt/Cleanup

#### Issue(s)

* #4191

#### Test Plan

- [ ] 💪 Manual
- [x]  Unit test
- [x] 💚 E2E
This commit is contained in:
ashmrtn 2023-09-25 19:50:14 -07:00 committed by GitHub
parent a91c823e42
commit 5c4419fdc0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 69 additions and 61 deletions

View File

@ -35,9 +35,6 @@ const (
var (
_ data.BackupCollection = &Collection{}
_ data.Item = &Item{}
_ data.ItemInfo = &Item{}
_ data.ItemModTime = &Item{}
_ data.Item = &metadata.Item{}
_ data.ItemModTime = &metadata.Item{}
)
@ -244,22 +241,6 @@ func (oc Collection) DoNotMergeItems() bool {
return oc.doNotMergeItems
}
// Item represents a single item retrieved from OneDrive
type Item struct {
id string
data io.ReadCloser
info details.ItemInfo
}
// Deleted implements an interface function. However, OneDrive items are marked
// as deleted by adding them to the exclude list so this can always return
// false.
func (i Item) Deleted() bool { return false }
func (i *Item) ID() string { return i.id }
func (i *Item) ToReader() io.ReadCloser { return i.data }
func (i *Item) Info() (details.ItemInfo, error) { return i.info, nil }
func (i *Item) ModTime() time.Time { return i.info.Modified() }
// getDriveItemContent fetch drive item's contents with retries
func (oc *Collection) getDriveItemContent(
ctx context.Context,
@ -470,6 +451,54 @@ func (oc *Collection) streamItems(ctx context.Context, errs *fault.Bus) {
oc.reportAsCompleted(ctx, int(stats.itemsFound), int(stats.itemsRead), stats.byteCount)
}
type lazyItemGetter struct {
info *details.ItemInfo
item models.DriveItemable
driveID string
suffix string
itemExtensionFactory []extensions.CreateItemExtensioner
contentGetter func(
ctx context.Context,
driveID string,
item models.DriveItemable,
errs *fault.Bus) (io.ReadCloser, error)
}
func (lig *lazyItemGetter) GetData(
ctx context.Context,
errs *fault.Bus,
) (io.ReadCloser, *details.ItemInfo, bool, error) {
rc, err := lig.contentGetter(ctx, lig.driveID, lig.item, errs)
if err != nil {
return nil, nil, false, clues.Stack(err)
}
extRc, extData, err := extensions.AddItemExtensions(
ctx,
rc,
*lig.info,
lig.itemExtensionFactory)
if err != nil {
err := clues.Wrap(err, "adding extensions").
WithClues(ctx).
Label(fault.LabelForceNoBackupCreation)
return nil, nil, false, err
}
lig.info.Extension.Data = extData.Data
// display/log the item download
progReader, _ := observe.ItemProgress(
ctx,
extRc,
observe.ItemBackupMsg,
clues.Hide(ptr.Val(lig.item.GetName())+lig.suffix),
ptr.Val(lig.item.GetSize()))
return progReader, lig.info, false, nil
}
func (oc *Collection) streamDriveItem(
ctx context.Context,
parentPath *path.Builder,
@ -530,45 +559,23 @@ func (oc *Collection) streamDriveItem(
if isFile {
dataSuffix := metadata.DataFileSuffix
// Construct a new lazy readCloser to feed to the collection consumer.
// Use a LazyItem to feed to the collection consumer.
// This ensures that downloads won't be attempted unless that consumer
// attempts to read bytes. Assumption is that kopia will check things
// like file modtimes before attempting to read.
itemReader := lazy.NewLazyReadCloser(func() (io.ReadCloser, error) {
rc, err := oc.getDriveItemContent(ctx, oc.driveID, item, errs)
if err != nil {
return nil, err
}
extRc, extData, err := extensions.AddItemExtensions(
oc.data <- data.NewLazyItem(
ctx,
rc,
itemInfo,
itemExtensionFactory)
if err != nil {
err := clues.Wrap(err, "adding extensions").Label(fault.LabelForceNoBackupCreation)
errs.AddRecoverable(ctx, err)
return nil, err
}
itemInfo.Extension.Data = extData.Data
// display/log the item download
progReader, _ := observe.ItemProgress(
ctx,
extRc,
observe.ItemBackupMsg,
clues.Hide(itemName+dataSuffix),
itemSize)
return progReader, nil
})
oc.data <- &Item{
id: itemID + dataSuffix,
data: itemReader,
info: itemInfo,
}
&lazyItemGetter{
info: &itemInfo,
item: item,
driveID: oc.driveID,
itemExtensionFactory: itemExtensionFactory,
contentGetter: oc.getDriveItemContent,
suffix: dataSuffix,
},
itemID+dataSuffix,
itemInfo.Modified(),
errs)
}
metaReader := lazy.NewLazyReadCloser(func() (io.ReadCloser, error) {

View File

@ -981,15 +981,17 @@ func (suite *CollectionUnitSuite) TestItemExtensions() {
ei, ok := collItem.(data.ItemInfo)
assert.True(t, ok)
itemInfo, err := ei.Info()
require.NoError(t, err, clues.ToCore(err))
r := collItem.ToReader()
_, err = io.ReadAll(collItem.ToReader())
_, err = io.ReadAll(r)
test.expectReadErr(t, err, clues.ToCore(err))
err = collItem.ToReader().Close()
err = r.Close()
test.expectCloseErr(t, err, clues.ToCore(err))
itemInfo, err := ei.Info()
require.NoError(t, err, clues.ToCore(err))
// Verify extension data
test.expect(t, itemInfo, test.payload)
})

View File

@ -17,7 +17,6 @@ import (
"github.com/alcionai/corso/src/internal/common/ptr"
"github.com/alcionai/corso/src/internal/data"
"github.com/alcionai/corso/src/internal/m365/collection/drive"
"github.com/alcionai/corso/src/internal/m365/collection/drive/metadata"
odStub "github.com/alcionai/corso/src/internal/m365/service/onedrive/stub"
m365Stub "github.com/alcionai/corso/src/internal/m365/stub"
@ -731,7 +730,7 @@ func compareDriveItem(
)
if !isMeta {
oitem := item.(*drive.Item)
oitem := item.(data.ItemInfo)
info, err := oitem.Info()
if !assert.NoError(t, err, clues.ToCore(err)) {