add virtual filesystem support
This commit is contained in:
parent
54ae170929
commit
657f7b4be2
79
copy.go
79
copy.go
|
@ -2,7 +2,6 @@ package vfscopy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
)
|
)
|
||||||
|
@ -15,31 +14,39 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
// Copy copies src to dest, doesn't matter if src is a directory or a file.
|
// Copy copies src to dest, doesn't matter if src is a directory or a file.
|
||||||
func Copy(src, dest string, opt ...Options) error {
|
func Copy(vfs FileSystem, src, dest string, opt ...Options) error {
|
||||||
info, err := os.Lstat(src)
|
//info, err := fs.Lstat(src)
|
||||||
|
f, err := vfs.Open(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return switchboard(src, dest, info, assure(opt...))
|
info, err := f.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return switchboard(vfs, src, dest, f, info, assure(opt...))
|
||||||
}
|
}
|
||||||
|
|
||||||
// switchboard switches proper copy functions regarding file type, etc...
|
// switchboard switches proper copy functions regarding file type, etc...
|
||||||
// If there would be anything else here, add a case to this switchboard.
|
// If there would be anything else here, add a case to this switchboard.
|
||||||
func switchboard(src, dest string, info os.FileInfo, opt Options) error {
|
func switchboard(
|
||||||
|
vfs FileSystem, src, dest string, f File, info os.FileInfo, opt Options,
|
||||||
|
) error {
|
||||||
switch {
|
switch {
|
||||||
case info.Mode()&os.ModeSymlink != 0:
|
//case info.Mode()&os.ModeSymlink != 0:
|
||||||
return onsymlink(src, dest, opt)
|
// TODO
|
||||||
|
//return onsymlink(vfs, src, dest, opt)
|
||||||
case info.IsDir():
|
case info.IsDir():
|
||||||
return dcopy(src, dest, info, opt)
|
return dcopy(vfs, src, dest, f, info, opt)
|
||||||
default:
|
default:
|
||||||
return fcopy(src, dest, info, opt)
|
return fcopy(vfs, src, dest, f, info, opt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// copy decide if this src should be copied or not.
|
// copy decide if this src should be copied or not.
|
||||||
// Because this "copy" could be called recursively,
|
// Because this "copy" could be called recursively,
|
||||||
// "info" MUST be given here, NOT nil.
|
// "info" MUST be given here, NOT nil.
|
||||||
func copy(src, dest string, info os.FileInfo, opt Options) error {
|
func copy(vfs FileSystem, src, dest string, f File, info os.FileInfo, opt Options) error {
|
||||||
skip, err := opt.Skip(src)
|
skip, err := opt.Skip(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -47,40 +54,40 @@ func copy(src, dest string, info os.FileInfo, opt Options) error {
|
||||||
if skip {
|
if skip {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return switchboard(src, dest, info, opt)
|
return switchboard(vfs, src, dest, f, info, opt)
|
||||||
}
|
}
|
||||||
|
|
||||||
// fcopy is for just a file,
|
// fcopy is for just a file,
|
||||||
// with considering existence of parent directory
|
// with considering existence of parent directory
|
||||||
// and file permission.
|
// and file permission.
|
||||||
func fcopy(src, dest string, info os.FileInfo, opt Options) (err error) {
|
func fcopy(vfs FileSystem, src, dest string, f File, info os.FileInfo, opt Options) (err error) {
|
||||||
|
|
||||||
if err = os.MkdirAll(filepath.Dir(dest), os.ModePerm); err != nil {
|
if err = os.MkdirAll(filepath.Dir(dest), os.ModePerm); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err := os.Create(dest)
|
df, err := os.Create(dest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer fclose(f, &err)
|
defer fclose(df, &err)
|
||||||
|
|
||||||
if err = os.Chmod(f.Name(), info.Mode()|opt.AddPermission); err != nil {
|
if err = os.Chmod(df.Name(), info.Mode()|opt.AddPermission); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
s, err := os.Open(src)
|
s, err := vfs.Open(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer fclose(s, &err)
|
defer fclose(s, &err)
|
||||||
|
|
||||||
if _, err = io.Copy(f, s); err != nil {
|
if _, err = io.Copy(df, s); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if opt.Sync {
|
if opt.Sync {
|
||||||
err = f.Sync()
|
err = df.Sync()
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
@ -89,7 +96,7 @@ func fcopy(src, dest string, info os.FileInfo, opt Options) (err error) {
|
||||||
// dcopy is for a directory,
|
// dcopy is for a directory,
|
||||||
// with scanning contents inside the directory
|
// with scanning contents inside the directory
|
||||||
// and pass everything to "copy" recursively.
|
// and pass everything to "copy" recursively.
|
||||||
func dcopy(srcdir, destdir string, info os.FileInfo, opt Options) (err error) {
|
func dcopy(vfs FileSystem, srcdir, destdir string, d File, info os.FileInfo, opt Options) (err error) {
|
||||||
|
|
||||||
originalMode := info.Mode()
|
originalMode := info.Mode()
|
||||||
|
|
||||||
|
@ -100,28 +107,34 @@ func dcopy(srcdir, destdir string, info os.FileInfo, opt Options) (err error) {
|
||||||
// Recover dir mode with original one.
|
// Recover dir mode with original one.
|
||||||
defer chmod(destdir, originalMode|opt.AddPermission, &err)
|
defer chmod(destdir, originalMode|opt.AddPermission, &err)
|
||||||
|
|
||||||
contents, err := ioutil.ReadDir(srcdir)
|
fileInfos, err := d.Readdir(-1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, content := range contents {
|
for _, newInfo := range fileInfos {
|
||||||
cs, cd := filepath.Join(srcdir, content.Name()), filepath.Join(destdir, content.Name())
|
cs, cd := filepath.Join(
|
||||||
|
srcdir, newInfo.Name()),
|
||||||
|
filepath.Join(destdir, newInfo.Name())
|
||||||
|
|
||||||
if err = copy(cs, cd, content, opt); err != nil {
|
f, err := vfs.Open(cs)
|
||||||
|
if nil != err {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := copy(vfs, cs, cd, f, newInfo, opt); err != nil {
|
||||||
// If any error, exit immediately
|
// If any error, exit immediately
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func onsymlink(src, dest string, opt Options) error {
|
/*
|
||||||
|
func onsymlink(vfs FileSystem, src, dest string, opt Options) error {
|
||||||
switch opt.OnSymlink(src) {
|
switch opt.OnSymlink(src) {
|
||||||
case Shallow:
|
case Shallow:
|
||||||
return lcopy(src, dest)
|
return lcopy(vfs, src, dest)
|
||||||
case Deep:
|
case Deep:
|
||||||
orig, err := filepath.EvalSymlinks(src)
|
orig, err := filepath.EvalSymlinks(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -131,24 +144,28 @@ func onsymlink(src, dest string, opt Options) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return copy(orig, dest, info, opt)
|
return copy(vfs, orig, dest, 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(src, dest string) error {
|
func lcopy(vfs FileSystem, src, dest string) error {
|
||||||
|
/*
|
||||||
|
// TODO
|
||||||
src, err := os.Readlink(src)
|
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 {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,7 +175,7 @@ func lcopy(src, dest string) error {
|
||||||
// fclose ANYHOW closes file,
|
// fclose ANYHOW closes file,
|
||||||
// with asiging error raised during Close,
|
// with asiging error raised during Close,
|
||||||
// BUT respecting the error already reported.
|
// BUT respecting the error already reported.
|
||||||
func fclose(f *os.File, reported *error) {
|
func fclose(f File, reported *error) {
|
||||||
if err := f.Close(); *reported == nil {
|
if err := f.Close(); *reported == nil {
|
||||||
*reported = err
|
*reported = err
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,8 @@ func TestRecursiveCopy(t *testing.T) {
|
||||||
return Shallow
|
return Shallow
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if err := Copy( "./fixtures/src/", "/tmp/dst/", opts,); nil != err {
|
vfs := Dir("./fixtures/src/")
|
||||||
|
if err := Copy(vfs, ".", "/tmp/dst/", opts,); nil != err {
|
||||||
t.Fatalf("error: %v", err)
|
t.Fatalf("error: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Baz
|
|
@ -0,0 +1,68 @@
|
||||||
|
package vfscopy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"io"
|
||||||
|
"path/filepath"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FileSystem is copied from http.FileSystem
|
||||||
|
type FileSystem interface {
|
||||||
|
Open(name string) (File, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// File is copied from http.File
|
||||||
|
type File interface {
|
||||||
|
io.Closer
|
||||||
|
io.Reader
|
||||||
|
io.Seeker
|
||||||
|
Readdir(count int) ([]os.FileInfo, error)
|
||||||
|
Stat() (os.FileInfo, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dir is an implementation of a Virtual FileSystem
|
||||||
|
type Dir string
|
||||||
|
|
||||||
|
// mapDirOpenError maps the provided non-nil error from opening name
|
||||||
|
// to a possibly better non-nil error. In particular, it turns OS-specific errors
|
||||||
|
// about opening files in non-directories into os.ErrNotExist. See Issue 18984.
|
||||||
|
func mapDirOpenError(originalErr error, name string) error {
|
||||||
|
if os.IsNotExist(originalErr) || os.IsPermission(originalErr) {
|
||||||
|
return originalErr
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(name, string(filepath.Separator))
|
||||||
|
for i := range parts {
|
||||||
|
if parts[i] == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fi, err := os.Stat(strings.Join(parts[:i+1], string(filepath.Separator)))
|
||||||
|
if err != nil {
|
||||||
|
return originalErr
|
||||||
|
}
|
||||||
|
if !fi.IsDir() {
|
||||||
|
return os.ErrNotExist
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return originalErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open opens a file relative to a virtual filesystem
|
||||||
|
func (d Dir) Open(name string) (File, error) {
|
||||||
|
if filepath.Separator != '/' && strings.ContainsRune(name, filepath.Separator) {
|
||||||
|
return nil, errors.New("http: invalid character in file path")
|
||||||
|
}
|
||||||
|
dir := string(d)
|
||||||
|
if dir == "" {
|
||||||
|
dir = "."
|
||||||
|
}
|
||||||
|
fullName := filepath.Join(dir, filepath.FromSlash(path.Clean("/"+name)))
|
||||||
|
f, err := os.Open(fullName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, mapDirOpenError(err, fullName)
|
||||||
|
}
|
||||||
|
return f, nil
|
||||||
|
}
|
Loading…
Reference in New Issue