aboutsummaryrefslogtreecommitdiffstats
path: root/formats/objfile/reader.go
blob: 99ed754441e7024e23b1cf37e6cab8030dc5d42c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
package objfile

import (
	"errors"
	"io"

	"gopkg.in/src-d/go-git.v4/core"

	"github.com/klauspost/compress/zlib"
)

var (
	// ErrZLib is returned when the objfile contains invalid zlib data.
	ErrZLib = errors.New("objfile: invalid zlib data")
)

// Reader reads and decodes compressed objfile data from a provided io.Reader.
//
// Reader implements io.ReadCloser. Close should be called when finished with
// the Reader. Close will not close the underlying io.Reader.
type Reader struct {
	header header
	hash   core.Hash // final computed hash stored after Close

	r            io.Reader     // provided reader wrapped in decompressor and tee
	decompressor io.ReadCloser // provided reader wrapped in decompressor, retained for calling Close
	h            core.Hasher   // streaming SHA1 hash of decoded data
}

// NewReader returns a new Reader reading from r.
//
// Calling NewReader causes it to immediately read in header data from r
// containing size and type information. Any errors encountered in that
// process will be returned in err.
//
// The returned Reader implements io.ReadCloser. Close should be called when
// finished with the Reader. Close will not close the underlying io.Reader.
func NewReader(r io.Reader) (*Reader, error) {
	reader := &Reader{}
	return reader, reader.init(r)
}

// init prepares the zlib decompressor for the given input as well as a hasher
// for computing its hash.
//
// init immediately reads header data from the input and stores it. This leaves
// the Reader in a state that is ready to read content.
func (r *Reader) init(input io.Reader) (err error) {
	r.decompressor, err = zlib.NewReader(input)
	if err != nil {
		// TODO: Make this error match the ZLibErr in formats/packfile/reader.go?
		return ErrZLib
	}

	err = r.header.Read(r.decompressor)
	if err != nil {
		r.decompressor.Close()
		return
	}

	r.h = core.NewHasher(r.header.t, r.header.size)
	r.r = io.TeeReader(r.decompressor, r.h) // All reads from the decompressor also write to the hash

	return
}

// Read reads len(p) bytes into p from the object data stream. It returns
// the number of bytes read (0 <= n <= len(p)) and any error encountered. Even
// if Read returns n < len(p), it may use all of p as scratch space during the
// call.
//
// If Read encounters the end of the data stream it will return err == io.EOF,
// either in the current call if n > 0 or in a subsequent call.
func (r *Reader) Read(p []byte) (n int, err error) {
	if r.r == nil {
		return 0, ErrClosed
	}

	return r.r.Read(p)
}

// Type returns the type of the object.
func (r *Reader) Type() core.ObjectType {
	return r.header.t
}

// Size returns the uncompressed size of the object in bytes.
func (r *Reader) Size() int64 {
	return r.header.size
}

// Hash returns the hash of the object data stream that has been read so far.
// It can be called before or after Close.
func (r *Reader) Hash() core.Hash {
	if r.r != nil {
		return r.h.Sum() // Not yet closed, return hash of data read so far
	}
	return r.hash
}

// Close releases any resources consumed by the Reader.
//
// Calling Close does not close the wrapped io.Reader originally passed to
// NewReader.
func (r *Reader) Close() (err error) {
	if r.r == nil {
		// TODO: Consider returning ErrClosed here?
		return nil // Already closed
	}

	// Release the decompressor's resources
	err = r.decompressor.Close()

	// Save the hash because we're about to throw away the hasher
	r.hash = r.h.Sum()

	// Release references
	r.r = nil // Indicates closed state
	r.decompressor = nil
	r.h.Hash = nil

	return
}

// FillObject fills the given object from an object entry
func (r *Reader) FillObject(obj core.Object) error {
	obj.SetType(r.header.t)
	obj.SetSize(r.header.size)
	w, err := obj.Writer()
	if err != nil {
		return err
	}
	_, err = io.Copy(w, r.r)

	return err
}