aboutsummaryrefslogtreecommitdiff
path: root/object/store/packed/reader.go
diff options
context:
space:
mode:
Diffstat (limited to 'object/store/packed/reader.go')
-rw-r--r--object/store/packed/reader.go226
1 files changed, 226 insertions, 0 deletions
diff --git a/object/store/packed/reader.go b/object/store/packed/reader.go
new file mode 100644
index 00000000..cf433cfc
--- /dev/null
+++ b/object/store/packed/reader.go
@@ -0,0 +1,226 @@
+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()
+}