From 564c8ecc84ed1bfc28ea3a0251020051906b8548 Mon Sep 17 00:00:00 2001 From: Runxi Yu Date: Sat, 21 Feb 2026 04:55:02 +0800 Subject: objectstore/loose: Add streaming writer --- objectstore/loose/write_bytes.go | 109 ++++++--------------------------------- 1 file changed, 15 insertions(+), 94 deletions(-) (limited to 'objectstore/loose/write_bytes.go') diff --git a/objectstore/loose/write_bytes.go b/objectstore/loose/write_bytes.go index fe2bafb9..1f7ab59f 100644 --- a/objectstore/loose/write_bytes.go +++ b/objectstore/loose/write_bytes.go @@ -1,123 +1,44 @@ package loose import ( - "compress/zlib" - "crypto/rand" - "errors" - "fmt" - "io/fs" - "os" - "path/filepath" + "bytes" - "codeberg.org/lindenii/furgit/objectheader" "codeberg.org/lindenii/furgit/objectid" "codeberg.org/lindenii/furgit/objecttype" ) -const tempObjectFilePrefix = "tmp_obj_" - // WriteBytesFull writes a full serialized object as "type size\\x00content". func (store *Store) WriteBytesFull(raw []byte) (objectid.ObjectID, error) { var zero objectid.ObjectID - if _, _, err := parseRaw(raw); err != nil { + writer, finalize, err := store.WriteWriterFull() + if err != nil { return zero, err } - - id := store.algo.Sum(raw) - relPath, err := store.objectPath(id) - if err != nil { + if _, err := bytes.NewReader(raw).WriteTo(writer); err != nil { + _ = writer.Close() return zero, err } - if err := store.writeCompressedAtomic(relPath, raw); err != nil { + if err := writer.Close(); err != nil { return zero, err } - return id, nil + return finalize() } // WriteBytesContent writes typed content bytes as a loose object. func (store *Store) WriteBytesContent(ty objecttype.Type, content []byte) (objectid.ObjectID, error) { var zero objectid.ObjectID - header, ok := objectheader.Encode(ty, int64(len(content))) - if !ok { - return zero, fmt.Errorf("objectstore/loose: failed to encode object header for type %d", ty) - } - - raw := make([]byte, len(header)+len(content)) - copy(raw, header) - copy(raw[len(header):], content) - return store.WriteBytesFull(raw) -} - -// writeCompressedAtomic compresses raw and writes it to relPath atomically. -func (store *Store) writeCompressedAtomic(relPath string, raw []byte) error { - if _, err := store.root.Stat(relPath); err == nil { - return nil - } else if !errors.Is(err, fs.ErrNotExist) { - return err - } - - dir := filepath.Dir(relPath) - if err := store.root.MkdirAll(dir, 0o755); err != nil { - return err - } - - tmpRelPath, tmpFile, err := store.createTempObjectFile(dir) + writer, finalize, err := store.WriteWriterContent(ty, int64(len(content))) if err != nil { - return err - } - - cleanup := true - defer func() { - if tmpFile != nil { - _ = tmpFile.Close() - } - if cleanup { - _ = store.root.Remove(tmpRelPath) - } - }() - - zw := zlib.NewWriter(tmpFile) - if _, err := zw.Write(raw); err != nil { - _ = zw.Close() - return err - } - if err := zw.Close(); err != nil { - return err - } - if err := tmpFile.Sync(); err != nil { - return err - } - if err := tmpFile.Close(); err != nil { - return err + return zero, err } - tmpFile = nil - - if err := store.root.Rename(tmpRelPath, relPath); err != nil { - if errors.Is(err, fs.ErrExist) { - return nil - } - return err + if _, err := bytes.NewReader(content).WriteTo(writer); err != nil { + _ = writer.Close() + return zero, err } - - cleanup = false - return nil -} - -// createTempObjectFile creates a unique temporary object file within dir. -func (store *Store) createTempObjectFile(dir string) (string, *os.File, error) { - for range 16 { - relPath := filepath.Join(dir, tempObjectFilePrefix+rand.Text()) - file, err := store.root.OpenFile(relPath, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0o644) - if err == nil { - return relPath, file, nil - } - if errors.Is(err, fs.ErrExist) { - continue - } - return "", nil, err + if err := writer.Close(); err != nil { + return zero, err } - - return "", nil, errors.New("objectstore/loose: failed to create temporary object file") + return finalize() } -- cgit v1.3.1-10-gc9f91