diff --git a/src/internal/m365/collection/drive/delta_tree.go b/src/internal/m365/collection/drive/delta_tree.go new file mode 100644 index 000000000..9c553f0c2 --- /dev/null +++ b/src/internal/m365/collection/drive/delta_tree.go @@ -0,0 +1,80 @@ +package drive + +import ( + "time" + + "github.com/alcionai/corso/src/pkg/path" +) + +// folderyMcFolderFace owns our delta processing tree. +type folderyMcFolderFace struct { + // tenant/service/resource/category/driveID + // (or whatever variant the service defines) + // allows the tree to focus only on folder structure, + // and minimizes the possibility of multi-prefix path bugs. + prefix path.Path + + // the root of the tree; + // new, moved, and notMoved collections + collections *nodeyMcNodeFace + + // the majority of operations we perform can be handled with + // a folder ID lookup instead of re-walking the entire tree. + // Ex: adding a new file to its parent folder. + folderIDToNode map[string]*nodeyMcNodeFace + + // tombstones don't need to form a tree. + // we only need the folder ID and their previous path. + tombstones map[string]path.Path + + // it's just a sensible place to store the data, since we're + // already pushing file additions through the api. + excludeFileIDs map[string]struct{} +} + +func newFolderyMcFolderFace( + prefix path.Path, +) *folderyMcFolderFace { + return &folderyMcFolderFace{ + prefix: prefix, + folderIDToNode: map[string]*nodeyMcNodeFace{}, + tombstones: map[string]path.Path{}, + excludeFileIDs: map[string]struct{}{}, + } +} + +type nodeyMcNodeFace struct { + // required for mid-enumeration folder moves, else we have to walk + // the tree completely to remove the node from its old parent. + parent *nodeyMcNodeFace + // the microsoft item ID. Mostly because we might as well + // attach that to the node if we're also attaching the dir. + id string + // single directory name, not a path + name string + // only contains the folders starting at and including '/root:' + prev path.Path + // map folderID -> node + childDirs map[string]*nodeyMcNodeFace + // items are keyed by item ID + items map[string]time.Time + // for special handling protocols around packages + isPackage bool +} + +func newNodeyMcNodeFace( + parent *nodeyMcNodeFace, + id, name string, + prev path.Path, + isPackage bool, +) *nodeyMcNodeFace { + return &nodeyMcNodeFace{ + parent: parent, + id: id, + name: name, + prev: prev, + childDirs: map[string]*nodeyMcNodeFace{}, + items: map[string]time.Time{}, + isPackage: isPackage, + } +} diff --git a/src/internal/m365/collection/drive/delta_tree_test.go b/src/internal/m365/collection/drive/delta_tree_test.go new file mode 100644 index 000000000..0a0931ec6 --- /dev/null +++ b/src/internal/m365/collection/drive/delta_tree_test.go @@ -0,0 +1,56 @@ +package drive + +import ( + "testing" + + "github.com/alcionai/clues" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + + "github.com/alcionai/corso/src/internal/tester" + "github.com/alcionai/corso/src/pkg/path" +) + +type DeltaTreeUnitSuite struct { + tester.Suite +} + +func TestDeltaTreeUnitSuite(t *testing.T) { + suite.Run(t, &DeltaTreeUnitSuite{Suite: tester.NewUnitSuite(t)}) +} + +func (suite *DeltaTreeUnitSuite) TestNewFolderyMcFolderFace() { + var ( + t = suite.T() + p, err = path.BuildPrefix("t", "r", path.OneDriveService, path.FilesCategory) + ) + + require.NoError(t, err, clues.ToCore(err)) + + folderFace := newFolderyMcFolderFace(p) + assert.Equal(t, p, folderFace.prefix) + assert.Nil(t, folderFace.collections) + assert.NotNil(t, folderFace.folderIDToNode) + assert.NotNil(t, folderFace.tombstones) + assert.NotNil(t, folderFace.excludeFileIDs) +} + +func (suite *DeltaTreeUnitSuite) TestNewNodeyMcNodeFace() { + var ( + t = suite.T() + parent = &nodeyMcNodeFace{} + p, err = path.Build("t", "r", path.SharePointService, path.LibrariesCategory, false, "drive-id", "root:") + ) + + require.NoError(t, err, clues.ToCore(err)) + + nodeFace := newNodeyMcNodeFace(parent, "id", "name", p, true) + assert.Equal(t, parent, nodeFace.parent) + assert.Equal(t, "id", nodeFace.id) + assert.Equal(t, "name", nodeFace.name) + assert.Equal(t, p, nodeFace.prev) + assert.True(t, nodeFace.isPackage) + assert.NotNil(t, nodeFace.childDirs) + assert.NotNil(t, nodeFace.items) +}