github.com/Schaudge/grailbase@v0.0.0-20240223061707-44c758a471c0/file/loopbackfs/loopbackfs.go (about)

     1  package loopbackfs
     2  
     3  import (
     4  	"context"
     5  	"io/ioutil"
     6  	"os"
     7  	"path"
     8  	"time"
     9  
    10  	"github.com/Schaudge/grailbase/file/fsnode"
    11  	"github.com/Schaudge/grailbase/ioctx/fsctx"
    12  	"github.com/Schaudge/grailbase/ioctx/spliceio"
    13  )
    14  
    15  // New returns an fsnode.T representing a path on the local filesystem.
    16  // TODO: Replace this with a generic io/fs.FS wrapper around os.DirFS, after upgrading Go.
    17  func New(name string, path string) (fsnode.T, error) {
    18  	info, err := os.Stat(path)
    19  	if err != nil {
    20  		return nil, err
    21  	}
    22  	node := newT(name, info, path)
    23  	if node == nil {
    24  		return nil, os.ErrInvalid
    25  	}
    26  	return node, nil
    27  }
    28  
    29  func newT(name string, info os.FileInfo, path string) fsnode.T {
    30  	switch info.Mode() & os.ModeType {
    31  	case os.ModeDir:
    32  		// Temporary hack: record the original path so we can peek at it.
    33  		// TODO: Eventually, adapt our libraries to operate on FS's directly.
    34  		// TODO: Consider preserving executable bits. But, copying permissions without also checking
    35  		// owner UID/GID may not make sense.
    36  		info := fsnode.NewDirInfo(name).WithModTime(info.ModTime()).WithSys(path).
    37  			WithCacheableFor(time.Hour)
    38  		return parent{dir: path, FileInfo: info}
    39  	case 0:
    40  		info := fsnode.NewRegInfo(name).WithModTime(info.ModTime()).WithSys(path).
    41  			WithCacheableFor(time.Hour)
    42  		return leaf{path, info}
    43  	}
    44  	return nil
    45  }
    46  
    47  type parent struct {
    48  	fsnode.ParentReadOnly
    49  	dir string
    50  	fsnode.FileInfo
    51  }
    52  
    53  var _ fsnode.Parent = parent{}
    54  
    55  func (p parent) FSNodeT() {}
    56  
    57  func (p parent) Child(_ context.Context, name string) (fsnode.T, error) {
    58  	return New(name, path.Join(p.dir, name))
    59  }
    60  
    61  type iterator struct {
    62  	dir      string
    63  	fetched  bool
    64  	delegate fsnode.Iterator
    65  }
    66  
    67  func (p parent) Children() fsnode.Iterator { return &iterator{dir: p.dir} }
    68  
    69  func (it *iterator) ensureFetched() error {
    70  	if it.fetched {
    71  		if it.delegate == nil {
    72  			return os.ErrClosed
    73  		}
    74  		return nil
    75  	}
    76  	entries, err := ioutil.ReadDir(it.dir)
    77  	if err != nil {
    78  		return err
    79  	}
    80  	nodes := make([]fsnode.T, 0, len(entries))
    81  	for _, info := range entries {
    82  		fullPath := path.Join(it.dir, info.Name())
    83  		node := newT(info.Name(), info, fullPath)
    84  		if node == nil {
    85  			continue
    86  		}
    87  		nodes = append(nodes, node)
    88  	}
    89  	it.fetched = true
    90  	it.delegate = fsnode.NewIterator(nodes...)
    91  	return nil
    92  }
    93  
    94  func (it *iterator) Next(ctx context.Context) (fsnode.T, error) {
    95  	if err := it.ensureFetched(); err != nil {
    96  		return nil, err
    97  	}
    98  	return it.delegate.Next(ctx)
    99  }
   100  
   101  func (it *iterator) Close(context.Context) error {
   102  	it.fetched = true
   103  	it.delegate = nil
   104  	return nil
   105  }
   106  
   107  type leaf struct {
   108  	path string
   109  	fsnode.FileInfo
   110  }
   111  
   112  var _ fsnode.Leaf = leaf{}
   113  
   114  func (l leaf) FSNodeT() {}
   115  
   116  func (l leaf) OpenFile(context.Context, int) (fsctx.File, error) {
   117  	file, err := os.Open(l.path)
   118  	if err != nil {
   119  		return nil, err
   120  	}
   121  	return (*spliceio.OSFile)(file), nil
   122  }