1
0
mirror of https://github.com/ManyakRus/crud_generator.git synced 2024-12-22 00:36:41 +02:00
crud_generator/vendor/github.com/otiai10/copy/copy.go
2023-11-15 13:58:31 +03:00

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)
}