Skip to content

Commit

Permalink
feat: Add FileCopyMethod option / API
Browse files Browse the repository at this point in the history
This will be used so we can have multiple implementations for copying
a regular file. Currently, there is just CopyBytes, which is the old
fcopy code.

The FileCopyMethod struct is a struct with an un-exported member.
This is done to make the type opaque so the user can't make their
own copy function. There are still design questions left about
passing around the `Options` struct and `os.FileInfo`, so I opted
to do this so that can be figured out later.

The type can be made into `type FileCopyMethod func(...)` later,
and it won't be a breaking change. Or, instead, a
`UserCopy(func (src, dest string) error) FileCopyMethod`
function can be added.
  • Loading branch information
eth-p committed Aug 24, 2024
1 parent 2f93b8f commit 7e68063
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 67 deletions.
68 changes: 1 addition & 67 deletions copy.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,73 +86,7 @@ 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
return opt.FileCopyFunc.fcopy(src, dest, info, opt)
}

// dcopy is for a directory,
Expand Down
88 changes: 88 additions & 0 deletions copy_methods.go
Original file line number Diff line number Diff line change
@@ -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) {

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
},
}
13 changes: 13 additions & 0 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ type Options struct {
// RenameDestination can specify the destination file or dir name if needed to rename.
RenameDestination func(src, dest string) (string, error)

// FileCopyFunc specifies the method by which a regular file is copied.
// The default is CopyBytes.
FileCopyFunc FileCopyMethod

// Specials includes special files to be copied. default false.
Specials bool

Expand Down Expand Up @@ -119,6 +123,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)
}

// getDefaultOptions provides default options,
// which would be modified by usage-side.
func getDefaultOptions(src, dest string) Options {
Expand All @@ -134,6 +143,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
FileCopyFunc: 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},
Expand All @@ -158,6 +168,9 @@ func assureOptions(src, dest string, opts ...Options) Options {
} else if opts[0].PermissionControl == nil {
opts[0].PermissionControl = PerservePermission
}
if opts[0].FileCopyFunc.fcopy == nil {
opts[0].FileCopyFunc = defopt.FileCopyFunc
}
opts[0].intent.src = defopt.intent.src
opts[0].intent.dest = defopt.intent.dest
return opts[0]
Expand Down

0 comments on commit 7e68063

Please sign in to comment.