diff --git a/copy.go b/copy.go index f9787cd..22c0151 100644 --- a/copy.go +++ b/copy.go @@ -86,73 +86,8 @@ func copyNextOrSkip(src, dest string, info os.FileInfo, opt Options) error { // 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 + err, _ = opt.FileCopyMethod.fcopy(src, dest, info, opt) + return err } // dcopy is for a directory, diff --git a/copy_methods.go b/copy_methods.go new file mode 100644 index 0000000..6647b69 --- /dev/null +++ b/copy_methods.go @@ -0,0 +1,88 @@ +package copy + +import ( + "errors" + "io" + "os" + "path/filepath" +) + +// ErrUnsupportedCopyMethod is returned when the FileCopyMethod specified in +// Options is not supported. +var ErrUnsupportedCopyMethod = errors.New( + "copy method not supported", +) + +// CopyBytes copies the file contents by reading the source file into a buffer, +// then writing the buffer back to the destination file. +var CopyBytes = FileCopyMethod{ + fcopy: func(src, dest string, info os.FileInfo, opt Options) (err error, skipFile bool) { + + 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, true + } + 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, false + } + 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, false + } + + if opt.Sync { + err = f.Sync() + } + + if opt.PreserveOwner { + if err := preserveOwner(src, dest, info); err != nil { + return err, false + } + } + if opt.PreserveTimes { + if err := preserveTimes(info, dest); err != nil { + return err, false + } + } + + return + }, +} diff --git a/options.go b/options.go index c1db48c..709c7f4 100644 --- a/options.go +++ b/options.go @@ -27,6 +27,16 @@ type Options struct { // RenameDestination can specify the destination file or dir name if needed to rename. RenameDestination func(src, dest string) (string, error) + // FileCopyMethod specifies the method by which a regular file is copied. + // The default is CopyBytes. + // + // Available implementations: + // - CopyBytes (best compatibility) + // + // Some implementations may not be supported on the target GOOS, or on + // the user's filesystem. When these fail, an error will be returned. + FileCopyMethod FileCopyMethod + // Specials includes special files to be copied. default false. Specials bool @@ -119,6 +129,11 @@ const ( Untouchable ) +// FileCopyMethod represents one of the ways that a regular file can be copied. +type FileCopyMethod struct { + fcopy func(src, dest string, info os.FileInfo, opt Options) (err error, skipFile bool) +} + // getDefaultOptions provides default options, // which would be modified by usage-side. func getDefaultOptions(src, dest string) Options { @@ -134,6 +149,7 @@ func getDefaultOptions(src, dest string) Options { Sync: false, // Do not sync Specials: false, // Do not copy special files PreserveTimes: false, // Do not preserve the modification time + FileCopyMethod: CopyBytes, // Copy by bytes CopyBufferSize: 0, // Do not specify, use default bufsize (32*1024) WrapReader: nil, // Do not wrap src files, use them as they are. intent: intent{src, dest, nil, nil}, @@ -158,6 +174,9 @@ func assureOptions(src, dest string, opts ...Options) Options { } else if opts[0].PermissionControl == nil { opts[0].PermissionControl = PerservePermission } + if opts[0].FileCopyMethod.fcopy == nil { + opts[0].FileCopyMethod = defopt.FileCopyMethod + } opts[0].intent.src = defopt.intent.src opts[0].intent.dest = defopt.intent.dest return opts[0]