github.com/graemephi/kahugo@v0.62.3-0.20211121071557-d78c0423784d/hugofs/slice_fs.go (about)

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