Core logic for exporting data from OneDrive Next: https://github.com/alcionai/corso/pull/3820 --- #### Does this PR need a docs update or release note? - [ ] ✅ Yes, it's included - [x] 🕐 Yes, but in a later PR - [ ] ⛔ No #### Type of change <!--- Please check the type of change your PR introduces: ---> - [x] 🌻 Feature - [ ] 🐛 Bugfix - [ ] 🗺️ Documentation - [ ] 🤖 Supportability/Tests - [ ] 💻 CI/Deployment - [ ] 🧹 Tech Debt/Cleanup #### Issue(s) <!-- Can reference multiple issues. Use one of the following "magic words" - "closes, fixes" to auto-close the Github issue. --> * https://github.com/alcionai/corso/issues/3670 * https://github.com/alcionai/corso/pull/3797 #### Test Plan <!-- How will this be tested prior to merging.--> - [ ] 💪 Manual - [x] ⚡ Unit test - [ ] 💚 E2E
167 lines
4.2 KiB
Go
167 lines
4.2 KiB
Go
package onedrive
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
"github.com/alcionai/clues"
|
|
|
|
"github.com/alcionai/corso/src/internal/data"
|
|
"github.com/alcionai/corso/src/internal/m365/onedrive/metadata"
|
|
"github.com/alcionai/corso/src/internal/version"
|
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
|
"github.com/alcionai/corso/src/pkg/control"
|
|
"github.com/alcionai/corso/src/pkg/export"
|
|
"github.com/alcionai/corso/src/pkg/fault"
|
|
"github.com/alcionai/corso/src/pkg/path"
|
|
)
|
|
|
|
var _ export.Collection = &exportCollection{}
|
|
|
|
// exportCollection is the implementation of export.ExportCollection for OneDrive
|
|
type exportCollection struct {
|
|
// baseDir contains the path of the collection
|
|
baseDir string
|
|
|
|
// backingCollection is the restore collection from which we will
|
|
// create the export collection.
|
|
backingCollection data.RestoreCollection
|
|
|
|
// backupVersion is the backupVersion of the backup this collection was part
|
|
// of. This is required to figure out how to get the name of the
|
|
// item.
|
|
backupVersion int
|
|
}
|
|
|
|
func (ec exportCollection) BasePath() string {
|
|
return ec.baseDir
|
|
}
|
|
|
|
func (ec exportCollection) Items(ctx context.Context) <-chan export.Item {
|
|
ch := make(chan export.Item)
|
|
go items(ctx, ec, ch)
|
|
|
|
return ch
|
|
}
|
|
|
|
// items converts items in backing collection to export items
|
|
func items(ctx context.Context, ec exportCollection, ch chan<- export.Item) {
|
|
defer close(ch)
|
|
|
|
errs := fault.New(false)
|
|
|
|
// There will only be a single item in the backingCollections
|
|
// for OneDrive
|
|
for item := range ec.backingCollection.Items(ctx, errs) {
|
|
itemUUID := item.UUID()
|
|
if isMetadataFile(itemUUID, ec.backupVersion) {
|
|
continue
|
|
}
|
|
|
|
name, err := getItemName(ctx, itemUUID, ec.backupVersion, ec.backingCollection)
|
|
|
|
ch <- export.Item{
|
|
ID: itemUUID,
|
|
Data: export.ItemData{
|
|
Name: name,
|
|
Body: item.ToReader(),
|
|
},
|
|
Error: err,
|
|
}
|
|
}
|
|
|
|
eitems, erecovereable := errs.ItemsAndRecovered()
|
|
|
|
// Return all the items that we failed to get from kopia at the end
|
|
for _, err := range eitems {
|
|
ch <- export.Item{
|
|
ID: err.ID,
|
|
Error: &err,
|
|
}
|
|
}
|
|
|
|
for _, ec := range erecovereable {
|
|
ch <- export.Item{
|
|
Error: ec,
|
|
}
|
|
}
|
|
}
|
|
|
|
// isMetadataFile is used to determine if a path corresponds to a
|
|
// metadata file. This is OneDrive specific logic and depends on the
|
|
// version of the backup unlike metadata.IsMetadataFile which only has
|
|
// to be concerned about the current version.
|
|
func isMetadataFile(id string, backupVersion int) bool {
|
|
if backupVersion < version.OneDrive1DataAndMetaFiles {
|
|
return false
|
|
}
|
|
|
|
return strings.HasSuffix(id, metadata.MetaFileSuffix) ||
|
|
strings.HasSuffix(id, metadata.DirMetaFileSuffix)
|
|
}
|
|
|
|
// getItemName is used to get the name of the item.
|
|
// How we get the name depends on the version of the backup.
|
|
func getItemName(
|
|
ctx context.Context,
|
|
id string,
|
|
backupVersion int,
|
|
fin data.FetchItemByNamer,
|
|
) (string, error) {
|
|
if backupVersion < version.OneDrive1DataAndMetaFiles {
|
|
return id, nil
|
|
}
|
|
|
|
if backupVersion < version.OneDrive5DirMetaNoName {
|
|
return strings.TrimSuffix(id, metadata.DataFileSuffix), nil
|
|
}
|
|
|
|
if strings.HasSuffix(id, metadata.DataFileSuffix) {
|
|
trimmedName := strings.TrimSuffix(id, metadata.DataFileSuffix)
|
|
metaName := trimmedName + metadata.MetaFileSuffix
|
|
|
|
meta, err := fetchAndReadMetadata(ctx, fin, metaName)
|
|
if err != nil {
|
|
return "", clues.Wrap(err, "getting metadata").WithClues(ctx)
|
|
}
|
|
|
|
return meta.FileName, nil
|
|
}
|
|
|
|
return "", clues.New("invalid item id").WithClues(ctx)
|
|
}
|
|
|
|
// ExportRestoreCollections will create the export collections for the
|
|
// given restore collections.
|
|
func ExportRestoreCollections(
|
|
ctx context.Context,
|
|
backupVersion int,
|
|
exportCfg control.ExportConfig,
|
|
opts control.Options,
|
|
dcs []data.RestoreCollection,
|
|
deets *details.Builder,
|
|
errs *fault.Bus,
|
|
) ([]export.Collection, error) {
|
|
var (
|
|
el = errs.Local()
|
|
ec = make([]export.Collection, 0, len(dcs))
|
|
)
|
|
|
|
for _, dc := range dcs {
|
|
drivePath, err := path.ToDrivePath(dc.FullPath())
|
|
if err != nil {
|
|
return nil, clues.Wrap(err, "transforming path to drive path").WithClues(ctx)
|
|
}
|
|
|
|
baseDir := path.Builder{}.Append(drivePath.Folders...)
|
|
|
|
ec = append(ec, exportCollection{
|
|
baseDir: baseDir.String(),
|
|
backingCollection: dc,
|
|
backupVersion: backupVersion,
|
|
})
|
|
}
|
|
|
|
return ec, el.Failure()
|
|
}
|