// Copyright Earl Warren // Copyright Loïc Dachary // SPDX-License-Identifier: MIT package objects import ( "crypto/sha256" "encoding/hex" "fmt" "io" "os" "path/filepath" "runtime" ) type Interface interface { Save(rc io.ReadCloser) (string, string) } type helper struct { dir *string } func (o *helper) getDir() string { if o.dir == nil { dir, err := os.MkdirTemp("", "objectHelper") if err != nil { panic(err) } runtime.SetFinalizer(o, func(o *helper) { err := os.RemoveAll(dir) if err != nil { panic(err) } }) o.dir = &dir } return *o.dir } func (o *helper) getPath(sha string) string { return filepath.Join(o.getDir(), sha[0:2], sha[2:4], sha) } func (o *helper) Save(rc io.ReadCloser) (string, string) { tempFile, err := os.CreateTemp("", "object") if err != nil { panic(err) } tempRemoved := false defer func() { if !tempRemoved { _ = os.Remove(tempFile.Name()) } }() // reader defer rc.Close() // writer to file f, err := os.OpenFile(tempFile.Name(), os.O_CREATE|os.O_RDWR, 0o644) if err != nil { panic(err) } defer f.Close() // writer to sha256 h := sha256.New() // copy reader to file & sha256 w := io.MultiWriter(f, h) if _, err := io.Copy(w, rc); err != nil { panic(fmt.Errorf("while copying object: %w", err)) } // finalize writer to sha256 sha := hex.EncodeToString(h.Sum(nil)) // finalize writer to file if err := tempFile.Close(); err != nil { panic(err) } path := o.getPath(sha) if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { panic(err) } if err := os.Rename(tempFile.Name(), path); err != nil { panic(err) } tempRemoved = true return sha, path } func NewObjectsHelper() Interface { return &helper{} }