Repository: otiai10/copy
Pull request #61 (2021-05-07 07:52:39)

Allow specifying bufsize for file copy
CC and thanks to @guenhter
Close #60 


Patch:
diff --git a/.gitignore b/.gitignore
index a73927e..d65ce17 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@ test/data.copy
 coverage.txt
 vendor
 .vagrant
+.idea/
diff --git a/copy.go b/copy.go
index 6273c3d..77bf9f9 100644
--- a/copy.go
+++ b/copy.go
@@ -47,10 +47,10 @@ func switchboard(src, dest string, info os.FileInfo, opt Options) (err error) {
 	return err
 }
 
-// copy decide if this src should be copied or not.
+// 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 copy(src, dest string, info os.FileInfo, opt Options) error {
+func copyNextOrSkip(src, dest string, info os.FileInfo, opt Options) error {
 	skip, err := opt.Skip(src)
 	if err != nil {
 		return err
@@ -58,7 +58,6 @@ func copy(src, dest string, info os.FileInfo, opt Options) error {
 	if skip {
 		return nil
 	}
-
 	return switchboard(src, dest, info, opt)
 }
 
@@ -87,8 +86,18 @@ func fcopy(src, dest string, info os.FileInfo, opt Options) (err error) {
 	}
 	defer fclose(s, &err)
 
-	if _, err = io.Copy(f, s); err != nil {
-		return
+	var buf []byte = nil
+	var w io.Writer = f
+	// var r io.Reader = s
+	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, s, buf); err != nil {
+		return err
 	}
 
 	if opt.Sync {
@@ -138,7 +147,7 @@ func dcopy(srcdir, destdir string, info os.FileInfo, opt Options) (err error) {
 	for _, content := range contents {
 		cs, cd := filepath.Join(srcdir, content.Name()), filepath.Join(destdir, content.Name())
 
-		if err = copy(cs, cd, content, opt); err != nil {
+		if err = copyNextOrSkip(cs, cd, content, opt); err != nil {
 			// If any error, exit immediately
 			return
 		}
@@ -164,7 +173,7 @@ func onsymlink(src, dest string, info os.FileInfo, opt Options) error {
 		if err != nil {
 			return err
 		}
-		return copy(orig, dest, info, opt)
+		return copyNextOrSkip(orig, dest, info, opt)
 	case Skip:
 		fallthrough
 	default:
diff --git a/options.go b/options.go
index 35c9294..18e87ac 100644
--- a/options.go
+++ b/options.go
@@ -28,6 +28,11 @@ type Options struct {
 	// On linux we can preserve only up to 1 millisecond accuracy.
 	PreserveTimes bool
 
+	// The byte size of the buffer to use for copying files.
+	// If zero, the internal default buffer of 32KB is used.
+	// See https://golang.org/pkg/io/#CopyBuffer for more information.
+	CopyBufferSize uint
+
 	intent struct {
 		src  string
 		dest string
@@ -69,9 +74,10 @@ func getDefaultOptions(src, dest string) Options {
 		Skip: func(string) (bool, error) {
 			return false, nil // Don't skip
 		},
-		AddPermission: 0,     // Add nothing
-		Sync:          false, // Do not sync
-		PreserveTimes: false, // Do not preserve the modification time
+		AddPermission:  0,     // Add nothing
+		Sync:           false, // Do not sync
+		PreserveTimes:  false, // Do not preserve the modification time
+		CopyBufferSize: 0,     // Do not specify, use default bufsize (32*1024)
 		intent: struct {
 			src  string
 			dest string


Test patch:
diff --git a/all_test.go b/all_test.go
index 5359777..67f1659 100644
--- a/all_test.go
+++ b/all_test.go
@@ -25,7 +25,6 @@ func teardown(m *testing.M) {
 }
 
 func TestCopy(t *testing.T) {
-
 	err := Copy("./test/data/case00", "./test/data.copy/case00")
 	Expect(t, err).ToBe(nil)
 	info, err := os.Stat("./test/data.copy/case00/README.md")
@@ -40,6 +39,9 @@ func TestCopy(t *testing.T) {
 	When(t, "specified src is just a file", func(t *testing.T) {
 		err := Copy("test/data/case01/README.md", "test/data.copy/case01/README.md")
 		Expect(t, err).ToBe(nil)
+		content, err := ioutil.ReadFile("test/data.copy/case01/README.md")
+		Expect(t, err).ToBe(nil)
+		Expect(t, string(content)).ToBe("case01 - README.md")
 	})
 
 	When(t, "source directory includes symbolic link", func(t *testing.T) {
@@ -298,3 +300,16 @@ func TestOptions_OnDirExists(t *testing.T) {
 		Expect(t, err).ToBe(nil)
 	})
 }
+
+func TestOptions_CopyBufferSize(t *testing.T) {
+	opt := Options{
+		CopyBufferSize: 512,
+	}
+
+	err := Copy("test/data/case12", "test/data.copy/case12", opt)
+	Expect(t, err).ToBe(nil)
+
+	content, err := ioutil.ReadFile("test/data.copy/case12/README.md")
+	Expect(t, err).ToBe(nil)
+	Expect(t, string(content)).ToBe("case12 - README.md")
+}
diff --git a/test/data/case01/README.md b/test/data/case01/README.md
index e69de29..882169c 100644
--- a/test/data/case01/README.md
+++ b/test/data/case01/README.md
@@ -0,0 +1,1 @@
+case01 - README.md
\ No newline at end of file
diff --git a/test/data/case12/README.md b/test/data/case12/README.md
new file mode 100644
index 0000000..b18df51
--- /dev/null
+++ b/test/data/case12/README.md
@@ -0,0 +1,1 @@
+case12 - README.md
\ No newline at end of file