aboutsummaryrefslogtreecommitdiffstats
path: root/formats/objfile/writer.go
diff options
context:
space:
mode:
Diffstat (limited to 'formats/objfile/writer.go')
-rw-r--r--formats/objfile/writer.go142
1 files changed, 142 insertions, 0 deletions
diff --git a/formats/objfile/writer.go b/formats/objfile/writer.go
new file mode 100644
index 0000000..d9d40f0
--- /dev/null
+++ b/formats/objfile/writer.go
@@ -0,0 +1,142 @@
+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
+}