shithub: furgit

ref: df1f2fb3daa1acd25c88510f259d5535fb482126
dir: /object/store/loose/read_reader.go/

View raw version
package loose

import (
	"bufio"
	"bytes"
	"errors"
	"io"
	"os"

	"codeberg.org/lindenii/furgit/internal/compress/zlib"
	"codeberg.org/lindenii/furgit/internal/iolimit"
	objectid "codeberg.org/lindenii/furgit/object/id"
	objecttype "codeberg.org/lindenii/furgit/object/type"
)

type objectReader struct {
	// reader is the stream exposed by Read.
	reader io.Reader
	// file is the underlying loose object file and is closed by Close.
	file *os.File
	// zr is the zlib decoder and is closed by Close.
	zr io.ReadCloser
}

func (reader *objectReader) Read(dst []byte) (int, error) {
	return reader.reader.Read(dst)
}

func (reader *objectReader) Close() error {
	errZlib := reader.zr.Close()
	errFile := reader.file.Close()

	return errors.Join(errZlib, errFile)
}

// openInflated opens and zlib-decodes a loose object file.
// The caller owns both returned closers and must close them.
func (store *Store) openInflated(id objectid.ObjectID) (*os.File, io.ReadCloser, error) {
	file, err := store.openObject(id)
	if err != nil {
		return nil, nil, err
	}

	zr, err := zlib.NewReader(file)
	if err != nil {
		_ = file.Close()

		return nil, nil, err
	}

	return file, zr, nil
}

// ReadReaderFull reads a full serialized object stream as "type size\0content".
//
// Close releases resources only. It does not drain unread data for additional
// validation. In particular, malformed trailing compressed data, trailing bytes
// past the declared object size, and the zlib Adler-32 trailer may go
// unverified unless the caller reads to io.EOF.
func (store *Store) ReadReaderFull(id objectid.ObjectID) (io.ReadCloser, error) {
	file, zr, err := store.openInflated(id)
	if err != nil {
		return nil, err
	}

	br := bufio.NewReader(zr)

	header, _, size, err := readHeader(br)
	if err != nil {
		_ = zr.Close()
		_ = file.Close()

		return nil, err
	}

	return &objectReader{
		reader: io.MultiReader(
			bytes.NewReader(header),
			iolimit.ExpectLengthReader(br, size),
		),
		file: file,
		zr:   zr,
	}, nil
}

// ReadReaderContent reads an object's type, declared content length, and
// content stream.
//
// Close releases resources only. It does not drain unread data for additional
// validation. In particular, malformed trailing compressed data, trailing bytes
// past the declared object size, and the zlib Adler-32 trailer may go
// unverified unless the caller reads to io.EOF.
func (store *Store) ReadReaderContent(id objectid.ObjectID) (objecttype.Type, int64, io.ReadCloser, error) {
	file, zr, err := store.openInflated(id)
	if err != nil {
		return objecttype.TypeInvalid, 0, nil, err
	}

	br := bufio.NewReader(zr)

	_, ty, size, err := readHeader(br)
	if err != nil {
		_ = zr.Close()
		_ = file.Close()

		return objecttype.TypeInvalid, 0, nil, err
	}

	return ty, size, &objectReader{
		reader: iolimit.ExpectLengthReader(br, size),
		file:   file,
		zr:     zr,
	}, nil
}