Initial QSfera import
This commit is contained in:
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2015 Dmitri Shuralyov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
// Package filter offers an http.FileSystem wrapper with the ability to keep or skip files.
|
||||
package filter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
pathpkg "path"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Func is a selection function which is provided two arguments,
|
||||
// its '/'-separated cleaned rooted absolute path (i.e., it always begins with "/"),
|
||||
// and the os.FileInfo of the considered file.
|
||||
//
|
||||
// The path is cleaned via pathpkg.Clean("/" + path).
|
||||
//
|
||||
// For example, if the considered file is named "a" and it's inside a directory "dir",
|
||||
// then the value of path will be "/dir/a".
|
||||
type Func func(path string, fi os.FileInfo) bool
|
||||
|
||||
// Keep returns a filesystem that contains only those entries in source for which
|
||||
// keep returns true.
|
||||
func Keep(source http.FileSystem, keep Func) http.FileSystem {
|
||||
return &filterFS{source: source, keep: keep}
|
||||
}
|
||||
|
||||
// Skip returns a filesystem that contains everything in source, except entries
|
||||
// for which skip returns true.
|
||||
func Skip(source http.FileSystem, skip Func) http.FileSystem {
|
||||
keep := func(path string, fi os.FileInfo) bool {
|
||||
return !skip(path, fi)
|
||||
}
|
||||
return &filterFS{source: source, keep: keep}
|
||||
}
|
||||
|
||||
type filterFS struct {
|
||||
source http.FileSystem
|
||||
keep Func // Keep entries that keep returns true for.
|
||||
}
|
||||
|
||||
func (fs *filterFS) Open(path string) (http.File, error) {
|
||||
f, err := fs.source.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fi, err := f.Stat()
|
||||
if err != nil {
|
||||
f.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !fs.keep(clean(path), fi) {
|
||||
// Skip.
|
||||
f.Close()
|
||||
return nil, &os.PathError{Op: "open", Path: path, Err: os.ErrNotExist}
|
||||
}
|
||||
|
||||
if !fi.IsDir() {
|
||||
return f, nil
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
fis, err := f.Readdir(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var entries []os.FileInfo
|
||||
for _, fi := range fis {
|
||||
if !fs.keep(clean(pathpkg.Join(path, fi.Name())), fi) {
|
||||
// Skip.
|
||||
continue
|
||||
}
|
||||
entries = append(entries, fi)
|
||||
}
|
||||
|
||||
return &dir{
|
||||
name: fi.Name(),
|
||||
entries: entries,
|
||||
modTime: fi.ModTime(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// clean turns a potentially relative path into an absolute one.
|
||||
//
|
||||
// This is needed to normalize path parameter for selection function.
|
||||
func clean(path string) string {
|
||||
return pathpkg.Clean("/" + path)
|
||||
}
|
||||
|
||||
// dir is an opened dir instance.
|
||||
type dir struct {
|
||||
name string
|
||||
modTime time.Time
|
||||
entries []os.FileInfo
|
||||
pos int // Position within entries for Seek and Readdir.
|
||||
}
|
||||
|
||||
func (d *dir) Read([]byte) (int, error) {
|
||||
return 0, fmt.Errorf("cannot Read from directory %s", d.name)
|
||||
}
|
||||
func (d *dir) Close() error { return nil }
|
||||
func (d *dir) Stat() (os.FileInfo, error) { return d, nil }
|
||||
|
||||
func (d *dir) Name() string { return d.name }
|
||||
func (d *dir) Size() int64 { return 0 }
|
||||
func (d *dir) Mode() os.FileMode { return 0755 | os.ModeDir }
|
||||
func (d *dir) ModTime() time.Time { return d.modTime }
|
||||
func (d *dir) IsDir() bool { return true }
|
||||
func (d *dir) Sys() interface{} { return nil }
|
||||
|
||||
func (d *dir) Seek(offset int64, whence int) (int64, error) {
|
||||
if offset == 0 && whence == io.SeekStart {
|
||||
d.pos = 0
|
||||
return 0, nil
|
||||
}
|
||||
return 0, fmt.Errorf("unsupported Seek in directory %s", d.name)
|
||||
}
|
||||
|
||||
func (d *dir) Readdir(count int) ([]os.FileInfo, error) {
|
||||
if d.pos >= len(d.entries) && count > 0 {
|
||||
return nil, io.EOF
|
||||
}
|
||||
if count <= 0 || count > len(d.entries)-d.pos {
|
||||
count = len(d.entries) - d.pos
|
||||
}
|
||||
e := d.entries[d.pos : d.pos+count]
|
||||
d.pos += count
|
||||
return e, nil
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package filter
|
||||
|
||||
import (
|
||||
"os"
|
||||
pathpkg "path"
|
||||
)
|
||||
|
||||
// FilesWithExtensions returns a filter func that selects files (but not directories)
|
||||
// that have any of the given extensions. For example:
|
||||
//
|
||||
// filter.FilesWithExtensions(".go", ".html")
|
||||
//
|
||||
// Would select both .go and .html files. It would not select any directories.
|
||||
func FilesWithExtensions(exts ...string) Func {
|
||||
return func(path string, fi os.FileInfo) bool {
|
||||
if fi.IsDir() {
|
||||
return false
|
||||
}
|
||||
for _, ext := range exts {
|
||||
if pathpkg.Ext(path) == ext {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
// Package union offers a simple http.FileSystem that can unify multiple filesystems at various mount points.
|
||||
package union
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// New creates an union filesystem with the provided mapping of mount points to filesystems.
|
||||
//
|
||||
// Each mount point must be of form "/mydir". It must start with a '/', and contain a single directory name.
|
||||
func New(mapping map[string]http.FileSystem) http.FileSystem {
|
||||
u := &unionFS{
|
||||
ns: make(map[string]http.FileSystem),
|
||||
root: &dirInfo{
|
||||
name: "/",
|
||||
},
|
||||
}
|
||||
for mountPoint, fs := range mapping {
|
||||
u.bind(mountPoint, fs)
|
||||
}
|
||||
return u
|
||||
}
|
||||
|
||||
type unionFS struct {
|
||||
ns map[string]http.FileSystem // Key is mount point, e.g., "/mydir".
|
||||
root *dirInfo
|
||||
}
|
||||
|
||||
// bind mounts fs at mountPoint.
|
||||
// mountPoint must be of form "/mydir". It must start with a '/', and contain a single directory name.
|
||||
func (u *unionFS) bind(mountPoint string, fs http.FileSystem) {
|
||||
u.ns[mountPoint] = fs
|
||||
u.root.entries = append(u.root.entries, &dirInfo{
|
||||
name: mountPoint[1:],
|
||||
})
|
||||
}
|
||||
|
||||
// Open opens the named file.
|
||||
func (u *unionFS) Open(path string) (http.File, error) {
|
||||
// TODO: Maybe clean path?
|
||||
if path == "/" {
|
||||
return &dir{
|
||||
dirInfo: u.root,
|
||||
}, nil
|
||||
}
|
||||
for prefix, fs := range u.ns {
|
||||
if path == prefix || strings.HasPrefix(path, prefix+"/") {
|
||||
innerPath := path[len(prefix):]
|
||||
if innerPath == "" {
|
||||
innerPath = "/"
|
||||
}
|
||||
return fs.Open(innerPath)
|
||||
}
|
||||
}
|
||||
return nil, &os.PathError{Op: "open", Path: path, Err: os.ErrNotExist}
|
||||
}
|
||||
|
||||
// dirInfo is a static definition of a directory.
|
||||
type dirInfo struct {
|
||||
name string
|
||||
entries []os.FileInfo
|
||||
}
|
||||
|
||||
func (d *dirInfo) Read([]byte) (int, error) {
|
||||
return 0, fmt.Errorf("cannot Read from directory %s", d.name)
|
||||
}
|
||||
func (d *dirInfo) Close() error { return nil }
|
||||
func (d *dirInfo) Stat() (os.FileInfo, error) { return d, nil }
|
||||
|
||||
func (d *dirInfo) Name() string { return d.name }
|
||||
func (d *dirInfo) Size() int64 { return 0 }
|
||||
func (d *dirInfo) Mode() os.FileMode { return 0755 | os.ModeDir }
|
||||
func (d *dirInfo) ModTime() time.Time { return time.Time{} } // Actual mod time is not computed because it's expensive and rarely needed.
|
||||
func (d *dirInfo) IsDir() bool { return true }
|
||||
func (d *dirInfo) Sys() interface{} { return nil }
|
||||
|
||||
// dir is an opened dir instance.
|
||||
type dir struct {
|
||||
*dirInfo
|
||||
pos int // Position within entries for Seek and Readdir.
|
||||
}
|
||||
|
||||
func (d *dir) Seek(offset int64, whence int) (int64, error) {
|
||||
if offset == 0 && whence == io.SeekStart {
|
||||
d.pos = 0
|
||||
return 0, nil
|
||||
}
|
||||
return 0, fmt.Errorf("unsupported Seek in directory %s", d.dirInfo.name)
|
||||
}
|
||||
|
||||
func (d *dir) Readdir(count int) ([]os.FileInfo, error) {
|
||||
if d.pos >= len(d.dirInfo.entries) && count > 0 {
|
||||
return nil, io.EOF
|
||||
}
|
||||
if count <= 0 || count > len(d.dirInfo.entries)-d.pos {
|
||||
count = len(d.dirInfo.entries) - d.pos
|
||||
}
|
||||
e := d.dirInfo.entries[d.pos : d.pos+count]
|
||||
d.pos += count
|
||||
return e, nil
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package vfsutil
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
// File implements http.FileSystem using the native file system restricted to a
|
||||
// specific file served at root.
|
||||
//
|
||||
// While the FileSystem.Open method takes '/'-separated paths, a File's string
|
||||
// value is a filename on the native file system, not a URL, so it is separated
|
||||
// by filepath.Separator, which isn't necessarily '/'.
|
||||
type File string
|
||||
|
||||
func (f File) Open(name string) (http.File, error) {
|
||||
if name != "/" {
|
||||
return nil, &os.PathError{Op: "open", Path: name, Err: os.ErrNotExist}
|
||||
}
|
||||
return os.Open(string(f))
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
// Package vfsutil implements some I/O utility functions for http.FileSystem.
|
||||
package vfsutil
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
// ReadDir reads the contents of the directory associated with file and
|
||||
// returns a slice of FileInfo values in directory order.
|
||||
func ReadDir(fs http.FileSystem, name string) ([]os.FileInfo, error) {
|
||||
f, err := fs.Open(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
return f.Readdir(0)
|
||||
}
|
||||
|
||||
// Stat returns the FileInfo structure describing file.
|
||||
func Stat(fs http.FileSystem, name string) (os.FileInfo, error) {
|
||||
f, err := fs.Open(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
return f.Stat()
|
||||
}
|
||||
|
||||
// ReadFile reads the file named by path from fs and returns the contents.
|
||||
func ReadFile(fs http.FileSystem, path string) ([]byte, error) {
|
||||
rc, err := fs.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rc.Close()
|
||||
return io.ReadAll(rc)
|
||||
}
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
package vfsutil
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
pathpkg "path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// Walk walks the filesystem rooted at root, calling walkFn for each file or
|
||||
// directory in the filesystem, including root. All errors that arise visiting files
|
||||
// and directories are filtered by walkFn. The files are walked in lexical
|
||||
// order.
|
||||
func Walk(fs http.FileSystem, root string, walkFn filepath.WalkFunc) error {
|
||||
info, err := Stat(fs, root)
|
||||
if err != nil {
|
||||
return walkFn(root, nil, err)
|
||||
}
|
||||
return walk(fs, root, info, walkFn)
|
||||
}
|
||||
|
||||
// readDirNames reads the directory named by dirname and returns
|
||||
// a sorted list of directory entries.
|
||||
func readDirNames(fs http.FileSystem, dirname string) ([]string, error) {
|
||||
fis, err := ReadDir(fs, dirname)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
names := make([]string, len(fis))
|
||||
for i := range fis {
|
||||
names[i] = fis[i].Name()
|
||||
}
|
||||
sort.Strings(names)
|
||||
return names, nil
|
||||
}
|
||||
|
||||
// walk recursively descends path, calling walkFn.
|
||||
func walk(fs http.FileSystem, path string, info os.FileInfo, walkFn filepath.WalkFunc) error {
|
||||
err := walkFn(path, info, nil)
|
||||
if err != nil {
|
||||
if info.IsDir() && err == filepath.SkipDir {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if !info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
names, err := readDirNames(fs, path)
|
||||
if err != nil {
|
||||
return walkFn(path, info, err)
|
||||
}
|
||||
|
||||
for _, name := range names {
|
||||
filename := pathpkg.Join(path, name)
|
||||
fileInfo, err := Stat(fs, filename)
|
||||
if err != nil {
|
||||
if err := walkFn(filename, fileInfo, err); err != nil && err != filepath.SkipDir {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
err = walk(fs, filename, fileInfo, walkFn)
|
||||
if err != nil {
|
||||
if !fileInfo.IsDir() || err != filepath.SkipDir {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WalkFilesFunc is the type of the function called for each file or directory visited by WalkFiles.
|
||||
// It's like filepath.WalkFunc, except it provides an additional ReadSeeker parameter for file being visited.
|
||||
type WalkFilesFunc func(path string, info os.FileInfo, rs io.ReadSeeker, err error) error
|
||||
|
||||
// WalkFiles walks the filesystem rooted at root, calling walkFn for each file or
|
||||
// directory in the filesystem, including root. In addition to FileInfo, it passes an
|
||||
// ReadSeeker to walkFn for each file it visits.
|
||||
func WalkFiles(fs http.FileSystem, root string, walkFn WalkFilesFunc) error {
|
||||
file, info, err := openStat(fs, root)
|
||||
if err != nil {
|
||||
return walkFn(root, nil, nil, err)
|
||||
}
|
||||
return walkFiles(fs, root, info, file, walkFn)
|
||||
}
|
||||
|
||||
// walkFiles recursively descends path, calling walkFn.
|
||||
// It closes the input file after it's done with it, so the caller shouldn't.
|
||||
func walkFiles(fs http.FileSystem, path string, info os.FileInfo, file http.File, walkFn WalkFilesFunc) error {
|
||||
err := walkFn(path, info, file, nil)
|
||||
file.Close()
|
||||
if err != nil {
|
||||
if info.IsDir() && err == filepath.SkipDir {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if !info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
names, err := readDirNames(fs, path)
|
||||
if err != nil {
|
||||
return walkFn(path, info, nil, err)
|
||||
}
|
||||
|
||||
for _, name := range names {
|
||||
filename := pathpkg.Join(path, name)
|
||||
file, fileInfo, err := openStat(fs, filename)
|
||||
if err != nil {
|
||||
if err := walkFn(filename, nil, nil, err); err != nil && err != filepath.SkipDir {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
err = walkFiles(fs, filename, fileInfo, file, walkFn)
|
||||
// file is closed by walkFiles, so we don't need to close it here.
|
||||
if err != nil {
|
||||
if !fileInfo.IsDir() || err != filepath.SkipDir {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// openStat performs Open and Stat and returns results, or first error encountered.
|
||||
// The caller is responsible for closing the returned file when done.
|
||||
func openStat(fs http.FileSystem, name string) (http.File, os.FileInfo, error) {
|
||||
f, err := fs.Open(name)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
fi, err := f.Stat()
|
||||
if err != nil {
|
||||
f.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
return f, fi, nil
|
||||
}
|
||||
Reference in New Issue
Block a user