diff --git a/src/internal/connector/onedrive/collections.go b/src/internal/connector/onedrive/collections.go index 1e36b1ea3..e3d60fe2b 100644 --- a/src/internal/connector/onedrive/collections.go +++ b/src/internal/connector/onedrive/collections.go @@ -180,7 +180,15 @@ func deserializeMetadata( // Go through and remove partial results (i.e. path mapping but no delta URL // or vice-versa). - for k := range prevDeltas { + for k, v := range prevDeltas { + // Remove entries with an empty delta token as it's not useful. + if len(v) == 0 { + delete(prevDeltas, k) + delete(prevFolders, k) + } + + // Remove entries without a folders map as we can't tell kopia the + // hierarchy changes. if _, ok := prevFolders[k]; !ok { delete(prevDeltas, k) } @@ -287,17 +295,21 @@ func (c *Collections) Get( return nil, nil, err } + // It's alright to have an empty folders map (i.e. no folders found) but not + // an empty delta token. This is because when deserializing the metadata we + // remove entries for which there is no corresponding delta token/folder. If + // we leave empty delta tokens then we may end up setting the State field + // for collections when not actually getting delta results. if len(delta) > 0 { deltaURLs[driveID] = delta } - if len(paths) > 0 { - folderPaths[driveID] = map[string]string{} - - for id, p := range paths { - folderPaths[driveID][id] = p - } - } + // Avoid the edge case where there's no paths but we do have a valid delta + // token. We can accomplish this by adding an empty paths map for this + // drive. If we don't have this then the next backup won't use the delta + // token because it thinks the folder paths weren't persisted. + folderPaths[driveID] = map[string]string{} + maps.Copy(folderPaths[driveID], paths) maps.Copy(excludedItems, excluded) } diff --git a/src/internal/connector/onedrive/collections_test.go b/src/internal/connector/onedrive/collections_test.go index c250afe2a..39e094cd6 100644 --- a/src/internal/connector/onedrive/collections_test.go +++ b/src/internal/connector/onedrive/collections_test.go @@ -711,6 +711,60 @@ func (suite *OneDriveCollectionsSuite) TestDeserializeMetadata() { expectedPaths: map[string]map[string]string{}, errCheck: assert.NoError, }, + { + // An empty path map but valid delta results in metadata being returned + // since it's possible to have a drive with no folders other than the + // root. + name: "EmptyPaths", + cols: []func() []graph.MetadataCollectionEntry{ + func() []graph.MetadataCollectionEntry { + return []graph.MetadataCollectionEntry{ + graph.NewMetadataEntry( + graph.DeltaURLsFileName, + map[string]string{driveID1: deltaURL1}, + ), + graph.NewMetadataEntry( + graph.PreviousPathFileName, + map[string]map[string]string{ + driveID1: {}, + }, + ), + } + }, + }, + expectedDeltas: map[string]string{driveID1: deltaURL1}, + expectedPaths: map[string]map[string]string{driveID1: {}}, + errCheck: assert.NoError, + }, + { + // An empty delta map but valid path results in no metadata for that drive + // being returned since the path map is only useful if we have a valid + // delta. + name: "EmptyDeltas", + cols: []func() []graph.MetadataCollectionEntry{ + func() []graph.MetadataCollectionEntry { + return []graph.MetadataCollectionEntry{ + graph.NewMetadataEntry( + graph.DeltaURLsFileName, + map[string]string{ + driveID1: "", + }, + ), + graph.NewMetadataEntry( + graph.PreviousPathFileName, + map[string]map[string]string{ + driveID1: { + folderID1: path1, + }, + }, + ), + } + }, + }, + expectedDeltas: map[string]string{}, + expectedPaths: map[string]map[string]string{}, + errCheck: assert.NoError, + }, { name: "SuccessTwoDrivesTwoCollections", cols: []func() []graph.MetadataCollectionEntry{