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. // ItemInfo returns the details.ItemInfo for the item.
type ItemInfo interface { type ItemInfo interface {
Info() details.ItemInfo Info() (details.ItemInfo, error)
} }
// ItemSize returns the size of the item in bytes. // ItemSize returns the size of the item in bytes.

View File

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

View File

@ -132,7 +132,7 @@ func (rw *restoreStreamReader) Read(p []byte) (n int, err error) {
} }
type itemDetails struct { type itemDetails struct {
infoFunc func() (details.ItemInfo, error) infoer data.ItemInfo
repoPath path.Path repoPath path.Path
prevPath path.Path prevPath path.Path
locationPath *path.Builder 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 // These items were sourced from a base snapshot or were cached in kopia so we
// never had to materialize their details in-memory. // never had to materialize their details in-memory.
if d.infoFunc == nil || d.cached { if d.infoer == nil || d.cached {
if d.prevPath == nil { if d.prevPath == nil {
cp.errs.AddRecoverable(ctx, clues.New("finished file sourced from previous backup with no previous path"). cp.errs.AddRecoverable(ctx, clues.New("finished file sourced from previous backup with no previous path").
WithClues(ctx). WithClues(ctx).
@ -230,7 +230,7 @@ func (cp *corsoProgress) FinishedFile(relativePath string, err error) {
return return
} }
info, err := d.infoFunc() info, err := d.infoer.Info()
if err != nil { if err != nil {
cp.errs.AddRecoverable(ctx, clues.Wrap(err, "getting ItemInfo"). cp.errs.AddRecoverable(ctx, clues.Wrap(err, "getting ItemInfo").
WithClues(ctx). WithClues(ctx).
@ -412,11 +412,7 @@ func collectionEntries(
// element. Add to pending set before calling the callback to avoid race // element. Add to pending set before calling the callback to avoid race
// conditions when the item is completed. // conditions when the item is completed.
d := &itemDetails{ d := &itemDetails{
// TODO(ashmrtn): Update API in data package to return an error and infoer: ei,
// then remove this wrapper.
infoFunc: func() (details.ItemInfo, error) {
return ei.Info(), nil
},
repoPath: itemPath, repoPath: itemPath,
// Also use the current path as the previous path for this item. This // 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 // 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() 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 { type testInfo struct {
info *itemDetails info *itemDetails
err error err error
@ -394,13 +406,7 @@ var finishedFileTable = []struct {
return map[string]testInfo{ return map[string]testInfo{
fname: { fname: {
info: &itemDetails{ info: &itemDetails{
infoFunc: func() (details.ItemInfo, error) { infoer: mockExchangeMailInfoer{},
return details.ItemInfo{
Exchange: &details.ExchangeInfo{
ItemType: details.ExchangeMail,
},
}, nil
},
repoPath: fpath, repoPath: fpath,
locationPath: path.Builder{}.Append(fpath.Folders()...), locationPath: path.Builder{}.Append(fpath.Folders()...),
}, },
@ -432,13 +438,7 @@ var finishedFileTable = []struct {
return map[string]testInfo{ return map[string]testInfo{
fname: { fname: {
info: &itemDetails{ info: &itemDetails{
infoFunc: func() (details.ItemInfo, error) { infoer: mockExchangeMailInfoer{},
return details.ItemInfo{
Exchange: &details.ExchangeInfo{
ItemType: details.ExchangeMail,
},
}, nil
},
repoPath: fpath, repoPath: fpath,
}, },
err: assert.AnError, err: assert.AnError,
@ -530,7 +530,7 @@ func (suite *CorsoProgressUnitSuite) TestFinishedFile() {
} }
if cachedTest.dropInfo { 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 // Deleted implements an interface function. However, OneDrive items are marked
// as deleted by adding them to the exclude list so this can always return // as deleted by adding them to the exclude list so this can always return
// false. // false.
func (i Item) Deleted() bool { return false } func (i Item) Deleted() bool { return false }
func (i *Item) ID() string { return i.id } func (i *Item) ID() string { return i.id }
func (i *Item) ToReader() io.ReadCloser { return i.data } func (i *Item) ToReader() io.ReadCloser { return i.data }
func (i *Item) Info() details.ItemInfo { return i.info } func (i *Item) Info() (details.ItemInfo, error) { return i.info, nil }
func (i *Item) ModTime() time.Time { return i.info.Modified() } func (i *Item) ModTime() time.Time { return i.info.Modified() }
// getDriveItemContent fetch drive item's contents with retries // getDriveItemContent fetch drive item's contents with retries
func (oc *Collection) getDriveItemContent( func (oc *Collection) getDriveItemContent(

View File

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

View File

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

View File

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

View File

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

View File

@ -183,9 +183,13 @@ func (suite *SharePointCollectionSuite) TestCollection_Items() {
item := readItems[0] item := readItems[0]
shareInfo, ok := item.(data.ItemInfo) shareInfo, ok := item.(data.ItemInfo)
require.True(t, ok) require.True(t, ok)
require.NotNil(t, shareInfo.Info())
require.NotNil(t, shareInfo.Info().SharePoint) info, err := shareInfo.Info()
assert.Equal(t, test.itemName, shareInfo.Info().SharePoint.ItemName) 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 { if !isMeta {
oitem := item.(*drive.Item) 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 { 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 // Don't need to check SharePoint because it was added after we stopped
// adding meta files to backup details. // 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 { } else if info.SharePoint != nil {
displayName = oitem.Info().SharePoint.ItemName displayName = info.SharePoint.ItemName
} else { } else {
assert.Fail(t, "ItemInfo is not SharePoint or OneDrive") assert.Fail(t, "ItemInfo is not SharePoint or OneDrive")
} }