package packed
import (
"bytes"
"fmt"
"io"
"lindenii.org/go/furgit/internal/compress/zlib"
"lindenii.org/go/furgit/internal/format/packfile"
"lindenii.org/go/furgit/internal/iolimit"
"lindenii.org/go/furgit/object/header"
"lindenii.org/go/furgit/object/id"
"lindenii.org/go/furgit/object/typ"
"lindenii.org/go/lgo/intconv"
)
// ReadBytesContent reads an object's type and content bytes,
// fully resolving delta chains.
func (packed *Packed) ReadBytesContent(objectID id.ObjectID) (typ.Type, []byte, error) {
p, offset, err := packed.lookup(objectID)
if err != nil {
return typ.Unknown, nil, err
}
entryType, content, err := packed.unpackEntry(p, offset)
if err != nil {
return typ.Unknown, nil, err
}
ty, err := objectTypeOf(entryType)
if err != nil {
return typ.Unknown, nil, err
}
return ty, content, nil
}
// ReadBytesFull reads a full serialized object as "type size\x00content",
// fully resolving delta chains.
func (packed *Packed) ReadBytesFull(objectID id.ObjectID) ([]byte, error) {
ty, content, err := packed.ReadBytesContent(objectID)
if err != nil {
return nil, err
}
raw := header.Append(make([]byte, 0, len(content)+32), ty, len(content))
return append(raw, content...), nil
}
// ReadHeader reads an object's type and declared content length.
//
// For delta entries this resolves the chained base type
// and the declared delta result size,
// without reconstructing content.
func (packed *Packed) ReadHeader(objectID id.ObjectID) (typ.Type, int, error) {
p, offset, err := packed.lookup(objectID)
if err != nil {
return typ.Unknown, 0, err
}
entryHeader, payload, err := p.entryHeaderAt(offset, packed.objectFormat)
if err != nil {
return typ.Unknown, 0, err
}
var size int
if entryHeader.Type.IsDelta() {
size, err = deltaResultSize(payload, entryHeader.Size)
if err != nil {
return typ.Unknown, 0, fmt.Errorf("%w: pack %q: %w", ErrMalformedPackedStore, p.name, err)
}
} else {
size, err = intconv.Uint64ToInt(entryHeader.Size)
if err != nil {
return typ.Unknown, 0, fmt.Errorf("%w: pack %q: object size overflows int: %w", ErrMalformedPackedStore, p.name, err)
}
}
entryType, err := packed.resolveType(p, offset, entryHeader)
if err != nil {
return typ.Unknown, 0, err
}
ty, err := objectTypeOf(entryType)
if err != nil {
return typ.Unknown, 0, err
}
return ty, size, nil
}
// ReadSize reads an object's declared content length.
//
// Unlike ReadHeader,
// this never walks delta chains.
func (packed *Packed) ReadSize(objectID id.ObjectID) (int, error) {
p, offset, err := packed.lookup(objectID)
if err != nil {
return 0, err
}
entryHeader, payload, err := p.entryHeaderAt(offset, packed.objectFormat)
if err != nil {
return 0, err
}
if !entryHeader.Type.IsDelta() {
size, err := intconv.Uint64ToInt(entryHeader.Size)
if err != nil {
return 0, fmt.Errorf("%w: pack %q: object size overflows int: %w", ErrMalformedPackedStore, p.name, err)
}
return size, nil
}
size, err := deltaResultSize(payload, entryHeader.Size)
if err != nil {
return 0, fmt.Errorf("%w: pack %q: %w", ErrMalformedPackedStore, p.name, err)
}
return size, nil
}
// ReadReaderContent reads an object's type,
// declared content length, and content stream.
//
// Non-delta entries stream directly from the pack;
// delta entries are fully resolved in memory first.
// Close releases resources only
// and does not drain unread data for additional validation.
func (packed *Packed) ReadReaderContent(objectID id.ObjectID) (typ.Type, int, io.ReadCloser, error) {
p, offset, err := packed.lookup(objectID)
if err != nil {
return typ.Unknown, 0, nil, err
}
entryHeader, payload, err := p.entryHeaderAt(offset, packed.objectFormat)
if err != nil {
return typ.Unknown, 0, nil, err
}
if !entryHeader.Type.IsBase() {
entryType, content, err := packed.unpackEntry(p, offset)
if err != nil {
return typ.Unknown, 0, nil, err
}
ty, err := objectTypeOf(entryType)
if err != nil {
return typ.Unknown, 0, nil, err
}
return ty, len(content), io.NopCloser(bytes.NewReader(content)), nil
}
ty, err := objectTypeOf(entryHeader.Type)
if err != nil {
return typ.Unknown, 0, nil, err
}
size, err := intconv.Uint64ToInt(entryHeader.Size)
if err != nil {
return typ.Unknown, 0, nil, fmt.Errorf("%w: pack %q: object size overflows int: %w", ErrMalformedPackedStore, p.name, err)
}
zr, err := zlib.NewReaderBytes(payload)
if err != nil {
return typ.Unknown, 0, nil, fmt.Errorf("%w: pack %q: %w", ErrMalformedPackedStore, p.name, err)
}
return ty, size, &objectReader{
reader: iolimit.ExpectLengthReader(zr, size),
zr: zr,
}, nil
}
// ReadReaderFull reads a full serialized object stream
// as "type size\x00content".
//
// Non-delta entries stream directly from the pack;
// delta entries are fully resolved in memory first.
// Close releases resources only
// and does not drain unread data for additional validation.
func (packed *Packed) ReadReaderFull(objectID id.ObjectID) (io.ReadCloser, error) {
ty, size, reader, err := packed.ReadReaderContent(objectID)
if err != nil {
return nil, err
}
headerBytes := header.Append(nil, ty, size)
return &objectReader{
reader: io.MultiReader(bytes.NewReader(headerBytes), reader),
zr: reader,
}, nil
}
// objectTypeOf converts one packfile entry type
// into an ordinary object type.
func objectTypeOf(entryType packfile.EntryType) (typ.Type, error) {
ty, err := entryType.ObjectType()
if err != nil {
return typ.Unknown, fmt.Errorf("%w: %w", ErrMalformedPackedStore, err)
}
return ty, nil
}
// objectReader streams one packed object payload
// and owns the underlying decompressor.
type objectReader struct {
// reader is the stream exposed by Read.
reader io.Reader
// zr is closed by Close.
zr io.Closer
}
func (reader *objectReader) Read(dst []byte) (int, error) {
return reader.reader.Read(dst)
}
func (reader *objectReader) Close() error {
return reader.zr.Close()
}