mirror of
https://github.com/ManyakRus/crud_generator.git
synced 2024-12-22 00:36:41 +02:00
332 lines
7.3 KiB
Go
332 lines
7.3 KiB
Go
package copy
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"io/fs"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"golang.org/x/sync/errgroup"
|
|
"golang.org/x/sync/semaphore"
|
|
)
|
|
|
|
type timespec struct {
|
|
Mtime time.Time
|
|
Atime time.Time
|
|
Ctime time.Time
|
|
}
|
|
|
|
// Copy copies src to dest, doesn't matter if src is a directory or a file.
|
|
func Copy(src, dest string, opts ...Options) error {
|
|
opt := assureOptions(src, dest, opts...)
|
|
if opt.NumOfWorkers > 1 {
|
|
opt.intent.sem = semaphore.NewWeighted(opt.NumOfWorkers)
|
|
opt.intent.ctx = context.Background()
|
|
}
|
|
if opt.FS != nil {
|
|
info, err := fs.Stat(opt.FS, src)
|
|
if err != nil {
|
|
return onError(src, dest, err, opt)
|
|
}
|
|
return switchboard(src, dest, info, opt)
|
|
}
|
|
info, err := os.Lstat(src)
|
|
if err != nil {
|
|
return onError(src, dest, err, opt)
|
|
}
|
|
return switchboard(src, dest, info, 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) (err error) {
|
|
if info.Mode()&os.ModeDevice != 0 && !opt.Specials {
|
|
return onError(src, dest, err, opt)
|
|
}
|
|
|
|
switch {
|
|
case info.Mode()&os.ModeSymlink != 0:
|
|
err = onsymlink(src, dest, opt)
|
|
case info.IsDir():
|
|
err = dcopy(src, dest, info, opt)
|
|
case info.Mode()&os.ModeNamedPipe != 0:
|
|
err = pcopy(dest, info)
|
|
default:
|
|
err = fcopy(src, dest, info, opt)
|
|
}
|
|
|
|
return onError(src, dest, err, opt)
|
|
}
|
|
|
|
// copyNextOrSkip decide if this src should be copied or not.
|
|
// Because this "copy" could be called recursively,
|
|
// "info" MUST be given here, NOT nil.
|
|
func copyNextOrSkip(src, dest string, info os.FileInfo, opt Options) error {
|
|
if opt.Skip != nil {
|
|
skip, err := opt.Skip(info, src, dest)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if skip {
|
|
return nil
|
|
}
|
|
}
|
|
return switchboard(src, dest, 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) {
|
|
|
|
var readcloser io.ReadCloser
|
|
if opt.FS != nil {
|
|
readcloser, err = opt.FS.Open(src)
|
|
} else {
|
|
readcloser, err = os.Open(src)
|
|
}
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil
|
|
}
|
|
return
|
|
}
|
|
defer fclose(readcloser, &err)
|
|
|
|
if err = os.MkdirAll(filepath.Dir(dest), os.ModePerm); err != nil {
|
|
return
|
|
}
|
|
|
|
f, err := os.Create(dest)
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer fclose(f, &err)
|
|
|
|
chmodfunc, err := opt.PermissionControl(info, dest)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
chmodfunc(&err)
|
|
|
|
var buf []byte = nil
|
|
var w io.Writer = f
|
|
var r io.Reader = readcloser
|
|
|
|
if opt.WrapReader != nil {
|
|
r = opt.WrapReader(r)
|
|
}
|
|
|
|
if opt.CopyBufferSize != 0 {
|
|
buf = make([]byte, opt.CopyBufferSize)
|
|
// Disable using `ReadFrom` by io.CopyBuffer.
|
|
// See https://github.com/otiai10/copy/pull/60#discussion_r627320811 for more details.
|
|
w = struct{ io.Writer }{f}
|
|
// r = struct{ io.Reader }{s}
|
|
}
|
|
|
|
if _, err = io.CopyBuffer(w, r, buf); err != nil {
|
|
return err
|
|
}
|
|
|
|
if opt.Sync {
|
|
err = f.Sync()
|
|
}
|
|
|
|
if opt.PreserveOwner {
|
|
if err := preserveOwner(src, dest, info); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if opt.PreserveTimes {
|
|
if err := preserveTimes(info, dest); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// 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) {
|
|
if skip, err := onDirExists(opt, srcdir, destdir); err != nil {
|
|
return err
|
|
} else if skip {
|
|
return nil
|
|
}
|
|
|
|
// Make dest dir with 0755 so that everything writable.
|
|
chmodfunc, err := opt.PermissionControl(info, destdir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer chmodfunc(&err)
|
|
|
|
var contents []os.FileInfo
|
|
if opt.FS != nil {
|
|
entries, err := fs.ReadDir(opt.FS, srcdir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, e := range entries {
|
|
info, err := e.Info()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
contents = append(contents, info)
|
|
}
|
|
} else {
|
|
contents, err = ioutil.ReadDir(srcdir)
|
|
}
|
|
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil
|
|
}
|
|
return
|
|
}
|
|
|
|
if yes, err := shouldCopyDirectoryConcurrent(opt, srcdir, destdir); err != nil {
|
|
return err
|
|
} else if yes {
|
|
if err := dcopyConcurrent(srcdir, destdir, contents, opt); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
if err := dcopySequential(srcdir, destdir, contents, opt); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if opt.PreserveTimes {
|
|
if err := preserveTimes(info, destdir); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if opt.PreserveOwner {
|
|
if err := preserveOwner(srcdir, destdir, info); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func dcopySequential(srcdir, destdir string, contents []os.FileInfo, opt Options) error {
|
|
for _, content := range contents {
|
|
cs, cd := filepath.Join(srcdir, content.Name()), filepath.Join(destdir, content.Name())
|
|
|
|
if err := copyNextOrSkip(cs, cd, content, opt); err != nil {
|
|
// If any error, exit immediately
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Copy this directory concurrently regarding semaphore of opt.intent
|
|
func dcopyConcurrent(srcdir, destdir string, contents []os.FileInfo, opt Options) error {
|
|
group, ctx := errgroup.WithContext(opt.intent.ctx)
|
|
getRoutine := func(cs, cd string, content os.FileInfo) func() error {
|
|
return func() error {
|
|
if content.IsDir() {
|
|
return copyNextOrSkip(cs, cd, content, opt)
|
|
}
|
|
if err := opt.intent.sem.Acquire(ctx, 1); err != nil {
|
|
return err
|
|
}
|
|
err := copyNextOrSkip(cs, cd, content, opt)
|
|
opt.intent.sem.Release(1)
|
|
return err
|
|
}
|
|
}
|
|
for _, content := range contents {
|
|
csd := filepath.Join(srcdir, content.Name())
|
|
cdd := filepath.Join(destdir, content.Name())
|
|
group.Go(getRoutine(csd, cdd, content))
|
|
}
|
|
return group.Wait()
|
|
}
|
|
|
|
func onDirExists(opt Options, srcdir, destdir string) (bool, error) {
|
|
_, err := os.Stat(destdir)
|
|
if err == nil && opt.OnDirExists != nil && destdir != opt.intent.dest {
|
|
switch opt.OnDirExists(srcdir, destdir) {
|
|
case Replace:
|
|
if err := os.RemoveAll(destdir); err != nil {
|
|
return false, err
|
|
}
|
|
case Untouchable:
|
|
return true, nil
|
|
} // case "Merge" is default behaviour. Go through.
|
|
} else if err != nil && !os.IsNotExist(err) {
|
|
return true, err // Unwelcome error type...!
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
func onsymlink(src, dest string, opt Options) error {
|
|
switch opt.OnSymlink(src) {
|
|
case Shallow:
|
|
if err := lcopy(src, dest); err != nil {
|
|
return err
|
|
}
|
|
if opt.PreserveTimes {
|
|
return preserveLtimes(src, dest)
|
|
}
|
|
return nil
|
|
case Deep:
|
|
orig, err := os.Readlink(src)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
info, err := os.Lstat(orig)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return copyNextOrSkip(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 {
|
|
src, err := os.Readlink(src)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
return os.Symlink(src, dest)
|
|
}
|
|
|
|
// fclose ANYHOW closes file,
|
|
// with asiging error raised during Close,
|
|
// BUT respecting the error already reported.
|
|
func fclose(f io.Closer, reported *error) {
|
|
if err := f.Close(); *reported == nil {
|
|
*reported = err
|
|
}
|
|
}
|
|
|
|
// onError lets caller to handle errors
|
|
// occured when copying a file.
|
|
func onError(src, dest string, err error, opt Options) error {
|
|
if opt.OnError == nil {
|
|
return err
|
|
}
|
|
|
|
return opt.OnError(src, dest, err)
|
|
}
|