package tools
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"hash"
"io"
"os"
"github.com/git-lfs/git-lfs/v3/errors"
"github.com/git-lfs/git-lfs/v3/tr"
)
const (
memoryBufferLimit = 1024
)
func CopyWithCallback(writer io.Writer, reader io.Reader, totalSize int64, cb CopyCallback) (int64, error) {
if success, _ := CloneFile(writer, reader); success {
if cb != nil {
cb(totalSize, totalSize, 0)
}
return totalSize, nil
}
if cb == nil {
return io.Copy(writer, reader)
}
cbReader := &CallbackReader{
C: cb,
TotalSize: totalSize,
Reader: reader,
}
return io.Copy(writer, cbReader)
}
func NewLfsContentHash() hash.Hash {
return sha256.New()
}
type HashingReader struct {
reader io.Reader
hasher hash.Hash
}
func NewHashingReader(r io.Reader) *HashingReader {
return &HashingReader{r, NewLfsContentHash()}
}
func NewHashingReaderPreloadHash(r io.Reader, hash hash.Hash) *HashingReader {
return &HashingReader{r, hash}
}
func (r *HashingReader) Hash() string {
return hex.EncodeToString(r.hasher.Sum(nil))
}
func (r *HashingReader) Read(b []byte) (int, error) {
w, err := r.reader.Read(b)
if err == nil || err == io.EOF {
_, e := r.hasher.Write(b[0:w])
if e != nil && err == nil {
return w, e
}
}
return w, err
}
type RetriableReader struct {
reader io.Reader
}
func NewRetriableReader(r io.Reader) io.Reader {
return &RetriableReader{r}
}
func (r *RetriableReader) Read(b []byte) (int, error) {
n, err := r.reader.Read(b)
if err == nil || err == io.EOF || errors.IsRetriableError(err) {
return n, err
}
return n, errors.NewRetriableError(err)
}
func Spool(to io.Writer, from io.Reader, dir string) (n int64, err error) {
buf := make([]byte, memoryBufferLimit)
if bn, err := from.Read(buf); err != nil && err != io.EOF {
return int64(bn), err
} else {
buf = buf[:bn]
}
var spool io.Reader = bytes.NewReader(buf)
if err != io.EOF {
tmp, err := os.CreateTemp(dir, "")
if err != nil {
return 0, errors.Wrap(err, tr.Tr.Get("Unable to create temporary file for spooling"))
}
defer func() {
tmp.Close()
os.Remove(tmp.Name())
}()
if n, err = io.Copy(tmp, from); err != nil {
return n, errors.Wrap(err, tr.Tr.Get("unable to spool"))
}
if _, err = tmp.Seek(0, io.SeekStart); err != nil {
return 0, errors.Wrap(err, tr.Tr.Get("unable to seek"))
}
spool = io.MultiReader(spool, tmp)
}
return io.Copy(to, spool)
}
func SplitOnNul(data []byte, atEOF bool) (advance int, token []byte, err error) {
for i := 0; i < len(data); i++ {
if data[i] == '\x00' {
return i + 1, data[:i], nil
}
}
return 0, nil, nil
}