Update item info api (#4183)

Update the API for Item.Info to return an error.
This can then be leveraged to add lazy readers
to exchange backups

See #2023 for more info on how to add lazy
readers to exchange

---

#### 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)

* #2023

#### Test Plan

- [ ] 💪 Manual
- [x]  Unit test
- [x] 💚 E2E
This commit is contained in:
ashmrtn 2023-09-12 14:36:22 -07:00 committed by GitHub
parent 039c2463fc
commit 1fe37e4ba9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 55 additions and 46 deletions

View File

@ -93,7 +93,7 @@ type PreviousLocationPather interface {
// ItemInfo returns the details.ItemInfo for the item.
type ItemInfo interface {
Info() details.ItemInfo
Info() (details.ItemInfo, error)
}
// ItemSize returns the size of the item in bytes.

View File

@ -17,7 +17,10 @@ import (
// Item
// ---------------------------------------------------------------------------
var _ data.Item = &Item{}
var (
_ data.Item = &Item{}
_ data.ItemInfo = &Item{}
)
type Item struct {
DeletedFlag bool
@ -45,8 +48,8 @@ func (s *Item) ToReader() io.ReadCloser {
return s.Reader
}
func (s *Item) Info() details.ItemInfo {
return s.ItemInfo
func (s *Item) Info() (details.ItemInfo, error) {
return s.ItemInfo, nil
}
func (s *Item) Size() int64 {

View File

@ -132,7 +132,7 @@ func (rw *restoreStreamReader) Read(p []byte) (n int, err error) {
}
type itemDetails struct {
infoFunc func() (details.ItemInfo, error)
infoer data.ItemInfo
repoPath path.Path
prevPath path.Path
locationPath *path.Builder
@ -204,7 +204,7 @@ func (cp *corsoProgress) FinishedFile(relativePath string, err error) {
// These items were sourced from a base snapshot or were cached in kopia so we
// never had to materialize their details in-memory.
if d.infoFunc == nil || d.cached {
if d.infoer == nil || d.cached {
if d.prevPath == nil {
cp.errs.AddRecoverable(ctx, clues.New("finished file sourced from previous backup with no previous path").
WithClues(ctx).
@ -230,7 +230,7 @@ func (cp *corsoProgress) FinishedFile(relativePath string, err error) {
return
}
info, err := d.infoFunc()
info, err := d.infoer.Info()
if err != nil {
cp.errs.AddRecoverable(ctx, clues.Wrap(err, "getting ItemInfo").
WithClues(ctx).
@ -412,11 +412,7 @@ func collectionEntries(
// element. Add to pending set before calling the callback to avoid race
// conditions when the item is completed.
d := &itemDetails{
// TODO(ashmrtn): Update API in data package to return an error and
// then remove this wrapper.
infoFunc: func() (details.ItemInfo, error) {
return ei.Info(), nil
},
infoer: ei,
repoPath: itemPath,
// Also use the current path as the previous path for this item. This
// is so that if the item is marked as cached and we need to merge

View File

@ -373,6 +373,18 @@ func (suite *CorsoProgressUnitSuite) SetupSuite() {
suite.targetFileName = suite.targetFilePath.ToBuilder().Dir().String()
}
var _ data.ItemInfo = &mockExchangeMailInfoer{}
type mockExchangeMailInfoer struct{}
func (m mockExchangeMailInfoer) Info() (details.ItemInfo, error) {
return details.ItemInfo{
Exchange: &details.ExchangeInfo{
ItemType: details.ExchangeMail,
},
}, nil
}
type testInfo struct {
info *itemDetails
err error
@ -394,13 +406,7 @@ var finishedFileTable = []struct {
return map[string]testInfo{
fname: {
info: &itemDetails{
infoFunc: func() (details.ItemInfo, error) {
return details.ItemInfo{
Exchange: &details.ExchangeInfo{
ItemType: details.ExchangeMail,
},
}, nil
},
infoer: mockExchangeMailInfoer{},
repoPath: fpath,
locationPath: path.Builder{}.Append(fpath.Folders()...),
},
@ -432,13 +438,7 @@ var finishedFileTable = []struct {
return map[string]testInfo{
fname: {
info: &itemDetails{
infoFunc: func() (details.ItemInfo, error) {
return details.ItemInfo{
Exchange: &details.ExchangeInfo{
ItemType: details.ExchangeMail,
},
}, nil
},
infoer: mockExchangeMailInfoer{},
repoPath: fpath,
},
err: assert.AnError,
@ -530,7 +530,7 @@ func (suite *CorsoProgressUnitSuite) TestFinishedFile() {
}
if cachedTest.dropInfo {
v.info.infoFunc = nil
v.info.infoer = nil
}
}

View File

@ -255,11 +255,11 @@ type Item struct {
// 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 { return i.info }
func (i *Item) ModTime() time.Time { return i.info.Modified() }
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(

View File

@ -980,7 +980,9 @@ func (suite *CollectionUnitTestSuite) TestItemExtensions() {
ei, ok := collItem.(data.ItemInfo)
assert.True(t, ok)
itemInfo := ei.Info()
itemInfo, err := ei.Info()
require.NoError(t, err, clues.ToCore(err))
_, err = io.ReadAll(collItem.ToReader())
test.expectReadErr(t, err, clues.ToCore(err))

View File

@ -320,8 +320,8 @@ func (i Item) Deleted() bool {
return i.deleted
}
func (i *Item) Info() details.ItemInfo {
return details.ItemInfo{Exchange: i.info}
func (i *Item) Info() (details.ItemInfo, error) {
return details.ItemInfo{Exchange: i.info}, nil
}
func (i *Item) ModTime() time.Time {

View File

@ -168,8 +168,8 @@ func (i Item) Deleted() bool {
return i.deleted
}
func (i *Item) Info() details.ItemInfo {
return details.ItemInfo{Groups: i.info}
func (i *Item) Info() (details.ItemInfo, error) {
return details.ItemInfo{Groups: i.info}, nil
}
func (i *Item) ModTime() time.Time {

View File

@ -149,8 +149,8 @@ func (sd Item) Deleted() bool {
return sd.deleted
}
func (sd *Item) Info() details.ItemInfo {
return details.ItemInfo{SharePoint: sd.info}
func (sd *Item) Info() (details.ItemInfo, error) {
return details.ItemInfo{SharePoint: sd.info}, nil
}
func (sd *Item) ModTime() time.Time {

View File

@ -183,9 +183,13 @@ func (suite *SharePointCollectionSuite) TestCollection_Items() {
item := readItems[0]
shareInfo, ok := item.(data.ItemInfo)
require.True(t, ok)
require.NotNil(t, shareInfo.Info())
require.NotNil(t, shareInfo.Info().SharePoint)
assert.Equal(t, test.itemName, shareInfo.Info().SharePoint.ItemName)
info, err := shareInfo.Info()
require.NoError(t, err, clues.ToCore(err))
assert.NotNil(t, info)
assert.NotNil(t, info.SharePoint)
assert.Equal(t, test.itemName, info.SharePoint.ItemName)
})
}
}

View File

@ -732,16 +732,20 @@ func compareDriveItem(
if !isMeta {
oitem := item.(*drive.Item)
info := oitem.Info()
info, err := oitem.Info()
if !assert.NoError(t, err, clues.ToCore(err)) {
return true
}
if info.OneDrive != nil {
displayName = oitem.Info().OneDrive.ItemName
displayName = info.OneDrive.ItemName
// Don't need to check SharePoint because it was added after we stopped
// adding meta files to backup details.
assert.False(t, oitem.Info().OneDrive.IsMeta, "meta marker for non meta item %s", name)
assert.False(t, info.OneDrive.IsMeta, "meta marker for non meta item %s", name)
} else if info.SharePoint != nil {
displayName = oitem.Info().SharePoint.ItemName
displayName = info.SharePoint.ItemName
} else {
assert.Fail(t, "ItemInfo is not SharePoint or OneDrive")
}