...

Source file src/github.com/shurcooL/httpfs/union/union.go

Documentation: github.com/shurcooL/httpfs/union

     1  // Package union offers a simple http.FileSystem that can unify multiple filesystems at various mount points.
     2  package union
     3  
     4  import (
     5  	"fmt"
     6  	"io"
     7  	"net/http"
     8  	"os"
     9  	"strings"
    10  	"time"
    11  )
    12  
    13  // New creates an union filesystem with the provided mapping of mount points to filesystems.
    14  //
    15  // Each mount point must be of form "/mydir". It must start with a '/', and contain a single directory name.
    16  func New(mapping map[string]http.FileSystem) http.FileSystem {
    17  	u := &unionFS{
    18  		ns: make(map[string]http.FileSystem),
    19  		root: &dirInfo{
    20  			name: "/",
    21  		},
    22  	}
    23  	for mountPoint, fs := range mapping {
    24  		u.bind(mountPoint, fs)
    25  	}
    26  	return u
    27  }
    28  
    29  type unionFS struct {
    30  	ns   map[string]http.FileSystem // Key is mount point, e.g., "/mydir".
    31  	root *dirInfo
    32  }
    33  
    34  // bind mounts fs at mountPoint.
    35  // mountPoint must be of form "/mydir". It must start with a '/', and contain a single directory name.
    36  func (u *unionFS) bind(mountPoint string, fs http.FileSystem) {
    37  	u.ns[mountPoint] = fs
    38  	u.root.entries = append(u.root.entries, &dirInfo{
    39  		name: mountPoint[1:],
    40  	})
    41  }
    42  
    43  // Open opens the named file.
    44  func (u *unionFS) Open(path string) (http.File, error) {
    45  	// TODO: Maybe clean path?
    46  	if path == "/" {
    47  		return &dir{
    48  			dirInfo: u.root,
    49  		}, nil
    50  	}
    51  	for prefix, fs := range u.ns {
    52  		if path == prefix || strings.HasPrefix(path, prefix+"/") {
    53  			innerPath := path[len(prefix):]
    54  			if innerPath == "" {
    55  				innerPath = "/"
    56  			}
    57  			return fs.Open(innerPath)
    58  		}
    59  	}
    60  	return nil, &os.PathError{Op: "open", Path: path, Err: os.ErrNotExist}
    61  }
    62  
    63  // dirInfo is a static definition of a directory.
    64  type dirInfo struct {
    65  	name    string
    66  	entries []os.FileInfo
    67  }
    68  
    69  func (d *dirInfo) Read([]byte) (int, error) {
    70  	return 0, fmt.Errorf("cannot Read from directory %s", d.name)
    71  }
    72  func (d *dirInfo) Close() error               { return nil }
    73  func (d *dirInfo) Stat() (os.FileInfo, error) { return d, nil }
    74  
    75  func (d *dirInfo) Name() string       { return d.name }
    76  func (d *dirInfo) Size() int64        { return 0 }
    77  func (d *dirInfo) Mode() os.FileMode  { return 0755 | os.ModeDir }
    78  func (d *dirInfo) ModTime() time.Time { return time.Time{} } // Actual mod time is not computed because it's expensive and rarely needed.
    79  func (d *dirInfo) IsDir() bool        { return true }
    80  func (d *dirInfo) Sys() interface{}   { return nil }
    81  
    82  // dir is an opened dir instance.
    83  type dir struct {
    84  	*dirInfo
    85  	pos int // Position within entries for Seek and Readdir.
    86  }
    87  
    88  func (d *dir) Seek(offset int64, whence int) (int64, error) {
    89  	if offset == 0 && whence == io.SeekStart {
    90  		d.pos = 0
    91  		return 0, nil
    92  	}
    93  	return 0, fmt.Errorf("unsupported Seek in directory %s", d.dirInfo.name)
    94  }
    95  
    96  func (d *dir) Readdir(count int) ([]os.FileInfo, error) {
    97  	if d.pos >= len(d.dirInfo.entries) && count > 0 {
    98  		return nil, io.EOF
    99  	}
   100  	if count <= 0 || count > len(d.dirInfo.entries)-d.pos {
   101  		count = len(d.dirInfo.entries) - d.pos
   102  	}
   103  	e := d.dirInfo.entries[d.pos : d.pos+count]
   104  	d.pos += count
   105  	return e, nil
   106  }
   107  

View as plain text