aboutsummaryrefslogtreecommitdiff
path: root/object/store/loose/write_writer.go
blob: 0d6b5b80377af27d988edb22e9ddbbb7e22c8a3a (about) (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
package loose

import (
	"errors"
	"hash"
	"os"

	"codeberg.org/lindenii/furgit/internal/compress/zlib"
)

const tempObjectFilePrefix = "tmp_obj_"

// streamWriter incrementally hashes and deflates an object into a temp file.
// Finalize validates size accounting and atomically renames the temp file.
type streamWriter struct {
	// store owns path and root operations used by this write session.
	store *Store
	// file is the temporary destination file under objects/.
	file *os.File
	// zw compresses raw object bytes into file.
	zw *zlib.Writer
	// hash receives the same raw bytes used to compute the resulting object ID.
	hash hash.Hash

	// tmpRelPath is the relative path of file under the objects root.
	tmpRelPath string

	// fullMode selects full-object input ("type size\0content") as opposed to content-only input.
	fullMode bool

	// headerBuf accumulates header bytes while fullMode parses up to the first NUL.
	headerBuf []byte
	// headerDone reports whether the full-object header has been parsed.
	headerDone bool
	// expectedContentLeft tracks remaining declared content bytes.
	expectedContentLeft int64

	closed    bool
	finalized bool
}

// newStreamWriter creates a stream writer with a temp file rooted in objects/.
func (store *Store) newStreamWriter(fullMode bool) (*streamWriter, error) {
	hashFn, err := store.algo.New()
	if err != nil {
		return nil, err
	}

	tmpRelPath, file, err := store.createTempObjectFile(".")
	if err != nil {
		return nil, err
	}

	return &streamWriter{
		store:      store,
		file:       file,
		zw:         zlib.NewWriter(file),
		hash:       hashFn,
		tmpRelPath: tmpRelPath,
		fullMode:   fullMode,
		headerBuf:  make([]byte, 0, 64),
	}, nil
}

// Write validates and writes raw bytes into the stream.
// In full mode, it parses and enforces the streamed header-declared content size.
func (writer *streamWriter) Write(src []byte) (int, error) {
	if writer.finalized {
		return 0, errors.New("objectstore/loose: write after finalize")
	}

	if writer.closed {
		return 0, errors.New("objectstore/loose: write after close")
	}

	if writer.fullMode {
		err := writer.acceptFull(src)
		if err != nil {
			return 0, err
		}
	} else {
		err := writer.acceptContent(int64(len(src)))
		if err != nil {
			return 0, err
		}
	}

	err := writer.writeRawChunk(src)
	if err != nil {
		return 0, err
	}

	return len(src), nil
}