slice_fs.go (6187B)
1 // Copyright 2019 The Hugo Authors. All rights reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13
14 package hugofs
15
16 import (
17 "fmt"
18 "os"
19 "syscall"
20 "time"
21
22 "errors"
23
24 "github.com/spf13/afero"
25 )
26
27 var (
28 _ afero.Fs = (*SliceFs)(nil)
29 _ afero.Lstater = (*SliceFs)(nil)
30 _ FilesystemsUnwrapper = (*SliceFs)(nil)
31 _ afero.File = (*sliceDir)(nil)
32 )
33
34 func NewSliceFs(dirs ...FileMetaInfo) (afero.Fs, error) {
35 if len(dirs) == 0 {
36 return NoOpFs, nil
37 }
38
39 for _, dir := range dirs {
40 if !dir.IsDir() {
41 return nil, errors.New("this fs supports directories only")
42 }
43 }
44
45 fs := &SliceFs{
46 dirs: dirs,
47 }
48
49 return fs, nil
50 }
51
52 // SliceFs is an ordered composite filesystem.
53 type SliceFs struct {
54 dirs []FileMetaInfo
55 }
56
57 func (fs *SliceFs) UnwrapFilesystems() []afero.Fs {
58 var fss []afero.Fs
59 for _, dir := range fs.dirs {
60 fss = append(fss, dir.Meta().Fs)
61 }
62 return fss
63 }
64
65 func (fs *SliceFs) Chmod(n string, m os.FileMode) error {
66 return syscall.EPERM
67 }
68
69 func (fs *SliceFs) Chtimes(n string, a, m time.Time) error {
70 return syscall.EPERM
71 }
72
73 func (fs *SliceFs) Chown(n string, uid, gid int) error {
74 return syscall.EPERM
75 }
76
77 func (fs *SliceFs) LstatIfPossible(name string) (os.FileInfo, bool, error) {
78 fi, _, err := fs.pickFirst(name)
79 if err != nil {
80 return nil, false, err
81 }
82
83 if fi.IsDir() {
84 return decorateFileInfo(fi, fs, fs.getOpener(name), "", "", nil), false, nil
85 }
86
87 return nil, false, fmt.Errorf("lstat: files not supported: %q", name)
88 }
89
90 func (fs *SliceFs) Mkdir(n string, p os.FileMode) error {
91 return syscall.EPERM
92 }
93
94 func (fs *SliceFs) MkdirAll(n string, p os.FileMode) error {
95 return syscall.EPERM
96 }
97
98 func (fs *SliceFs) Name() string {
99 return "SliceFs"
100 }
101
102 func (fs *SliceFs) Open(name string) (afero.File, error) {
103 fi, idx, err := fs.pickFirst(name)
104 if err != nil {
105 return nil, err
106 }
107
108 if !fi.IsDir() {
109 panic("currently only dirs in here")
110 }
111
112 return &sliceDir{
113 lfs: fs,
114 idx: idx,
115 dirname: name,
116 }, nil
117 }
118
119 func (fs *SliceFs) OpenFile(name string, flag int, perm os.FileMode) (afero.File, error) {
120 panic("not implemented")
121 }
122
123 func (fs *SliceFs) ReadDir(name string) ([]os.FileInfo, error) {
124 panic("not implemented")
125 }
126
127 func (fs *SliceFs) Remove(n string) error {
128 return syscall.EPERM
129 }
130
131 func (fs *SliceFs) RemoveAll(p string) error {
132 return syscall.EPERM
133 }
134
135 func (fs *SliceFs) Rename(o, n string) error {
136 return syscall.EPERM
137 }
138
139 func (fs *SliceFs) Stat(name string) (os.FileInfo, error) {
140 fi, _, err := fs.LstatIfPossible(name)
141 return fi, err
142 }
143
144 func (fs *SliceFs) Create(n string) (afero.File, error) {
145 return nil, syscall.EPERM
146 }
147
148 func (fs *SliceFs) getOpener(name string) func() (afero.File, error) {
149 return func() (afero.File, error) {
150 return fs.Open(name)
151 }
152 }
153
154 func (fs *SliceFs) pickFirst(name string) (os.FileInfo, int, error) {
155 for i, mfs := range fs.dirs {
156 meta := mfs.Meta()
157 fs := meta.Fs
158 fi, _, err := lstatIfPossible(fs, name)
159 if err == nil {
160 // Gotta match!
161 return fi, i, nil
162 }
163
164 if !os.IsNotExist(err) {
165 // Real error
166 return nil, -1, err
167 }
168 }
169
170 // Not found
171 return nil, -1, os.ErrNotExist
172 }
173
174 func (fs *SliceFs) readDirs(name string, startIdx, count int) ([]os.FileInfo, error) {
175 collect := func(lfs *FileMeta) ([]os.FileInfo, error) {
176 d, err := lfs.Fs.Open(name)
177 if err != nil {
178 if !os.IsNotExist(err) {
179 return nil, err
180 }
181 return nil, nil
182 } else {
183 defer d.Close()
184 dirs, err := d.Readdir(-1)
185 if err != nil {
186 return nil, err
187 }
188 return dirs, nil
189 }
190 }
191
192 var dirs []os.FileInfo
193
194 for i := startIdx; i < len(fs.dirs); i++ {
195 mfs := fs.dirs[i]
196
197 fis, err := collect(mfs.Meta())
198 if err != nil {
199 return nil, err
200 }
201
202 dirs = append(dirs, fis...)
203
204 }
205
206 seen := make(map[string]bool)
207 var duplicates []int
208 for i, fi := range dirs {
209 if !fi.IsDir() {
210 continue
211 }
212
213 if seen[fi.Name()] {
214 duplicates = append(duplicates, i)
215 } else {
216 // Make sure it's opened by this filesystem.
217 dirs[i] = decorateFileInfo(fi, fs, fs.getOpener(fi.(FileMetaInfo).Meta().Filename), "", "", nil)
218 seen[fi.Name()] = true
219 }
220 }
221
222 // Remove duplicate directories, keep first.
223 if len(duplicates) > 0 {
224 for i := len(duplicates) - 1; i >= 0; i-- {
225 idx := duplicates[i]
226 dirs = append(dirs[:idx], dirs[idx+1:]...)
227 }
228 }
229
230 if count > 0 && len(dirs) >= count {
231 return dirs[:count], nil
232 }
233
234 return dirs, nil
235 }
236
237 type sliceDir struct {
238 lfs *SliceFs
239 idx int
240 dirname string
241 }
242
243 func (f *sliceDir) Close() error {
244 return nil
245 }
246
247 func (f *sliceDir) Name() string {
248 return f.dirname
249 }
250
251 func (f *sliceDir) Read(p []byte) (n int, err error) {
252 panic("not implemented")
253 }
254
255 func (f *sliceDir) ReadAt(p []byte, off int64) (n int, err error) {
256 panic("not implemented")
257 }
258
259 func (f *sliceDir) Readdir(count int) ([]os.FileInfo, error) {
260 return f.lfs.readDirs(f.dirname, f.idx, count)
261 }
262
263 func (f *sliceDir) Readdirnames(count int) ([]string, error) {
264 dirsi, err := f.Readdir(count)
265 if err != nil {
266 return nil, err
267 }
268
269 dirs := make([]string, len(dirsi))
270 for i, d := range dirsi {
271 dirs[i] = d.Name()
272 }
273 return dirs, nil
274 }
275
276 func (f *sliceDir) Seek(offset int64, whence int) (int64, error) {
277 panic("not implemented")
278 }
279
280 func (f *sliceDir) Stat() (os.FileInfo, error) {
281 panic("not implemented")
282 }
283
284 func (f *sliceDir) Sync() error {
285 panic("not implemented")
286 }
287
288 func (f *sliceDir) Truncate(size int64) error {
289 panic("not implemented")
290 }
291
292 func (f *sliceDir) Write(p []byte) (n int, err error) {
293 panic("not implemented")
294 }
295
296 func (f *sliceDir) WriteAt(p []byte, off int64) (n int, err error) {
297 panic("not implemented")
298 }
299
300 func (f *sliceDir) WriteString(s string) (ret int, err error) {
301 panic("not implemented")
302 }