Restore with OneDrive/SharePoint ID (#3151)
Allow restoring OneDrive and SharePoint items by M365 ID as well as file name and ShortRef Does create an edgecase where attempting to restore an item named the same as another item's M365 ID causes both items to be restored Moves the consts for OneDrive file suffixes to a different package so that the selectors package can also import them without a cycle --- #### Does this PR need a docs update or release note? - [x] ✅ Yes, it's included - [ ] 🕐 Yes, but in a later PR - [ ] ⛔ No #### Type of change - [x] 🌻 Feature - [ ] 🐛 Bugfix - [ ] 🗺️ Documentation - [ ] 🤖 Supportability/Tests - [ ] 💻 CI/Deployment - [ ] 🧹 Tech Debt/Cleanup #### Issue(s) * closes #3142 #### Test Plan - [x] 💪 Manual - [ ] ⚡ Unit test - [ ] 💚 E2E
This commit is contained in:
parent
d5eee4479d
commit
9664afaa12
@ -24,6 +24,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- ParentPath of json output for Exchange calendar now shows names instead of IDs.
|
- ParentPath of json output for Exchange calendar now shows names instead of IDs.
|
||||||
- Fixed failure when downloading huge amount of attachments
|
- Fixed failure when downloading huge amount of attachments
|
||||||
|
|
||||||
|
### Known Issues
|
||||||
|
- Restoring a OneDrive or SharePoint file with the same name as a file with that name as its M365 ID may restore both items.
|
||||||
|
|
||||||
## [v0.6.1] (beta) - 2023-03-21
|
## [v0.6.1] (beta) - 2023-03-21
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
18
src/cli/utils/testdata/opts.go
vendored
18
src/cli/utils/testdata/opts.go
vendored
@ -2,12 +2,14 @@ package testdata
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/alcionai/clues"
|
"github.com/alcionai/clues"
|
||||||
|
|
||||||
"github.com/alcionai/corso/src/cli/utils"
|
"github.com/alcionai/corso/src/cli/utils"
|
||||||
"github.com/alcionai/corso/src/internal/common"
|
"github.com/alcionai/corso/src/internal/common"
|
||||||
|
"github.com/alcionai/corso/src/internal/connector/onedrive/metadata"
|
||||||
"github.com/alcionai/corso/src/pkg/backup"
|
"github.com/alcionai/corso/src/pkg/backup"
|
||||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
"github.com/alcionai/corso/src/pkg/backup/details/testdata"
|
"github.com/alcionai/corso/src/pkg/backup/details/testdata"
|
||||||
@ -411,11 +413,13 @@ var (
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "NoSelectRepoItemName",
|
Name: "SelectRepoItemName",
|
||||||
Expected: []details.DetailsEntry{},
|
Expected: []details.DetailsEntry{
|
||||||
|
testdata.OneDriveItems[0],
|
||||||
|
},
|
||||||
Opts: utils.OneDriveOpts{
|
Opts: utils.OneDriveOpts{
|
||||||
FileName: []string{
|
FileName: []string{
|
||||||
testdata.OneDriveItemPath1.Item(),
|
strings.TrimSuffix(testdata.OneDriveItemPath1.Item(), metadata.DataFileSuffix),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -530,11 +534,13 @@ var (
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "NoSelectRepoItemName",
|
Name: "SelectRepoItemName",
|
||||||
Expected: []details.DetailsEntry{},
|
Expected: []details.DetailsEntry{
|
||||||
|
testdata.SharePointLibraryItems[0],
|
||||||
|
},
|
||||||
Opts: utils.SharePointOpts{
|
Opts: utils.SharePointOpts{
|
||||||
FileName: []string{
|
FileName: []string{
|
||||||
testdata.SharePointLibraryItemPath1.Item(),
|
strings.TrimSuffix(testdata.SharePointLibraryItemPath1.Item(), metadata.DataFileSuffix),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
package metadata
|
package metadata
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/alcionai/corso/src/internal/connector/onedrive"
|
"github.com/alcionai/corso/src/internal/connector/onedrive/metadata"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
)
|
)
|
||||||
|
|
||||||
func IsMetadataFile(p path.Path) bool {
|
func IsMetadataFile(p path.Path) bool {
|
||||||
switch p.Service() {
|
switch p.Service() {
|
||||||
case path.OneDriveService:
|
case path.OneDriveService:
|
||||||
return onedrive.IsMetaFile(p.Item())
|
return metadata.HasMetaSuffix(p.Item())
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import (
|
|||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
"github.com/alcionai/corso/src/internal/connector/graph/metadata"
|
"github.com/alcionai/corso/src/internal/connector/graph/metadata"
|
||||||
"github.com/alcionai/corso/src/internal/connector/onedrive"
|
odmetadata "github.com/alcionai/corso/src/internal/connector/onedrive/metadata"
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
)
|
)
|
||||||
@ -29,12 +29,12 @@ var (
|
|||||||
|
|
||||||
notMetaSuffixes = []string{
|
notMetaSuffixes = []string{
|
||||||
"",
|
"",
|
||||||
onedrive.DataFileSuffix,
|
odmetadata.DataFileSuffix,
|
||||||
}
|
}
|
||||||
|
|
||||||
metaSuffixes = []string{
|
metaSuffixes = []string{
|
||||||
onedrive.MetaFileSuffix,
|
odmetadata.MetaFileSuffix,
|
||||||
onedrive.DirMetaFileSuffix,
|
odmetadata.DirMetaFileSuffix,
|
||||||
}
|
}
|
||||||
|
|
||||||
cases = []testCase{
|
cases = []testCase{
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/common/ptr"
|
"github.com/alcionai/corso/src/internal/common/ptr"
|
||||||
exchMock "github.com/alcionai/corso/src/internal/connector/exchange/mock"
|
exchMock "github.com/alcionai/corso/src/internal/connector/exchange/mock"
|
||||||
"github.com/alcionai/corso/src/internal/connector/onedrive"
|
"github.com/alcionai/corso/src/internal/connector/onedrive"
|
||||||
|
"github.com/alcionai/corso/src/internal/connector/onedrive/metadata"
|
||||||
"github.com/alcionai/corso/src/internal/connector/support"
|
"github.com/alcionai/corso/src/internal/connector/support"
|
||||||
"github.com/alcionai/corso/src/internal/data"
|
"github.com/alcionai/corso/src/internal/data"
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
@ -730,7 +731,7 @@ func compareOneDriveItem(
|
|||||||
) bool {
|
) bool {
|
||||||
// Skip OneDrive permissions in the folder that used to be the root. We don't
|
// Skip OneDrive permissions in the folder that used to be the root. We don't
|
||||||
// have a good way to materialize these in the test right now.
|
// have a good way to materialize these in the test right now.
|
||||||
if rootDir && item.UUID() == onedrive.DirMetaFileSuffix {
|
if rootDir && item.UUID() == metadata.DirMetaFileSuffix {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -742,8 +743,7 @@ func compareOneDriveItem(
|
|||||||
var (
|
var (
|
||||||
displayName string
|
displayName string
|
||||||
name = item.UUID()
|
name = item.UUID()
|
||||||
isMeta = strings.HasSuffix(name, onedrive.MetaFileSuffix) ||
|
isMeta = metadata.HasMetaSuffix(name)
|
||||||
strings.HasSuffix(name, onedrive.DirMetaFileSuffix)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if isMeta {
|
if isMeta {
|
||||||
@ -780,7 +780,7 @@ func compareOneDriveItem(
|
|||||||
|
|
||||||
key := name
|
key := name
|
||||||
|
|
||||||
if strings.HasSuffix(name, onedrive.MetaFileSuffix) {
|
if strings.HasSuffix(name, metadata.MetaFileSuffix) {
|
||||||
key = itemMeta.FileName
|
key = itemMeta.FileName
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -851,7 +851,7 @@ func compareOneDriveItem(
|
|||||||
// Display name in ItemInfo should match the name the file was given in the
|
// Display name in ItemInfo should match the name the file was given in the
|
||||||
// test. Name used for the lookup key has a `.data` suffix to make it unique
|
// test. Name used for the lookup key has a `.data` suffix to make it unique
|
||||||
// from the metadata files' lookup keys.
|
// from the metadata files' lookup keys.
|
||||||
assert.Equal(t, fileData.FileName, displayName+onedrive.DataFileSuffix)
|
assert.Equal(t, fileData.FileName, displayName+metadata.DataFileSuffix)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -1225,8 +1225,7 @@ func collectionsForInfo(
|
|||||||
// We do not count metadata files against item count
|
// We do not count metadata files against item count
|
||||||
if backupVersion > 0 &&
|
if backupVersion > 0 &&
|
||||||
(service == path.OneDriveService || service == path.SharePointService) &&
|
(service == path.OneDriveService || service == path.SharePointService) &&
|
||||||
(strings.HasSuffix(info.items[i].name, onedrive.MetaFileSuffix) ||
|
metadata.HasMetaSuffix(info.items[i].name) {
|
||||||
strings.HasSuffix(info.items[i].name, onedrive.DirMetaFileSuffix)) {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/common/ptr"
|
"github.com/alcionai/corso/src/internal/common/ptr"
|
||||||
"github.com/alcionai/corso/src/internal/connector/graph"
|
"github.com/alcionai/corso/src/internal/connector/graph"
|
||||||
"github.com/alcionai/corso/src/internal/connector/onedrive"
|
"github.com/alcionai/corso/src/internal/connector/onedrive"
|
||||||
|
"github.com/alcionai/corso/src/internal/connector/onedrive/metadata"
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
"github.com/alcionai/corso/src/internal/version"
|
"github.com/alcionai/corso/src/internal/version"
|
||||||
"github.com/alcionai/corso/src/pkg/account"
|
"github.com/alcionai/corso/src/pkg/account"
|
||||||
@ -165,43 +166,43 @@ func (c *onedriveCollection) withFile(name string, fileData []byte, perm permDat
|
|||||||
c.items = append(c.items, onedriveItemWithData(
|
c.items = append(c.items, onedriveItemWithData(
|
||||||
c.t,
|
c.t,
|
||||||
name,
|
name,
|
||||||
name+onedrive.DataFileSuffix,
|
name+metadata.DataFileSuffix,
|
||||||
fileData))
|
fileData))
|
||||||
|
|
||||||
case version.OneDrive1DataAndMetaFiles, 2, version.OneDrive3IsMetaMarker,
|
case version.OneDrive1DataAndMetaFiles, 2, version.OneDrive3IsMetaMarker,
|
||||||
version.OneDrive4DirIncludesPermissions, version.OneDrive5DirMetaNoName:
|
version.OneDrive4DirIncludesPermissions, version.OneDrive5DirMetaNoName:
|
||||||
c.items = append(c.items, onedriveItemWithData(
|
c.items = append(c.items, onedriveItemWithData(
|
||||||
c.t,
|
c.t,
|
||||||
name+onedrive.DataFileSuffix,
|
name+metadata.DataFileSuffix,
|
||||||
name+onedrive.DataFileSuffix,
|
name+metadata.DataFileSuffix,
|
||||||
fileData))
|
fileData))
|
||||||
|
|
||||||
metadata := onedriveMetadata(
|
md := onedriveMetadata(
|
||||||
c.t,
|
c.t,
|
||||||
"",
|
"",
|
||||||
name+onedrive.MetaFileSuffix,
|
name+metadata.MetaFileSuffix,
|
||||||
name+onedrive.MetaFileSuffix,
|
name+metadata.MetaFileSuffix,
|
||||||
perm,
|
perm,
|
||||||
c.backupVersion >= versionPermissionSwitchedToID)
|
c.backupVersion >= versionPermissionSwitchedToID)
|
||||||
c.items = append(c.items, metadata)
|
c.items = append(c.items, md)
|
||||||
c.aux = append(c.aux, metadata)
|
c.aux = append(c.aux, md)
|
||||||
|
|
||||||
case version.OneDrive6NameInMeta, version.OneDrive7LocationRef:
|
case version.OneDrive6NameInMeta, version.OneDrive7LocationRef:
|
||||||
c.items = append(c.items, onedriveItemWithData(
|
c.items = append(c.items, onedriveItemWithData(
|
||||||
c.t,
|
c.t,
|
||||||
name+onedrive.DataFileSuffix,
|
name+metadata.DataFileSuffix,
|
||||||
name+onedrive.DataFileSuffix,
|
name+metadata.DataFileSuffix,
|
||||||
fileData))
|
fileData))
|
||||||
|
|
||||||
metadata := onedriveMetadata(
|
md := onedriveMetadata(
|
||||||
c.t,
|
c.t,
|
||||||
name,
|
name,
|
||||||
name+onedrive.MetaFileSuffix,
|
name+metadata.MetaFileSuffix,
|
||||||
name,
|
name,
|
||||||
perm,
|
perm,
|
||||||
c.backupVersion >= versionPermissionSwitchedToID)
|
c.backupVersion >= versionPermissionSwitchedToID)
|
||||||
c.items = append(c.items, metadata)
|
c.items = append(c.items, md)
|
||||||
c.aux = append(c.aux, metadata)
|
c.aux = append(c.aux, md)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
assert.FailNowf(c.t, "bad backup version", "version %d", c.backupVersion)
|
assert.FailNowf(c.t, "bad backup version", "version %d", c.backupVersion)
|
||||||
@ -222,8 +223,8 @@ func (c *onedriveCollection) withFolder(name string, perm permData) *onedriveCol
|
|||||||
onedriveMetadata(
|
onedriveMetadata(
|
||||||
c.t,
|
c.t,
|
||||||
"",
|
"",
|
||||||
name+onedrive.DirMetaFileSuffix,
|
name+metadata.DirMetaFileSuffix,
|
||||||
name+onedrive.DirMetaFileSuffix,
|
name+metadata.DirMetaFileSuffix,
|
||||||
perm,
|
perm,
|
||||||
c.backupVersion >= versionPermissionSwitchedToID))
|
c.backupVersion >= versionPermissionSwitchedToID))
|
||||||
|
|
||||||
@ -255,16 +256,16 @@ func (c *onedriveCollection) withPermissions(perm permData) *onedriveCollection
|
|||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
metadata := onedriveMetadata(
|
md := onedriveMetadata(
|
||||||
c.t,
|
c.t,
|
||||||
name,
|
name,
|
||||||
metaName+onedrive.DirMetaFileSuffix,
|
metaName+metadata.DirMetaFileSuffix,
|
||||||
metaName+onedrive.DirMetaFileSuffix,
|
metaName+metadata.DirMetaFileSuffix,
|
||||||
perm,
|
perm,
|
||||||
c.backupVersion >= versionPermissionSwitchedToID)
|
c.backupVersion >= versionPermissionSwitchedToID)
|
||||||
|
|
||||||
c.items = append(c.items, metadata)
|
c.items = append(c.items, md)
|
||||||
c.aux = append(c.aux, metadata)
|
c.aux = append(c.aux, md)
|
||||||
|
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
@ -17,6 +16,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/common/ptr"
|
"github.com/alcionai/corso/src/internal/common/ptr"
|
||||||
"github.com/alcionai/corso/src/internal/connector/graph"
|
"github.com/alcionai/corso/src/internal/connector/graph"
|
||||||
"github.com/alcionai/corso/src/internal/connector/onedrive/api"
|
"github.com/alcionai/corso/src/internal/connector/onedrive/api"
|
||||||
|
"github.com/alcionai/corso/src/internal/connector/onedrive/metadata"
|
||||||
"github.com/alcionai/corso/src/internal/connector/support"
|
"github.com/alcionai/corso/src/internal/connector/support"
|
||||||
"github.com/alcionai/corso/src/internal/data"
|
"github.com/alcionai/corso/src/internal/data"
|
||||||
"github.com/alcionai/corso/src/internal/observe"
|
"github.com/alcionai/corso/src/internal/observe"
|
||||||
@ -39,18 +39,10 @@ const (
|
|||||||
// be retried
|
// be retried
|
||||||
maxDownloadRetires = 3
|
maxDownloadRetires = 3
|
||||||
|
|
||||||
MetaFileSuffix = ".meta"
|
|
||||||
DirMetaFileSuffix = ".dirmeta"
|
|
||||||
DataFileSuffix = ".data"
|
|
||||||
|
|
||||||
// Used to compare in case of OneNote files
|
// Used to compare in case of OneNote files
|
||||||
MaxOneNoteFileSize = 2 * 1024 * 1024 * 1024
|
MaxOneNoteFileSize = 2 * 1024 * 1024 * 1024
|
||||||
)
|
)
|
||||||
|
|
||||||
func IsMetaFile(name string) bool {
|
|
||||||
return strings.HasSuffix(name, MetaFileSuffix) || strings.HasSuffix(name, DirMetaFileSuffix)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_ data.BackupCollection = &Collection{}
|
_ data.BackupCollection = &Collection{}
|
||||||
_ data.Stream = &Item{}
|
_ data.Stream = &Item{}
|
||||||
@ -524,12 +516,12 @@ func (oc *Collection) populateItems(ctx context.Context, errs *fault.Bus) {
|
|||||||
atomic.AddInt64(&itemsFound, 1)
|
atomic.AddInt64(&itemsFound, 1)
|
||||||
|
|
||||||
metaFileName = itemID
|
metaFileName = itemID
|
||||||
metaSuffix = MetaFileSuffix
|
metaSuffix = metadata.MetaFileSuffix
|
||||||
} else {
|
} else {
|
||||||
atomic.AddInt64(&dirsFound, 1)
|
atomic.AddInt64(&dirsFound, 1)
|
||||||
|
|
||||||
// metaFileName not set for directories so we get just ".dirmeta"
|
// metaFileName not set for directories so we get just ".dirmeta"
|
||||||
metaSuffix = DirMetaFileSuffix
|
metaSuffix = metadata.DirMetaFileSuffix
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch metadata for the file
|
// Fetch metadata for the file
|
||||||
@ -556,7 +548,7 @@ func (oc *Collection) populateItems(ctx context.Context, errs *fault.Bus) {
|
|||||||
ctx = clues.Add(ctx, "backup_item_info", itemInfo)
|
ctx = clues.Add(ctx, "backup_item_info", itemInfo)
|
||||||
|
|
||||||
if isFile {
|
if isFile {
|
||||||
dataSuffix := DataFileSuffix
|
dataSuffix := metadata.DataFileSuffix
|
||||||
|
|
||||||
// Construct a new lazy readCloser to feed to the collection consumer.
|
// Construct a new lazy readCloser to feed to the collection consumer.
|
||||||
// This ensures that downloads won't be attempted unless that consumer
|
// This ensures that downloads won't be attempted unless that consumer
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import (
|
|||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
"github.com/alcionai/corso/src/internal/connector/graph"
|
"github.com/alcionai/corso/src/internal/connector/graph"
|
||||||
|
"github.com/alcionai/corso/src/internal/connector/onedrive/metadata"
|
||||||
"github.com/alcionai/corso/src/internal/connector/support"
|
"github.com/alcionai/corso/src/internal/connector/support"
|
||||||
"github.com/alcionai/corso/src/internal/data"
|
"github.com/alcionai/corso/src/internal/data"
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
@ -266,7 +267,7 @@ func (suite *CollectionUnitTestSuite) TestCollection() {
|
|||||||
readItem := readItems[0]
|
readItem := readItems[0]
|
||||||
readItemInfo := readItem.(data.StreamInfo)
|
readItemInfo := readItem.(data.StreamInfo)
|
||||||
|
|
||||||
assert.Equal(t, testItemID+DataFileSuffix, readItem.UUID())
|
assert.Equal(t, testItemID+metadata.DataFileSuffix, readItem.UUID())
|
||||||
|
|
||||||
require.Implements(t, (*data.StreamModTime)(nil), readItem)
|
require.Implements(t, (*data.StreamModTime)(nil), readItem)
|
||||||
mt := readItem.(data.StreamModTime)
|
mt := readItem.(data.StreamModTime)
|
||||||
@ -292,7 +293,7 @@ func (suite *CollectionUnitTestSuite) TestCollection() {
|
|||||||
if test.source == OneDriveSource {
|
if test.source == OneDriveSource {
|
||||||
readItemMeta := readItems[1]
|
readItemMeta := readItems[1]
|
||||||
|
|
||||||
assert.Equal(t, testItemID+MetaFileSuffix, readItemMeta.UUID())
|
assert.Equal(t, testItemID+metadata.MetaFileSuffix, readItemMeta.UUID())
|
||||||
|
|
||||||
readMetaData, err := io.ReadAll(readItemMeta.ToReader())
|
readMetaData, err := io.ReadAll(readItemMeta.ToReader())
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
@ -588,7 +589,7 @@ func (suite *CollectionUnitTestSuite) TestCollectionPermissionBackupLatestModTim
|
|||||||
require.Equal(t, 1, collStatus.Metrics.Successes)
|
require.Equal(t, 1, collStatus.Metrics.Successes)
|
||||||
|
|
||||||
for _, i := range readItems {
|
for _, i := range readItems {
|
||||||
if strings.HasSuffix(i.UUID(), MetaFileSuffix) {
|
if strings.HasSuffix(i.UUID(), metadata.MetaFileSuffix) {
|
||||||
content, err := io.ReadAll(i.ToReader())
|
content, err := io.ReadAll(i.ToReader())
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
require.Equal(t, content, []byte("{}"))
|
require.Equal(t, content, []byte("{}"))
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/common/ptr"
|
"github.com/alcionai/corso/src/internal/common/ptr"
|
||||||
"github.com/alcionai/corso/src/internal/connector/graph"
|
"github.com/alcionai/corso/src/internal/connector/graph"
|
||||||
"github.com/alcionai/corso/src/internal/connector/onedrive/api"
|
"github.com/alcionai/corso/src/internal/connector/onedrive/api"
|
||||||
|
"github.com/alcionai/corso/src/internal/connector/onedrive/metadata"
|
||||||
"github.com/alcionai/corso/src/internal/connector/support"
|
"github.com/alcionai/corso/src/internal/connector/support"
|
||||||
"github.com/alcionai/corso/src/internal/data"
|
"github.com/alcionai/corso/src/internal/data"
|
||||||
"github.com/alcionai/corso/src/internal/observe"
|
"github.com/alcionai/corso/src/internal/observe"
|
||||||
@ -447,7 +448,7 @@ func (c *Collections) Get(
|
|||||||
}
|
}
|
||||||
|
|
||||||
service, category := c.source.toPathServiceCat()
|
service, category := c.source.toPathServiceCat()
|
||||||
metadata, err := graph.MakeMetadataCollection(
|
md, err := graph.MakeMetadataCollection(
|
||||||
c.tenant,
|
c.tenant,
|
||||||
c.resourceOwner,
|
c.resourceOwner,
|
||||||
service,
|
service,
|
||||||
@ -464,7 +465,7 @@ func (c *Collections) Get(
|
|||||||
// empty/missing and default to a full backup.
|
// empty/missing and default to a full backup.
|
||||||
logger.CtxErr(ctx, err).Info("making metadata collection for future incremental backups")
|
logger.CtxErr(ctx, err).Info("making metadata collection for future incremental backups")
|
||||||
} else {
|
} else {
|
||||||
collections = append(collections, metadata)
|
collections = append(collections, md)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(ashmrtn): Track and return the set of items to exclude.
|
// TODO(ashmrtn): Track and return the set of items to exclude.
|
||||||
@ -535,8 +536,8 @@ func (c *Collections) handleDelete(
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
excluded[itemID+DataFileSuffix] = struct{}{}
|
excluded[itemID+metadata.DataFileSuffix] = struct{}{}
|
||||||
excluded[itemID+MetaFileSuffix] = struct{}{}
|
excluded[itemID+metadata.MetaFileSuffix] = struct{}{}
|
||||||
// Exchange counts items streamed through it which includes deletions so
|
// Exchange counts items streamed through it which includes deletions so
|
||||||
// add that here too.
|
// add that here too.
|
||||||
c.NumFiles++
|
c.NumFiles++
|
||||||
@ -853,8 +854,8 @@ func (c *Collections) UpdateCollections(
|
|||||||
// Always add a file to the excluded list. The file may have been
|
// Always add a file to the excluded list. The file may have been
|
||||||
// renamed/moved/modified, so we still have to drop the
|
// renamed/moved/modified, so we still have to drop the
|
||||||
// original one and download a fresh copy.
|
// original one and download a fresh copy.
|
||||||
excluded[itemID+DataFileSuffix] = struct{}{}
|
excluded[itemID+metadata.DataFileSuffix] = struct{}{}
|
||||||
excluded[itemID+MetaFileSuffix] = struct{}{}
|
excluded[itemID+metadata.MetaFileSuffix] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import (
|
|||||||
gapi "github.com/alcionai/corso/src/internal/connector/graph/api"
|
gapi "github.com/alcionai/corso/src/internal/connector/graph/api"
|
||||||
"github.com/alcionai/corso/src/internal/connector/onedrive/api"
|
"github.com/alcionai/corso/src/internal/connector/onedrive/api"
|
||||||
"github.com/alcionai/corso/src/internal/connector/onedrive/api/mock"
|
"github.com/alcionai/corso/src/internal/connector/onedrive/api/mock"
|
||||||
|
"github.com/alcionai/corso/src/internal/connector/onedrive/metadata"
|
||||||
"github.com/alcionai/corso/src/internal/connector/support"
|
"github.com/alcionai/corso/src/internal/connector/support"
|
||||||
"github.com/alcionai/corso/src/internal/data"
|
"github.com/alcionai/corso/src/internal/data"
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
@ -147,8 +148,8 @@ func (suite *OneDriveCollectionsUnitSuite) TestGetCanonicalPath() {
|
|||||||
func getDelList(files ...string) map[string]struct{} {
|
func getDelList(files ...string) map[string]struct{} {
|
||||||
delList := map[string]struct{}{}
|
delList := map[string]struct{}{}
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
delList[file+DataFileSuffix] = struct{}{}
|
delList[file+metadata.DataFileSuffix] = struct{}{}
|
||||||
delList[file+MetaFileSuffix] = struct{}{}
|
delList[file+metadata.MetaFileSuffix] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
return delList
|
return delList
|
||||||
|
|||||||
13
src/internal/connector/onedrive/metadata/consts.go
Normal file
13
src/internal/connector/onedrive/metadata/consts.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package metadata
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
const (
|
||||||
|
MetaFileSuffix = ".meta"
|
||||||
|
DirMetaFileSuffix = ".dirmeta"
|
||||||
|
DataFileSuffix = ".data"
|
||||||
|
)
|
||||||
|
|
||||||
|
func HasMetaSuffix(name string) bool {
|
||||||
|
return strings.HasSuffix(name, MetaFileSuffix) || strings.HasSuffix(name, DirMetaFileSuffix)
|
||||||
|
}
|
||||||
@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
"github.com/alcionai/corso/src/internal/common/ptr"
|
"github.com/alcionai/corso/src/internal/common/ptr"
|
||||||
"github.com/alcionai/corso/src/internal/connector/graph"
|
"github.com/alcionai/corso/src/internal/connector/graph"
|
||||||
|
"github.com/alcionai/corso/src/internal/connector/onedrive/metadata"
|
||||||
"github.com/alcionai/corso/src/internal/data"
|
"github.com/alcionai/corso/src/internal/data"
|
||||||
"github.com/alcionai/corso/src/internal/version"
|
"github.com/alcionai/corso/src/internal/version"
|
||||||
"github.com/alcionai/corso/src/pkg/account"
|
"github.com/alcionai/corso/src/pkg/account"
|
||||||
@ -69,10 +70,10 @@ func getCollectionMetadata(
|
|||||||
|
|
||||||
// Root folder doesn't have a metadata file associated with it.
|
// Root folder doesn't have a metadata file associated with it.
|
||||||
folders := collectionPath.Folders()
|
folders := collectionPath.Folders()
|
||||||
metaName := folders[len(folders)-1] + DirMetaFileSuffix
|
metaName := folders[len(folders)-1] + metadata.DirMetaFileSuffix
|
||||||
|
|
||||||
if backupVersion >= version.OneDrive5DirMetaNoName {
|
if backupVersion >= version.OneDrive5DirMetaNoName {
|
||||||
metaName = DirMetaFileSuffix
|
metaName = metadata.DirMetaFileSuffix
|
||||||
}
|
}
|
||||||
|
|
||||||
meta, err := fetchAndReadMetadata(ctx, dc, metaName)
|
meta, err := fetchAndReadMetadata(ctx, dc, metaName)
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import (
|
|||||||
|
|
||||||
"github.com/alcionai/corso/src/internal/common/ptr"
|
"github.com/alcionai/corso/src/internal/common/ptr"
|
||||||
"github.com/alcionai/corso/src/internal/connector/graph"
|
"github.com/alcionai/corso/src/internal/connector/graph"
|
||||||
|
"github.com/alcionai/corso/src/internal/connector/onedrive/metadata"
|
||||||
"github.com/alcionai/corso/src/internal/connector/support"
|
"github.com/alcionai/corso/src/internal/connector/support"
|
||||||
"github.com/alcionai/corso/src/internal/data"
|
"github.com/alcionai/corso/src/internal/data"
|
||||||
"github.com/alcionai/corso/src/internal/diagnostics"
|
"github.com/alcionai/corso/src/internal/diagnostics"
|
||||||
@ -304,14 +305,14 @@ func restoreItem(
|
|||||||
|
|
||||||
// only v1+ backups from this point on
|
// only v1+ backups from this point on
|
||||||
|
|
||||||
if strings.HasSuffix(itemUUID, MetaFileSuffix) {
|
if strings.HasSuffix(itemUUID, metadata.MetaFileSuffix) {
|
||||||
// Just skip this for the moment since we moved the code to the above
|
// Just skip this for the moment since we moved the code to the above
|
||||||
// item restore path. We haven't yet stopped fetching these items in
|
// item restore path. We haven't yet stopped fetching these items in
|
||||||
// RestoreOp, so we still need to handle them in some way.
|
// RestoreOp, so we still need to handle them in some way.
|
||||||
return details.ItemInfo{}, true, nil
|
return details.ItemInfo{}, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasSuffix(itemUUID, DirMetaFileSuffix) {
|
if strings.HasSuffix(itemUUID, metadata.DirMetaFileSuffix) {
|
||||||
// Only the version.OneDrive1DataAndMetaFiles needed to deserialize the
|
// Only the version.OneDrive1DataAndMetaFiles needed to deserialize the
|
||||||
// permission for child folders here. Later versions can request
|
// permission for child folders here. Later versions can request
|
||||||
// permissions inline when processing the collection.
|
// permissions inline when processing the collection.
|
||||||
@ -327,7 +328,7 @@ func restoreItem(
|
|||||||
return details.ItemInfo{}, true, clues.Wrap(err, "getting directory metadata").WithClues(ctx)
|
return details.ItemInfo{}, true, clues.Wrap(err, "getting directory metadata").WithClues(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
trimmedPath := strings.TrimSuffix(itemPath.String(), DirMetaFileSuffix)
|
trimmedPath := strings.TrimSuffix(itemPath.String(), metadata.DirMetaFileSuffix)
|
||||||
folderMetas[trimmedPath] = meta
|
folderMetas[trimmedPath] = meta
|
||||||
|
|
||||||
return details.ItemInfo{}, true, nil
|
return details.ItemInfo{}, true, nil
|
||||||
@ -424,7 +425,7 @@ func restoreV1File(
|
|||||||
itemPath path.Path,
|
itemPath path.Path,
|
||||||
itemData data.Stream,
|
itemData data.Stream,
|
||||||
) (details.ItemInfo, error) {
|
) (details.ItemInfo, error) {
|
||||||
trimmedName := strings.TrimSuffix(itemData.UUID(), DataFileSuffix)
|
trimmedName := strings.TrimSuffix(itemData.UUID(), metadata.DataFileSuffix)
|
||||||
|
|
||||||
itemID, itemInfo, err := restoreData(
|
itemID, itemInfo, err := restoreData(
|
||||||
ctx,
|
ctx,
|
||||||
@ -446,7 +447,7 @@ func restoreV1File(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fetch item permissions from the collection and restore them.
|
// Fetch item permissions from the collection and restore them.
|
||||||
metaName := trimmedName + MetaFileSuffix
|
metaName := trimmedName + metadata.MetaFileSuffix
|
||||||
|
|
||||||
meta, err := fetchAndReadMetadata(ctx, fetcher, metaName)
|
meta, err := fetchAndReadMetadata(ctx, fetcher, metaName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -485,10 +486,10 @@ func restoreV6File(
|
|||||||
itemPath path.Path,
|
itemPath path.Path,
|
||||||
itemData data.Stream,
|
itemData data.Stream,
|
||||||
) (details.ItemInfo, error) {
|
) (details.ItemInfo, error) {
|
||||||
trimmedName := strings.TrimSuffix(itemData.UUID(), DataFileSuffix)
|
trimmedName := strings.TrimSuffix(itemData.UUID(), metadata.DataFileSuffix)
|
||||||
|
|
||||||
// Get metadata file so we can determine the file name.
|
// Get metadata file so we can determine the file name.
|
||||||
metaName := trimmedName + MetaFileSuffix
|
metaName := trimmedName + metadata.MetaFileSuffix
|
||||||
|
|
||||||
meta, err := fetchAndReadMetadata(ctx, fetcher, metaName)
|
meta, err := fetchAndReadMetadata(ctx, fetcher, metaName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@ -18,7 +18,7 @@ import (
|
|||||||
"golang.org/x/exp/maps"
|
"golang.org/x/exp/maps"
|
||||||
|
|
||||||
exchMock "github.com/alcionai/corso/src/internal/connector/exchange/mock"
|
exchMock "github.com/alcionai/corso/src/internal/connector/exchange/mock"
|
||||||
"github.com/alcionai/corso/src/internal/connector/onedrive"
|
"github.com/alcionai/corso/src/internal/connector/onedrive/metadata"
|
||||||
"github.com/alcionai/corso/src/internal/data"
|
"github.com/alcionai/corso/src/internal/data"
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
@ -390,8 +390,8 @@ func (suite *KopiaIntegrationSuite) TestBackupCollections_NoDetailsForMeta() {
|
|||||||
locPath,
|
locPath,
|
||||||
3)
|
3)
|
||||||
mc.Names[0] = testFileName
|
mc.Names[0] = testFileName
|
||||||
mc.Names[1] = testFileName + onedrive.MetaFileSuffix
|
mc.Names[1] = testFileName + metadata.MetaFileSuffix
|
||||||
mc.Names[2] = storePath.Folders()[0] + onedrive.DirMetaFileSuffix
|
mc.Names[2] = storePath.Folders()[0] + metadata.DirMetaFileSuffix
|
||||||
|
|
||||||
return []data.BackupCollection{mc}
|
return []data.BackupCollection{mc}
|
||||||
},
|
},
|
||||||
@ -456,7 +456,7 @@ func (suite *KopiaIntegrationSuite) TestBackupCollections_NoDetailsForMeta() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.False(t, onedrive.IsMetaFile(entry.RepoRef), "metadata entry in details")
|
assert.False(t, metadata.HasMetaSuffix(entry.RepoRef), "metadata entry in details")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shouldn't have any items to merge because the cached files are metadata
|
// Shouldn't have any items to merge because the cached files are metadata
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/common"
|
"github.com/alcionai/corso/src/internal/common"
|
||||||
"github.com/alcionai/corso/src/internal/common/crash"
|
"github.com/alcionai/corso/src/internal/common/crash"
|
||||||
"github.com/alcionai/corso/src/internal/connector"
|
"github.com/alcionai/corso/src/internal/connector"
|
||||||
"github.com/alcionai/corso/src/internal/connector/onedrive"
|
"github.com/alcionai/corso/src/internal/connector/onedrive/metadata"
|
||||||
"github.com/alcionai/corso/src/internal/data"
|
"github.com/alcionai/corso/src/internal/data"
|
||||||
"github.com/alcionai/corso/src/internal/events"
|
"github.com/alcionai/corso/src/internal/events"
|
||||||
"github.com/alcionai/corso/src/internal/kopia"
|
"github.com/alcionai/corso/src/internal/kopia"
|
||||||
@ -471,7 +471,7 @@ func getBackupDetails(
|
|||||||
if b.Version >= version.OneDrive1DataAndMetaFiles && b.Version < version.OneDrive3IsMetaMarker {
|
if b.Version >= version.OneDrive1DataAndMetaFiles && b.Version < version.OneDrive3IsMetaMarker {
|
||||||
for _, d := range deets.Entries {
|
for _, d := range deets.Entries {
|
||||||
if d.OneDrive != nil {
|
if d.OneDrive != nil {
|
||||||
d.OneDrive.IsMeta = onedrive.IsMetaFile(d.RepoRef)
|
d.OneDrive.IsMeta = metadata.HasMetaSuffix(d.RepoRef)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,10 +3,12 @@ package selectors
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/alcionai/clues"
|
"github.com/alcionai/clues"
|
||||||
|
|
||||||
"github.com/alcionai/corso/src/internal/common"
|
"github.com/alcionai/corso/src/internal/common"
|
||||||
|
"github.com/alcionai/corso/src/internal/connector/onedrive/metadata"
|
||||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/filters"
|
"github.com/alcionai/corso/src/pkg/filters"
|
||||||
@ -400,9 +402,11 @@ func (c oneDriveCategory) pathValues(
|
|||||||
// Ignore `drives/<driveID>/root:` for folder comparison
|
// Ignore `drives/<driveID>/root:` for folder comparison
|
||||||
rFld := path.Builder{}.Append(repo.Folders()...).PopFront().PopFront().PopFront().String()
|
rFld := path.Builder{}.Append(repo.Folders()...).PopFront().PopFront().PopFront().String()
|
||||||
|
|
||||||
|
itemID := strings.TrimSuffix(repo.Item(), metadata.DataFileSuffix)
|
||||||
|
|
||||||
result := map[categorizer][]string{
|
result := map[categorizer][]string{
|
||||||
OneDriveFolder: {rFld},
|
OneDriveFolder: {rFld},
|
||||||
OneDriveItem: {ent.OneDrive.ItemName, ent.ShortRef},
|
OneDriveItem: {ent.OneDrive.ItemName, ent.ShortRef, itemID},
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(ent.LocationRef) > 0 {
|
if len(ent.LocationRef) > 0 {
|
||||||
|
|||||||
@ -270,7 +270,7 @@ func (suite *OneDriveSelectorSuite) TestOneDriveCategory_PathValues() {
|
|||||||
|
|
||||||
expected := map[categorizer][]string{
|
expected := map[categorizer][]string{
|
||||||
OneDriveFolder: {"dir1/dir2"},
|
OneDriveFolder: {"dir1/dir2"},
|
||||||
OneDriveItem: {fileName, shortRef},
|
OneDriveItem: {fileName, shortRef, fileName + "-id"},
|
||||||
}
|
}
|
||||||
|
|
||||||
ent := details.DetailsEntry{
|
ent := details.DetailsEntry{
|
||||||
|
|||||||
@ -3,10 +3,12 @@ package selectors
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/alcionai/clues"
|
"github.com/alcionai/clues"
|
||||||
|
|
||||||
"github.com/alcionai/corso/src/internal/common"
|
"github.com/alcionai/corso/src/internal/common"
|
||||||
|
"github.com/alcionai/corso/src/internal/connector/onedrive/metadata"
|
||||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/filters"
|
"github.com/alcionai/corso/src/pkg/filters"
|
||||||
@ -522,6 +524,7 @@ func (c sharePointCategory) pathValues(
|
|||||||
folderCat, itemCat categorizer
|
folderCat, itemCat categorizer
|
||||||
itemName = repo.Item()
|
itemName = repo.Item()
|
||||||
dropDriveFolderPrefix bool
|
dropDriveFolderPrefix bool
|
||||||
|
itemID string
|
||||||
)
|
)
|
||||||
|
|
||||||
switch c {
|
switch c {
|
||||||
@ -532,6 +535,7 @@ func (c sharePointCategory) pathValues(
|
|||||||
|
|
||||||
dropDriveFolderPrefix = true
|
dropDriveFolderPrefix = true
|
||||||
folderCat, itemCat = SharePointLibraryFolder, SharePointLibraryItem
|
folderCat, itemCat = SharePointLibraryFolder, SharePointLibraryItem
|
||||||
|
itemID = strings.TrimSuffix(itemName, metadata.DataFileSuffix)
|
||||||
itemName = ent.SharePoint.ItemName
|
itemName = ent.SharePoint.ItemName
|
||||||
|
|
||||||
case SharePointList, SharePointListItem:
|
case SharePointList, SharePointListItem:
|
||||||
@ -555,6 +559,10 @@ func (c sharePointCategory) pathValues(
|
|||||||
itemCat: {itemName, ent.ShortRef},
|
itemCat: {itemName, ent.ShortRef},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(itemID) > 0 {
|
||||||
|
result[itemCat] = append(result[itemCat], itemID)
|
||||||
|
}
|
||||||
|
|
||||||
if len(ent.LocationRef) > 0 {
|
if len(ent.LocationRef) > 0 {
|
||||||
result[folderCat] = append(result[folderCat], ent.LocationRef)
|
result[folderCat] = append(result[folderCat], ent.LocationRef)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -353,7 +353,7 @@ func (suite *SharePointSelectorSuite) TestSharePointCategory_PathValues() {
|
|||||||
pathElems: driveElems,
|
pathElems: driveElems,
|
||||||
expected: map[categorizer][]string{
|
expected: map[categorizer][]string{
|
||||||
SharePointLibraryFolder: {"dir1/dir2"},
|
SharePointLibraryFolder: {"dir1/dir2"},
|
||||||
SharePointLibraryItem: {itemName, shortRef},
|
SharePointLibraryItem: {itemName, shortRef, itemName + "-id"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user