package driver import ( "context" "errors" "sort" "github.com/sirupsen/logrus" ) // ErrSkipDir is used as a return value from onFileFunc to indicate that // the directory named in the call is to be skipped. It is not returned // as an error by any function. var ErrSkipDir = errors.New("skip this directory") // WalkFn is called once per file by Walk type WalkFn func(fileInfo FileInfo) error // WalkFallback traverses a filesystem defined within driver, starting // from the given path, calling f on each file. It uses the List method and Stat to drive itself. // If the returned error from the WalkFn is ErrSkipDir and fileInfo refers // to a directory, the directory will not be entered and Walk // will continue the traversal. If fileInfo refers to a normal file, processing stops func WalkFallback(ctx context.Context, driver StorageDriver, from string, f WalkFn) error { children, err := driver.List(ctx, from) if err != nil { return err } sort.Stable(sort.StringSlice(children)) for _, child := range children { // TODO(stevvooe): Calling driver.Stat for every entry is quite // expensive when running against backends with a slow Stat // implementation, such as s3. This is very likely a serious // performance bottleneck. fileInfo, err := driver.Stat(ctx, child) if err != nil { switch err.(type) { case PathNotFoundError: // repository was removed in between listing and enumeration. Ignore it. logrus.WithField("path", child).Infof("ignoring deleted path") continue default: return err } } err = f(fileInfo) if err == nil && fileInfo.IsDir() { if err := WalkFallback(ctx, driver, child, f); err != nil { return err } } else if err == ErrSkipDir { // Stop iteration if it's a file, otherwise noop if it's a directory if !fileInfo.IsDir() { return nil } } else if err != nil { return err } } return nil }