github.com/stffabi/git-lfs@v2.3.5-0.20180214015214-8eeaa8d88902+incompatible/git/odb/object_reader.go (about)

     1  package odb
     2  
     3  import (
     4  	"bufio"
     5  	"compress/zlib"
     6  	"io"
     7  	"io/ioutil"
     8  	"strconv"
     9  	"strings"
    10  
    11  	"github.com/pkg/errors"
    12  )
    13  
    14  // ObjectReader provides an io.Reader implementation that can read Git object
    15  // headers, as well as provide an uncompressed view into the object contents
    16  // itself.
    17  type ObjectReader struct {
    18  	// header is the object header type
    19  	header *struct {
    20  		// typ is the ObjectType encoded in the header pointed at by
    21  		// this reader.
    22  		typ ObjectType
    23  		// size is the number of uncompressed bytes following the header
    24  		// that encodes the object.
    25  		size int64
    26  	}
    27  	// r is the underling uncompressed reader.
    28  	r *bufio.Reader
    29  
    30  	// closeFn supplies an optional function that, when called, frees an
    31  	// resources (open files, memory, etc) held by this instance of the
    32  	// *ObjectReader.
    33  	//
    34  	// closeFn returns any error encountered when closing/freeing resources
    35  	// held.
    36  	//
    37  	// It is allowed to be nil.
    38  	closeFn func() error
    39  }
    40  
    41  // NewObjectReader takes a given io.Reader that yields zlib-compressed data, and
    42  // returns an *ObjectReader wrapping it, or an error if one occurred during
    43  // construction time.
    44  func NewObjectReader(r io.Reader) (*ObjectReader, error) {
    45  	return NewObjectReadCloser(ioutil.NopCloser(r))
    46  }
    47  
    48  // NewObjectReader takes a given io.Reader that yields uncompressed data and
    49  // returns an *ObjectReader wrapping it, or an error if one occurred during
    50  // construction time.
    51  func NewUncompressedObjectReader(r io.Reader) (*ObjectReader, error) {
    52  	return NewUncompressedObjectReadCloser(ioutil.NopCloser(r))
    53  }
    54  
    55  // NewObjectReadCloser takes a given io.Reader that yields zlib-compressed data, and
    56  // returns an *ObjectReader wrapping it, or an error if one occurred during
    57  // construction time.
    58  //
    59  // It also calls the Close() function given by the implementation "r" of the
    60  // type io.Closer.
    61  func NewObjectReadCloser(r io.ReadCloser) (*ObjectReader, error) {
    62  	zr, err := zlib.NewReader(r)
    63  	if err != nil {
    64  		return nil, err
    65  	}
    66  
    67  	return &ObjectReader{
    68  		r: bufio.NewReader(zr),
    69  		closeFn: func() error {
    70  			if err := zr.Close(); err != nil {
    71  				return err
    72  			}
    73  			if err := r.Close(); err != nil {
    74  				return err
    75  			}
    76  			return nil
    77  		},
    78  	}, nil
    79  }
    80  
    81  // NewUncompressObjectReadCloser takes a given io.Reader that yields
    82  // uncompressed data, and returns an *ObjectReader wrapping it, or an error if
    83  // one occurred during construction time.
    84  //
    85  // It also calls the Close() function given by the implementation "r" of the
    86  // type io.Closer.
    87  func NewUncompressedObjectReadCloser(r io.ReadCloser) (*ObjectReader, error) {
    88  	return &ObjectReader{
    89  		r:       bufio.NewReader(r),
    90  		closeFn: r.Close,
    91  	}, nil
    92  }
    93  
    94  // Header returns information about the Object's header, or an error if one
    95  // occurred while reading the data.
    96  //
    97  // Header information is cached, so this function is safe to call at any point
    98  // during the object read, and can be called more than once.
    99  func (r *ObjectReader) Header() (typ ObjectType, size int64, err error) {
   100  	if r.header != nil {
   101  		return r.header.typ, r.header.size, nil
   102  	}
   103  
   104  	typs, err := r.r.ReadString(' ')
   105  	if err != nil {
   106  		return UnknownObjectType, 0, err
   107  	}
   108  	if len(typs) == 0 {
   109  		return UnknownObjectType, 0, errors.Errorf(
   110  			"git/odb: object type must not be empty",
   111  		)
   112  	}
   113  	typs = strings.TrimSuffix(typs, " ")
   114  
   115  	sizeStr, err := r.r.ReadString('\x00')
   116  	if err != nil {
   117  		return UnknownObjectType, 0, err
   118  	}
   119  	sizeStr = strings.TrimSuffix(sizeStr, "\x00")
   120  
   121  	size, err = strconv.ParseInt(sizeStr, 10, 64)
   122  	if err != nil {
   123  		return UnknownObjectType, 0, err
   124  	}
   125  
   126  	r.header = &struct {
   127  		typ  ObjectType
   128  		size int64
   129  	}{
   130  		ObjectTypeFromString(typs),
   131  		size,
   132  	}
   133  
   134  	return r.header.typ, r.header.size, nil
   135  }
   136  
   137  // Read reads uncompressed bytes into the buffer "p", and returns the number of
   138  // uncompressed bytes read. Otherwise, it returns any error encountered along
   139  // the way.
   140  //
   141  // This function is safe to call before reading the Header information, as any
   142  // call to Read() will ensure that read has been called at least once.
   143  func (r *ObjectReader) Read(p []byte) (n int, err error) {
   144  	if _, _, err = r.Header(); err != nil {
   145  		return 0, err
   146  	}
   147  	return r.r.Read(p)
   148  }
   149  
   150  // Close frees any resources held by the ObjectReader and must be called before
   151  // disposing of this instance.
   152  //
   153  // It returns any error encountered by the *ObjectReader during close.
   154  func (r *ObjectReader) Close() error {
   155  	if r.closeFn == nil {
   156  		return nil
   157  	}
   158  	return r.closeFn()
   159  }