diff --git a/CHANGELOG.md b/CHANGELOG.md index a9bd9b447..eb25867b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Show owner information when doing backup list in json format +### Fixed +- Corso-generated .meta files and permissions no longer appear in the backup details. + ### Known Issues - Folders and Calendars containing zero items or subfolders are not included in the backup. diff --git a/src/cmd/factory/impl/common.go b/src/cmd/factory/impl/common.go index 2e418e273..ff7ed093e 100644 --- a/src/cmd/factory/impl/common.go +++ b/src/cmd/factory/impl/common.go @@ -15,8 +15,8 @@ import ( "github.com/alcionai/corso/src/internal/connector/graph" "github.com/alcionai/corso/src/internal/connector/mockconnector" "github.com/alcionai/corso/src/internal/data" + "github.com/alcionai/corso/src/internal/version" "github.com/alcionai/corso/src/pkg/account" - "github.com/alcionai/corso/src/pkg/backup" "github.com/alcionai/corso/src/pkg/backup/details" "github.com/alcionai/corso/src/pkg/control" "github.com/alcionai/corso/src/pkg/credentials" @@ -94,7 +94,7 @@ func generateAndRestoreItems( Infof(ctx, "Generating %d %s items in %s\n", howMany, cat, Destination) - return gc.RestoreDataCollections(ctx, backup.Version, acct, sel, dest, opts, dataColls, errs) + return gc.RestoreDataCollections(ctx, version.Backup, acct, sel, dest, opts, dataColls, errs) } // ------------------------------------------------------------------------------------------ diff --git a/src/internal/connector/graph_connector_helper_test.go b/src/internal/connector/graph_connector_helper_test.go index a3adae175..5a5a8eb99 100644 --- a/src/internal/connector/graph_connector_helper_test.go +++ b/src/internal/connector/graph_connector_helper_test.go @@ -712,6 +712,7 @@ func compareOneDriveItem( } name := item.UUID() + if strings.HasSuffix(name, onedrive.MetaFileSuffix) || strings.HasSuffix(name, onedrive.DirMetaFileSuffix) { var ( @@ -1050,10 +1051,12 @@ func collectionsForInfo( allInfo []colInfo, backupVersion int, ) (int, int, []data.RestoreCollection, map[string]map[string][]byte) { - collections := make([]data.RestoreCollection, 0, len(allInfo)) - expectedData := make(map[string]map[string][]byte, len(allInfo)) - totalItems := 0 - kopiaEntries := 0 + var ( + collections = make([]data.RestoreCollection, 0, len(allInfo)) + expectedData = make(map[string]map[string][]byte, len(allInfo)) + totalItems = 0 + kopiaEntries = 0 + ) for _, info := range allInfo { pth := mustToDataLayerPath( diff --git a/src/internal/connector/graph_connector_onedrive_test.go b/src/internal/connector/graph_connector_onedrive_test.go index 104b0f7cd..c0a5d351a 100644 --- a/src/internal/connector/graph_connector_onedrive_test.go +++ b/src/internal/connector/graph_connector_onedrive_test.go @@ -14,8 +14,8 @@ import ( "github.com/alcionai/corso/src/internal/connector/graph" "github.com/alcionai/corso/src/internal/connector/onedrive" "github.com/alcionai/corso/src/internal/tester" + "github.com/alcionai/corso/src/internal/version" "github.com/alcionai/corso/src/pkg/account" - "github.com/alcionai/corso/src/pkg/backup" "github.com/alcionai/corso/src/pkg/control" "github.com/alcionai/corso/src/pkg/path" ) @@ -175,9 +175,7 @@ func (c *onedriveCollection) withFile( name+onedrive.DataFileSuffix, fileData)) - case 1: - fallthrough - case 2: + case 1, 2, 3: c.items = append(c.items, onedriveItemWithData( c.t, name+onedrive.DataFileSuffix, @@ -209,9 +207,7 @@ func (c *onedriveCollection) withFolder( case 0: return c - case 1: - fallthrough - case 2: + case 1, 2, 3: c.items = append( c.items, onedriveMetadata( @@ -219,8 +215,7 @@ func (c *onedriveCollection) withFolder( "", name+onedrive.DirMetaFileSuffix, user, - roles), - ) + roles)) default: assert.FailNowf(c.t, "bad backup version", "version %d", c.backupVersion) @@ -237,7 +232,7 @@ func (c *onedriveCollection) withPermissions( ) *onedriveCollection { // These versions didn't store permissions for the folder or didn't store them // in the folder's collection. - if c.backupVersion < 3 { + if c.backupVersion < version.OneDriveXIncludesPermissions { return c } @@ -281,7 +276,7 @@ type onedriveColInfo struct { type onedriveTest struct { name string // Version this test first be run for. Will run from - // [startVersion, backup.Version] inclusive. + // [startVersion, version.Backup] inclusive. startVersion int cols []onedriveColInfo } @@ -467,17 +462,17 @@ func (suite *GraphConnectorOneDriveIntegrationSuite) TestRestoreAndBackup_Multip } for _, test := range table { - expected := testDataForInfo(suite.T(), test.cols, backup.Version) + expected := testDataForInfo(suite.T(), test.cols, version.Backup) - for version := test.startVersion; version <= backup.Version; version++ { - suite.T().Run(fmt.Sprintf("%s_Version%d", test.name, version), func(t *testing.T) { - input := testDataForInfo(t, test.cols, version) + for vn := test.startVersion; vn <= version.Backup; vn++ { + suite.T().Run(fmt.Sprintf("%s_Version%d", test.name, vn), func(t *testing.T) { + input := testDataForInfo(t, test.cols, vn) testData := restoreBackupInfoMultiVersion{ service: path.OneDriveService, resource: Users, - backupVersion: version, - countMeta: version == 0, + backupVersion: vn, + countMeta: vn == 0, collectionsPrevious: input, collectionsLatest: expected, } @@ -691,17 +686,17 @@ func (suite *GraphConnectorOneDriveIntegrationSuite) TestPermissionsRestoreAndBa } for _, test := range table { - expected := testDataForInfo(suite.T(), test.cols, backup.Version) + expected := testDataForInfo(suite.T(), test.cols, version.Backup) - for version := test.startVersion; version <= backup.Version; version++ { - suite.T().Run(fmt.Sprintf("%s_Version%d", test.name, version), func(t *testing.T) { - input := testDataForInfo(t, test.cols, version) + for vn := test.startVersion; vn <= version.Backup; vn++ { + suite.T().Run(fmt.Sprintf("%s_Version%d", test.name, vn), func(t *testing.T) { + input := testDataForInfo(t, test.cols, vn) testData := restoreBackupInfoMultiVersion{ service: path.OneDriveService, resource: Users, - backupVersion: version, - countMeta: version == 0, + backupVersion: vn, + countMeta: vn == 0, collectionsPrevious: input, collectionsLatest: expected, } @@ -764,17 +759,17 @@ func (suite *GraphConnectorOneDriveIntegrationSuite) TestPermissionsBackupAndNoR } for _, test := range table { - expected := testDataForInfo(suite.T(), test.cols, backup.Version) + expected := testDataForInfo(suite.T(), test.cols, version.Backup) - for version := test.startVersion; version <= backup.Version; version++ { - suite.T().Run(fmt.Sprintf("Version%d", version), func(t *testing.T) { - input := testDataForInfo(t, test.cols, version) + for vn := test.startVersion; vn <= version.Backup; vn++ { + suite.T().Run(fmt.Sprintf("Version%d", vn), func(t *testing.T) { + input := testDataForInfo(t, test.cols, vn) testData := restoreBackupInfoMultiVersion{ service: path.OneDriveService, resource: Users, - backupVersion: version, - countMeta: version == 0, + backupVersion: vn, + countMeta: vn == 0, collectionsPrevious: input, collectionsLatest: expected, } @@ -811,7 +806,7 @@ func (suite *GraphConnectorOneDriveIntegrationSuite) TestPermissionsRestoreAndNo test := restoreBackupInfoMultiVersion{ service: path.OneDriveService, resource: Users, - backupVersion: backup.Version, + backupVersion: version.Backup, countMeta: false, collectionsPrevious: []colInfo{ newOneDriveCollection( @@ -821,7 +816,7 @@ func (suite *GraphConnectorOneDriveIntegrationSuite) TestPermissionsRestoreAndNo driveID, "root:", }, - backup.Version, + version.Backup, ). withFile( fileName, @@ -843,7 +838,7 @@ func (suite *GraphConnectorOneDriveIntegrationSuite) TestPermissionsRestoreAndNo "root:", folderBName, }, - backup.Version, + version.Backup, ). withFile( fileName, @@ -865,7 +860,7 @@ func (suite *GraphConnectorOneDriveIntegrationSuite) TestPermissionsRestoreAndNo driveID, "root:", }, - backup.Version, + version.Backup, ). withFile( fileName, @@ -887,7 +882,7 @@ func (suite *GraphConnectorOneDriveIntegrationSuite) TestPermissionsRestoreAndNo "root:", folderBName, }, - backup.Version, + version.Backup, ). withFile( fileName, diff --git a/src/internal/connector/graph_connector_test.go b/src/internal/connector/graph_connector_test.go index 152e85331..1216f4a04 100644 --- a/src/internal/connector/graph_connector_test.go +++ b/src/internal/connector/graph_connector_test.go @@ -17,8 +17,8 @@ import ( "github.com/alcionai/corso/src/internal/connector/support" "github.com/alcionai/corso/src/internal/data" "github.com/alcionai/corso/src/internal/tester" + "github.com/alcionai/corso/src/internal/version" "github.com/alcionai/corso/src/pkg/account" - "github.com/alcionai/corso/src/pkg/backup" "github.com/alcionai/corso/src/pkg/control" "github.com/alcionai/corso/src/pkg/fault" "github.com/alcionai/corso/src/pkg/path" @@ -237,7 +237,7 @@ func (suite *GraphConnectorIntegrationSuite) TestRestoreFailsBadService() { deets, err := suite.connector.RestoreDataCollections( ctx, - backup.Version, + version.Backup, acct, sel, dest, @@ -314,7 +314,7 @@ func (suite *GraphConnectorIntegrationSuite) TestEmptyCollections() { deets, err := suite.connector.RestoreDataCollections( ctx, - backup.Version, + version.Backup, suite.acct, test.sel, dest, @@ -527,13 +527,13 @@ func runRestoreBackupTest( t, config, test.collections, - backup.Version) + version.Backup) runRestore( t, ctx, config, - backup.Version, + version.Backup, collections, totalItems) @@ -590,7 +590,7 @@ func runRestoreBackupTestVersions( t, config, test.collectionsLatest, - backup.Version) + version.Backup) runBackupAndCompare( t, @@ -930,7 +930,7 @@ func (suite *GraphConnectorIntegrationSuite) TestMultiFolderBackupDifferentNames suite.user, dest, []colInfo{collection}, - backup.Version, + version.Backup, ) allItems += totalItems @@ -948,7 +948,7 @@ func (suite *GraphConnectorIntegrationSuite) TestMultiFolderBackupDifferentNames restoreGC := loadConnector(ctx, t, graph.HTTPClient(graph.NoTimeout()), test.resource) deets, err := restoreGC.RestoreDataCollections( ctx, - backup.Version, + version.Backup, suite.acct, restoreSel, dest, diff --git a/src/internal/connector/onedrive/collection.go b/src/internal/connector/onedrive/collection.go index 095c42941..814e14965 100644 --- a/src/internal/connector/onedrive/collection.go +++ b/src/internal/connector/onedrive/collection.go @@ -406,7 +406,7 @@ func (oc *Collection) populateItems(ctx context.Context) { // TODO(meain): Remove this once we change to always // backing up permissions. Until then we cannot rely - // on weather the previous data is what we need as the + // on whether the previous data is what we need as the // user might have not backup up permissions in the // previous run. metaItemInfo := details.ItemInfo{} @@ -415,6 +415,7 @@ func (oc *Collection) populateItems(ctx context.Context) { ItemName: itemInfo.OneDrive.ItemName, DriveName: itemInfo.OneDrive.DriveName, ItemType: itemInfo.OneDrive.ItemType, + IsMeta: true, Modified: time.Now(), // set to current time to always refresh Owner: itemInfo.OneDrive.Owner, ParentPath: itemInfo.OneDrive.ParentPath, diff --git a/src/internal/connector/onedrive/restore.go b/src/internal/connector/onedrive/restore.go index f87927a54..51068ca4f 100644 --- a/src/internal/connector/onedrive/restore.go +++ b/src/internal/connector/onedrive/restore.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "io" - "math" "runtime/trace" "sort" "strings" @@ -20,6 +19,7 @@ import ( "github.com/alcionai/corso/src/internal/data" D "github.com/alcionai/corso/src/internal/diagnostics" "github.com/alcionai/corso/src/internal/observe" + "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/fault" @@ -27,23 +27,10 @@ import ( "github.com/alcionai/corso/src/pkg/path" ) -const ( - // copyBufferSize is used for chunked upload - // Microsoft recommends 5-10MB buffers - // https://docs.microsoft.com/en-us/graph/api/driveitem-createuploadsession?view=graph-rest-1.0#best-practices - copyBufferSize = 5 * 1024 * 1024 - - // versionWithDataAndMetaFiles is the corso backup format version - // in which we split from storing just the data to storing both - // the data and metadata in two files. - versionWithDataAndMetaFiles = 1 - // versionWithNameInMeta points to the backup format version where we begin - // storing files in kopia with their item ID instead of their OneDrive file - // name. - // TODO(ashmrtn): Update this to a real value when we merge the file name - // change. Set to MAXINT for now to keep the if-check using it working. - versionWithNameInMeta = math.MaxInt -) +// copyBufferSize is used for chunked upload +// Microsoft recommends 5-10MB buffers +// https://docs.microsoft.com/en-us/graph/api/driveitem-createuploadsession?view=graph-rest-1.0#best-practices +const copyBufferSize = 5 * 1024 * 1024 func getParentPermissions( parentPath path.Path, @@ -288,7 +275,7 @@ func RestoreCollection( continue } - if source == OneDriveSource && backupVersion >= versionWithDataAndMetaFiles { + if source == OneDriveSource && backupVersion >= version.OneDrive1DataAndMetaFiles { name := itemData.UUID() if strings.HasSuffix(name, DataFileSuffix) { @@ -300,7 +287,7 @@ func RestoreCollection( err error ) - if backupVersion < versionWithNameInMeta { + if backupVersion < version.OneDriveXNameInMeta { itemInfo, err = restoreV1File( ctx, source, diff --git a/src/internal/operations/backup_integration_test.go b/src/internal/operations/backup_integration_test.go index 899befb74..00bd20813 100644 --- a/src/internal/operations/backup_integration_test.go +++ b/src/internal/operations/backup_integration_test.go @@ -27,6 +27,7 @@ import ( "github.com/alcionai/corso/src/internal/kopia" "github.com/alcionai/corso/src/internal/model" "github.com/alcionai/corso/src/internal/tester" + "github.com/alcionai/corso/src/internal/version" "github.com/alcionai/corso/src/pkg/account" "github.com/alcionai/corso/src/pkg/backup" "github.com/alcionai/corso/src/pkg/backup/details" @@ -341,7 +342,7 @@ func generateContainerOfItems( deets, err := gc.RestoreDataCollections( ctx, - backup.Version, + version.Backup, acct, sel, dest, diff --git a/src/internal/version/backup.go b/src/internal/version/backup.go new file mode 100644 index 000000000..25b719fd9 --- /dev/null +++ b/src/internal/version/backup.go @@ -0,0 +1,37 @@ +package version + +import "math" + +const Backup = 3 + +// Various labels to refer to important version changes. +// Labels don't need 1:1 service:version representation. Add a new +// label when it's important to mark a delta in behavior that's handled +// somewhere in the logic. +// Labels should state their application, the backup version number, +// and the colloquial purpose of the label. +const ( + // OneDrive1DataAndMetaFiles is the corso backup format version + // in which we split from storing just the data to storing both + // the data and metadata in two files. + OneDrive1DataAndMetaFiles = 1 + + // OneDrive3IsMetaMarker is a small improvement on + // VersionWithDataAndMetaFiles, but has a marker IsMeta which + // specifies if the file is a meta file or a data file. + OneDrive3IsMetaMarker = 3 + + // OneDrive4IncludesPermissions includes permissions in the backup. + // Note that this is larger than the current backup version. That's + // because it isn't implemented yet. But we have tests based on this, + // so maybe we just keep bumping the verson ahead of the backup until + // it gets implemented. + OneDriveXIncludesPermissions = Backup + 1 + + // OneDriveXNameInMeta points to the backup format version where we begin + // storing files in kopia with their item ID instead of their OneDrive file + // name. + // TODO(ashmrtn): Update this to a real value when we merge the file name + // change. Set to MAXINT for now to keep the if-check using it working. + OneDriveXNameInMeta = math.MaxInt +) diff --git a/src/pkg/backup/backup.go b/src/pkg/backup/backup.go index 0044736bc..2cd08bc1c 100644 --- a/src/pkg/backup/backup.go +++ b/src/pkg/backup/backup.go @@ -10,12 +10,11 @@ import ( "github.com/alcionai/corso/src/internal/connector/support" "github.com/alcionai/corso/src/internal/model" "github.com/alcionai/corso/src/internal/stats" + "github.com/alcionai/corso/src/internal/version" "github.com/alcionai/corso/src/pkg/fault" "github.com/alcionai/corso/src/pkg/selectors" ) -const Version = 2 - // Backup represents the result of a backup operation type Backup struct { model.BaseModel @@ -72,7 +71,7 @@ func New( Errors: errs.Data(), ReadWrites: rw, StartAndEndTime: se, - Version: Version, + Version: version.Backup, } } diff --git a/src/pkg/backup/details/details.go b/src/pkg/backup/details/details.go index 9374c429d..bb8b57e1c 100644 --- a/src/pkg/backup/details/details.go +++ b/src/pkg/backup/details/details.go @@ -35,10 +35,12 @@ type DetailsModel struct { // Print writes the DetailModel Entries to StdOut, in the format // requested by the caller. func (dm DetailsModel) PrintEntries(ctx context.Context) { + sl := dm.FilterMetaFiles() + if print.JSONFormat() { - printJSON(ctx, dm) + printJSON(ctx, sl) } else { - printTable(ctx, dm) + printTable(ctx, sl) } } @@ -71,13 +73,13 @@ func printJSON(ctx context.Context, dm DetailsModel) { print.All(ctx, ents...) } -// Paths returns the list of Paths for non-folder items extracted from the -// Entries slice. +// Paths returns the list of Paths for non-folder and non-meta items extracted +// from the Entries slice. func (dm DetailsModel) Paths() []string { r := make([]string, 0, len(dm.Entries)) for _, ent := range dm.Entries { - if ent.Folder != nil { + if ent.Folder != nil || ent.isMetaFile() { continue } @@ -89,21 +91,49 @@ func (dm DetailsModel) Paths() []string { // Items returns a slice of *ItemInfo that does not contain any FolderInfo // entries. Required because not all folders in the details are valid resource -// paths. +// paths, and we want to slice out metadata. func (dm DetailsModel) Items() []*DetailsEntry { res := make([]*DetailsEntry, 0, len(dm.Entries)) for i := 0; i < len(dm.Entries); i++ { - if dm.Entries[i].Folder != nil { + ent := dm.Entries[i] + if ent.Folder != nil || ent.isMetaFile() { continue } - res = append(res, &dm.Entries[i]) + res = append(res, &ent) } return res } +// FilterMetaFiles returns a copy of the Details with all of the +// .meta files removed from the entries. +func (dm DetailsModel) FilterMetaFiles() DetailsModel { + d2 := DetailsModel{ + Entries: []DetailsEntry{}, + } + + for _, ent := range dm.Entries { + if !ent.isMetaFile() { + d2.Entries = append(d2.Entries, ent) + } + } + + return d2 +} + +// Check if a file is a metadata file. These are used to store +// additional data like permissions in case of OneDrive and are not to +// be treated as regular files. +func (de DetailsEntry) isMetaFile() bool { + return de.ItemInfo.OneDrive != nil && de.ItemInfo.OneDrive.IsMeta +} + +// --------------------------------------------------------------------------- +// Builder +// --------------------------------------------------------------------------- + // Builder should be used to create a details model. type Builder struct { d Details @@ -583,6 +613,7 @@ type OneDriveInfo struct { ItemName string `json:"itemName,omitempty"` DriveName string `json:"driveName,omitempty"` ItemType ItemType `json:"itemType,omitempty"` + IsMeta bool `json:"isMeta,omitempty"` Modified time.Time `json:"modified,omitempty"` Owner string `json:"owner,omitempty"` ParentPath string `json:"parentPath"` diff --git a/src/pkg/backup/details/details_test.go b/src/pkg/backup/details/details_test.go index cf4cdcbb5..12231a623 100644 --- a/src/pkg/backup/details/details_test.go +++ b/src/pkg/backup/details/details_test.go @@ -224,6 +224,69 @@ var pathItemsTable = []struct { expectRepoRefs: []string{"abcde", "12345"}, expectLocationRefs: []string{"locationref", "locationref2"}, }, + { + name: "multiple entries with meta file", + ents: []DetailsEntry{ + { + RepoRef: "abcde", + LocationRef: "locationref", + }, + { + RepoRef: "foo.meta", + LocationRef: "locationref.dirmeta", + ItemInfo: ItemInfo{ + OneDrive: &OneDriveInfo{IsMeta: false}, + }, + }, + { + RepoRef: "is-meta-file", + LocationRef: "locationref-meta-file", + ItemInfo: ItemInfo{ + OneDrive: &OneDriveInfo{IsMeta: true}, + }, + }, + }, + expectRepoRefs: []string{"abcde", "foo.meta"}, + expectLocationRefs: []string{"locationref", "locationref.dirmeta"}, + }, + { + name: "multiple entries with folder and meta file", + ents: []DetailsEntry{ + { + RepoRef: "abcde", + LocationRef: "locationref", + }, + { + RepoRef: "12345", + LocationRef: "locationref2", + }, + { + RepoRef: "foo.meta", + LocationRef: "locationref.dirmeta", + ItemInfo: ItemInfo{ + OneDrive: &OneDriveInfo{IsMeta: false}, + }, + }, + { + RepoRef: "is-meta-file", + LocationRef: "locationref-meta-file", + ItemInfo: ItemInfo{ + OneDrive: &OneDriveInfo{IsMeta: true}, + }, + }, + { + RepoRef: "deadbeef", + LocationRef: "locationref3", + ItemInfo: ItemInfo{ + Folder: &FolderInfo{ + DisplayName: "test folder", + }, + }, + }, + }, + expectRepoRefs: []string{"abcde", "12345", "foo.meta"}, + expectLocationRefs: []string{"locationref", "locationref2", "locationref.dirmeta"}, + }, } func (suite *DetailsUnitSuite) TestDetailsModel_Path() { @@ -259,6 +322,38 @@ func (suite *DetailsUnitSuite) TestDetailsModel_Items() { } } +func (suite *DetailsUnitSuite) TestDetailsModel_FilterMetaFiles() { + t := suite.T() + + d := &DetailsModel{ + Entries: []DetailsEntry{ + { + RepoRef: "a.data", + ItemInfo: ItemInfo{ + OneDrive: &OneDriveInfo{IsMeta: false}, + }, + }, + { + RepoRef: "b.meta", + ItemInfo: ItemInfo{ + OneDrive: &OneDriveInfo{IsMeta: false}, + }, + }, + { + RepoRef: "c.meta", + ItemInfo: ItemInfo{ + OneDrive: &OneDriveInfo{IsMeta: true}, + }, + }, + }, + } + + d2 := d.FilterMetaFiles() + + assert.Len(t, d2.Entries, 2) + assert.Len(t, d.Entries, 3) +} + func (suite *DetailsUnitSuite) TestDetails_AddFolders() { itemTime := time.Date(2022, 10, 21, 10, 0, 0, 0, time.UTC) folderTimeOlderThanItem := time.Date(2022, 9, 21, 10, 0, 0, 0, time.UTC) diff --git a/src/pkg/repository/repository.go b/src/pkg/repository/repository.go index 10654305a..a28f7f2fb 100644 --- a/src/pkg/repository/repository.go +++ b/src/pkg/repository/repository.go @@ -2,6 +2,7 @@ package repository import ( "context" + "strings" "time" "github.com/alcionai/clues" @@ -9,12 +10,14 @@ import ( "github.com/pkg/errors" "github.com/alcionai/corso/src/internal/common/crash" + "github.com/alcionai/corso/src/internal/connector/onedrive" "github.com/alcionai/corso/src/internal/events" "github.com/alcionai/corso/src/internal/kopia" "github.com/alcionai/corso/src/internal/model" "github.com/alcionai/corso/src/internal/observe" "github.com/alcionai/corso/src/internal/operations" "github.com/alcionai/corso/src/internal/streamstore" + "github.com/alcionai/corso/src/internal/version" "github.com/alcionai/corso/src/pkg/account" "github.com/alcionai/corso/src/pkg/backup" "github.com/alcionai/corso/src/pkg/backup/details" @@ -358,6 +361,20 @@ func (r repository) BackupDetails( return nil, nil, errs.Fail(err) } + // Retroactively fill in isMeta information for items in older + // backup versions without that info + // version.Restore2 introduces the IsMeta flag, so only v1 needs a check. + if b.Version >= version.OneDrive1DataAndMetaFiles && b.Version < version.OneDrive3IsMetaMarker { + for _, d := range deets.Entries { + if d.OneDrive != nil { + if strings.HasSuffix(d.RepoRef, onedrive.MetaFileSuffix) || + strings.HasSuffix(d.RepoRef, onedrive.DirMetaFileSuffix) { + d.OneDrive.IsMeta = true + } + } + } + } + return deets, b, errs }