From 40081e7a0694db0ebe2b59e7545ecc43128c8f40 Mon Sep 17 00:00:00 2001 From: Ophestra Date: Sat, 3 Jan 2026 22:49:01 +0900 Subject: internal/pkg: implement caching for directories This works on any directories and should be robust against any bad state the artifact curing process might have failed at. Signed-off-by: Ophestra --- internal/pkg/pkg.go | 97 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) (limited to 'internal/pkg/pkg.go') diff --git a/internal/pkg/pkg.go b/internal/pkg/pkg.go index 64df5df4..e8547d16 100644 --- a/internal/pkg/pkg.go +++ b/internal/pkg/pkg.go @@ -8,7 +8,10 @@ import ( "encoding/binary" "errors" "io" + "io/fs" "os" + "path" + "path/filepath" "slices" "sync" @@ -190,6 +193,100 @@ func (c *Cache) pathnameIdent(id *ID) *check.Absolute { ) } +// Store looks up an identifier, and if it is not present, calls makeArtifact +// with a private working directory and stores its result instead. An optional +// checksum can be passed via the result buffer which is used to validate the +// produced directory. +func (c *Cache) Store( + id ID, + makeArtifact func(work *check.Absolute) error, + buf *Checksum, + validate bool, +) ( + pathname *check.Absolute, + store bool, + err error, +) { + pathname = c.pathnameIdent(&id) + c.mu.Lock() + defer c.mu.Unlock() + + _, err = os.Lstat(pathname.String()) + if err == nil || !errors.Is(err, os.ErrNotExist) { + return + } + store = true + + var ( + workPathname *check.Absolute + workPathnameRaw string + ) + if workPathnameRaw, err = os.MkdirTemp( + c.base.Append(dirWork).String(), + path.Base(pathname.String()+".*"), + ); err != nil { + return + } else if workPathname, err = check.NewAbs(workPathnameRaw); err != nil { + return + } + defer func() { + if err != nil { + chmodErr := filepath.WalkDir(workPathname.String(), func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() { + return os.Chmod(path, 0700) + } + return nil + }) + removeErr := os.RemoveAll(workPathname.String()) + if chmodErr != nil || removeErr != nil { + err = errors.Join(err, chmodErr, removeErr) + } else if errors.Is(err, os.ErrExist) { + // two artifacts may be backed by the same file + err = nil + } + } + }() + if err = os.Chmod(workPathname.String(), 0700); err != nil { + return + } + + if err = makeArtifact(workPathname); err != nil { + return + } + var checksum Checksum + if checksum, err = HashDir(workPathname); err != nil { + return + } + if validate { + if checksum != *buf { + err = &ChecksumMismatchError{checksum, *buf} + return + } + } else { + *buf = checksum + } + + checksumPathname := c.pathname(&checksum) + if err = os.Rename( + workPathname.String(), + checksumPathname.String(), + ); err != nil { + if !errors.Is(err, os.ErrExist) { + return + } + } + if linkErr := os.Symlink( + "../"+dirChecksum+"/"+path.Base(checksumPathname.String()), + pathname.String(), + ); linkErr != nil { + err = linkErr + } + return +} + // storeFile stores the contents of a [File]. An optional checksum can be // passed via the result buffer which is used to validate the submitted data. // -- cgit v1.3.1