add symlink support

This commit is contained in:
AJ ONeal 2020-10-23 14:29:09 -06:00
parent 657f7b4be2
commit aee651a375
3 changed files with 99 additions and 15 deletions

14
README.md Normal file
View File

@ -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)
}
```

24
copy.go
View File

@ -2,6 +2,7 @@ package vfscopy
import ( import (
"io" "io"
"fmt"
"os" "os"
"path/filepath" "path/filepath"
) )
@ -33,9 +34,9 @@ func switchboard(
vfs FileSystem, src, dest string, f File, info os.FileInfo, opt Options, vfs FileSystem, src, dest string, f File, info os.FileInfo, opt Options,
) error { ) error {
switch { switch {
//case info.Mode()&os.ModeSymlink != 0: case info.Mode()&os.ModeSymlink != 0:
// TODO // TODO
//return onsymlink(vfs, src, dest, opt) return onsymlink(vfs, src, dest, opt)
case info.IsDir(): case info.IsDir():
return dcopy(vfs, src, dest, f, info, opt) return dcopy(vfs, src, dest, f, info, opt)
default: default:
@ -130,39 +131,40 @@ func dcopy(vfs FileSystem, srcdir, destdir string, d File, info os.FileInfo, opt
return return
} }
/*
func onsymlink(vfs FileSystem, src, dest string, opt Options) error { func onsymlink(vfs FileSystem, src, dest string, opt Options) error {
fmt.Println("lstat happy")
switch opt.OnSymlink(src) { switch opt.OnSymlink(src) {
case Shallow: case Shallow:
return lcopy(vfs, src, dest) return lcopy(vfs, src, dest)
case Deep: case Deep:
orig, err := filepath.EvalSymlinks(src) orig, err := vfs.EvalSymlinks(src)
if err != nil { if err != nil {
return err return err
} }
info, err := os.Lstat(orig) f, err := vfs.Open(orig)
if err != nil { if err != nil {
return err 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: case Skip:
fallthrough fallthrough
default: default:
return nil // do nothing return nil // do nothing
} }
} }
*/
// lcopy is for a symlink, // lcopy is for a symlink,
// with just creating a new symlink by replicating src symlink. // with just creating a new symlink by replicating src symlink.
func lcopy(vfs FileSystem, src, dest string) error { func lcopy(vfs FileSystem, src, dest string) error {
/* src, err := vfs.Readlink(src)
// TODO
src, err := os.Readlink(src)
if err != nil { if err != nil {
return err return err
} }
*/
// Create the directories on the path to the dest symlink. // Create the directories on the path to the dest symlink.
if err := os.MkdirAll(filepath.Dir(dest), os.ModePerm); err != nil { if err := os.MkdirAll(filepath.Dir(dest), os.ModePerm); err != nil {

76
vfs.go
View File

@ -9,9 +9,16 @@ import (
"errors" "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 { type FileSystem interface {
Open(name string) (File, error) Open(name string) (File, error)
Readlink(name string) (string, error)
EvalSymlinks(name string) (string, error)
} }
// File is copied from http.File // File is copied from http.File
@ -23,6 +30,33 @@ type File interface {
Stat() (os.FileInfo, error) 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 // Dir is an implementation of a Virtual FileSystem
type Dir string type Dir string
@ -50,19 +84,53 @@ func mapDirOpenError(originalErr error, name string) error {
return originalErr return originalErr
} }
// Open opens a file relative to a virtual filesystem func (d Dir) fullName(name string) (string, error) {
func (d Dir) Open(name string) (File, error) {
if filepath.Separator != '/' && strings.ContainsRune(name, filepath.Separator) { 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) dir := string(d)
if dir == "" { if dir == "" {
dir = "." dir = "."
} }
fullName := filepath.Join(dir, filepath.FromSlash(path.Clean("/"+name))) 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) f, err := os.Open(fullName)
if err != nil { if err != nil {
return nil, mapDirOpenError(err, fullName) return nil, mapDirOpenError(err, fullName)
} }
return f, nil 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
}