vfscopy/copy.go

208 lines
4.7 KiB
Go
Raw Normal View History

2020-10-23 19:53:09 +00:00
package vfscopy
import (
"io"
"os"
"path/filepath"
)
const (
// tmpPermissionForDirectory makes the destination directory writable,
// so that stuff can be copied recursively even if any original directory is NOT writable.
// See https://github.com/otiai10/copy/pull/9 for more information.
tmpPermissionForDirectory = os.FileMode(0755)
)
// Copy copies src to dest, doesn't matter if src is a directory or a file.
2020-10-23 19:55:37 +00:00
func Copy(vfs FileSystem, src, dest string, opt ...Options) error {
//info, err := fs.Lstat(src)
f, err := vfs.Open(src)
2020-10-23 19:53:09 +00:00
if err != nil {
return err
}
2020-10-23 19:55:37 +00:00
info, err := f.Stat()
if err != nil {
return err
}
return switchboard(vfs, src, dest, f, info, assure(opt...))
2020-10-23 19:53:09 +00:00
}
// switchboard switches proper copy functions regarding file type, etc...
// If there would be anything else here, add a case to this switchboard.
2020-10-23 19:55:37 +00:00
func switchboard(
vfs FileSystem, src, dest string, f File, info os.FileInfo, opt Options,
) error {
2020-10-23 19:53:09 +00:00
switch {
2020-10-23 19:55:37 +00:00
//case info.Mode()&os.ModeSymlink != 0:
// TODO
//return onsymlink(vfs, src, dest, opt)
2020-10-23 19:53:09 +00:00
case info.IsDir():
2020-10-23 19:55:37 +00:00
return dcopy(vfs, src, dest, f, info, opt)
2020-10-23 19:53:09 +00:00
default:
2020-10-23 19:55:37 +00:00
return fcopy(vfs, src, dest, f, info, opt)
2020-10-23 19:53:09 +00:00
}
}
// copy decide if this src should be copied or not.
// Because this "copy" could be called recursively,
// "info" MUST be given here, NOT nil.
2020-10-23 19:55:37 +00:00
func copy(vfs FileSystem, src, dest string, f File, info os.FileInfo, opt Options) error {
2020-10-23 19:53:09 +00:00
skip, err := opt.Skip(src)
if err != nil {
return err
}
if skip {
return nil
}
2020-10-23 19:55:37 +00:00
return switchboard(vfs, src, dest, f, info, opt)
2020-10-23 19:53:09 +00:00
}
// fcopy is for just a file,
// with considering existence of parent directory
// and file permission.
2020-10-23 19:55:37 +00:00
func fcopy(vfs FileSystem, src, dest string, f File, info os.FileInfo, opt Options) (err error) {
2020-10-23 19:53:09 +00:00
if err = os.MkdirAll(filepath.Dir(dest), os.ModePerm); err != nil {
return
}
2020-10-23 19:55:37 +00:00
df, err := os.Create(dest)
2020-10-23 19:53:09 +00:00
if err != nil {
return
}
2020-10-23 19:55:37 +00:00
defer fclose(df, &err)
2020-10-23 19:53:09 +00:00
2020-10-23 19:55:37 +00:00
if err = os.Chmod(df.Name(), info.Mode()|opt.AddPermission); err != nil {
2020-10-23 19:53:09 +00:00
return
}
2020-10-23 19:55:37 +00:00
s, err := vfs.Open(src)
2020-10-23 19:53:09 +00:00
if err != nil {
return
}
defer fclose(s, &err)
2020-10-23 19:55:37 +00:00
if _, err = io.Copy(df, s); err != nil {
2020-10-23 19:53:09 +00:00
return
}
if opt.Sync {
2020-10-23 19:55:37 +00:00
err = df.Sync()
2020-10-23 19:53:09 +00:00
}
return
}
// dcopy is for a directory,
// with scanning contents inside the directory
// and pass everything to "copy" recursively.
2020-10-23 19:55:37 +00:00
func dcopy(vfs FileSystem, srcdir, destdir string, d File, info os.FileInfo, opt Options) (err error) {
2020-10-23 19:53:09 +00:00
originalMode := info.Mode()
// Make dest dir with 0755 so that everything writable.
if err = os.MkdirAll(destdir, tmpPermissionForDirectory); err != nil {
return
}
// Recover dir mode with original one.
defer chmod(destdir, originalMode|opt.AddPermission, &err)
2020-10-23 19:55:37 +00:00
fileInfos, err := d.Readdir(-1)
2020-10-23 19:53:09 +00:00
if err != nil {
return
}
2020-10-23 19:55:37 +00:00
for _, newInfo := range fileInfos {
cs, cd := filepath.Join(
srcdir, newInfo.Name()),
filepath.Join(destdir, newInfo.Name())
2020-10-23 19:53:09 +00:00
2020-10-23 19:55:37 +00:00
f, err := vfs.Open(cs)
if nil != err {
return err
}
if err := copy(vfs, cs, cd, f, newInfo, opt); err != nil {
2020-10-23 19:53:09 +00:00
// If any error, exit immediately
2020-10-23 19:55:37 +00:00
return err
2020-10-23 19:53:09 +00:00
}
}
return
}
2020-10-23 19:55:37 +00:00
/*
func onsymlink(vfs FileSystem, src, dest string, opt Options) error {
2020-10-23 19:53:09 +00:00
switch opt.OnSymlink(src) {
case Shallow:
2020-10-23 19:55:37 +00:00
return lcopy(vfs, src, dest)
2020-10-23 19:53:09 +00:00
case Deep:
orig, err := filepath.EvalSymlinks(src)
if err != nil {
return err
}
info, err := os.Lstat(orig)
if err != nil {
return err
}
2020-10-23 19:55:37 +00:00
return copy(vfs, orig, dest, info, opt)
2020-10-23 19:53:09 +00:00
case Skip:
fallthrough
default:
return nil // do nothing
}
}
2020-10-23 19:55:37 +00:00
*/
2020-10-23 19:53:09 +00:00
// lcopy is for a symlink,
// with just creating a new symlink by replicating src symlink.
2020-10-23 19:55:37 +00:00
func lcopy(vfs FileSystem, src, dest string) error {
/*
// TODO
2020-10-23 19:53:09 +00:00
src, err := os.Readlink(src)
if err != nil {
return err
}
2020-10-23 19:55:37 +00:00
*/
2020-10-23 19:53:09 +00:00
// Create the directories on the path to the dest symlink.
2020-10-23 19:55:37 +00:00
if err := os.MkdirAll(filepath.Dir(dest), os.ModePerm); err != nil {
2020-10-23 19:53:09 +00:00
return err
}
return os.Symlink(src, dest)
}
// fclose ANYHOW closes file,
// with asiging error raised during Close,
// BUT respecting the error already reported.
2020-10-23 19:55:37 +00:00
func fclose(f File, reported *error) {
2020-10-23 19:53:09 +00:00
if err := f.Close(); *reported == nil {
*reported = err
}
}
// chmod ANYHOW changes file mode,
// with asiging error raised during Chmod,
// BUT respecting the error already reported.
func chmod(dir string, mode os.FileMode, reported *error) {
if err := os.Chmod(dir, mode); *reported == nil {
*reported = err
}
}
// assure Options struct, should be called only once.
// All optional values MUST NOT BE nil/zero after assured.
func assure(opts ...Options) Options {
if len(opts) == 0 {
return getDefaultOptions()
}
defopt := getDefaultOptions()
if opts[0].OnSymlink == nil {
opts[0].OnSymlink = defopt.OnSymlink
}
if opts[0].Skip == nil {
opts[0].Skip = defopt.Skip
}
return opts[0]
}