1package vfsutil
2
3import (
4 "io"
5 "net/http"
6 "os"
7 pathpkg "path"
8 "path/filepath"
9 "sort"
10)
11
12// Walk walks the filesystem rooted at root, calling walkFn for each file or
13// directory in the filesystem, including root. All errors that arise visiting files
14// and directories are filtered by walkFn. The files are walked in lexical
15// order.
16func Walk(fs http.FileSystem, root string, walkFn filepath.WalkFunc) error {
17 info, err := Stat(fs, root)
18 if err != nil {
19 return walkFn(root, nil, err)
20 }
21 return walk(fs, root, info, walkFn)
22}
23
24// readDirNames reads the directory named by dirname and returns
25// a sorted list of directory entries.
26func readDirNames(fs http.FileSystem, dirname string) ([]string, error) {
27 fis, err := ReadDir(fs, dirname)
28 if err != nil {
29 return nil, err
30 }
31 names := make([]string, len(fis))
32 for i := range fis {
33 names[i] = fis[i].Name()
34 }
35 sort.Strings(names)
36 return names, nil
37}
38
39// walk recursively descends path, calling walkFn.
40func walk(fs http.FileSystem, path string, info os.FileInfo, walkFn filepath.WalkFunc) error {
41 err := walkFn(path, info, nil)
42 if err != nil {
43 if info.IsDir() && err == filepath.SkipDir {
44 return nil
45 }
46 return err
47 }
48
49 if !info.IsDir() {
50 return nil
51 }
52
53 names, err := readDirNames(fs, path)
54 if err != nil {
55 return walkFn(path, info, err)
56 }
57
58 for _, name := range names {
59 filename := pathpkg.Join(path, name)
60 fileInfo, err := Stat(fs, filename)
61 if err != nil {
62 if err := walkFn(filename, fileInfo, err); err != nil && err != filepath.SkipDir {
63 return err
64 }
65 } else {
66 err = walk(fs, filename, fileInfo, walkFn)
67 if err != nil {
68 if !fileInfo.IsDir() || err != filepath.SkipDir {
69 return err
70 }
71 }
72 }
73 }
74 return nil
75}
76
77// WalkFilesFunc is the type of the function called for each file or directory visited by WalkFiles.
78// It's like filepath.WalkFunc, except it provides an additional ReadSeeker parameter for file being visited.
79type WalkFilesFunc func(path string, info os.FileInfo, rs io.ReadSeeker, err error) error
80
81// WalkFiles walks the filesystem rooted at root, calling walkFn for each file or
82// directory in the filesystem, including root. In addition to FileInfo, it passes an
83// ReadSeeker to walkFn for each file it visits.
84func WalkFiles(fs http.FileSystem, root string, walkFn WalkFilesFunc) error {
85 file, info, err := openStat(fs, root)
86 if err != nil {
87 return walkFn(root, nil, nil, err)
88 }
89 return walkFiles(fs, root, info, file, walkFn)
90}
91
92// walkFiles recursively descends path, calling walkFn.
93// It closes the input file after it's done with it, so the caller shouldn't.
94func walkFiles(fs http.FileSystem, path string, info os.FileInfo, file http.File, walkFn WalkFilesFunc) error {
95 err := walkFn(path, info, file, nil)
96 file.Close()
97 if err != nil {
98 if info.IsDir() && err == filepath.SkipDir {
99 return nil
100 }
101 return err
102 }
103
104 if !info.IsDir() {
105 return nil
106 }
107
108 names, err := readDirNames(fs, path)
109 if err != nil {
110 return walkFn(path, info, nil, err)
111 }
112
113 for _, name := range names {
114 filename := pathpkg.Join(path, name)
115 file, fileInfo, err := openStat(fs, filename)
116 if err != nil {
117 if err := walkFn(filename, nil, nil, err); err != nil && err != filepath.SkipDir {
118 return err
119 }
120 } else {
121 err = walkFiles(fs, filename, fileInfo, file, walkFn)
122 // file is closed by walkFiles, so we don't need to close it here.
123 if err != nil {
124 if !fileInfo.IsDir() || err != filepath.SkipDir {
125 return err
126 }
127 }
128 }
129 }
130 return nil
131}
132
133// openStat performs Open and Stat and returns results, or first error encountered.
134// The caller is responsible for closing the returned file when done.
135func openStat(fs http.FileSystem, name string) (http.File, os.FileInfo, error) {
136 f, err := fs.Open(name)
137 if err != nil {
138 return nil, nil, err
139 }
140 fi, err := f.Stat()
141 if err != nil {
142 f.Close()
143 return nil, nil, err
144 }
145 return f, fi, nil
146}