return t, nil
}
-// Close closes the files of the tree.
-func (t *Tree) Close() error {
- err := t.journal.Close()
- if t.order != nil {
- err = errors.Join(err, t.order.Close())
- t.order = nil
- }
-
- for level, index := range t.levels {
- err = errors.Join(err, index.Close())
-
- delete(t.levels, level)
- }
-
- return err
-}
-
-// Add writes a new node to the tree.
-func (t *Tree) Add(node Node) error {
- if t.failure != nil {
- return fmt.Errorf("tree needs a reopen after a failed write: %w", t.failure)
- }
-
- err := t.check(node)
- if err != nil {
- return err
- }
-
- line, err := json.Marshal(node)
- if err != nil {
- return fmt.Errorf("encode node: %w", err)
- }
-
- place, err := t.journal.Write(t.clock().Format(time.DateOnly), append(line, '\n'))
- if err == nil {
- err = t.addIndex(NodeKey{node.Level, node.Index}, place)
- }
-
- if err != nil {
- t.failure = err
- return fmt.Errorf("write node (%d, %d): %w", node.Level, node.Index, err)
- }
-
- return nil
-}
-
-// Built tells if the node (l, i) is built.
-func (t *Tree) Built(level, index int) (bool, error) {
- _, built, err := t.place(level, index)
- return built, err
-}
-
-// Node reads the node (l, i) if it is built.
-func (t *Tree) Node(level, index int) (Node, bool, error) {
- place, built, err := t.place(level, index)
- if err != nil || !built {
- return Node{}, false, err
- }
-
- line, err := t.journal.Read(place)
- if err != nil {
- return Node{}, false, fmt.Errorf("read node (%d, %d): %w", level, index, err)
- }
-
- var node Node
-
- err = json.Unmarshal(line, &node)
- if err != nil {
- return Node{}, false, fmt.Errorf("decode node (%d, %d): %w", level, index, err)
- }
-
- if node.Level != level || node.Index != index {
- return Node{}, false, fmt.Errorf("index of node (%d, %d) points to node (%d, %d)", level, index, node.Level, node.Index)
- }
-
- return node, true, nil
-}
-
-// Keys returns the keys of the built nodes, ordered by level and then by index.
-func (t *Tree) Keys() ([]NodeKey, error) {
- keys := make([]NodeKey, 0, t.order.count)
- for n := range t.order.count {
- record, _, err := t.order.Read(n)
- if err != nil {
- return nil, err
- }
-
- key, _ := treeDecodeRecord(record)
- keys = append(keys, key)
- }
-
- slices.SortFunc(keys, func(a, b NodeKey) int {
- return cmp.Or(cmp.Compare(a.level, b.level), cmp.Compare(a.index, b.index))
- })
-
- return keys, nil
-}
-
func (t *Tree) open(ctx context.Context, total int) error {
var err error
t.order, err = openRecords(filepath.Join(t.dir, "tree.idx"), treeRecordBytes)
if err != nil {
- return err
+ return fmt.Errorf("open node order index: %w", err)
}
start := Position{}
if t.order.count == 0 {
err = t.removeLevels()
if err != nil {
- return err
+ return fmt.Errorf("remove empty level indexes: %w", err)
}
} else {
record, _, err := t.order.Read(t.order.count - 1)
if err != nil {
- return err
+ return fmt.Errorf("read last node order: %w", err)
}
// A crash can stop an add between the two index writes, so the
node, last := treeDecodeRecord(record)
err = t.writeLevel(node, last)
if err != nil {
- return err
+ return fmt.Errorf("restore last node level: %w", err)
}
start = Position{
})
}
+// Close closes the files of the tree.
+func (t *Tree) Close() error {
+ err := t.journal.Close()
+ if t.order != nil {
+ err = errors.Join(err, t.order.Close())
+ t.order = nil
+ }
+
+ for level, index := range t.levels {
+ err = errors.Join(err, index.Close())
+
+ delete(t.levels, level)
+ }
+
+ return err
+}
+
+// removeLevels removes the level indexes, which are not valid without the order
+// index.
+func (t *Tree) removeLevels() error {
+ paths, err := filepath.Glob(filepath.Join(t.dir, "tree-*.idx"))
+ if err != nil {
+ return fmt.Errorf("list level indexes: %w", err)
+ }
+
+ for _, path := range paths {
+ err = os.Remove(path)
+ if err != nil {
+ return fmt.Errorf("remove level index: %w", err)
+ }
+ }
+
+ return nil
+}
+
+// writeLevel writes the position of node to its level index.
+func (t *Tree) writeLevel(node NodeKey, place Position) error {
+ level, err := t.level(node.level)
+ if err != nil {
+ return fmt.Errorf("open node level index: %w", err)
+ }
+
+ record := make([]byte, positionBytes)
+ encodePosition(record, place)
+
+ return level.Write(node.index, record)
+}
+
// check makes sure that node is valid and can join the tree now.
func (t *Tree) check(node Node) error {
if node.Level < 0 || node.Level >= bits.UintSize-2 || node.Index < 0 {
built, err := t.Built(node.Level, node.Index)
if err != nil {
- return err
+ return fmt.Errorf("check target node: %w", err)
}
if built {
for _, index := range []int{2 * node.Index, 2*node.Index + 1} {
built, err = t.Built(node.Level-1, index)
if err != nil {
- return err
+ return fmt.Errorf("check child node: %w", err)
}
if !built {
err := t.order.Append(record)
if err != nil {
- return err
+ return fmt.Errorf("append node order: %w", err)
}
return t.writeLevel(node, place)
}
+// level returns the index of level, and opens it at first use.
+func (t *Tree) level(level int) (*Records, error) {
+ index, open := t.levels[level]
+ if open {
+ return index, nil
+ }
+
+ index, err := openRecords(filepath.Join(t.dir, fmt.Sprintf("tree-%d.idx", level)), positionBytes)
+ if err != nil {
+ return nil, err
+ }
+
+ t.levels[level] = index
+
+ return index, nil
+}
+
+// Built tells if the node (l, i) is built.
+func (t *Tree) Built(level, index int) (bool, error) {
+ _, built, err := t.place(level, index)
+ return built, err
+}
+
// place returns the position of the node (l, i) if it is built.
func (t *Tree) place(level, index int) (Position, bool, error) {
if level < 0 || level >= bits.UintSize-2 || index < 0 {
records, err := t.level(level)
if err != nil {
- return Position{}, false, err
+ return Position{}, false, fmt.Errorf("open node level index: %w", err)
}
record, found, err := records.Read(index)
if err != nil || !found {
- return Position{}, false, err
+ if err != nil {
+ return Position{}, false, fmt.Errorf("read node level record: %w", err)
+ }
+
+ return Position{}, false, nil
}
place := decodePosition(record)
return place, place.length > 0, nil
}
-// removeLevels removes the level indexes, which are not valid without the order
-// index.
-func (t *Tree) removeLevels() error {
- paths, err := filepath.Glob(filepath.Join(t.dir, "tree-*.idx"))
+// Add writes a new node to the tree.
+func (t *Tree) Add(node Node) error {
+ if t.failure != nil {
+ return fmt.Errorf("tree needs a reopen after a failed write: %w", t.failure)
+ }
+
+ err := t.check(node)
if err != nil {
- return fmt.Errorf("list level indexes: %w", err)
+ return fmt.Errorf("validate node: %w", err)
}
- for _, path := range paths {
- err = os.Remove(path)
- if err != nil {
- return fmt.Errorf("remove level index: %w", err)
- }
+ line, err := json.Marshal(node)
+ if err != nil {
+ return fmt.Errorf("encode node: %w", err)
+ }
+
+ place, err := t.journal.Write(t.clock().Format(time.DateOnly), append(line, '\n'))
+ if err == nil {
+ err = t.addIndex(NodeKey{node.Level, node.Index}, place)
+ }
+
+ if err != nil {
+ t.failure = err
+ return fmt.Errorf("write node (%d, %d): %w", node.Level, node.Index, err)
}
return nil
}
-// writeLevel writes the position of node to its level index.
-func (t *Tree) writeLevel(node NodeKey, place Position) error {
- level, err := t.level(node.level)
+// Node reads the node (l, i) if it is built.
+func (t *Tree) Node(level, index int) (Node, bool, error) {
+ place, built, err := t.place(level, index)
+ if err != nil || !built {
+ if err != nil {
+ return Node{}, false, fmt.Errorf("find node position: %w", err)
+ }
+
+ return Node{}, false, nil
+ }
+
+ line, err := t.journal.Read(place)
if err != nil {
- return err
+ return Node{}, false, fmt.Errorf("read node (%d, %d): %w", level, index, err)
}
- record := make([]byte, positionBytes)
- encodePosition(record, place)
+ var node Node
- return level.Write(node.index, record)
-}
+ err = json.Unmarshal(line, &node)
+ if err != nil {
+ return Node{}, false, fmt.Errorf("decode node (%d, %d): %w", level, index, err)
+ }
-// level returns the index of level, and opens it at first use.
-func (t *Tree) level(level int) (*Records, error) {
- index, open := t.levels[level]
- if open {
- return index, nil
+ if node.Level != level || node.Index != index {
+ return Node{}, false, fmt.Errorf("index of node (%d, %d) points to node (%d, %d)", level, index, node.Level, node.Index)
}
- index, err := openRecords(filepath.Join(t.dir, fmt.Sprintf("tree-%d.idx", level)), positionBytes)
- if err != nil {
- return nil, err
+ return node, true, nil
+}
+
+// Keys returns the keys of the built nodes, ordered by level and then by index.
+func (t *Tree) Keys() ([]NodeKey, error) {
+ keys := make([]NodeKey, 0, t.order.count)
+ for n := range t.order.count {
+ record, _, err := t.order.Read(n)
+ if err != nil {
+ return nil, err
+ }
+
+ key, _ := treeDecodeRecord(record)
+ keys = append(keys, key)
}
- t.levels[level] = index
+ slices.SortFunc(keys, func(a, b NodeKey) int {
+ return cmp.Or(cmp.Compare(a.level, b.level), cmp.Compare(a.index, b.index))
+ })
- return index, nil
+ return keys, nil
}
// Count returns the number of built nodes.
return t.order.count
}
-// leafSource returns the text that the leaf of message holds or summarizes.
-func leafSource(message Message) string {
- return message.Kind + ": " + message.Text
+// treeDecodeRecord reads the key and the position of a record of
+// index/tree.idx.
+func treeDecodeRecord(record []byte) (NodeKey, Position) {
+ key := NodeKey{
+ level: int(binary.LittleEndian.Uint32(record[positionBytes+8:])),
+ index: int(binary.LittleEndian.Uint64(record[positionBytes:])),
+ }
+
+ return key, decodePosition(record)
+}
+
+// nodeEnd returns the ID after the last message of node (l, i).
+func nodeEnd(level, index int) int {
+ return (index + 1) << level
}
// freeLeaf returns a text-only leaf when its source fits in a node. Images need
}, true
}
+// leafSource returns the text that the leaf of message holds or summarizes.
+func leafSource(message Message) string {
+ return message.Kind + ": " + message.Text
+}
+
// freeMerge returns the parent of two sibling nodes when both texts, joined by
// a newline, fit in a node. Such a parent needs no model call.
func freeMerge(left, right Node) (Node, bool) {
Size: len(text),
}, true
}
-
-// nodeEnd returns the ID after the last message of node (l, i).
-func nodeEnd(level, index int) int {
- return (index + 1) << level
-}
-
-// treeDecodeRecord reads the key and the position of a record of
-// index/tree.idx.
-func treeDecodeRecord(record []byte) (NodeKey, Position) {
- key := NodeKey{
- level: int(binary.LittleEndian.Uint32(record[positionBytes+8:])),
- index: int(binary.LittleEndian.Uint64(record[positionBytes:])),
- }
-
- return key, decodePosition(record)
-}