add symlink support
This commit is contained in:
parent
657f7b4be2
commit
aee651a375
|
@ -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
24
copy.go
|
@ -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
76
vfs.go
|
@ -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
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue