X-Git-Url: https://git.giacomolli.uk/onechat/blobdiff_plain/80e348724041a694002fc38f57e8868b9b9a2be9..f4509310b5abe5012cb83bed3960404e8fa92217:/tree.go?ds=inline diff --git a/tree.go b/tree.go index ad30eeb..765016e 100644 --- a/tree.go +++ b/tree.go @@ -88,122 +88,24 @@ func openTree(ctx context.Context, root string, total int, clock func() time.Tim 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 @@ -211,7 +113,7 @@ func (t *Tree) open(ctx context.Context, total int) error { 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{ @@ -220,7 +122,7 @@ func (t *Tree) open(ctx context.Context, total int) error { } } - return t.journal.Scan(ctx, start, func(line []byte, place Position) error { + err = t.journal.Scan(ctx, start, func(line []byte, place Position) error { var node Node err := json.Unmarshal(line, &node) @@ -230,15 +132,78 @@ func (t *Tree) open(ctx context.Context, total int) error { err = t.check(node) if err != nil { - return err + return fmt.Errorf("validate scanned node: %w", err) } if nodeEnd(node.Level, node.Index) > total { return fmt.Errorf("node (%d, %d) covers messages after the log", node.Level, node.Index) } - return t.addIndex(NodeKey{node.Level, node.Index}, place) + err = t.addIndex(NodeKey{node.Level, node.Index}, place) + if err != nil { + return fmt.Errorf("index scanned node: %w", err) + } + + return nil }) + if err != nil { + return fmt.Errorf("scan node journal: %w", err) + } + + return 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 +} + +// 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) + + err = level.Write(node.index, record) + if err != nil { + return fmt.Errorf("write node level record: %w", err) + } + + return nil } // check makes sure that node is valid and can join the tree now. @@ -253,7 +218,7 @@ func (t *Tree) check(node Node) error { built, err := t.Built(node.Level, node.Index) if err != nil { - return err + return fmt.Errorf("check target node: %w", err) } if built { @@ -267,7 +232,7 @@ func (t *Tree) check(node Node) error { 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 { @@ -287,10 +252,38 @@ func (t *Tree) addIndex(node NodeKey, place Position) error { err := t.order.Append(record) if err != nil { - return err + return fmt.Errorf("append node order: %w", err) + } + + err = t.writeLevel(node, place) + if err != nil { + return fmt.Errorf("write indexed node level: %w", err) } - return t.writeLevel(node, place) + return nil +} + +// 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. @@ -301,12 +294,16 @@ func (t *Tree) place(level, index int) (Position, bool, error) { 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) @@ -314,52 +311,83 @@ func (t *Tree) place(level, index int) (Position, bool, error) { 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. @@ -367,9 +395,20 @@ func (t *Tree) Count() int { 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 @@ -388,6 +427,11 @@ func freeLeaf(message Message) (Node, bool) { }, 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) { @@ -404,19 +448,3 @@ 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) -}