diff options
| author | Rose Hogenson <rosehogenson@posteo.net> | 2025-04-11 10:22:36 -0700 |
|---|---|---|
| committer | Rose Hogenson <rosehogenson@posteo.net> | 2025-04-11 10:22:36 -0700 |
| commit | aa52e78484b6ce69765f4c4adff27321b99b2251 (patch) | |
| tree | a4d091be04727284b2fc376ffbdcdd03d00d415d /internal/cp/cp.go | |
| parent | License with GPL3 (diff) | |
| download | ccp-aa52e78484b6ce69765f4c4adff27321b99b2251.tar.zst | |
Fix concurrency
Diffstat (limited to 'internal/cp/cp.go')
| -rw-r--r-- | internal/cp/cp.go | 218 |
1 files changed, 124 insertions, 94 deletions
diff --git a/internal/cp/cp.go b/internal/cp/cp.go index d944f9c..7350343 100644 --- a/internal/cp/cp.go +++ b/internal/cp/cp.go @@ -6,7 +6,8 @@ import ( "io" "io/fs" "path" - "sync" + "slices" + "strings" "gitlab.com/rhogenson/ccp/internal/wfs" "gitlab.com/rhogenson/ccp/internal/wfs/sftpfs" @@ -31,10 +32,54 @@ func (p FSPath) String() string { return p.Path } +func (p FSPath) WalkDir(fn fs.WalkDirFunc) error { + return fs.WalkDir(p.FS, p.Path, fn) +} + +func (p FSPath) Stat() (fs.FileInfo, error) { + return fs.Stat(p.FS, p.Path) +} + +func (p FSPath) Lstat() (fs.FileInfo, error) { + return wfs.Lstat(p.FS, p.Path) +} + +func (p FSPath) RemoveAll() error { + return wfs.RemoveAll(p.FS, p.Path) +} + +func (p FSPath) Open() (fs.File, error) { + return p.FS.Open(p.Path) +} + +func (p FSPath) Create(mode fs.FileMode) (io.WriteCloser, error) { + return p.FS.Create(p.Path, mode) +} + +func (p FSPath) ReadLink() (string, error) { + return wfs.ReadLink(p.FS, p.Path) +} + +func (p FSPath) SymlinkFrom(target string) error { + return p.FS.Symlink(target, p.Path) +} + +func (p FSPath) Mkdir() error { + return p.FS.Mkdir(p.Path) +} + +func (p FSPath) MkdirMode(mode fs.FileMode) error { + return wfs.MkdirMode(p.FS, p.Path, mode) +} + +func (p FSPath) Chmod(mode fs.FileMode) error { + return p.FS.Chmod(p.Path, mode) +} + func size(srcs []FSPath) int64 { var n int64 = 0 for _, src := range srcs { - fs.WalkDir(src.FS, src.Path, func(_ string, d fs.DirEntry, err error) error { + src.WalkDir(func(_ string, d fs.DirEntry, err error) error { if err != nil { return nil } @@ -54,31 +99,21 @@ func size(srcs []FSPath) int64 { return n } -func fileExists(path FSPath) bool { - _, err := wfs.Lstat(path.FS, path.Path) +func (p FSPath) exists() bool { + _, err := p.Lstat() return !errors.Is(err, fs.ErrNotExist) } type copier struct { - p Progress - sem chan struct{} - + p Progress force bool } -func (c *copier) g(fn func()) { - c.sem <- struct{}{} - go func() { - defer func() { <-c.sem }() - fn() - }() -} - func (c *copier) openWithRetry(path FSPath, fn func() error) error { - if err := fn(); err == nil || !c.force || !fileExists(path) { + if err := fn(); err == nil || !c.force || !path.exists() { return err } - if err := wfs.RemoveAll(path.FS, path.Path); err != nil { + if err := path.RemoveAll(); err != nil { return err } return fn() @@ -87,7 +122,7 @@ func (c *copier) openWithRetry(path FSPath, fn func() error) error { func (c *copier) copyRegularFile(src, dst FSPath) error { c.p.FileStart(src.String(), dst.String()) - in, err := src.FS.Open(src.Path) + in, err := src.Open() if err != nil { return err } @@ -99,7 +134,7 @@ func (c *copier) copyRegularFile(src, dst FSPath) error { var out io.WriteCloser if err := c.openWithRetry(dst, func() error { var err error - out, err = dst.FS.Create(dst.Path, stat.Mode().Perm()) + out, err = dst.Create(stat.Mode().Perm()) return err }); err != nil { return err @@ -125,83 +160,31 @@ func (c *copier) copyRegularFile(src, dst FSPath) error { return nil } -func (c *copier) copySymlink(src FSPath, d fs.DirEntry, dst FSPath) error { - target, err := wfs.ReadLink(src.FS, src.Path) - if err != nil { - return err - } - if err := c.openWithRetry(dst, func() error { - return dst.FS.Symlink(target, dst.Path) - }); err != nil { - return err - } - c.p.Progress(1) - return nil -} - -func (c *copier) copyDir(src FSPath, d fs.DirEntry, dst FSPath) error { - stat, err := d.Info() +func (c *copier) copySymlink(src FSPath, dst FSPath) error { + target, err := src.ReadLink() if err != nil { return err } - hasWritePerm := stat.Mode()&0300 == 0300 if err := c.openWithRetry(dst, func() error { - if hasWritePerm { - return wfs.MkdirMode(dst.FS, dst.Path, stat.Mode().Perm()) - } else { - return dst.FS.Mkdir(dst.Path) - } + return dst.SymlinkFrom(target) }); err != nil { return err } - entries, err := fs.ReadDir(src.FS, src.Path) - wg := new(sync.WaitGroup) - <-c.sem - for _, entry := range entries { - wg.Add(1) - c.g(func() { - defer wg.Done() - src := FSPath{src.FS, path.Join(src.Path, entry.Name())} - if err := c.copyFile(src, entry, FSPath{dst.FS, path.Join(dst.Path, entry.Name())}); err != nil { - c.p.FileDone(src.String(), err) - } - }) - } - wg.Wait() - c.sem <- struct{}{} - if err != nil { - return err - } - if !hasWritePerm { - if err := dst.FS.Chmod(dst.Path, stat.Mode().Perm()); err != nil { - return err - } - } c.p.Progress(1) return nil } -func (c *copier) copyFile(src FSPath, d fs.DirEntry, dst FSPath) error { - switch d.Type() { - case 0: // regular file - return c.copyRegularFile(src, dst) - case fs.ModeDir: - return c.copyDir(src, d, dst) - case fs.ModeSymlink: - return c.copySymlink(src, d, dst) - default: - return fmt.Errorf("%s: unknown file type %s", src, d.Type()) - } -} - func Copy(progress Progress, srcs []FSPath, dstRoot FSPath, force bool) { + done := make(chan struct{}) go func() { + defer close(done) progress.Max(size(srcs)) }() + defer func() { <-done }() dstIsDir := true if len(srcs) == 1 { - stat, err := fs.Stat(dstRoot.FS, dstRoot.Path) + stat, err := dstRoot.Stat() dstIsDir = err == nil && stat.IsDir() } @@ -210,28 +193,75 @@ func Copy(progress Progress, srcs []FSPath, dstRoot FSPath, force bool) { sem := make(chan struct{}, maxConcurrency) c := &copier{ p: progress, - sem: sem, force: force, } - wg := new(sync.WaitGroup) + type roDir struct { + path FSPath + mode fs.FileMode + } + var roDirs []roDir for _, srcRoot := range srcs { - wg.Add(1) - c.g(func() { - defer wg.Done() - dstRoot := dstRoot - if dstIsDir { - dstRoot.Path = path.Join(dstRoot.Path, path.Base(srcRoot.Path)) - } - stat, err := fs.Stat(srcRoot.FS, srcRoot.Path) + dstRoot := dstRoot + if dstIsDir { + dstRoot.Path = path.Join(dstRoot.Path, path.Base(srcRoot.Path)) + } + srcRoot.Path = path.Clean(srcRoot.Path) + srcRoot.WalkDir(func(srcPath string, d fs.DirEntry, err error) error { + src := FSPath{srcRoot.FS, srcPath} + dst := FSPath{dstRoot.FS, path.Join(dstRoot.Path, strings.TrimPrefix(srcPath, srcRoot.Path))} if err != nil { - progress.FileDone(srcRoot.String(), err) - return + progress.FileDone(src.String(), err) + return nil } - if err := c.copyFile(srcRoot, fs.FileInfoToDirEntry(stat), dstRoot); err != nil { - progress.FileDone(srcRoot.String(), err) - return + switch d.Type() { + case 0: + sem <- struct{}{} + go func() { + defer func() { <-sem }() + if err := c.copyRegularFile(src, dst); err != nil { + progress.FileDone(src.String(), err) + } + }() + case fs.ModeDir: + stat, err := d.Info() + if err != nil { + progress.FileDone(src.String(), err) + return nil + } + hasWritePerm := stat.Mode()&0300 == 0300 + if err := c.openWithRetry(dst, func() error { + if hasWritePerm { + return dst.MkdirMode(stat.Mode().Perm()) + } else { + return dst.Mkdir() + } + }); err != nil { + progress.FileDone(src.String(), err) + return nil + } + if hasWritePerm { + progress.Progress(1) + } else { + roDirs = append(roDirs, roDir{dst, stat.Mode().Perm()}) + } + case fs.ModeSymlink: + if err := c.copySymlink(src, dst); err != nil { + progress.FileDone(src.String(), err) + } + default: + progress.FileDone(src.String(), fmt.Errorf("%s: unknown file type %s", src, d.Type())) } + return nil }) } - wg.Wait() + for range maxConcurrency { + sem <- struct{}{} + } + for _, d := range slices.Backward(roDirs) { + if err := d.path.Chmod(d.mode); err != nil { + progress.FileDone(d.path.String(), err) + continue + } + progress.Progress(1) + } } |
