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 }