diff --git a/copy.go b/copy.go index 4a51843..9327020 100644 --- a/copy.go +++ b/copy.go @@ -2,7 +2,6 @@ package vfscopy import ( "io" - "io/ioutil" "os" "path/filepath" ) @@ -15,31 +14,39 @@ const ( ) // Copy copies src to dest, doesn't matter if src is a directory or a file. -func Copy(src, dest string, opt ...Options) error { - info, err := os.Lstat(src) +func Copy(vfs FileSystem, src, dest string, opt ...Options) error { + //info, err := fs.Lstat(src) + f, err := vfs.Open(src) if err != nil { 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... // 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 { - case info.Mode()&os.ModeSymlink != 0: - return onsymlink(src, dest, opt) + //case info.Mode()&os.ModeSymlink != 0: + // TODO + //return onsymlink(vfs, src, dest, opt) case info.IsDir(): - return dcopy(src, dest, info, opt) + return dcopy(vfs, src, dest, f, info, opt) 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. // Because this "copy" could be called recursively, // "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) if err != nil { return err @@ -47,40 +54,40 @@ func copy(src, dest string, info os.FileInfo, opt Options) error { if skip { return nil } - return switchboard(src, dest, info, opt) + return switchboard(vfs, src, dest, f, info, opt) } // fcopy is for just a file, // with considering existence of parent directory // 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 { return } - f, err := os.Create(dest) + df, err := os.Create(dest) if err != nil { 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 } - s, err := os.Open(src) + s, err := vfs.Open(src) if err != nil { return } defer fclose(s, &err) - if _, err = io.Copy(f, s); err != nil { + if _, err = io.Copy(df, s); err != nil { return } if opt.Sync { - err = f.Sync() + err = df.Sync() } return @@ -89,7 +96,7 @@ func fcopy(src, dest string, info os.FileInfo, opt Options) (err error) { // dcopy is for a directory, // with scanning contents inside the directory // 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() @@ -100,28 +107,34 @@ func dcopy(srcdir, destdir string, info os.FileInfo, opt Options) (err error) { // Recover dir mode with original one. defer chmod(destdir, originalMode|opt.AddPermission, &err) - contents, err := ioutil.ReadDir(srcdir) + fileInfos, err := d.Readdir(-1) if err != nil { return } - for _, content := range contents { - cs, cd := filepath.Join(srcdir, content.Name()), filepath.Join(destdir, content.Name()) + for _, newInfo := range fileInfos { + 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 - return + return err } } return } -func onsymlink(src, dest string, opt Options) error { - +/* +func onsymlink(vfs FileSystem, src, dest string, opt Options) error { switch opt.OnSymlink(src) { case Shallow: - return lcopy(src, dest) + return lcopy(vfs, src, dest) case Deep: orig, err := filepath.EvalSymlinks(src) if err != nil { @@ -131,24 +144,28 @@ func onsymlink(src, dest string, opt Options) error { if err != nil { return err } - return copy(orig, dest, info, opt) + return copy(vfs, orig, dest, 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(src, dest string) error { +func lcopy(vfs FileSystem, src, dest string) error { + /* + // TODO src, err := os.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 { + if err := os.MkdirAll(filepath.Dir(dest), os.ModePerm); err != nil { return err } @@ -158,7 +175,7 @@ func lcopy(src, dest string) error { // fclose ANYHOW closes file, // with asiging error raised during Close, // 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 { *reported = err } diff --git a/copy_test.go b/copy_test.go index 47fca9f..4fb8e47 100644 --- a/copy_test.go +++ b/copy_test.go @@ -11,7 +11,8 @@ func TestRecursiveCopy(t *testing.T) { 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) } } diff --git a/fixtures/src/baz/baz.txt b/fixtures/src/baz/baz.txt new file mode 100644 index 0000000..a688182 --- /dev/null +++ b/fixtures/src/baz/baz.txt @@ -0,0 +1 @@ +Baz diff --git a/vfs.go b/vfs.go new file mode 100644 index 0000000..e5a6da5 --- /dev/null +++ b/vfs.go @@ -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 +}