func()

in iterator/fs.go [127:244]


func (obj *Fs) Recurse(ctx context.Context, scan interfaces.ScanFunc) ([]interfaces.Iterator, error) {

	mu := &sync.Mutex{} // guards any concurrently modified state
	iterators := []interfaces.Iterator{}

	obj.Logf("running %s", obj.String())

	if !obj.Path.IsAbs() {
		return nil, fmt.Errorf("path is not absolute")
	}

	// it's a single file, not a directory
	if !obj.Path.IsDir() {
		// XXX: symlink detection?
		fileInfo, err := os.Stat(obj.Path.Path()) // XXX: stat or Lstat?
		if err != nil {
			return nil, errwrap.Wrapf(err, "could not stat during single file scan")
		}
		if fileInfo.IsDir() {
			return nil, fmt.Errorf("input path contained no trailing slash but is a dir")
		}
		uid := FileScheme + obj.Path.String() // the (ugly) default
		if obj.GenUID != nil {
			var err error
			uid, err = obj.GenUID(obj.Path)
			if err != nil {
				// probable programming error
				return nil, errwrap.Wrapf(err, "the GetUID func failed")
			}
		}
		info := &interfaces.Info{
			FileInfo: fileInfo,
			UID:      uid,
		}
		return nil, errwrap.Wrapf(scan(ctx, obj.Path, info), "single file scan func failed")
	}

	// TODO: Replace this with a parallel walk for performance
	// TODO: Maybe add a separate flag/switch for it in the options?
	// TODO: Make sure result aggregation and skipdir support still works!
	// TODO: Replace this with a walk that accepts safepath types instead.
	err := filepath.Walk(obj.Path.Path(), func(path string, fileInfo fs.FileInfo, err error) error {
		if err != nil {
			// prevent panic by handling failure accessing a path
			return errwrap.Wrapf(err, "fail inside walk with: %s", path)
		}

		safePath, err := safepath.ParseIntoPath(path, fileInfo.IsDir())
		if err != nil {
			return err
		}

		// Mechanism to end a particularly long walk early if needed...
		// In an effort to short-circuit things if needed, we run a
		// check ourselves and break out early if we see that we have
		// cancelled early.
		select {
		case <-ctx.Done():
			return errwrap.Wrapf(ctx.Err(), "ended walk early")
		default:
		}

		// skip symlinks
		if fileInfo.Mode()&os.ModeSymlink == os.ModeSymlink {
			return nil
		}

		// Check for a .gitmodules file.
		gitIterators, err := obj.GitSubmodulesHelper(ctx, safePath)
		if err != nil {
			return err
		}
		if gitIterators != nil && len(gitIterators) > 0 {
			mu.Lock()
			for _, iterator := range gitIterators {
				iterators = append(iterators, iterator)
			}
			mu.Unlock()
		}

		// Skip iterating over certain paths.
		if skip, err := SkipPath(safePath, fileInfo); skip || err != nil {
			if obj.Debug && (skip || err == interfaces.SkipDir) {
				obj.Logf("skipping: %s", safePath.String())
			}
			return err // nil to skip, interfaces.SkipDir, or error
		}

		if obj.Debug {
			obj.Logf("visited file or dir: %q", path)
		}

		uid := FileScheme + safePath.String() // the (ugly) default
		if obj.GenUID != nil {
			var err error
			uid, err = obj.GenUID(safePath)
			if err != nil {
				// probable programming error
				return errwrap.Wrapf(err, "the GetUID func failed")
			}
		}
		info := &interfaces.Info{
			FileInfo: fileInfo,
			UID:      uid,
		}
		// We want to ignore the ErrUnknownLicense results, and error if
		// we hit any actual errors that we should bubble upwards.
		if err := scan(ctx, safePath, info); err != nil && !errors.Is(err, interfaces.ErrUnknownLicense) {
			// XXX: ShutdownOnError?
			return errwrap.Wrapf(err, "scan func failed")
		}

		return nil
	})
	//if obj.Debug { obj.Logf("walk done!") } // debug

	return iterators, errwrap.Wrapf(err, "walk failed")
}