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 (
"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 {

76
vfs.go
View File

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