+// ready tells if both children of the node key are built and the node is not.
+func (m *Memory) ready(key NodeKey) (bool, error) {
+ for _, index := range []int{2 * key.index, 2*key.index + 1} {
+ built, err := m.tree.Built(key.level-1, index)
+ if err != nil || !built {
+ if err != nil {
+ return false, fmt.Errorf("check child readiness: %w", err)
+ }
+
+ return false, nil
+ }
+ }
+
+ built, err := m.tree.Built(key.level, key.index)
+ if err != nil {
+ return !built, fmt.Errorf("check parent readiness: %w", err)
+ }
+
+ return !built, nil
+}
+
+// children reads the two children of the node key.
+func (m *Memory) children(key NodeKey) (Node, Node, error) {
+ var children [2]Node
+
+ for n := range children {
+ child, built, err := m.tree.Node(key.level-1, 2*key.index+n)
+ if err != nil {
+ return Node{}, Node{}, fmt.Errorf("read child node: %w", err)
+ }
+
+ if !built {
+ return Node{}, Node{}, fmt.Errorf("node (%d, %d) is not built", key.level-1, 2*key.index+n)
+ }
+
+ children[n] = child
+ }
+
+ return children[0], children[1], nil
+}
+
+func (m *Memory) context(level, index int) string {
+ end := index
+ if level > 0 {
+ end = nodeEnd(level, index)
+ }
+
+ return m.cview.Context(end)
+}
+
+// Append logs a new message, adds it to the tree and the views, and starts the
+// builds that are ready, also the failed ones.
+func (m *Memory) Append(kind, text string) (Message, error) {
+ return m.AppendDated(kind, text, "")
+}
+
+// AppendDated appends a message with its RFC 3339 date and builds its nodes. An
+// empty date uses the clock, as Append does.
+func (m *Memory) AppendDated(kind, text, date string) (Message, error) {
+ m.mutex.Lock()
+ defer m.mutex.Unlock()
+
+ return m.appendDated(kind, text, date)
+}
+
+// appendDated appends under the memory lock and updates the views and builds.
+func (m *Memory) appendDated(kind, text, date string) (Message, error) {
+ message, err := m.log.AppendDated(kind, text, date)
+ if err != nil {
+ return Message{}, fmt.Errorf("append dated log message: %w", err)
+ }
+
+ defer m.syncer.Written()
+
+ err = m.addMessage(message)
+ if err != nil {
+ return Message{}, fmt.Errorf("add dated message to memory: %w", err)
+ }
+
+ err = m.save()
+ if err != nil {
+ return Message{}, fmt.Errorf("save dated message view: %w", err)
+ }
+
+ m.queue.Retry()
+ m.dispatch()
+
+ return message, nil
+}
+
+// AppendOnce appends a queued message. prepare saves its log ID before the
+// append, under the memory lock. A saved ID permits recovery after a stop
+// between that save and the append, without a second log entry. prepare must
+// not call Memory. The caller stops delivery after a failed append.
+func (m *Memory) AppendOnce(kind, text string, id *int, prepare func(int) error) (Message, error) {
+ m.mutex.Lock()
+ defer m.mutex.Unlock()
+
+ if id != nil && *id < m.log.Len() {
+ message, err := m.log.Message(*id)
+ if err != nil {
+ return Message{}, fmt.Errorf("read queued message: %w", err)
+ }
+
+ if message.Kind != kind || message.Text != text {
+ return Message{}, errors.New("queued message does not match its log entry")
+ }
+
+ return message, nil
+ }
+
+ if id != nil && *id != m.log.Len() {
+ return Message{}, errors.New("queued message is beyond the log")
+ }
+
+ err := prepare(m.log.Len())
+ if err != nil {
+ return Message{}, fmt.Errorf("prepare queued message: %w", err)
+ }
+
+ message, err := m.appendDated(kind, text, "")
+ if err != nil {
+ return Message{}, fmt.Errorf("append queued message: %w", err)
+ }
+
+ return message, nil
+}
+