1package copy
  2
  3import (
  4	"io"
  5	"io/fs"
  6	"os"
  7)
  8
  9// Options specifies optional actions on copying.
 10type Options struct {
 11
 12	// OnSymlink can specify what to do on symlink
 13	OnSymlink func(src string) SymlinkAction
 14
 15	// OnDirExists can specify what to do when there is a directory already existing in destination.
 16	OnDirExists func(src, dest string) DirExistsAction
 17
 18	// OnErr lets called decide whether or not to continue on particular copy error.
 19	OnError func(src, dest string, err error) error
 20
 21	// Skip can specify which files should be skipped
 22	Skip func(srcinfo os.FileInfo, src, dest string) (bool, error)
 23
 24	// Specials includes special files to be copied. default false.
 25	Specials bool
 26
 27	// AddPermission to every entities,
 28	// NO MORE THAN 0777
 29	// @OBSOLETE
 30	// Use `PermissionControl = AddPermission(perm)` instead
 31	AddPermission os.FileMode
 32
 33	// PermissionControl can preserve or even add permission to
 34	// every entries, for example
 35	//
 36	//		opt.PermissionControl = AddPermission(0222)
 37	//
 38	// See permission_control.go for more detail.
 39	PermissionControl PermissionControlFunc
 40
 41	// Sync file after copy.
 42	// Useful in case when file must be on the disk
 43	// (in case crash happens, for example),
 44	// at the expense of some performance penalty
 45	Sync bool
 46
 47	// Preserve the atime and the mtime of the entries.
 48	// On linux we can preserve only up to 1 millisecond accuracy.
 49	PreserveTimes bool
 50
 51	// Preserve the uid and the gid of all entries.
 52	PreserveOwner bool
 53
 54	// The byte size of the buffer to use for copying files.
 55	// If zero, the internal default buffer of 32KB is used.
 56	// See https://golang.org/pkg/io/#CopyBuffer for more information.
 57	CopyBufferSize uint
 58
 59	// If you want to add some limitation on reading src file,
 60	// you can wrap the src and provide new reader,
 61	// such as `RateLimitReader` in the test case.
 62	WrapReader func(src io.Reader) io.Reader
 63
 64	// If given, copy.Copy refers to this fs.FS instead of the OS filesystem.
 65	// e.g., You can use embed.FS to copy files from embedded filesystem.
 66	FS fs.FS
 67
 68	intent struct {
 69		src  string
 70		dest string
 71	}
 72}
 73
 74// SymlinkAction represents what to do on symlink.
 75type SymlinkAction int
 76
 77const (
 78	// Deep creates hard-copy of contents.
 79	Deep SymlinkAction = iota
 80	// Shallow creates new symlink to the dest of symlink.
 81	Shallow
 82	// Skip does nothing with symlink.
 83	Skip
 84)
 85
 86// DirExistsAction represents what to do on dest dir.
 87type DirExistsAction int
 88
 89const (
 90	// Merge preserves or overwrites existing files under the dir (default behavior).
 91	Merge DirExistsAction = iota
 92	// Replace deletes all contents under the dir and copy src files.
 93	Replace
 94	// Untouchable does nothing for the dir, and leaves it as it is.
 95	Untouchable
 96)
 97
 98// getDefaultOptions provides default options,
 99// which would be modified by usage-side.
100func getDefaultOptions(src, dest string) Options {
101	return Options{
102		OnSymlink: func(string) SymlinkAction {
103			return Shallow // Do shallow copy
104		},
105		OnDirExists:       nil,                // Default behavior is "Merge".
106		OnError:           nil,                // Default is "accept error"
107		Skip:              nil,                // Do not skip anything
108		AddPermission:     0,                  // Add nothing
109		PermissionControl: PerservePermission, // Just preserve permission
110		Sync:              false,              // Do not sync
111		Specials:          false,              // Do not copy special files
112		PreserveTimes:     false,              // Do not preserve the modification time
113		CopyBufferSize:    0,                  // Do not specify, use default bufsize (32*1024)
114		WrapReader:        nil,                // Do not wrap src files, use them as they are.
115		intent: struct {
116			src  string
117			dest string
118		}{src, dest},
119	}
120}
121
122// assureOptions struct, should be called only once.
123// All optional values MUST NOT BE nil/zero after assured.
124func assureOptions(src, dest string, opts ...Options) Options {
125	defopt := getDefaultOptions(src, dest)
126	if len(opts) == 0 {
127		return defopt
128	}
129	if opts[0].OnSymlink == nil {
130		opts[0].OnSymlink = defopt.OnSymlink
131	}
132	if opts[0].Skip == nil {
133		opts[0].Skip = defopt.Skip
134	}
135	if opts[0].AddPermission > 0 {
136		opts[0].PermissionControl = AddPermission(opts[0].AddPermission)
137	} else if opts[0].PermissionControl == nil {
138		opts[0].PermissionControl = PerservePermission
139	}
140	opts[0].intent.src = defopt.intent.src
141	opts[0].intent.dest = defopt.intent.dest
142	return opts[0]
143}