| package common | |
| import ( | |
| "fmt" | |
| "io" | |
| "io/fs" | |
| "log" | |
| "os" | |
| "sync" | |
| "time" | |
| ) | |
| // From https://opensource.com/article/18/6/copying-files-go | |
| func CopyFile(src string, dst string) error { | |
| source, err := os.Open(src) | |
| if err != nil { | |
| return err | |
| } | |
| defer source.Close() | |
| destination, err := os.Create(dst) | |
| if err != nil { | |
| return err | |
| } | |
| defer destination.Close() | |
| _, err = io.Copy(destination, source) | |
| return err | |
| } | |
| func Copy(opts CopyOpts) { | |
| if !opts.srcInfo.Mode().IsRegular() { | |
| log.Fatalf("%s is not a regular file", opts.src) | |
| } | |
| opModifier := "" | |
| const fallback = " (fallback)" | |
| if opts.hardlink { | |
| // hardlink this file | |
| if opts.verbose { | |
| fmt.Printf("hardlink%s %v => %v\n", opModifier, opts.src, opts.dst) | |
| } | |
| err := os.Link(opts.src, opts.dst) | |
| if err != nil { | |
| if opts.verbose { | |
| fmt.Printf("hardlink failed: %v\n", err) | |
| opModifier = fallback | |
| } | |
| // fallback to clonefile or copy | |
| } else { | |
| return | |
| } | |
| } | |
| // clone this file | |
| if opts.verbose { | |
| fmt.Printf("clonefile%s %v => %v\n", opModifier, opts.src, opts.dst) | |
| } | |
| switch supported, err := cloneFile(opts.src, opts.dst); { | |
| case !supported: | |
| if opts.verbose { | |
| fmt.Print("clonefile skipped: not supported by platform\n") | |
| } | |
| // fallback to copy | |
| case supported && err == nil: | |
| return | |
| case supported && err != nil: | |
| if opts.verbose { | |
| fmt.Printf("clonefile failed: %v\n", err) | |
| opModifier = fallback | |
| } | |
| // fallback to copy | |
| } | |
| // copy this file | |
| if opts.verbose { | |
| fmt.Printf("copy%s %v => %v\n", opModifier, opts.src, opts.dst) | |
| } | |
| err := CopyFile(opts.src, opts.dst) | |
| if err != nil { | |
| log.Fatal(err) | |
| } | |
| if opts.preserveMTime { | |
| accessTime := time.Now() | |
| err := os.Chtimes(opts.dst, accessTime, opts.srcInfo.ModTime()) | |
| if err != nil { | |
| log.Fatal(err) | |
| } | |
| } | |
| } | |
| type CopyWorker struct { | |
| queue <-chan CopyOpts | |
| } | |
| func NewCopyWorker(queue <-chan CopyOpts) *CopyWorker { | |
| return &CopyWorker{queue: queue} | |
| } | |
| func (w *CopyWorker) Run(wg *sync.WaitGroup) { | |
| defer wg.Done() | |
| for opts := range w.queue { | |
| Copy(opts) | |
| } | |
| } | |
| type CopyOpts struct { | |
| src, dst string | |
| srcInfo fs.FileInfo | |
| hardlink bool | |
| verbose bool | |
| preserveMTime bool | |
| } | |
| func NewCopyOpts(src string, dst string, srcInfo fs.FileInfo, hardlink bool, verbose bool, preserveMTime bool) CopyOpts { | |
| return CopyOpts{src: src, dst: dst, srcInfo: srcInfo, hardlink: hardlink, verbose: verbose, preserveMTime: preserveMTime} | |
| } |