aboutsummaryrefslogtreecommitdiffstats
path: root/formats/objfile/reader.go
blob: 5b17da02310c1db7a4d7e84f001601cf0f36506c (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
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
}