mirror of
https://github.com/diamondburned/cchat-gtk.git
synced 2024-11-01 03:54:16 +00:00
243 lines
5.7 KiB
Go
243 lines
5.7 KiB
Go
|
package container
|
||
|
|
||
|
import (
|
||
|
"github.com/diamondburned/cchat"
|
||
|
"github.com/google/btree"
|
||
|
)
|
||
|
|
||
|
// messageStore implements various data structures for optimized message get and
|
||
|
// insert.
|
||
|
type messageStore struct {
|
||
|
msgTree btree.BTree
|
||
|
messageIDs map[string]*gridMessage
|
||
|
messageNonces map[string]*gridMessage
|
||
|
}
|
||
|
|
||
|
func newMessageStore() *messageStore {
|
||
|
return &messageStore{
|
||
|
msgTree: *btree.New(2),
|
||
|
messageIDs: make(map[string]*gridMessage, 100),
|
||
|
messageNonces: make(map[string]*gridMessage, 5),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (ms *messageStore) Len() int {
|
||
|
return ms.msgTree.Len()
|
||
|
}
|
||
|
|
||
|
// InsertMessage inserts the message into the store and return the new index.
|
||
|
func (ms *messageStore) InsertMessage(msg *gridMessage) int {
|
||
|
return ms.replaceMessage(msg, false)
|
||
|
}
|
||
|
|
||
|
// SwapMessage overrides the old message with the same ID with the given one. It
|
||
|
// returns an index if the message is replaced, or -1 if the message is not.
|
||
|
func (ms *messageStore) SwapMessage(msg *gridMessage) int {
|
||
|
return ms.replaceMessage(msg, true)
|
||
|
}
|
||
|
|
||
|
func (ms *messageStore) replaceMessage(msg *gridMessage, replaceOnly bool) int {
|
||
|
var ix = -1
|
||
|
|
||
|
// Guarantee that no new messages are added.
|
||
|
if replaced := ms.msgTree.ReplaceOrInsert(msg); replaced == nil && replaceOnly {
|
||
|
// Nil is returned, meaning a new message is added. This is bad.
|
||
|
ms.msgTree.Delete(msg)
|
||
|
return ix
|
||
|
}
|
||
|
|
||
|
var id = msg.ID()
|
||
|
if id != "" {
|
||
|
ms.messageIDs[id] = msg
|
||
|
delete(ms.messageNonces, msg.Nonce()) // superfluous guarantee
|
||
|
} else {
|
||
|
// Assume nonce is non-empty. Probably not a good idea.
|
||
|
ms.messageNonces[msg.Nonce()] = msg
|
||
|
}
|
||
|
|
||
|
insertAt := ms.msgTree.Len() - 1
|
||
|
|
||
|
ms.msgTree.Descend(func(item btree.Item) bool {
|
||
|
if id == item.(*gridMessage).ID() {
|
||
|
ix = insertAt
|
||
|
return false // break
|
||
|
}
|
||
|
insertAt--
|
||
|
return true
|
||
|
})
|
||
|
|
||
|
return ix
|
||
|
}
|
||
|
|
||
|
func (ms *messageStore) MessageBefore(id cchat.ID) GridMessage {
|
||
|
return ms.getOffsetted(id, true)
|
||
|
}
|
||
|
|
||
|
func (ms *messageStore) MessageAfter(id cchat.ID) GridMessage {
|
||
|
return ms.getOffsetted(id, false)
|
||
|
}
|
||
|
|
||
|
// getOffsetted returns the unwrapped message.
|
||
|
func (ms *messageStore) getOffsetted(id cchat.ID, before bool) GridMessage {
|
||
|
var last, found *gridMessage
|
||
|
var next bool
|
||
|
|
||
|
// We need to ascend, as next and before implies ascending order from 0 to
|
||
|
// last.
|
||
|
ms.msgTree.Ascend(func(item btree.Item) bool {
|
||
|
message := item.(*gridMessage)
|
||
|
if next {
|
||
|
found = message
|
||
|
return false // break
|
||
|
}
|
||
|
if message.ID() == id {
|
||
|
if before {
|
||
|
found = last
|
||
|
return false // break
|
||
|
} else {
|
||
|
next = true
|
||
|
return true
|
||
|
}
|
||
|
}
|
||
|
last = message
|
||
|
return true
|
||
|
})
|
||
|
|
||
|
if found == nil {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
return found.GridMessage
|
||
|
}
|
||
|
|
||
|
// FirstMessage returns the earliest message.
|
||
|
func (ms *messageStore) FirstMessage() *gridMessage {
|
||
|
return ms.msgTree.Min().(*gridMessage)
|
||
|
}
|
||
|
|
||
|
// LastMessage returns the latest message.
|
||
|
func (ms *messageStore) LastMessage() *gridMessage {
|
||
|
return ms.msgTree.Max().(*gridMessage)
|
||
|
}
|
||
|
|
||
|
// NthMessage returns the nth message ordered from earliest to latest. It is
|
||
|
// fairly slow.
|
||
|
func (ms *messageStore) NthMessage(n int) (message *gridMessage) {
|
||
|
insertAt := ms.msgTree.Len() - 1
|
||
|
|
||
|
ms.msgTree.Descend(func(item btree.Item) bool {
|
||
|
if n == insertAt {
|
||
|
message = item.(*gridMessage)
|
||
|
return false // break
|
||
|
}
|
||
|
insertAt--
|
||
|
return true
|
||
|
})
|
||
|
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// LastMessageFrom returns the latest message with the given user ID. This is
|
||
|
// used for the input prompt.
|
||
|
func (ms *messageStore) LastMessageFrom(userID string) GridMessage {
|
||
|
return ms.FindMessage(func(gridMsg GridMessage) bool {
|
||
|
return gridMsg.AuthorID() == userID
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// FindMessage implicitly unwraps the GridMessage before passing it into the
|
||
|
// handler and returning it.
|
||
|
func (ms *messageStore) FindMessage(isMessage func(GridMessage) bool) (found GridMessage) {
|
||
|
ms.msgTree.Descend(func(item btree.Item) bool {
|
||
|
message := item.(*gridMessage)
|
||
|
// Ignore sending messages.
|
||
|
if message.presend != nil {
|
||
|
return true
|
||
|
}
|
||
|
if unwrapped := message.GridMessage; isMessage(unwrapped) {
|
||
|
found = unwrapped
|
||
|
return false
|
||
|
}
|
||
|
return true
|
||
|
})
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func (ms *messageStore) get(id, nonce string) *gridMessage {
|
||
|
if id != "" {
|
||
|
m, ok := ms.messageIDs[id]
|
||
|
if ok {
|
||
|
return m
|
||
|
}
|
||
|
}
|
||
|
|
||
|
m, ok := ms.messageNonces[nonce]
|
||
|
if ok {
|
||
|
return m
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (ms *messageStore) Message(msgID cchat.ID, nonce string) *gridMessage {
|
||
|
message := ms.get(msgID, nonce)
|
||
|
|
||
|
// If the message was obtained from a nonce, then try to move it off.
|
||
|
if nonce != "" && message != nil {
|
||
|
// Move the message from nonce state to ID.
|
||
|
delete(ms.messageNonces, nonce)
|
||
|
ms.messageIDs[msgID] = message
|
||
|
|
||
|
// Set the right ID.
|
||
|
message.presend.SetDone(msgID)
|
||
|
// Destroy the presend struct.
|
||
|
message.presend = nil
|
||
|
}
|
||
|
|
||
|
return message
|
||
|
}
|
||
|
|
||
|
func (ms *messageStore) DeleteMessage(msgID cchat.ID) {
|
||
|
m, ok := ms.messageIDs[msgID]
|
||
|
if ok {
|
||
|
ms.msgTree.Delete(m)
|
||
|
delete(ms.messageIDs, msgID)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (ms *messageStore) PopMessage(id cchat.ID) (popped *gridMessage, ix int) {
|
||
|
ix = ms.msgTree.Len() - 1
|
||
|
|
||
|
ms.msgTree.Descend(func(item btree.Item) bool {
|
||
|
if gridMsg := item.(*gridMessage); id == gridMsg.ID() {
|
||
|
popped = gridMsg
|
||
|
|
||
|
// Delete off of the state.
|
||
|
ms.msgTree.Delete(item)
|
||
|
delete(ms.messageIDs, id)
|
||
|
delete(ms.messageNonces, popped.Nonce()) // superfluous
|
||
|
|
||
|
return false // break
|
||
|
}
|
||
|
ix--
|
||
|
return true
|
||
|
})
|
||
|
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// PopEarliestMessages pops the n earliest messages. n can be less than or equal
|
||
|
// to 0, which would be a no-op.
|
||
|
func (ms *messageStore) PopEarliestMessages(n int) (poppedIxs int) {
|
||
|
for ; n > 0 && ms.Len() > 0; n-- {
|
||
|
gridMsg := ms.msgTree.DeleteMin().(*gridMessage)
|
||
|
delete(ms.messageIDs, gridMsg.ID())
|
||
|
delete(ms.messageNonces, gridMsg.Nonce())
|
||
|
|
||
|
// We can keep incrementing the index as we delete things. This is
|
||
|
// because we're deleting from 0 and up.
|
||
|
poppedIxs++
|
||
|
}
|
||
|
return
|
||
|
}
|