diff --git a/README.md b/README.md new file mode 100644 index 0000000..f81bfa9 --- /dev/null +++ b/README.md @@ -0,0 +1,14 @@ +# vfscopy + +Copy a Virtual FileSystem, such as +[http.FileSystem](https://golang.org/pkg/net/http/#FileSystem), +recursively to a native file system destination. + +```go +httpfs := http.Dir("/tmp/public/") +vfs := vfscopy.NewVFS(httpfs) + +if err := Copy(vfs, ".", "/tmp/dst/"); nil != err { + fmt.Fprintf(os.Stderr, "couldn't copy vfs: %v\n", err) +} +``` diff --git a/copy.go b/copy.go index 9327020..b35c106 100644 --- a/copy.go +++ b/copy.go @@ -2,6 +2,7 @@ package vfscopy import ( "io" + "fmt" "os" "path/filepath" ) @@ -33,9 +34,9 @@ func switchboard( vfs FileSystem, src, dest string, f File, info os.FileInfo, opt Options, ) error { switch { - //case info.Mode()&os.ModeSymlink != 0: + case info.Mode()&os.ModeSymlink != 0: // TODO - //return onsymlink(vfs, src, dest, opt) + return onsymlink(vfs, src, dest, opt) case info.IsDir(): return dcopy(vfs, src, dest, f, info, opt) default: @@ -130,39 +131,40 @@ func dcopy(vfs FileSystem, srcdir, destdir string, d File, info os.FileInfo, opt return } -/* func onsymlink(vfs FileSystem, src, dest string, opt Options) error { + fmt.Println("lstat happy") switch opt.OnSymlink(src) { case Shallow: return lcopy(vfs, src, dest) case Deep: - orig, err := filepath.EvalSymlinks(src) + orig, err := vfs.EvalSymlinks(src) if err != nil { return err } - info, err := os.Lstat(orig) + f, err := vfs.Open(orig) if err != nil { return err } - return copy(vfs, orig, dest, info, opt) + //info, err := os.Lstat(orig) + info, err := f.Stat() + if err != nil { + return err + } + return copy(vfs, orig, dest, f, info, opt) case Skip: fallthrough default: return nil // do nothing } } -*/ // lcopy is for a symlink, // with just creating a new symlink by replicating src symlink. func lcopy(vfs FileSystem, src, dest string) error { - /* - // TODO - src, err := os.Readlink(src) + src, err := vfs.Readlink(src) if err != nil { return err } - */ // Create the directories on the path to the dest symlink. if err := os.MkdirAll(filepath.Dir(dest), os.ModePerm); err != nil { diff --git a/vfs.go b/vfs.go index e5a6da5..51c0112 100644 --- a/vfs.go +++ b/vfs.go @@ -9,9 +9,16 @@ import ( "errors" ) -// FileSystem is copied from http.FileSystem +// HTTPFileSystem is copied from http.FileSystem +type HTTPFileSystem interface { + Open(name string) (File, error) +} + +// FileSystem is a Virtual FileSystem with Symlink support type FileSystem interface { Open(name string) (File, error) + Readlink(name string) (string, error) + EvalSymlinks(name string) (string, error) } // File is copied from http.File @@ -23,6 +30,33 @@ type File interface { Stat() (os.FileInfo, error) } +// VFS is a virtual FileSystem with possible Symlink support +type VFS struct { + FileSystem HTTPFileSystem +} + +// Open opens a file relative to a virtual filesystem +func (v *VFS) Open(name string) (File, error) { + return v.Open(name) +} + +// Readlink returns a "not implemented" error, +// which is okay because it is never called for http.FileSystem. +func (v *VFS) Readlink(name string) (string, error) { + return "", errors.New("not implemented") +} + +// EvalSymlinks returns the original name, +// which is okay because it is never called for http.FileSystem. +func (v *VFS) EvalSymlinks(name string) (string, error) { + return name, nil +} + +// NewVFS gives an http.FileSystem faux symlink support +func NewVFS(httpfs HTTPFileSystem) FileSystem { + return &VFS{ FileSystem: httpfs } +} + // Dir is an implementation of a Virtual FileSystem type Dir string @@ -50,19 +84,53 @@ func mapDirOpenError(originalErr error, name string) error { return originalErr } -// Open opens a file relative to a virtual filesystem -func (d Dir) Open(name string) (File, error) { +func (d Dir) fullName(name string) (string, error) { if filepath.Separator != '/' && strings.ContainsRune(name, filepath.Separator) { - return nil, errors.New("http: invalid character in file path") + return "", errors.New("http: invalid character in file path") } dir := string(d) if dir == "" { dir = "." } fullName := filepath.Join(dir, filepath.FromSlash(path.Clean("/"+name))) + return fullName, nil +} + +// Open opens a file relative to a virtual filesystem +func (d Dir) Open(name string) (File, error) { + fullName, err := d.fullName(name) + if nil != err { + return nil, err + } f, err := os.Open(fullName) if err != nil { return nil, mapDirOpenError(err, fullName) } return f, nil } + +// Readlink returns the destination of the named symbolic link. +func (d Dir) Readlink(name string) (string, error) { + name, err := d.fullName(name) + if nil != err { + return "", err + } + name, err = os.Readlink(name) + if err != nil { + return "", mapDirOpenError(err, name) + } + return name, nil +} + +// EvalSymlinks returns the destination of the named symbolic link. +func (d Dir) EvalSymlinks(name string) (string, error) { + name, err := d.fullName(name) + if nil != err { + return "", err + } + name, err = filepath.EvalSymlinks(name) + if err != nil { + return "", mapDirOpenError(err, name) + } + return name, nil +}