chunked.go 1.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273
  1. package blob
  2. import (
  3. "crypto/sha256"
  4. "errors"
  5. "io"
  6. "os"
  7. )
  8. // Chunk represents a range of bytes in a blob.
  9. type Chunk struct {
  10. Start int64
  11. End int64
  12. }
  13. // Size returns end minus start plus one.
  14. func (c Chunk) Size() int64 {
  15. return c.End - c.Start + 1
  16. }
  17. // Chunker writes to a blob in chunks.
  18. // Its zero value is invalid. Use [DiskCache.Chunked] to create a new Chunker.
  19. type Chunker struct {
  20. digest Digest
  21. size int64
  22. f *os.File // nil means pre-validated
  23. }
  24. // Chunked returns a new Chunker, ready for use storing a blob of the given
  25. // size in chunks.
  26. //
  27. // Use [Chunker.Put] to write data to the blob at specific offsets.
  28. func (c *DiskCache) Chunked(d Digest, size int64) (*Chunker, error) {
  29. name := c.GetFile(d)
  30. info, err := os.Stat(name)
  31. if err == nil && info.Size() == size {
  32. return &Chunker{}, nil
  33. }
  34. f, err := os.OpenFile(name, os.O_CREATE|os.O_WRONLY, 0o666)
  35. if err != nil {
  36. return nil, err
  37. }
  38. return &Chunker{digest: d, size: size, f: f}, nil
  39. }
  40. // Put copies chunk.Size() bytes from r to the blob at the given offset,
  41. // merging the data with the existing blob. It returns an error if any. As a
  42. // special case, if r has less than chunk.Size() bytes, Put returns
  43. // io.ErrUnexpectedEOF.
  44. func (c *Chunker) Put(chunk Chunk, d Digest, r io.Reader) error {
  45. if c.f == nil {
  46. return nil
  47. }
  48. cw := &checkWriter{
  49. d: d,
  50. size: chunk.Size(),
  51. h: sha256.New(),
  52. f: c.f,
  53. w: io.NewOffsetWriter(c.f, chunk.Start),
  54. }
  55. _, err := io.CopyN(cw, r, chunk.Size())
  56. if err != nil && errors.Is(err, io.EOF) {
  57. return io.ErrUnexpectedEOF
  58. }
  59. return err
  60. }
  61. // Close closes the underlying file.
  62. func (c *Chunker) Close() error {
  63. return c.f.Close()
  64. }