require rootID on tree construction
Turns out the root ID name isn't an appropriate match for establishing the root node. Instead, the backup hander is now extended with a getRootFolder method and will pass the expected root folder ID into the tree's constructor func to ensure we establish the correct root node.
This commit is contained in:
parent
3f98aa33de
commit
14225ad616
@ -173,9 +173,8 @@ func malwareItem(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func driveRootItem() models.DriveItemable {
|
func driveRootItem() models.DriveItemable {
|
||||||
name := rootName
|
|
||||||
item := models.NewDriveItem()
|
item := models.NewDriveItem()
|
||||||
item.SetName(&name)
|
item.SetName(ptr.To(rootName))
|
||||||
item.SetId(ptr.To(rootID))
|
item.SetId(ptr.To(rootID))
|
||||||
item.SetRoot(models.NewRoot())
|
item.SetRoot(models.NewRoot())
|
||||||
item.SetFolder(models.NewFolder())
|
item.SetFolder(models.NewFolder())
|
||||||
|
|||||||
@ -171,7 +171,12 @@ func (c *Collections) makeDriveCollections(
|
|||||||
return nil, nil, pagers.DeltaUpdate{}, clues.Wrap(err, "generating backup tree prefix")
|
return nil, nil, pagers.DeltaUpdate{}, clues.Wrap(err, "generating backup tree prefix")
|
||||||
}
|
}
|
||||||
|
|
||||||
tree := newFolderyMcFolderFace(ppfx)
|
root, err := c.handler.GetRootFolder(ctx, ptr.Val(drv.GetId()))
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, pagers.DeltaUpdate{}, clues.Wrap(err, "getting root folder")
|
||||||
|
}
|
||||||
|
|
||||||
|
tree := newFolderyMcFolderFace(ppfx, ptr.Val(root.GetId()))
|
||||||
|
|
||||||
counter.Add(count.PrevPaths, int64(len(prevPaths)))
|
counter.Add(count.PrevPaths, int64(len(prevPaths)))
|
||||||
|
|
||||||
|
|||||||
@ -585,7 +585,7 @@ func (suite *CollectionsTreeUnitSuite) TestCollections_PopulateTree() {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "nil page",
|
name: "nil page",
|
||||||
tree: newFolderyMcFolderFace(nil),
|
tree: newFolderyMcFolderFace(nil, rootID),
|
||||||
enumerator: mock.EnumerateItemsDeltaByDrive{
|
enumerator: mock.EnumerateItemsDeltaByDrive{
|
||||||
DrivePagers: map[string]*mock.DriveItemsDeltaPager{
|
DrivePagers: map[string]*mock.DriveItemsDeltaPager{
|
||||||
id(drive): {
|
id(drive): {
|
||||||
@ -608,7 +608,7 @@ func (suite *CollectionsTreeUnitSuite) TestCollections_PopulateTree() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "root only",
|
name: "root only",
|
||||||
tree: newFolderyMcFolderFace(nil),
|
tree: newFolderyMcFolderFace(nil, rootID),
|
||||||
enumerator: mock.EnumerateItemsDeltaByDrive{
|
enumerator: mock.EnumerateItemsDeltaByDrive{
|
||||||
DrivePagers: map[string]*mock.DriveItemsDeltaPager{
|
DrivePagers: map[string]*mock.DriveItemsDeltaPager{
|
||||||
id(drive): {
|
id(drive): {
|
||||||
@ -637,7 +637,7 @@ func (suite *CollectionsTreeUnitSuite) TestCollections_PopulateTree() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "root only on two pages",
|
name: "root only on two pages",
|
||||||
tree: newFolderyMcFolderFace(nil),
|
tree: newFolderyMcFolderFace(nil, rootID),
|
||||||
enumerator: mock.EnumerateItemsDeltaByDrive{
|
enumerator: mock.EnumerateItemsDeltaByDrive{
|
||||||
DrivePagers: map[string]*mock.DriveItemsDeltaPager{
|
DrivePagers: map[string]*mock.DriveItemsDeltaPager{
|
||||||
id(drive): {
|
id(drive): {
|
||||||
@ -666,7 +666,7 @@ func (suite *CollectionsTreeUnitSuite) TestCollections_PopulateTree() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "many folders in a hierarchy across multiple pages",
|
name: "many folders in a hierarchy across multiple pages",
|
||||||
tree: newFolderyMcFolderFace(nil),
|
tree: newFolderyMcFolderFace(nil, rootID),
|
||||||
enumerator: mock.EnumerateItemsDeltaByDrive{
|
enumerator: mock.EnumerateItemsDeltaByDrive{
|
||||||
DrivePagers: map[string]*mock.DriveItemsDeltaPager{
|
DrivePagers: map[string]*mock.DriveItemsDeltaPager{
|
||||||
id(drive): {
|
id(drive): {
|
||||||
@ -703,7 +703,7 @@ func (suite *CollectionsTreeUnitSuite) TestCollections_PopulateTree() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "many folders with files",
|
name: "many folders with files",
|
||||||
tree: newFolderyMcFolderFace(nil),
|
tree: newFolderyMcFolderFace(nil, rootID),
|
||||||
enumerator: mock.EnumerateItemsDeltaByDrive{
|
enumerator: mock.EnumerateItemsDeltaByDrive{
|
||||||
DrivePagers: map[string]*mock.DriveItemsDeltaPager{
|
DrivePagers: map[string]*mock.DriveItemsDeltaPager{
|
||||||
id(drive): {
|
id(drive): {
|
||||||
@ -751,7 +751,7 @@ func (suite *CollectionsTreeUnitSuite) TestCollections_PopulateTree() {
|
|||||||
// technically you won't see this behavior from graph deltas, since deletes always
|
// technically you won't see this behavior from graph deltas, since deletes always
|
||||||
// precede creates/updates. But it's worth checking that we can handle it anyways.
|
// precede creates/updates. But it's worth checking that we can handle it anyways.
|
||||||
name: "create, delete on next page",
|
name: "create, delete on next page",
|
||||||
tree: newFolderyMcFolderFace(nil),
|
tree: newFolderyMcFolderFace(nil, rootID),
|
||||||
enumerator: mock.EnumerateItemsDeltaByDrive{
|
enumerator: mock.EnumerateItemsDeltaByDrive{
|
||||||
DrivePagers: map[string]*mock.DriveItemsDeltaPager{
|
DrivePagers: map[string]*mock.DriveItemsDeltaPager{
|
||||||
id(drive): {
|
id(drive): {
|
||||||
@ -870,7 +870,7 @@ func (suite *CollectionsTreeUnitSuite) TestCollections_PopulateTree() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "hit folder limit during enumeration",
|
name: "hit folder limit during enumeration",
|
||||||
tree: newFolderyMcFolderFace(nil),
|
tree: newFolderyMcFolderFace(nil, rootID),
|
||||||
enumerator: mock.EnumerateItemsDeltaByDrive{
|
enumerator: mock.EnumerateItemsDeltaByDrive{
|
||||||
DrivePagers: map[string]*mock.DriveItemsDeltaPager{
|
DrivePagers: map[string]*mock.DriveItemsDeltaPager{
|
||||||
id(drive): {
|
id(drive): {
|
||||||
@ -1305,7 +1305,7 @@ func (suite *CollectionsTreeUnitSuite) TestCollections_AddFolderToTree() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "tombstone new folder in unpopulated tree",
|
name: "tombstone new folder in unpopulated tree",
|
||||||
tree: newFolderyMcFolderFace(nil),
|
tree: newFolderyMcFolderFace(nil, rootID),
|
||||||
folder: del,
|
folder: del,
|
||||||
limiter: newPagerLimiter(control.DefaultOptions()),
|
limiter: newPagerLimiter(control.DefaultOptions()),
|
||||||
expect: expected{
|
expect: expected{
|
||||||
@ -1553,7 +1553,7 @@ func (suite *CollectionsTreeUnitSuite) TestCollections_EnumeratePageOfItems_file
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "one file in a folder",
|
name: "one file in a folder",
|
||||||
tree: newFolderyMcFolderFace(nil),
|
tree: newFolderyMcFolderFace(nil, rootID),
|
||||||
page: pageItems(
|
page: pageItems(
|
||||||
driveItem(id(folder), name(folder), parentDir(), rootID, isFolder),
|
driveItem(id(folder), name(folder), parentDir(), rootID, isFolder),
|
||||||
driveItem(id(file), name(file), parentDir(name(folder)), id(folder), isFile)),
|
driveItem(id(file), name(file), parentDir(name(folder)), id(folder), isFile)),
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import (
|
|||||||
|
|
||||||
"github.com/alcionai/clues"
|
"github.com/alcionai/clues"
|
||||||
|
|
||||||
odConsts "github.com/alcionai/corso/src/internal/m365/service/onedrive/consts"
|
|
||||||
"github.com/alcionai/corso/src/pkg/logger"
|
"github.com/alcionai/corso/src/pkg/logger"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
)
|
)
|
||||||
@ -23,6 +22,10 @@ type folderyMcFolderFace struct {
|
|||||||
// new, moved, and notMoved root
|
// new, moved, and notMoved root
|
||||||
root *nodeyMcNodeFace
|
root *nodeyMcNodeFace
|
||||||
|
|
||||||
|
// the ID of the actual root folder.
|
||||||
|
// required to ensure correct population of the root node.
|
||||||
|
rootID string
|
||||||
|
|
||||||
// the majority of operations we perform can be handled with
|
// the majority of operations we perform can be handled with
|
||||||
// a folder ID lookup instead of re-walking the entire tree.
|
// a folder ID lookup instead of re-walking the entire tree.
|
||||||
// Ex: adding a new file to its parent folder.
|
// Ex: adding a new file to its parent folder.
|
||||||
@ -45,9 +48,11 @@ type folderyMcFolderFace struct {
|
|||||||
|
|
||||||
func newFolderyMcFolderFace(
|
func newFolderyMcFolderFace(
|
||||||
prefix path.Path,
|
prefix path.Path,
|
||||||
|
rootID string,
|
||||||
) *folderyMcFolderFace {
|
) *folderyMcFolderFace {
|
||||||
return &folderyMcFolderFace{
|
return &folderyMcFolderFace{
|
||||||
prefix: prefix,
|
prefix: prefix,
|
||||||
|
rootID: rootID,
|
||||||
folderIDToNode: map[string]*nodeyMcNodeFace{},
|
folderIDToNode: map[string]*nodeyMcNodeFace{},
|
||||||
tombstones: map[string]*nodeyMcNodeFace{},
|
tombstones: map[string]*nodeyMcNodeFace{},
|
||||||
fileIDToParentID: map[string]string{},
|
fileIDToParentID: map[string]string{},
|
||||||
@ -150,17 +155,12 @@ func (face *folderyMcFolderFace) setFolder(
|
|||||||
return clues.NewWC(ctx, "missing folder name")
|
return clues.NewWC(ctx, "missing folder name")
|
||||||
}
|
}
|
||||||
|
|
||||||
// drive doesn't normally allow the `:` character in folder names.
|
if len(parentID) == 0 && id != face.rootID {
|
||||||
// so `root:` is, by default, the only folder that can match this
|
|
||||||
// name. That makes this check a little bit brittle, but generally
|
|
||||||
// reliable, since we should always see the root first and can rely
|
|
||||||
// on the naming structure.
|
|
||||||
if len(parentID) == 0 && name != odConsts.RootPathDir {
|
|
||||||
return clues.NewWC(ctx, "non-root folder missing parent id")
|
return clues.NewWC(ctx, "non-root folder missing parent id")
|
||||||
}
|
}
|
||||||
|
|
||||||
// only set the root node once.
|
// only set the root node once.
|
||||||
if name == odConsts.RootPathDir {
|
if id == face.rootID {
|
||||||
if face.root == nil {
|
if face.root == nil {
|
||||||
root := newNodeyMcNodeFace(nil, id, name, isPackage)
|
root := newNodeyMcNodeFace(nil, id, name, isPackage)
|
||||||
face.root = root
|
face.root = root
|
||||||
|
|||||||
@ -20,7 +20,7 @@ import (
|
|||||||
var loc = path.NewElements("root:/foo/bar/baz/qux/fnords/smarf/voi/zumba/bangles/howdyhowdyhowdy")
|
var loc = path.NewElements("root:/foo/bar/baz/qux/fnords/smarf/voi/zumba/bangles/howdyhowdyhowdy")
|
||||||
|
|
||||||
func treeWithRoot() *folderyMcFolderFace {
|
func treeWithRoot() *folderyMcFolderFace {
|
||||||
tree := newFolderyMcFolderFace(nil)
|
tree := newFolderyMcFolderFace(nil, rootID)
|
||||||
rootey := newNodeyMcNodeFace(nil, rootID, rootName, false)
|
rootey := newNodeyMcNodeFace(nil, rootID, rootName, false)
|
||||||
tree.root = rootey
|
tree.root = rootey
|
||||||
tree.folderIDToNode[rootID] = rootey
|
tree.folderIDToNode[rootID] = rootey
|
||||||
@ -38,13 +38,13 @@ func treeWithTombstone() *folderyMcFolderFace {
|
|||||||
func treeWithFolders() *folderyMcFolderFace {
|
func treeWithFolders() *folderyMcFolderFace {
|
||||||
tree := treeWithRoot()
|
tree := treeWithRoot()
|
||||||
|
|
||||||
o := newNodeyMcNodeFace(tree.root, idx(folder, "parent"), namex(folder, "parent"), true)
|
parent := newNodeyMcNodeFace(tree.root, idx(folder, "parent"), namex(folder, "parent"), true)
|
||||||
tree.folderIDToNode[o.id] = o
|
tree.folderIDToNode[parent.id] = parent
|
||||||
tree.root.children[o.id] = o
|
tree.root.children[parent.id] = parent
|
||||||
|
|
||||||
f := newNodeyMcNodeFace(o, id(folder), name(folder), false)
|
f := newNodeyMcNodeFace(parent, id(folder), name(folder), false)
|
||||||
tree.folderIDToNode[f.id] = f
|
tree.folderIDToNode[f.id] = f
|
||||||
o.children[f.id] = f
|
parent.children[f.id] = f
|
||||||
|
|
||||||
return tree
|
return tree
|
||||||
}
|
}
|
||||||
@ -102,7 +102,7 @@ func (suite *DeltaTreeUnitSuite) TestNewFolderyMcFolderFace() {
|
|||||||
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
folderFace := newFolderyMcFolderFace(p)
|
folderFace := newFolderyMcFolderFace(p, rootID)
|
||||||
assert.Equal(t, p, folderFace.prefix)
|
assert.Equal(t, p, folderFace.prefix)
|
||||||
assert.Nil(t, folderFace.root)
|
assert.Nil(t, folderFace.root)
|
||||||
assert.NotNil(t, folderFace.folderIDToNode)
|
assert.NotNil(t, folderFace.folderIDToNode)
|
||||||
@ -144,7 +144,7 @@ func (suite *DeltaTreeUnitSuite) TestFolderyMcFolderFace_SetFolder() {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
tname: "add root",
|
tname: "add root",
|
||||||
tree: newFolderyMcFolderFace(nil),
|
tree: newFolderyMcFolderFace(nil, rootID),
|
||||||
id: rootID,
|
id: rootID,
|
||||||
name: rootName,
|
name: rootName,
|
||||||
isPackage: true,
|
isPackage: true,
|
||||||
@ -272,7 +272,7 @@ func (suite *DeltaTreeUnitSuite) TestFolderyMcFolderFace_AddTombstone() {
|
|||||||
{
|
{
|
||||||
name: "add tombstone",
|
name: "add tombstone",
|
||||||
id: id(folder),
|
id: id(folder),
|
||||||
tree: newFolderyMcFolderFace(nil),
|
tree: newFolderyMcFolderFace(nil, rootID),
|
||||||
expectErr: assert.NoError,
|
expectErr: assert.NoError,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -283,7 +283,7 @@ func (suite *DeltaTreeUnitSuite) TestFolderyMcFolderFace_AddTombstone() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "missing ID",
|
name: "missing ID",
|
||||||
tree: newFolderyMcFolderFace(nil),
|
tree: newFolderyMcFolderFace(nil, rootID),
|
||||||
expectErr: assert.Error,
|
expectErr: assert.Error,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -39,6 +39,7 @@ type BackupHandler interface {
|
|||||||
api.Getter
|
api.Getter
|
||||||
GetItemPermissioner
|
GetItemPermissioner
|
||||||
GetItemer
|
GetItemer
|
||||||
|
GetRootFolderer
|
||||||
NewDrivePagerer
|
NewDrivePagerer
|
||||||
EnumerateDriveItemsDeltaer
|
EnumerateDriveItemsDeltaer
|
||||||
|
|
||||||
|
|||||||
@ -182,6 +182,13 @@ func (h siteBackupHandler) EnumerateDriveItemsDelta(
|
|||||||
return h.ac.EnumerateDriveItemsDelta(ctx, driveID, prevDeltaLink, cc)
|
return h.ac.EnumerateDriveItemsDelta(ctx, driveID, prevDeltaLink, cc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h siteBackupHandler) GetRootFolder(
|
||||||
|
ctx context.Context,
|
||||||
|
driveID string,
|
||||||
|
) (models.DriveItemable, error) {
|
||||||
|
return h.ac.Drives().GetRootFolder(ctx, driveID)
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Restore
|
// Restore
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
@ -182,6 +182,13 @@ func (h userDriveBackupHandler) EnumerateDriveItemsDelta(
|
|||||||
return h.ac.EnumerateDriveItemsDelta(ctx, driveID, prevDeltaLink, cc)
|
return h.ac.EnumerateDriveItemsDelta(ctx, driveID, prevDeltaLink, cc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h userDriveBackupHandler) GetRootFolder(
|
||||||
|
ctx context.Context,
|
||||||
|
driveID string,
|
||||||
|
) (models.DriveItemable, error) {
|
||||||
|
return h.ac.Drives().GetRootFolder(ctx, driveID)
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Restore
|
// Restore
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
||||||
|
|
||||||
"github.com/alcionai/corso/src/internal/common/idname"
|
"github.com/alcionai/corso/src/internal/common/idname"
|
||||||
|
"github.com/alcionai/corso/src/internal/common/ptr"
|
||||||
odConsts "github.com/alcionai/corso/src/internal/m365/service/onedrive/consts"
|
odConsts "github.com/alcionai/corso/src/internal/m365/service/onedrive/consts"
|
||||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
@ -57,6 +58,18 @@ type BackupHandler[T any] struct {
|
|||||||
getCall int
|
getCall int
|
||||||
GetResps []*http.Response
|
GetResps []*http.Response
|
||||||
GetErrs []error
|
GetErrs []error
|
||||||
|
|
||||||
|
RootFolder models.DriveItemable
|
||||||
|
}
|
||||||
|
|
||||||
|
func stubRootFolder() models.DriveItemable {
|
||||||
|
item := models.NewDriveItem()
|
||||||
|
item.SetName(ptr.To(odConsts.RootPathDir))
|
||||||
|
item.SetId(ptr.To(odConsts.RootID))
|
||||||
|
item.SetRoot(models.NewRoot())
|
||||||
|
item.SetFolder(models.NewFolder())
|
||||||
|
|
||||||
|
return item
|
||||||
}
|
}
|
||||||
|
|
||||||
func DefaultOneDriveBH(resourceOwner string) *BackupHandler[models.DriveItemable] {
|
func DefaultOneDriveBH(resourceOwner string) *BackupHandler[models.DriveItemable] {
|
||||||
@ -81,6 +94,7 @@ func DefaultOneDriveBH(resourceOwner string) *BackupHandler[models.DriveItemable
|
|||||||
LocationIDFn: defaultOneDriveLocationIDer,
|
LocationIDFn: defaultOneDriveLocationIDer,
|
||||||
GetResps: []*http.Response{nil},
|
GetResps: []*http.Response{nil},
|
||||||
GetErrs: []error{clues.New("not defined")},
|
GetErrs: []error{clues.New("not defined")},
|
||||||
|
RootFolder: stubRootFolder(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,6 +119,7 @@ func DefaultSharePointBH(resourceOwner string) *BackupHandler[models.DriveItemab
|
|||||||
LocationIDFn: defaultSharePointLocationIDer,
|
LocationIDFn: defaultSharePointLocationIDer,
|
||||||
GetResps: []*http.Response{nil},
|
GetResps: []*http.Response{nil},
|
||||||
GetErrs: []error{clues.New("not defined")},
|
GetErrs: []error{clues.New("not defined")},
|
||||||
|
RootFolder: stubRootFolder(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -287,6 +302,10 @@ func (h BackupHandler[T]) IncludesDir(dir string) bool {
|
|||||||
selectors.OneDriveScope(scope).Matches(selectors.OneDriveFolder, dir)
|
selectors.OneDriveScope(scope).Matches(selectors.OneDriveFolder, dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h BackupHandler[T]) GetRootFolder(context.Context, string) (models.DriveItemable, error) {
|
||||||
|
return h.RootFolder, nil
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Get Itemer
|
// Get Itemer
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user