github.com/influx6/npkg@v0.8.8/nfs/filesystem.go (about)

     1  package nfs
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"io"
     7  	"path/filepath"
     8  	"strings"
     9  	"sync"
    10  	"time"
    11  )
    12  
    13  // errors ...
    14  var (
    15  	ErrExist       = errors.New("Path exists")
    16  	ErrNotExist    = errors.New("Path does not exists")
    17  	ErrToManyParts = errors.New("Prefix must contain only two parts: /bob/log")
    18  )
    19  
    20  // FileMode defines the mode value of a file.
    21  type FileMode uint32
    22  
    23  // FileInfo defines an interface representing a file into.
    24  type FileInfo interface {
    25  	Name() string
    26  	Size() int64
    27  	ModTime() time.Time
    28  	IsDir() bool
    29  }
    30  
    31  // File defines a interface for representing a file.
    32  type File interface {
    33  	io.Closer
    34  	io.Reader
    35  	io.Seeker
    36  	Readdir(count int) ([]FileInfo, error)
    37  	Stat() (FileInfo, error)
    38  }
    39  
    40  // FileSystem defines a interface for a virutal nfs
    41  // representing a file.
    42  type FileSystem interface {
    43  	Open(string) (File, error)
    44  }
    45  
    46  // New returns a new instance of a VirtualFileSystem as a FileSystem type.
    47  func New(fn GetFile) FileSystem {
    48  	return VirtualFileSystem{
    49  		GetFileFunc: fn,
    50  	}
    51  }
    52  
    53  // VirtualFile exposes a slice of []byte and associated name as
    54  // a http.File. It implements http.File interface.
    55  type VirtualFile struct {
    56  	*bytes.Reader
    57  	FileName string
    58  	FileSize int64
    59  	FileMod  time.Time
    60  }
    61  
    62  // NewVirtualFile returns a new instance of VirtualFile.
    63  func NewVirtualFile(r *bytes.Reader, filename string, size int64, mod time.Time) *VirtualFile {
    64  	return &VirtualFile{
    65  		Reader:   r,
    66  		FileSize: size,
    67  		FileName: filename,
    68  		FileMod:  mod,
    69  	}
    70  }
    71  
    72  // ModTime returns associated mode time for file.
    73  func (vf *VirtualFile) ModTime() time.Time {
    74  	if vf.FileMod.IsZero() {
    75  		return time.Now()
    76  	}
    77  
    78  	return vf.FileMod
    79  }
    80  
    81  // IsDir returns false because this is a virtual file.
    82  func (vf *VirtualFile) IsDir() bool {
    83  	return false
    84  }
    85  
    86  // Mode returns associated file mode of file.
    87  func (vf *VirtualFile) Mode() FileMode {
    88  	return 0700
    89  }
    90  
    91  // Size returns data file size.
    92  func (vf *VirtualFile) Size() int64 {
    93  	return vf.FileSize
    94  }
    95  
    96  // Name returns filename of giving file, either as a absolute or
    97  // relative path.
    98  func (vf *VirtualFile) Name() string {
    99  	return vf.FileName
   100  }
   101  
   102  // Stat returns VirtualFile which implements os.FileInfo for virtual
   103  // file to meet http.File interface.
   104  func (vf *VirtualFile) Stat() (FileInfo, error) {
   105  	return vf, nil
   106  }
   107  
   108  // Readdir returns nil slices as this is a file not a directory.
   109  func (vf *VirtualFile) Readdir(n int) ([]FileInfo, error) {
   110  	return nil, nil
   111  }
   112  
   113  // Close returns nothing.
   114  func (vf *VirtualFile) Close() error {
   115  	return nil
   116  }
   117  
   118  // GetFile define a function type that returns a VirtualFile type
   119  // or an error.
   120  type GetFile func(string) (File, error)
   121  
   122  // StripPrefix returns a new GetFile which wraps the previous provided
   123  // GetFile and always strips provided prefix from incoming path.
   124  func StripPrefix(prefix string, from GetFile) GetFile {
   125  	return func(path string) (File, error) {
   126  		return from(strings.TrimPrefix(path, prefix))
   127  	}
   128  }
   129  
   130  // VirtualFileSystem connects a series of functions which are provided
   131  // to retrieve bundled files and serve to a http server. It implements
   132  // http.FileSystem interface.
   133  type VirtualFileSystem struct {
   134  	GetFileFunc GetFile
   135  }
   136  
   137  // Open returns associated file with given name if found else
   138  // returning an error. It implements http.FileSystem.Open method.
   139  func (v VirtualFileSystem) Open(name string) (File, error) {
   140  	if v.GetFileFunc == nil {
   141  		return nil, ErrNotExist
   142  	}
   143  
   144  	vfile, err := v.GetFileFunc(name)
   145  	if err != nil {
   146  		return nil, err
   147  	}
   148  
   149  	return vfile, nil
   150  }
   151  
   152  type systemNode struct {
   153  	prefix string
   154  	root   FileSystem
   155  	nodes  map[string]FileSystem
   156  }
   157  
   158  // SystemGroup allows the combination of multiple nfs to
   159  // respond to incoming request based on initial path prefix.
   160  type SystemGroup struct {
   161  	ml      sync.Mutex
   162  	systems map[string]systemNode
   163  }
   164  
   165  // NewSystemGroup returns a new instance of SystemGroup.
   166  func NewSystemGroup() *SystemGroup {
   167  	return &SystemGroup{
   168  		systems: make(map[string]systemNode),
   169  	}
   170  }
   171  
   172  // MustRegister will panic if the prefix and FileSystem failed to register
   173  // It returns itself if successfully to allow chaining.
   174  func (fs *SystemGroup) MustRegister(prefix string, m FileSystem) *SystemGroup {
   175  	if err := fs.Register(prefix, m); err != nil {
   176  		panic(err)
   177  	}
   178  
   179  	return fs
   180  }
   181  
   182  // Register adds giving file system to handling paths with given prefix.
   183  func (fs *SystemGroup) Register(prefix string, m FileSystem) error {
   184  	defer fs.ml.Unlock()
   185  	fs.ml.Lock()
   186  
   187  	if strings.Contains(prefix, "\\") {
   188  		prefix = filepath.ToSlash(prefix)
   189  	}
   190  
   191  	prefix = strings.TrimPrefix(prefix, "/")
   192  	prefix = strings.TrimSuffix(prefix, "/")
   193  
   194  	parts := strings.Split(prefix, "/")
   195  	if len(parts) > 2 {
   196  		return ErrToManyParts
   197  	}
   198  
   199  	root := parts[0]
   200  	if len(parts) == 1 {
   201  		if node, ok := fs.systems[root]; ok {
   202  			node.root = m
   203  			fs.systems[root] = node
   204  			return nil
   205  		}
   206  
   207  		var node systemNode
   208  		node.prefix = root
   209  		node.root = m
   210  		node.nodes = make(map[string]FileSystem)
   211  		fs.systems[root] = node
   212  		return nil
   213  	}
   214  
   215  	tail := parts[1]
   216  	if node, ok := fs.systems[root]; ok {
   217  		node.nodes[tail] = m
   218  		fs.systems[root] = node
   219  		return nil
   220  	}
   221  
   222  	var node systemNode
   223  	node.prefix = root
   224  	node.nodes = make(map[string]FileSystem)
   225  	node.nodes[tail] = m
   226  	fs.systems[root] = node
   227  
   228  	return nil
   229  }
   230  
   231  // Open attempts to locate giving path from different file systems, else returning error.
   232  func (fs *SystemGroup) Open(path string) (File, error) {
   233  	if len(path) == 0 {
   234  		return nil, ErrNotExist
   235  	}
   236  
   237  	defer fs.ml.Unlock()
   238  	fs.ml.Lock()
   239  
   240  	if strings.Contains(path, "\\") {
   241  		path = filepath.ToSlash(path)
   242  	}
   243  
   244  	path = strings.TrimPrefix(path, "/")
   245  	path = strings.TrimSuffix(path, "/")
   246  
   247  	parts := strings.Split(path, "/")
   248  	if node, ok := fs.systems[parts[0]]; ok {
   249  		if len(parts) > 1 {
   250  			if subnode, ok := node.nodes[parts[1]]; ok {
   251  				return subnode.Open(filepath.Join(parts[2:]...))
   252  			}
   253  		}
   254  
   255  		if node.root != nil {
   256  			return node.root.Open(filepath.Join(parts[1:]...))
   257  		}
   258  	}
   259  
   260  	return nil, ErrNotExist
   261  }