115 lines
2.8 KiB
Go
115 lines
2.8 KiB
Go
|
package objfile
|
||
|
|
||
|
import (
|
||
|
"compress/zlib"
|
||
|
"errors"
|
||
|
"io"
|
||
|
"strconv"
|
||
|
|
||
|
"gopkg.in/src-d/go-git.v4/plumbing"
|
||
|
"gopkg.in/src-d/go-git.v4/plumbing/format/packfile"
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
ErrClosed = errors.New("objfile: already closed")
|
||
|
ErrHeader = errors.New("objfile: invalid header")
|
||
|
ErrNegativeSize = errors.New("objfile: negative object size")
|
||
|
)
|
||
|
|
||
|
// 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 {
|
||
|
multi io.Reader
|
||
|
zlib io.ReadCloser
|
||
|
hasher plumbing.Hasher
|
||
|
}
|
||
|
|
||
|
// NewReader returns a new Reader reading from r.
|
||
|
func NewReader(r io.Reader) (*Reader, error) {
|
||
|
zlib, err := zlib.NewReader(r)
|
||
|
if err != nil {
|
||
|
return nil, packfile.ErrZLib.AddDetails(err.Error())
|
||
|
}
|
||
|
|
||
|
return &Reader{
|
||
|
zlib: zlib,
|
||
|
}, nil
|
||
|
}
|
||
|
|
||
|
// Header reads the type and the size of object, and prepares the reader for read
|
||
|
func (r *Reader) Header() (t plumbing.ObjectType, size int64, err error) {
|
||
|
var raw []byte
|
||
|
raw, err = r.readUntil(' ')
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
t, err = plumbing.ParseObjectType(string(raw))
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
raw, err = r.readUntil(0)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
size, err = strconv.ParseInt(string(raw), 10, 64)
|
||
|
if err != nil {
|
||
|
err = ErrHeader
|
||
|
return
|
||
|
}
|
||
|
|
||
|
defer r.prepareForRead(t, size)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// readSlice reads one byte at a time from r until it encounters delim or an
|
||
|
// error.
|
||
|
func (r *Reader) readUntil(delim byte) ([]byte, error) {
|
||
|
var buf [1]byte
|
||
|
value := make([]byte, 0, 16)
|
||
|
for {
|
||
|
if n, err := r.zlib.Read(buf[:]); err != nil && (err != io.EOF || n == 0) {
|
||
|
if err == io.EOF {
|
||
|
return nil, ErrHeader
|
||
|
}
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
if buf[0] == delim {
|
||
|
return value, nil
|
||
|
}
|
||
|
|
||
|
value = append(value, buf[0])
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (r *Reader) prepareForRead(t plumbing.ObjectType, size int64) {
|
||
|
r.hasher = plumbing.NewHasher(t, size)
|
||
|
r.multi = io.TeeReader(r.zlib, r.hasher)
|
||
|
}
|
||
|
|
||
|
// 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) {
|
||
|
return r.multi.Read(p)
|
||
|
}
|
||
|
|
||
|
// Hash returns the hash of the object data stream that has been read so far.
|
||
|
func (r *Reader) Hash() plumbing.Hash {
|
||
|
return r.hasher.Sum()
|
||
|
}
|
||
|
|
||
|
// 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() error {
|
||
|
return r.zlib.Close()
|
||
|
}
|