More robust folder fetching for container cache (#3464)
Update folder cache population * try to fetch missing parent folders * refresh current folder if parent wasn't cached * only populate paths during populatePaths not during IDToFolder Manually tested email folder resolver population with stable reproducer and succeeded --- #### Does this PR need a docs update or release note? - [x] ✅ Yes, it's included - [ ] 🕐 Yes, but in a later PR - [ ] ⛔ No #### Type of change - [ ] 🌻 Feature - [x] 🐛 Bugfix - [ ] 🗺️ Documentation - [ ] 🤖 Supportability/Tests - [ ] 💻 CI/Deployment - [ ] 🧹 Tech Debt/Cleanup #### Issue(s) #### Test Plan - [x] 💪 Manual - [x] ⚡ Unit test - [ ] 💚 E2E
This commit is contained in:
parent
d0c153aa15
commit
9520de1a37
@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
### Fixed
|
### Fixed
|
||||||
|
- Fix Exchange folder cache population error when parent folder isn't found.
|
||||||
|
|
||||||
### Known Issues
|
### Known Issues
|
||||||
|
|
||||||
## [v0.8.0] (beta) - 2023-05-15
|
## [v0.8.0] (beta) - 2023-05-15
|
||||||
|
|||||||
@ -11,7 +11,29 @@ import (
|
|||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ graph.ContainerResolver = &contactFolderCache{}
|
var (
|
||||||
|
_ graph.ContainerResolver = &contactFolderCache{}
|
||||||
|
_ containerRefresher = &contactRefresher{}
|
||||||
|
)
|
||||||
|
|
||||||
|
type contactRefresher struct {
|
||||||
|
getter containerGetter
|
||||||
|
userID string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *contactRefresher) refreshContainer(
|
||||||
|
ctx context.Context,
|
||||||
|
id string,
|
||||||
|
) (graph.CachedContainer, error) {
|
||||||
|
c, err := r.getter.GetContainerByID(ctx, r.userID, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, clues.Stack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
f := graph.NewCacheFolder(c, nil, nil)
|
||||||
|
|
||||||
|
return &f, nil
|
||||||
|
}
|
||||||
|
|
||||||
type contactFolderCache struct {
|
type contactFolderCache struct {
|
||||||
*containerResolver
|
*containerResolver
|
||||||
@ -34,7 +56,7 @@ func (cfc *contactFolderCache) populateContactRoot(
|
|||||||
f,
|
f,
|
||||||
path.Builder{}.Append(ptr.Val(f.GetId())), // path of IDs
|
path.Builder{}.Append(ptr.Val(f.GetId())), // path of IDs
|
||||||
path.Builder{}.Append(baseContainerPath...)) // display location
|
path.Builder{}.Append(baseContainerPath...)) // display location
|
||||||
if err := cfc.addFolder(temp); err != nil {
|
if err := cfc.addFolder(&temp); err != nil {
|
||||||
return clues.Wrap(err, "adding resolver dir").WithClues(ctx)
|
return clues.Wrap(err, "adding resolver dir").WithClues(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,7 +99,10 @@ func (cfc *contactFolderCache) init(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if cfc.containerResolver == nil {
|
if cfc.containerResolver == nil {
|
||||||
cfc.containerResolver = newContainerResolver()
|
cfc.containerResolver = newContainerResolver(&contactRefresher{
|
||||||
|
userID: cfc.userID,
|
||||||
|
getter: cfc.getter,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return cfc.populateContactRoot(ctx, baseNode, baseContainerPath)
|
return cfc.populateContactRoot(ctx, baseNode, baseContainerPath)
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/common/ptr"
|
"github.com/alcionai/corso/src/internal/common/ptr"
|
||||||
"github.com/alcionai/corso/src/internal/connector/graph"
|
"github.com/alcionai/corso/src/internal/connector/graph"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
|
"github.com/alcionai/corso/src/pkg/logger"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -26,11 +27,18 @@ type containersEnumerator interface {
|
|||||||
EnumerateContainers(
|
EnumerateContainers(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
userID, baseDirID string,
|
userID, baseDirID string,
|
||||||
fn func(graph.CacheFolder) error,
|
fn func(graph.CachedContainer) error,
|
||||||
errs *fault.Bus,
|
errs *fault.Bus,
|
||||||
) error
|
) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type containerRefresher interface {
|
||||||
|
refreshContainer(
|
||||||
|
ctx context.Context,
|
||||||
|
dirID string,
|
||||||
|
) (graph.CachedContainer, error)
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// controller
|
// controller
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@ -40,59 +48,243 @@ type containersEnumerator interface {
|
|||||||
// folders if each folder is only a single character.
|
// folders if each folder is only a single character.
|
||||||
const maxIterations = 300
|
const maxIterations = 300
|
||||||
|
|
||||||
func newContainerResolver() *containerResolver {
|
func newContainerResolver(refresher containerRefresher) *containerResolver {
|
||||||
return &containerResolver{
|
return &containerResolver{
|
||||||
cache: map[string]graph.CachedContainer{},
|
cache: map[string]graph.CachedContainer{},
|
||||||
|
refresher: refresher,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type containerResolver struct {
|
type containerResolver struct {
|
||||||
cache map[string]graph.CachedContainer
|
cache map[string]graph.CachedContainer
|
||||||
|
refresher containerRefresher
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cr *containerResolver) IDToPath(
|
func (cr *containerResolver) IDToPath(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
folderID string,
|
folderID string,
|
||||||
) (*path.Builder, *path.Builder, error) {
|
) (*path.Builder, *path.Builder, error) {
|
||||||
return cr.idToPath(ctx, folderID, 0)
|
ctx = clues.Add(ctx, "container_id", folderID)
|
||||||
|
|
||||||
|
c, ok := cr.cache[folderID]
|
||||||
|
if !ok {
|
||||||
|
return nil, nil, clues.New("container not cached").WithClues(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
p := c.Path()
|
||||||
|
if p == nil {
|
||||||
|
return nil, nil, clues.New("cached container has no path").WithClues(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
return p, c.Location(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// refreshContainer attempts to fetch the container with the given ID from Graph
|
||||||
|
// API. Returns a graph.CachedContainer if the container was found. If the
|
||||||
|
// container was deleted, returns nil, true, nil to note the container should
|
||||||
|
// be removed from the cache.
|
||||||
|
func (cr *containerResolver) refreshContainer(
|
||||||
|
ctx context.Context,
|
||||||
|
id string,
|
||||||
|
) (graph.CachedContainer, bool, error) {
|
||||||
|
ctx = clues.Add(ctx, "refresh_container_id", id)
|
||||||
|
logger.Ctx(ctx).Debug("refreshing container")
|
||||||
|
|
||||||
|
if cr.refresher == nil {
|
||||||
|
return nil, false, clues.New("nil refresher").WithClues(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := cr.refresher.refreshContainer(ctx, id)
|
||||||
|
if err != nil && graph.IsErrDeletedInFlight(err) {
|
||||||
|
logger.Ctx(ctx).Debug("container deleted")
|
||||||
|
return nil, true, nil
|
||||||
|
} else if err != nil {
|
||||||
|
// This is some other error, just return it.
|
||||||
|
return nil, false, clues.Wrap(err, "refreshing container").WithClues(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// recoverContainer attempts to fetch a missing container from Graph API and
|
||||||
|
// populate the path for it. It returns
|
||||||
|
// - the ID path for the folder
|
||||||
|
// - the display name path for the folder
|
||||||
|
// - if the folder was deleted
|
||||||
|
// - any error that occurred
|
||||||
|
//
|
||||||
|
// If the folder is marked as deleted, child folders of this folder should be
|
||||||
|
// deleted if they haven't been moved to another folder.
|
||||||
|
func (cr *containerResolver) recoverContainer(
|
||||||
|
ctx context.Context,
|
||||||
|
folderID string,
|
||||||
|
depth int,
|
||||||
|
) (*path.Builder, *path.Builder, bool, error) {
|
||||||
|
c, deleted, err := cr.refreshContainer(ctx, folderID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, false, clues.Wrap(err, "fetching uncached container")
|
||||||
|
}
|
||||||
|
|
||||||
|
if deleted {
|
||||||
|
logger.Ctx(ctx).Debug("fetching uncached container showed it was deleted")
|
||||||
|
return nil, nil, deleted, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cr.addFolder(c); err != nil {
|
||||||
|
return nil, nil, false, clues.Wrap(err, "adding new container").WithClues(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retry populating this container's paths.
|
||||||
|
//
|
||||||
|
// TODO(ashmrtn): May want to bump the depth here just so we don't get stuck
|
||||||
|
// retrying too much if for some reason things keep moving around?
|
||||||
|
resolved, err := cr.idToPath(ctx, folderID, depth)
|
||||||
|
if err != nil {
|
||||||
|
err = clues.Wrap(err, "repopulating uncached container")
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolved.idPath, resolved.locPath, resolved.deleted, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type resolvedPath struct {
|
||||||
|
idPath *path.Builder
|
||||||
|
locPath *path.Builder
|
||||||
|
cached bool
|
||||||
|
deleted bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cr *containerResolver) idToPath(
|
func (cr *containerResolver) idToPath(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
folderID string,
|
folderID string,
|
||||||
depth int,
|
depth int,
|
||||||
) (*path.Builder, *path.Builder, error) {
|
) (resolvedPath, error) {
|
||||||
ctx = clues.Add(ctx, "container_id", folderID)
|
ctx = clues.Add(ctx, "container_id", folderID)
|
||||||
|
|
||||||
if depth >= maxIterations {
|
if depth >= maxIterations {
|
||||||
return nil, nil, clues.New("path contains cycle or is too tall").WithClues(ctx)
|
return resolvedPath{
|
||||||
|
idPath: nil,
|
||||||
|
locPath: nil,
|
||||||
|
cached: false,
|
||||||
|
deleted: false,
|
||||||
|
}, clues.New("path contains cycle or is too tall").WithClues(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
c, ok := cr.cache[folderID]
|
c, ok := cr.cache[folderID]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, nil, clues.New("folder not cached").WithClues(ctx)
|
pth, loc, deleted, err := cr.recoverContainer(ctx, folderID, depth)
|
||||||
|
if err != nil {
|
||||||
|
err = clues.Stack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolvedPath{
|
||||||
|
idPath: pth,
|
||||||
|
locPath: loc,
|
||||||
|
cached: false,
|
||||||
|
deleted: deleted,
|
||||||
|
}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
p := c.Path()
|
p := c.Path()
|
||||||
if p != nil {
|
if p != nil {
|
||||||
return p, c.Location(), nil
|
return resolvedPath{
|
||||||
|
idPath: p,
|
||||||
|
locPath: c.Location(),
|
||||||
|
cached: true,
|
||||||
|
deleted: false,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
parentPath, parentLoc, err := cr.idToPath(
|
resolved, err := cr.idToPath(
|
||||||
ctx,
|
ctx,
|
||||||
ptr.Val(c.GetParentFolderId()),
|
ptr.Val(c.GetParentFolderId()),
|
||||||
depth+1)
|
depth+1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, clues.Wrap(err, "retrieving parent folder")
|
return resolvedPath{
|
||||||
|
idPath: nil,
|
||||||
|
locPath: nil,
|
||||||
|
cached: true,
|
||||||
|
deleted: false,
|
||||||
|
}, clues.Wrap(err, "retrieving parent container")
|
||||||
}
|
}
|
||||||
|
|
||||||
fullPath := parentPath.Append(ptr.Val(c.GetId()))
|
if !resolved.cached {
|
||||||
|
logger.Ctx(ctx).Debug("parent container was refreshed")
|
||||||
|
|
||||||
|
newContainer, shouldDelete, err := cr.refreshContainer(ctx, folderID)
|
||||||
|
if err != nil {
|
||||||
|
return resolvedPath{
|
||||||
|
idPath: nil,
|
||||||
|
locPath: nil,
|
||||||
|
cached: true,
|
||||||
|
deleted: false,
|
||||||
|
}, clues.Wrap(err, "refreshing container").WithClues(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
if shouldDelete {
|
||||||
|
logger.Ctx(ctx).Debug("refreshing container showed it was deleted")
|
||||||
|
delete(cr.cache, folderID)
|
||||||
|
|
||||||
|
return resolvedPath{
|
||||||
|
idPath: nil,
|
||||||
|
locPath: nil,
|
||||||
|
cached: true,
|
||||||
|
deleted: true,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// See if the newer version of the current container we got back has
|
||||||
|
// changed. If it has then it could be that the container was moved prior to
|
||||||
|
// deleting the parent and we just hit some eventual consistency case in
|
||||||
|
// Graph.
|
||||||
|
//
|
||||||
|
// TODO(ashmrtn): May want to bump the depth here just so we don't get stuck
|
||||||
|
// retrying too much if for some reason things keep moving around?
|
||||||
|
if ptr.Val(newContainer.GetParentFolderId()) != ptr.Val(c.GetParentFolderId()) ||
|
||||||
|
ptr.Val(newContainer.GetDisplayName()) != ptr.Val(c.GetDisplayName()) {
|
||||||
|
delete(cr.cache, folderID)
|
||||||
|
|
||||||
|
if err := cr.addFolder(newContainer); err != nil {
|
||||||
|
return resolvedPath{
|
||||||
|
idPath: nil,
|
||||||
|
locPath: nil,
|
||||||
|
cached: false,
|
||||||
|
deleted: false,
|
||||||
|
}, clues.Wrap(err, "updating cached container").WithClues(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cr.idToPath(ctx, folderID, depth)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the parent wasn't found and refreshing the current container produced no
|
||||||
|
// diffs then delete the current container on the assumption that the parent
|
||||||
|
// was deleted and the current container will later get deleted via eventual
|
||||||
|
// consistency. If w're wrong then the container will get picked up again on
|
||||||
|
// the next backup.
|
||||||
|
if resolved.deleted {
|
||||||
|
logger.Ctx(ctx).Debug("deleting container since parent was deleted")
|
||||||
|
delete(cr.cache, folderID)
|
||||||
|
|
||||||
|
return resolvedPath{
|
||||||
|
idPath: nil,
|
||||||
|
locPath: nil,
|
||||||
|
cached: true,
|
||||||
|
deleted: true,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
fullPath := resolved.idPath.Append(ptr.Val(c.GetId()))
|
||||||
c.SetPath(fullPath)
|
c.SetPath(fullPath)
|
||||||
|
|
||||||
locPath := parentLoc.Append(ptr.Val(c.GetDisplayName()))
|
locPath := resolved.locPath.Append(ptr.Val(c.GetDisplayName()))
|
||||||
c.SetLocation(locPath)
|
c.SetLocation(locPath)
|
||||||
|
|
||||||
return fullPath, locPath, nil
|
return resolvedPath{
|
||||||
|
idPath: fullPath,
|
||||||
|
locPath: locPath,
|
||||||
|
cached: true,
|
||||||
|
deleted: false,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PathInCache is a utility function to return m365ID of a folder if the
|
// PathInCache is a utility function to return m365ID of a folder if the
|
||||||
@ -139,14 +331,14 @@ func (cr *containerResolver) LocationInCache(pathString string) (string, bool) {
|
|||||||
|
|
||||||
// addFolder adds a folder to the cache with the given ID. If the item is
|
// addFolder adds a folder to the cache with the given ID. If the item is
|
||||||
// already in the cache does nothing. The path for the item is not modified.
|
// already in the cache does nothing. The path for the item is not modified.
|
||||||
func (cr *containerResolver) addFolder(cf graph.CacheFolder) error {
|
func (cr *containerResolver) addFolder(cf graph.CachedContainer) error {
|
||||||
// Only require a non-nil non-empty parent if the path isn't already populated.
|
// Only require a non-nil non-empty parent if the path isn't already populated.
|
||||||
if cf.Path() != nil {
|
if cf.Path() != nil {
|
||||||
if err := checkIDAndName(cf.Container); err != nil {
|
if err := checkIDAndName(cf); err != nil {
|
||||||
return clues.Wrap(err, "adding item to cache")
|
return clues.Wrap(err, "adding item to cache")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if err := checkRequiredValues(cf.Container); err != nil {
|
if err := checkRequiredValues(cf); err != nil {
|
||||||
return clues.Wrap(err, "adding item to cache")
|
return clues.Wrap(err, "adding item to cache")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -155,7 +347,7 @@ func (cr *containerResolver) addFolder(cf graph.CacheFolder) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
cr.cache[ptr.Val(cf.GetId())] = &cf
|
cr.cache[ptr.Val(cf.GetId())] = cf
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -176,7 +368,7 @@ func (cr *containerResolver) AddToCache(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
f graph.Container,
|
f graph.Container,
|
||||||
) error {
|
) error {
|
||||||
temp := graph.CacheFolder{
|
temp := &graph.CacheFolder{
|
||||||
Container: f,
|
Container: f,
|
||||||
}
|
}
|
||||||
if err := cr.addFolder(temp); err != nil {
|
if err := cr.addFolder(temp); err != nil {
|
||||||
@ -185,7 +377,7 @@ func (cr *containerResolver) AddToCache(
|
|||||||
|
|
||||||
// Populate the path for this entry so calls to PathInCache succeed no matter
|
// Populate the path for this entry so calls to PathInCache succeed no matter
|
||||||
// when they're made.
|
// when they're made.
|
||||||
_, _, err := cr.IDToPath(ctx, ptr.Val(f.GetId()))
|
_, err := cr.idToPath(ctx, ptr.Val(f.GetId()), 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return clues.Wrap(err, "adding cache entry")
|
return clues.Wrap(err, "adding cache entry")
|
||||||
}
|
}
|
||||||
@ -208,7 +400,7 @@ func (cr *containerResolver) populatePaths(
|
|||||||
return el.Failure()
|
return el.Failure()
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, err := cr.IDToPath(ctx, ptr.Val(f.GetId()))
|
_, err := cr.idToPath(ctx, ptr.Val(f.GetId()), 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = clues.Wrap(err, "populating path")
|
err = clues.Wrap(err, "populating path")
|
||||||
el.AddRecoverable(err)
|
el.AddRecoverable(err)
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package exchange
|
package exchange
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
stdpath "path"
|
stdpath "path"
|
||||||
"testing"
|
"testing"
|
||||||
@ -232,8 +233,8 @@ func (suite *FolderCacheUnitSuite) TestAddFolder() {
|
|||||||
|
|
||||||
for _, test := range table {
|
for _, test := range table {
|
||||||
suite.Run(test.name, func() {
|
suite.Run(test.name, func() {
|
||||||
fc := newContainerResolver()
|
fc := newContainerResolver(nil)
|
||||||
err := fc.addFolder(test.cf)
|
err := fc.addFolder(&test.cf)
|
||||||
test.check(suite.T(), err, clues.ToCore(err))
|
test.check(suite.T(), err, clues.ToCore(err))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -293,7 +294,7 @@ func resolverWithContainers(numContainers int, useIDInPath bool) (*containerReso
|
|||||||
containers[i].expectedLocation = stdpath.Join(containers[i-1].expectedLocation, dn)
|
containers[i].expectedLocation = stdpath.Join(containers[i-1].expectedLocation, dn)
|
||||||
}
|
}
|
||||||
|
|
||||||
resolver := newContainerResolver()
|
resolver := newContainerResolver(nil)
|
||||||
|
|
||||||
for _, c := range containers {
|
for _, c := range containers {
|
||||||
resolver.cache[c.id] = c
|
resolver.cache[c.id] = c
|
||||||
@ -302,6 +303,37 @@ func resolverWithContainers(numContainers int, useIDInPath bool) (*containerReso
|
|||||||
return resolver, containers
|
return resolver, containers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// mock container refresher
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type refreshResult struct {
|
||||||
|
err error
|
||||||
|
c graph.CachedContainer
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockContainerRefresher struct {
|
||||||
|
// Folder ID -> result
|
||||||
|
entries map[string]refreshResult
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r mockContainerRefresher) refreshContainer(
|
||||||
|
ctx context.Context,
|
||||||
|
id string,
|
||||||
|
) (graph.CachedContainer, error) {
|
||||||
|
rr, ok := r.entries[id]
|
||||||
|
if !ok {
|
||||||
|
// May not be this precise error, but it's easy to get a handle on.
|
||||||
|
return nil, graph.ErrDeletedInFlight
|
||||||
|
}
|
||||||
|
|
||||||
|
if rr.err != nil {
|
||||||
|
return nil, rr.err
|
||||||
|
}
|
||||||
|
|
||||||
|
return rr.c, nil
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// configured unit suite
|
// configured unit suite
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@ -326,6 +358,160 @@ func TestConfiguredFolderCacheUnitSuite(t *testing.T) {
|
|||||||
suite.Run(t, &ConfiguredFolderCacheUnitSuite{Suite: tester.NewUnitSuite(t)})
|
suite.Run(t, &ConfiguredFolderCacheUnitSuite{Suite: tester.NewUnitSuite(t)})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (suite *ConfiguredFolderCacheUnitSuite) TestRefreshContainer_RefreshParent() {
|
||||||
|
ctx, flush := tester.NewContext()
|
||||||
|
defer flush()
|
||||||
|
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
resolver, containers := resolverWithContainers(4, true)
|
||||||
|
almostLast := containers[len(containers)-2]
|
||||||
|
last := containers[len(containers)-1]
|
||||||
|
|
||||||
|
refresher := mockContainerRefresher{
|
||||||
|
entries: map[string]refreshResult{
|
||||||
|
almostLast.id: {c: almostLast},
|
||||||
|
last.id: {c: last},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resolver.refresher = refresher
|
||||||
|
|
||||||
|
delete(resolver.cache, almostLast.id)
|
||||||
|
|
||||||
|
ferrs := fault.New(true)
|
||||||
|
err := resolver.populatePaths(ctx, ferrs)
|
||||||
|
require.NoError(t, err, "populating paths", clues.ToCore(err))
|
||||||
|
|
||||||
|
p, l, err := resolver.IDToPath(ctx, last.id)
|
||||||
|
require.NoError(t, err, "getting paths", clues.ToCore(err))
|
||||||
|
|
||||||
|
assert.Equal(t, last.expectedPath, p.String())
|
||||||
|
assert.Equal(t, last.expectedLocation, l.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ConfiguredFolderCacheUnitSuite) TestRefreshContainer_RefreshParent_NotFoundDeletes() {
|
||||||
|
ctx, flush := tester.NewContext()
|
||||||
|
defer flush()
|
||||||
|
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
resolver, containers := resolverWithContainers(4, true)
|
||||||
|
almostLast := containers[len(containers)-2]
|
||||||
|
last := containers[len(containers)-1]
|
||||||
|
|
||||||
|
refresher := mockContainerRefresher{
|
||||||
|
entries: map[string]refreshResult{
|
||||||
|
last.id: {c: last},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resolver.refresher = refresher
|
||||||
|
|
||||||
|
delete(resolver.cache, almostLast.id)
|
||||||
|
|
||||||
|
ferrs := fault.New(true)
|
||||||
|
err := resolver.populatePaths(ctx, ferrs)
|
||||||
|
require.NoError(t, err, "populating paths", clues.ToCore(err))
|
||||||
|
|
||||||
|
_, _, err = resolver.IDToPath(ctx, last.id)
|
||||||
|
assert.Error(t, err, "getting paths", clues.ToCore(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ConfiguredFolderCacheUnitSuite) TestRefreshContainer_RefreshAncestor_NotFoundDeletes() {
|
||||||
|
ctx, flush := tester.NewContext()
|
||||||
|
defer flush()
|
||||||
|
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
resolver, containers := resolverWithContainers(4, true)
|
||||||
|
gone := containers[0]
|
||||||
|
child := containers[1]
|
||||||
|
last := containers[len(containers)-1]
|
||||||
|
|
||||||
|
refresher := mockContainerRefresher{
|
||||||
|
entries: map[string]refreshResult{
|
||||||
|
child.id: {c: child},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resolver.refresher = refresher
|
||||||
|
|
||||||
|
delete(resolver.cache, gone.id)
|
||||||
|
|
||||||
|
ferrs := fault.New(true)
|
||||||
|
err := resolver.populatePaths(ctx, ferrs)
|
||||||
|
require.NoError(t, err, "populating paths", clues.ToCore(err))
|
||||||
|
|
||||||
|
_, _, err = resolver.IDToPath(ctx, last.id)
|
||||||
|
assert.Error(t, err, "getting paths", clues.ToCore(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ConfiguredFolderCacheUnitSuite) TestRefreshContainer_RefreshAncestor_NewParent() {
|
||||||
|
ctx, flush := tester.NewContext()
|
||||||
|
defer flush()
|
||||||
|
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
resolver, containers := resolverWithContainers(4, true)
|
||||||
|
other := containers[len(containers)-3]
|
||||||
|
gone := containers[len(containers)-2]
|
||||||
|
last := containers[len(containers)-1]
|
||||||
|
|
||||||
|
expected := *last
|
||||||
|
expected.parentID = other.id
|
||||||
|
expected.expectedPath = stdpath.Join(other.expectedPath, expected.id)
|
||||||
|
expected.expectedLocation = stdpath.Join(other.expectedLocation, expected.displayName)
|
||||||
|
|
||||||
|
refresher := mockContainerRefresher{
|
||||||
|
entries: map[string]refreshResult{
|
||||||
|
last.id: {c: &expected},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resolver.refresher = refresher
|
||||||
|
|
||||||
|
delete(resolver.cache, gone.id)
|
||||||
|
|
||||||
|
ferrs := fault.New(true)
|
||||||
|
err := resolver.populatePaths(ctx, ferrs)
|
||||||
|
require.NoError(t, err, "populating paths", clues.ToCore(err))
|
||||||
|
|
||||||
|
p, l, err := resolver.IDToPath(ctx, last.id)
|
||||||
|
require.NoError(t, err, "getting paths", clues.ToCore(err))
|
||||||
|
|
||||||
|
assert.Equal(t, expected.expectedPath, p.String())
|
||||||
|
assert.Equal(t, expected.expectedLocation, l.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ConfiguredFolderCacheUnitSuite) TestRefreshContainer_RefreshFolder_FolderDeleted() {
|
||||||
|
ctx, flush := tester.NewContext()
|
||||||
|
defer flush()
|
||||||
|
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
resolver, containers := resolverWithContainers(4, true)
|
||||||
|
parent := containers[len(containers)-2]
|
||||||
|
last := containers[len(containers)-1]
|
||||||
|
|
||||||
|
refresher := mockContainerRefresher{
|
||||||
|
entries: map[string]refreshResult{
|
||||||
|
parent.id: {c: parent},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resolver.refresher = refresher
|
||||||
|
|
||||||
|
delete(resolver.cache, parent.id)
|
||||||
|
|
||||||
|
ferrs := fault.New(true)
|
||||||
|
err := resolver.populatePaths(ctx, ferrs)
|
||||||
|
require.NoError(t, err, "populating paths", clues.ToCore(err))
|
||||||
|
|
||||||
|
_, _, err = resolver.IDToPath(ctx, last.id)
|
||||||
|
assert.Error(t, err, "getting paths", clues.ToCore(err))
|
||||||
|
}
|
||||||
|
|
||||||
func (suite *ConfiguredFolderCacheUnitSuite) TestDepthLimit() {
|
func (suite *ConfiguredFolderCacheUnitSuite) TestDepthLimit() {
|
||||||
ctx, flush := tester.NewContext()
|
ctx, flush := tester.NewContext()
|
||||||
defer flush()
|
defer flush()
|
||||||
@ -350,7 +536,7 @@ func (suite *ConfiguredFolderCacheUnitSuite) TestDepthLimit() {
|
|||||||
for _, test := range table {
|
for _, test := range table {
|
||||||
suite.Run(test.name, func() {
|
suite.Run(test.name, func() {
|
||||||
resolver, containers := resolverWithContainers(test.numContainers, false)
|
resolver, containers := resolverWithContainers(test.numContainers, false)
|
||||||
_, _, err := resolver.IDToPath(ctx, containers[len(containers)-1].id)
|
_, err := resolver.idToPath(ctx, containers[len(containers)-1].id, 0)
|
||||||
test.check(suite.T(), err, clues.ToCore(err))
|
test.check(suite.T(), err, clues.ToCore(err))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -384,6 +570,9 @@ func (suite *ConfiguredFolderCacheUnitSuite) TestLookupCachedFolderNoPathsCached
|
|||||||
ctx, flush := tester.NewContext()
|
ctx, flush := tester.NewContext()
|
||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
|
err := suite.fc.populatePaths(ctx, fault.New(true))
|
||||||
|
require.NoError(suite.T(), err, clues.ToCore(err))
|
||||||
|
|
||||||
for _, c := range suite.allContainers {
|
for _, c := range suite.allContainers {
|
||||||
suite.Run(ptr.Val(c.GetDisplayName()), func() {
|
suite.Run(ptr.Val(c.GetDisplayName()), func() {
|
||||||
t := suite.T()
|
t := suite.T()
|
||||||
@ -396,10 +585,14 @@ func (suite *ConfiguredFolderCacheUnitSuite) TestLookupCachedFolderNoPathsCached
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(ashmrtn): Remove this since the same cache can do IDs or locations.
|
||||||
func (suite *ConfiguredFolderCacheUnitSuite) TestLookupCachedFolderNoPathsCached_useID() {
|
func (suite *ConfiguredFolderCacheUnitSuite) TestLookupCachedFolderNoPathsCached_useID() {
|
||||||
ctx, flush := tester.NewContext()
|
ctx, flush := tester.NewContext()
|
||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
|
err := suite.fcWithID.populatePaths(ctx, fault.New(true))
|
||||||
|
require.NoError(suite.T(), err, clues.ToCore(err))
|
||||||
|
|
||||||
for _, c := range suite.containersWithID {
|
for _, c := range suite.containersWithID {
|
||||||
suite.Run(ptr.Val(c.GetDisplayName()), func() {
|
suite.Run(ptr.Val(c.GetDisplayName()), func() {
|
||||||
t := suite.T()
|
t := suite.T()
|
||||||
@ -419,6 +612,9 @@ func (suite *ConfiguredFolderCacheUnitSuite) TestLookupCachedFolderCachesPaths()
|
|||||||
t := suite.T()
|
t := suite.T()
|
||||||
c := suite.allContainers[len(suite.allContainers)-1]
|
c := suite.allContainers[len(suite.allContainers)-1]
|
||||||
|
|
||||||
|
err := suite.fc.populatePaths(ctx, fault.New(true))
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
p, l, err := suite.fc.IDToPath(ctx, c.id)
|
p, l, err := suite.fc.IDToPath(ctx, c.id)
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
assert.Equal(t, c.expectedPath, p.String())
|
assert.Equal(t, c.expectedPath, p.String())
|
||||||
@ -432,6 +628,7 @@ func (suite *ConfiguredFolderCacheUnitSuite) TestLookupCachedFolderCachesPaths()
|
|||||||
assert.Equal(t, c.expectedLocation, l.String())
|
assert.Equal(t, c.expectedLocation, l.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(ashmrtn): Remove this since the same cache can do IDs or locations.
|
||||||
func (suite *ConfiguredFolderCacheUnitSuite) TestLookupCachedFolderCachesPaths_useID() {
|
func (suite *ConfiguredFolderCacheUnitSuite) TestLookupCachedFolderCachesPaths_useID() {
|
||||||
ctx, flush := tester.NewContext()
|
ctx, flush := tester.NewContext()
|
||||||
defer flush()
|
defer flush()
|
||||||
@ -439,6 +636,9 @@ func (suite *ConfiguredFolderCacheUnitSuite) TestLookupCachedFolderCachesPaths_u
|
|||||||
t := suite.T()
|
t := suite.T()
|
||||||
c := suite.containersWithID[len(suite.containersWithID)-1]
|
c := suite.containersWithID[len(suite.containersWithID)-1]
|
||||||
|
|
||||||
|
err := suite.fcWithID.populatePaths(ctx, fault.New(true))
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
p, l, err := suite.fcWithID.IDToPath(ctx, c.id)
|
p, l, err := suite.fcWithID.IDToPath(ctx, c.id)
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
assert.Equal(t, c.expectedPath, p.String())
|
assert.Equal(t, c.expectedPath, p.String())
|
||||||
@ -457,12 +657,21 @@ func (suite *ConfiguredFolderCacheUnitSuite) TestLookupCachedFolderErrorsParentN
|
|||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
t := suite.T()
|
t := suite.T()
|
||||||
last := suite.allContainers[len(suite.allContainers)-1]
|
|
||||||
almostLast := suite.allContainers[len(suite.allContainers)-2]
|
almostLast := suite.allContainers[len(suite.allContainers)-2]
|
||||||
|
|
||||||
delete(suite.fc.cache, almostLast.id)
|
delete(suite.fc.cache, almostLast.id)
|
||||||
|
|
||||||
_, _, err := suite.fc.IDToPath(ctx, last.id)
|
err := suite.fc.populatePaths(ctx, fault.New(true))
|
||||||
|
assert.Error(t, err, clues.ToCore(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ConfiguredFolderCacheUnitSuite) TestLookupCachedFolder_Errors_PathsNotBuilt() {
|
||||||
|
ctx, flush := tester.NewContext()
|
||||||
|
defer flush()
|
||||||
|
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
_, _, err := suite.fc.IDToPath(ctx, suite.allContainers[len(suite.allContainers)-1].id)
|
||||||
assert.Error(t, err, clues.ToCore(err))
|
assert.Error(t, err, clues.ToCore(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -597,7 +597,7 @@ func (suite *DataCollectionsIntegrationSuite) TestEventsSerializationRegression(
|
|||||||
bdayID string
|
bdayID string
|
||||||
)
|
)
|
||||||
|
|
||||||
fn := func(gcf graph.CacheFolder) error {
|
fn := func(gcf graph.CachedContainer) error {
|
||||||
if ptr.Val(gcf.GetDisplayName()) == DefaultCalendar {
|
if ptr.Val(gcf.GetDisplayName()) == DefaultCalendar {
|
||||||
calID = ptr.Val(gcf.GetId())
|
calID = ptr.Val(gcf.GetId())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,7 +27,7 @@ func (ecc *eventCalendarCache) init(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
) error {
|
) error {
|
||||||
if ecc.containerResolver == nil {
|
if ecc.containerResolver == nil {
|
||||||
ecc.containerResolver = newContainerResolver()
|
ecc.containerResolver = newContainerResolver(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ecc.populateEventRoot(ctx)
|
return ecc.populateEventRoot(ctx)
|
||||||
@ -49,7 +49,7 @@ func (ecc *eventCalendarCache) populateEventRoot(ctx context.Context) error {
|
|||||||
f,
|
f,
|
||||||
path.Builder{}.Append(ptr.Val(f.GetId())), // storage path
|
path.Builder{}.Append(ptr.Val(f.GetId())), // storage path
|
||||||
path.Builder{}.Append(ptr.Val(f.GetDisplayName()))) // display location
|
path.Builder{}.Append(ptr.Val(f.GetDisplayName()))) // display location
|
||||||
if err := ecc.addFolder(temp); err != nil {
|
if err := ecc.addFolder(&temp); err != nil {
|
||||||
return clues.Wrap(err, "initializing calendar resolver").WithClues(ctx)
|
return clues.Wrap(err, "initializing calendar resolver").WithClues(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,7 +98,7 @@ func (ecc *eventCalendarCache) AddToCache(ctx context.Context, f graph.Container
|
|||||||
path.Builder{}.Append(ptr.Val(f.GetId())), // storage path
|
path.Builder{}.Append(ptr.Val(f.GetId())), // storage path
|
||||||
path.Builder{}.Append(ptr.Val(f.GetDisplayName()))) // display location
|
path.Builder{}.Append(ptr.Val(f.GetDisplayName()))) // display location
|
||||||
|
|
||||||
if err := ecc.addFolder(temp); err != nil {
|
if err := ecc.addFolder(&temp); err != nil {
|
||||||
return clues.Wrap(err, "adding container").WithClues(ctx)
|
return clues.Wrap(err, "adding container").WithClues(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -10,7 +10,29 @@ import (
|
|||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ graph.ContainerResolver = &mailFolderCache{}
|
var (
|
||||||
|
_ graph.ContainerResolver = &mailFolderCache{}
|
||||||
|
_ containerRefresher = &mailRefresher{}
|
||||||
|
)
|
||||||
|
|
||||||
|
type mailRefresher struct {
|
||||||
|
getter containerGetter
|
||||||
|
userID string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *mailRefresher) refreshContainer(
|
||||||
|
ctx context.Context,
|
||||||
|
id string,
|
||||||
|
) (graph.CachedContainer, error) {
|
||||||
|
c, err := r.getter.GetContainerByID(ctx, r.userID, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, clues.Stack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
f := graph.NewCacheFolder(c, nil, nil)
|
||||||
|
|
||||||
|
return &f, nil
|
||||||
|
}
|
||||||
|
|
||||||
// mailFolderCache struct used to improve lookup of directories within exchange.Mail
|
// mailFolderCache struct used to improve lookup of directories within exchange.Mail
|
||||||
// cache map of cachedContainers where the key = M365ID
|
// cache map of cachedContainers where the key = M365ID
|
||||||
@ -29,7 +51,10 @@ func (mc *mailFolderCache) init(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
) error {
|
) error {
|
||||||
if mc.containerResolver == nil {
|
if mc.containerResolver == nil {
|
||||||
mc.containerResolver = newContainerResolver()
|
mc.containerResolver = newContainerResolver(&mailRefresher{
|
||||||
|
userID: mc.userID,
|
||||||
|
getter: mc.getter,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return mc.populateMailRoot(ctx)
|
return mc.populateMailRoot(ctx)
|
||||||
@ -52,7 +77,7 @@ func (mc *mailFolderCache) populateMailRoot(ctx context.Context) error {
|
|||||||
// the user doesn't see in the regular UI for Exchange.
|
// the user doesn't see in the regular UI for Exchange.
|
||||||
path.Builder{}.Append(), // path of IDs
|
path.Builder{}.Append(), // path of IDs
|
||||||
path.Builder{}.Append()) // display location
|
path.Builder{}.Append()) // display location
|
||||||
if err := mc.addFolder(temp); err != nil {
|
if err := mc.addFolder(&temp); err != nil {
|
||||||
return clues.Wrap(err, "adding resolver dir").WithClues(ctx)
|
return clues.Wrap(err, "adding resolver dir").WithClues(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -118,7 +118,7 @@ func (c Contacts) GetContainerByID(
|
|||||||
func (c Contacts) EnumerateContainers(
|
func (c Contacts) EnumerateContainers(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
userID, baseDirID string,
|
userID, baseDirID string,
|
||||||
fn func(graph.CacheFolder) error,
|
fn func(graph.CachedContainer) error,
|
||||||
errs *fault.Bus,
|
errs *fault.Bus,
|
||||||
) error {
|
) error {
|
||||||
service, err := c.Service()
|
service, err := c.Service()
|
||||||
@ -166,7 +166,7 @@ func (c Contacts) EnumerateContainers(
|
|||||||
"container_display_name", ptr.Val(fold.GetDisplayName()))
|
"container_display_name", ptr.Val(fold.GetDisplayName()))
|
||||||
|
|
||||||
temp := graph.NewCacheFolder(fold, nil, nil)
|
temp := graph.NewCacheFolder(fold, nil, nil)
|
||||||
if err := fn(temp); err != nil {
|
if err := fn(&temp); err != nil {
|
||||||
errs.AddRecoverable(graph.Stack(fctx, err).Label(fault.LabelForceNoBackupCreation))
|
errs.AddRecoverable(graph.Stack(fctx, err).Label(fault.LabelForceNoBackupCreation))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@ -192,7 +192,7 @@ func (c Events) GetItem(
|
|||||||
func (c Events) EnumerateContainers(
|
func (c Events) EnumerateContainers(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
userID, baseDirID string,
|
userID, baseDirID string,
|
||||||
fn func(graph.CacheFolder) error,
|
fn func(graph.CachedContainer) error,
|
||||||
errs *fault.Bus,
|
errs *fault.Bus,
|
||||||
) error {
|
) error {
|
||||||
service, err := c.Service()
|
service, err := c.Service()
|
||||||
@ -239,7 +239,7 @@ func (c Events) EnumerateContainers(
|
|||||||
cd,
|
cd,
|
||||||
path.Builder{}.Append(ptr.Val(cd.GetId())), // storage path
|
path.Builder{}.Append(ptr.Val(cd.GetId())), // storage path
|
||||||
path.Builder{}.Append(ptr.Val(cd.GetDisplayName()))) // display location
|
path.Builder{}.Append(ptr.Val(cd.GetDisplayName()))) // display location
|
||||||
if err := fn(temp); err != nil {
|
if err := fn(&temp); err != nil {
|
||||||
errs.AddRecoverable(graph.Stack(fctx, err).Label(fault.LabelForceNoBackupCreation))
|
errs.AddRecoverable(graph.Stack(fctx, err).Label(fault.LabelForceNoBackupCreation))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@ -309,7 +309,7 @@ func (p *mailFolderPager) valuesIn(pl api.PageLinker) ([]models.MailFolderable,
|
|||||||
func (c Mail) EnumerateContainers(
|
func (c Mail) EnumerateContainers(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
userID, baseDirID string,
|
userID, baseDirID string,
|
||||||
fn func(graph.CacheFolder) error,
|
fn func(graph.CachedContainer) error,
|
||||||
errs *fault.Bus,
|
errs *fault.Bus,
|
||||||
) error {
|
) error {
|
||||||
service, err := c.Service()
|
service, err := c.Service()
|
||||||
@ -347,7 +347,7 @@ func (c Mail) EnumerateContainers(
|
|||||||
"container_name", ptr.Val(v.GetDisplayName()))
|
"container_name", ptr.Val(v.GetDisplayName()))
|
||||||
|
|
||||||
temp := graph.NewCacheFolder(v, nil, nil)
|
temp := graph.NewCacheFolder(v, nil, nil)
|
||||||
if err := fn(temp); err != nil {
|
if err := fn(&temp); err != nil {
|
||||||
errs.AddRecoverable(graph.Stack(fctx, err).Label(fault.LabelForceNoBackupCreation))
|
errs.AddRecoverable(graph.Stack(fctx, err).Label(fault.LabelForceNoBackupCreation))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user