...

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

Documentation: github.com/shurcooL/httpfs/filter

     1  // Package filter offers an http.FileSystem wrapper with the ability to keep or skip files.
     2  package filter
     3  
     4  import (
     5  	"fmt"
     6  	"io"
     7  	"net/http"
     8  	"os"
     9  	pathpkg "path"
    10  	"time"
    11  )
    12  
    13  // Func is a selection function which is provided two arguments,
    14  // its '/'-separated cleaned rooted absolute path (i.e., it always begins with "/"),
    15  // and the os.FileInfo of the considered file.
    16  //
    17  // The path is cleaned via pathpkg.Clean("/" + path).
    18  //
    19  // For example, if the considered file is named "a" and it's inside a directory "dir",
    20  // then the value of path will be "/dir/a".
    21  type Func func(path string, fi os.FileInfo) bool
    22  
    23  // Keep returns a filesystem that contains only those entries in source for which
    24  // keep returns true.
    25  func Keep(source http.FileSystem, keep Func) http.FileSystem {
    26  	return &filterFS{source: source, keep: keep}
    27  }
    28  
    29  // Skip returns a filesystem that contains everything in source, except entries
    30  // for which skip returns true.
    31  func Skip(source http.FileSystem, skip Func) http.FileSystem {
    32  	keep := func(path string, fi os.FileInfo) bool {
    33  		return !skip(path, fi)
    34  	}
    35  	return &filterFS{source: source, keep: keep}
    36  }
    37  
    38  type filterFS struct {
    39  	source http.FileSystem
    40  	keep   Func // Keep entries that keep returns true for.
    41  }
    42  
    43  func (fs *filterFS) Open(path string) (http.File, error) {
    44  	f, err := fs.source.Open(path)
    45  	if err != nil {
    46  		return nil, err
    47  	}
    48  
    49  	fi, err := f.Stat()
    50  	if err != nil {
    51  		f.Close()
    52  		return nil, err
    53  	}
    54  
    55  	if !fs.keep(clean(path), fi) {
    56  		// Skip.
    57  		f.Close()
    58  		return nil, &os.PathError{Op: "open", Path: path, Err: os.ErrNotExist}
    59  	}
    60  
    61  	if !fi.IsDir() {
    62  		return f, nil
    63  	}
    64  	defer f.Close()
    65  
    66  	fis, err := f.Readdir(0)
    67  	if err != nil {
    68  		return nil, err
    69  	}
    70  
    71  	var entries []os.FileInfo
    72  	for _, fi := range fis {
    73  		if !fs.keep(clean(pathpkg.Join(path, fi.Name())), fi) {
    74  			// Skip.
    75  			continue
    76  		}
    77  		entries = append(entries, fi)
    78  	}
    79  
    80  	return &dir{
    81  		name:    fi.Name(),
    82  		entries: entries,
    83  		modTime: fi.ModTime(),
    84  	}, nil
    85  }
    86  
    87  // clean turns a potentially relative path into an absolute one.
    88  //
    89  // This is needed to normalize path parameter for selection function.
    90  func clean(path string) string {
    91  	return pathpkg.Clean("/" + path)
    92  }
    93  
    94  // dir is an opened dir instance.
    95  type dir struct {
    96  	name    string
    97  	modTime time.Time
    98  	entries []os.FileInfo
    99  	pos     int // Position within entries for Seek and Readdir.
   100  }
   101  
   102  func (d *dir) Read([]byte) (int, error) {
   103  	return 0, fmt.Errorf("cannot Read from directory %s", d.name)
   104  }
   105  func (d *dir) Close() error               { return nil }
   106  func (d *dir) Stat() (os.FileInfo, error) { return d, nil }
   107  
   108  func (d *dir) Name() string       { return d.name }
   109  func (d *dir) Size() int64        { return 0 }
   110  func (d *dir) Mode() os.FileMode  { return 0755 | os.ModeDir }
   111  func (d *dir) ModTime() time.Time { return d.modTime }
   112  func (d *dir) IsDir() bool        { return true }
   113  func (d *dir) Sys() interface{}   { return nil }
   114  
   115  func (d *dir) Seek(offset int64, whence int) (int64, error) {
   116  	if offset == 0 && whence == io.SeekStart {
   117  		d.pos = 0
   118  		return 0, nil
   119  	}
   120  	return 0, fmt.Errorf("unsupported Seek in directory %s", d.name)
   121  }
   122  
   123  func (d *dir) Readdir(count int) ([]os.FileInfo, error) {
   124  	if d.pos >= len(d.entries) && count > 0 {
   125  		return nil, io.EOF
   126  	}
   127  	if count <= 0 || count > len(d.entries)-d.pos {
   128  		count = len(d.entries) - d.pos
   129  	}
   130  	e := d.entries[d.pos : d.pos+count]
   131  	d.pos += count
   132  	return e, nil
   133  }
   134  

View as plain text