1package object
2
3import (
4	"io"
5
6	"github.com/go-git/go-git/v5/plumbing"
7	"github.com/go-git/go-git/v5/plumbing/storer"
8	"github.com/go-git/go-git/v5/utils/ioutil"
9)
10
11// Blob is used to store arbitrary data - it is generally a file.
12type Blob struct {
13	// Hash of the blob.
14	Hash plumbing.Hash
15	// Size of the (uncompressed) blob.
16	Size int64
17
18	obj plumbing.EncodedObject
19}
20
21// GetBlob gets a blob from an object storer and decodes it.
22func GetBlob(s storer.EncodedObjectStorer, h plumbing.Hash) (*Blob, error) {
23	o, err := s.EncodedObject(plumbing.BlobObject, h)
24	if err != nil {
25		return nil, err
26	}
27
28	return DecodeBlob(o)
29}
30
31// DecodeObject decodes an encoded object into a *Blob.
32func DecodeBlob(o plumbing.EncodedObject) (*Blob, error) {
33	b := &Blob{}
34	if err := b.Decode(o); err != nil {
35		return nil, err
36	}
37
38	return b, nil
39}
40
41// ID returns the object ID of the blob. The returned value will always match
42// the current value of Blob.Hash.
43//
44// ID is present to fulfill the Object interface.
45func (b *Blob) ID() plumbing.Hash {
46	return b.Hash
47}
48
49// Type returns the type of object. It always returns plumbing.BlobObject.
50//
51// Type is present to fulfill the Object interface.
52func (b *Blob) Type() plumbing.ObjectType {
53	return plumbing.BlobObject
54}
55
56// Decode transforms a plumbing.EncodedObject into a Blob struct.
57func (b *Blob) Decode(o plumbing.EncodedObject) error {
58	if o.Type() != plumbing.BlobObject {
59		return ErrUnsupportedObject
60	}
61
62	b.Hash = o.Hash()
63	b.Size = o.Size()
64	b.obj = o
65
66	return nil
67}
68
69// Encode transforms a Blob into a plumbing.EncodedObject.
70func (b *Blob) Encode(o plumbing.EncodedObject) (err error) {
71	o.SetType(plumbing.BlobObject)
72
73	w, err := o.Writer()
74	if err != nil {
75		return err
76	}
77
78	defer ioutil.CheckClose(w, &err)
79
80	r, err := b.Reader()
81	if err != nil {
82		return err
83	}
84
85	defer ioutil.CheckClose(r, &err)
86
87	_, err = io.Copy(w, r)
88	return err
89}
90
91// Reader returns a reader allow the access to the content of the blob
92func (b *Blob) Reader() (io.ReadCloser, error) {
93	return b.obj.Reader()
94}
95
96// BlobIter provides an iterator for a set of blobs.
97type BlobIter struct {
98	storer.EncodedObjectIter
99	s storer.EncodedObjectStorer
100}
101
102// NewBlobIter takes a storer.EncodedObjectStorer and a
103// storer.EncodedObjectIter and returns a *BlobIter that iterates over all
104// blobs contained in the storer.EncodedObjectIter.
105//
106// Any non-blob object returned by the storer.EncodedObjectIter is skipped.
107func NewBlobIter(s storer.EncodedObjectStorer, iter storer.EncodedObjectIter) *BlobIter {
108	return &BlobIter{iter, s}
109}
110
111// Next moves the iterator to the next blob and returns a pointer to it. If
112// there are no more blobs, it returns io.EOF.
113func (iter *BlobIter) Next() (*Blob, error) {
114	for {
115		obj, err := iter.EncodedObjectIter.Next()
116		if err != nil {
117			return nil, err
118		}
119
120		if obj.Type() != plumbing.BlobObject {
121			continue
122		}
123
124		return DecodeBlob(obj)
125	}
126}
127
128// ForEach call the cb function for each blob contained on this iter until
129// an error happens or the end of the iter is reached. If ErrStop is sent
130// the iteration is stop but no error is returned. The iterator is closed.
131func (iter *BlobIter) ForEach(cb func(*Blob) error) error {
132	return iter.EncodedObjectIter.ForEach(func(obj plumbing.EncodedObject) error {
133		if obj.Type() != plumbing.BlobObject {
134			return nil
135		}
136
137		b, err := DecodeBlob(obj)
138		if err != nil {
139			return err
140		}
141
142		return cb(b)
143	})
144}
145