aboutsummaryrefslogblamecommitdiffstats
path: root/formats/objfile/writer.go
blob: d9d40f08244e20057f0cc55bd5c45df286dc8a10 (plain) (tree)








































                                                                                                      


                                                                              


                                                                             





                                               















                                                                               
                                    







































































                                                                                                      
package objfile

import (
	"errors"
	"io"

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

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

var (
	// ErrOverflow is returned when an attempt is made to write more data than
	// was declared in NewWriter.
	ErrOverflow = errors.New("objfile: declared data length exceeded (overflow)")
)

// Writer writes and encodes data in compressed objfile format to a provided
// io.Writer.
//
// Writer implements io.WriteCloser. Close should be called when finished with
// the Writer. Close will not close the underlying io.Writer.
type Writer struct {
	header header
	hash   core.Hash // final computed hash stored after Close

	w          io.Writer      // provided writer wrapped in compressor and tee
	compressor io.WriteCloser // provided writer wrapped in compressor, retained for calling Close
	h          core.Hasher    // streaming SHA1 hash of encoded data
	written    int64          // Number of bytes written
}

// NewWriter returns a new Writer writing to w.
//
// The provided t is the type of object being written. The provided size is the
// number of uncompressed bytes being written.
//
// Calling NewWriter causes it to immediately write header data containing
// size and type information. Any errors encountered in that process will be
// returned in err.
//
// If an invalid t is provided, core.ErrInvalidType is returned. If a negative
// size is provided, ErrNegativeSize is returned.
//
// The returned Writer implements io.WriteCloser. Close should be called when
// finished with the Writer. Close will not close the underlying io.Writer.
func NewWriter(w io.Writer, t core.ObjectType, size int64) (*Writer, error) {
	if !t.Valid() {
		return nil, core.ErrInvalidType
	}
	if size < 0 {
		return nil, ErrNegativeSize
	}
	writer := &Writer{
		header: header{t: t, size: size},
	}
	return writer, writer.init(w)
}

// init prepares the zlib compressor for the given output as well as a hasher
// for computing its hash.
//
// init immediately writes header data to the output. This leaves the writer in
// a state that is ready to write content.
func (w *Writer) init(output io.Writer) (err error) {
	w.compressor = zlib.NewWriter(output)

	err = w.header.Write(w.compressor)
	if err != nil {
		w.compressor.Close()
		return
	}

	w.h = core.NewHasher(w.header.t, w.header.size)
	w.w = io.MultiWriter(w.compressor, w.h) // All writes to the compressor also write to the hash

	return
}

// Write reads len(p) from p to the object data stream. It returns the number of
// bytes written from p (0 <= n <= len(p)) and any error encountered that caused
// the write to stop early. The slice data contained in p will not be modified.
//
// If writing len(p) bytes would exceed the size provided in NewWriter,
// ErrOverflow is returned without writing any data.
func (w *Writer) Write(p []byte) (n int, err error) {
	if w.w == nil {
		return 0, ErrClosed
	}

	if w.written+int64(len(p)) > w.header.size {
		return 0, ErrOverflow
	}

	n, err = w.w.Write(p)
	w.written += int64(n)

	return
}

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

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

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

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

	// Release the compressor's resources
	err = w.compressor.Close()

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

	// Release references
	w.w = nil // Indicates closed state
	w.compressor = nil
	w.h.Hash = nil

	return
}