-// 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
-}
-
-// 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{}, 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
-}
-
-// Wait returns when no build runs. The failed builds wait for the next message.
-func (m *Memory) Wait(ctx context.Context) error {
- for {
- m.mutex.Lock()
-
- running := m.queue.Running()
- changed := m.changed
- m.mutex.Unlock()
-
- if running == 0 {
- return nil
- }
-
- select {
- case <-changed:
- case <-ctx.Done():
- return ctx.Err()
- }
- }
-}
-
-// Settle returns when the chat view has no pending line, so that every message
-// so far is summarized. A pending line with no build that runs belongs to a
-// failed build, which waits for the next message, so Settle returns
-// errMemoryUnsettled in place of a wait that never ends.
-func (m *Memory) Settle(ctx context.Context) error {
- for {
- m.mutex.Lock()
-
- pending := m.chat.Pending()
- running := m.queue.Running()
- changed := m.changed
- m.mutex.Unlock()
-
- if !pending {
- return nil
- }
-
- if running == 0 {
- return errMemoryUnsettled
- }
-
- select {
- case <-changed:
- case <-ctx.Done():
- return ctx.Err()
- }
- }
-}
-
-// Flush returns when every write before the call is durable.
-func (m *Memory) Flush(ctx context.Context) error {
- return m.syncer.Flush(ctx)
-}
-
-// Snapshot flushes all memory writes and runs snapshot while no append or
-// summary write can change the log, indexes, tree, or views. snapshot must not
-// call Memory.
-func (m *Memory) Snapshot(ctx context.Context, snapshot func() error) error {
- m.mutex.Lock()
- defer m.mutex.Unlock()
-
- err := m.Flush(ctx)
- if err != nil {
- return fmt.Errorf("flush memory snapshot: %w", err)
- }
-
- err = snapshot()
- if err != nil {
- return fmt.Errorf("copy memory snapshot: %w", err)
- }
-
- return nil
-}
-
-// ViewLines returns the lines of the chat view, each with its newline, without
-// the leaf lines of the messages in skip.
-func (m *Memory) ViewLines(skip map[int]bool) []string {
- m.mutex.Lock()
- defer m.mutex.Unlock()
-
- return m.chat.LinesWithout(skip)
-}
-
-// Len returns the number of messages in the log.
-func (m *Memory) Len() int {
- m.mutex.Lock()
- defer m.mutex.Unlock()
-
- return m.log.Len()
-}
-
-// Message reads the message with the given ID.
-func (m *Memory) Message(id int) (Message, error) {
- m.mutex.Lock()
- defer m.mutex.Unlock()
-
- return m.log.Message(id)
-}
-
-// Node reads the node (l, i) if it is built.
-func (m *Memory) Node(level, index int) (Node, bool, error) {
- m.mutex.Lock()
- defer m.mutex.Unlock()
-
- return m.tree.Node(level, index)
-}
-