Skip to content

Commit 49b0b59

Browse files
authored
Merge pull request #164 from eth-p/feat-copymethod-api
feat: Add FileCopyMethod option / API
2 parents 2f93b8f + f530620 commit 49b0b59

File tree

4 files changed

+96
-39
lines changed

4 files changed

+96
-39
lines changed

.github/workflows/go.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ on:
55
branches: [ main, develop ]
66
pull_request:
77
branches: [ main, develop ]
8+
workflow_dispatch:
89

910
jobs:
1011

copy.go

Lines changed: 11 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -86,66 +86,38 @@ func copyNextOrSkip(src, dest string, info os.FileInfo, opt Options) error {
8686
// with considering existence of parent directory
8787
// and file permission.
8888
func fcopy(src, dest string, info os.FileInfo, opt Options) (err error) {
89-
90-
var readcloser io.ReadCloser
91-
if opt.FS != nil {
92-
readcloser, err = opt.FS.Open(src)
93-
} else {
94-
readcloser, err = os.Open(src)
95-
}
96-
if err != nil {
97-
if os.IsNotExist(err) {
98-
return nil
99-
}
89+
if err = os.MkdirAll(filepath.Dir(dest), os.ModePerm); err != nil {
10090
return
10191
}
102-
defer fclose(readcloser, &err)
10392

104-
if err = os.MkdirAll(filepath.Dir(dest), os.ModePerm); err != nil {
105-
return
93+
// Use FileCopyMethod to do copy.
94+
err, skipFile := opt.FileCopyMethod.fcopy(src, dest, info, opt)
95+
if skipFile {
96+
return nil
10697
}
10798

108-
f, err := os.Create(dest)
10999
if err != nil {
110-
return
100+
return err
111101
}
112-
defer fclose(f, &err)
113102

103+
// Change file permissions.
114104
chmodfunc, err := opt.PermissionControl(info, dest)
115105
if err != nil {
116106
return err
117107
}
118-
chmodfunc(&err)
119-
120-
var buf []byte = nil
121-
var w io.Writer = f
122-
var r io.Reader = readcloser
123-
124-
if opt.WrapReader != nil {
125-
r = opt.WrapReader(r)
126-
}
127-
128-
if opt.CopyBufferSize != 0 {
129-
buf = make([]byte, opt.CopyBufferSize)
130-
// Disable using `ReadFrom` by io.CopyBuffer.
131-
// See https://github.com/otiai10/copy/pull/60#discussion_r627320811 for more details.
132-
w = struct{ io.Writer }{f}
133-
// r = struct{ io.Reader }{s}
134-
}
135108

136-
if _, err = io.CopyBuffer(w, r, buf); err != nil {
109+
chmodfunc(&err)
110+
if err != nil {
137111
return err
138112
}
139113

140-
if opt.Sync {
141-
err = f.Sync()
142-
}
143-
114+
// Preserve file ownership and times.
144115
if opt.PreserveOwner {
145116
if err := preserveOwner(src, dest, info); err != nil {
146117
return err
147118
}
148119
}
120+
149121
if opt.PreserveTimes {
150122
if err := preserveTimes(info, dest); err != nil {
151123
return err

copy_methods.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package copy
2+
3+
import (
4+
"errors"
5+
"io"
6+
"os"
7+
)
8+
9+
// ErrUnsupportedCopyMethod is returned when the FileCopyMethod specified in
10+
// Options is not supported.
11+
var ErrUnsupportedCopyMethod = errors.New(
12+
"copy method not supported",
13+
)
14+
15+
// CopyBytes copies the file contents by reading the source file into a buffer,
16+
// then writing the buffer back to the destination file.
17+
var CopyBytes = FileCopyMethod{
18+
fcopy: func(src, dest string, info os.FileInfo, opt Options) (err error, skipFile bool) {
19+
var readcloser io.ReadCloser
20+
if opt.FS != nil {
21+
readcloser, err = opt.FS.Open(src)
22+
} else {
23+
readcloser, err = os.Open(src)
24+
}
25+
if err != nil {
26+
if os.IsNotExist(err) {
27+
return nil, true
28+
}
29+
return
30+
}
31+
defer fclose(readcloser, &err)
32+
33+
f, err := os.Create(dest)
34+
if err != nil {
35+
return
36+
}
37+
defer fclose(f, &err)
38+
39+
var buf []byte = nil
40+
var w io.Writer = f
41+
var r io.Reader = readcloser
42+
43+
if opt.WrapReader != nil {
44+
r = opt.WrapReader(r)
45+
}
46+
47+
if opt.CopyBufferSize != 0 {
48+
buf = make([]byte, opt.CopyBufferSize)
49+
// Disable using `ReadFrom` by io.CopyBuffer.
50+
// See https://github.com/otiai10/copy/pull/60#discussion_r627320811 for more details.
51+
w = struct{ io.Writer }{f}
52+
// r = struct{ io.Reader }{s}
53+
}
54+
55+
if _, err = io.CopyBuffer(w, r, buf); err != nil {
56+
return err, false
57+
}
58+
59+
if opt.Sync {
60+
err = f.Sync()
61+
}
62+
63+
return
64+
},
65+
}

options.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,16 @@ type Options struct {
2727
// RenameDestination can specify the destination file or dir name if needed to rename.
2828
RenameDestination func(src, dest string) (string, error)
2929

30+
// FileCopyMethod specifies the method by which a regular file is copied.
31+
// The default is CopyBytes.
32+
//
33+
// Available implementations:
34+
// - CopyBytes (best compatibility)
35+
//
36+
// Some implementations may not be supported on the target GOOS, or on
37+
// the user's filesystem. When these fail, an error will be returned.
38+
FileCopyMethod FileCopyMethod
39+
3040
// Specials includes special files to be copied. default false.
3141
Specials bool
3242

@@ -119,6 +129,11 @@ const (
119129
Untouchable
120130
)
121131

132+
// FileCopyMethod represents one of the ways that a regular file can be copied.
133+
type FileCopyMethod struct {
134+
fcopy func(src, dest string, info os.FileInfo, opt Options) (err error, skipFile bool)
135+
}
136+
122137
// getDefaultOptions provides default options,
123138
// which would be modified by usage-side.
124139
func getDefaultOptions(src, dest string) Options {
@@ -134,6 +149,7 @@ func getDefaultOptions(src, dest string) Options {
134149
Sync: false, // Do not sync
135150
Specials: false, // Do not copy special files
136151
PreserveTimes: false, // Do not preserve the modification time
152+
FileCopyMethod: CopyBytes, // Copy by bytes
137153
CopyBufferSize: 0, // Do not specify, use default bufsize (32*1024)
138154
WrapReader: nil, // Do not wrap src files, use them as they are.
139155
intent: intent{src, dest, nil, nil},
@@ -158,6 +174,9 @@ func assureOptions(src, dest string, opts ...Options) Options {
158174
} else if opts[0].PermissionControl == nil {
159175
opts[0].PermissionControl = PerservePermission
160176
}
177+
if opts[0].FileCopyMethod.fcopy == nil {
178+
opts[0].FileCopyMethod = defopt.FileCopyMethod
179+
}
161180
opts[0].intent.src = defopt.intent.src
162181
opts[0].intent.dest = defopt.intent.dest
163182
return opts[0]

0 commit comments

Comments
 (0)