Skip to content

Commit

Permalink
Merge pull request #164 from eth-p/feat-copymethod-api
Browse files Browse the repository at this point in the history
feat: Add FileCopyMethod option / API
  • Loading branch information
otiai10 authored Sep 25, 2024
2 parents 2f93b8f + f530620 commit 49b0b59
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 39 deletions.
1 change: 1 addition & 0 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
branches: [ main, develop ]
pull_request:
branches: [ main, develop ]
workflow_dispatch:

jobs:

Expand Down
50 changes: 11 additions & 39 deletions copy.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,66 +86,38 @@ 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
}
if err = os.MkdirAll(filepath.Dir(dest), os.ModePerm); err != nil {
return
}
defer fclose(readcloser, &err)

if err = os.MkdirAll(filepath.Dir(dest), os.ModePerm); err != nil {
return
// Use FileCopyMethod to do copy.
err, skipFile := opt.FileCopyMethod.fcopy(src, dest, info, opt)
if skipFile {
return nil
}

f, err := os.Create(dest)
if err != nil {
return
return err
}
defer fclose(f, &err)

// Change file permissions.
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 {
chmodfunc(&err)
if err != nil {
return err
}

if opt.Sync {
err = f.Sync()
}

// Preserve file ownership and times.
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
Expand Down
65 changes: 65 additions & 0 deletions copy_methods.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package copy

import (
"errors"
"io"
"os"
)

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

f, err := os.Create(dest)
if err != nil {
return
}
defer fclose(f, &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()
}

return
},
}
19 changes: 19 additions & 0 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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 {
Expand All @@ -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},
Expand All @@ -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]
Expand Down

0 comments on commit 49b0b59

Please sign in to comment.