diff --git a/state/store/defaultstore/message.go b/state/store/defaultstore/message.go index 1c7f140..5fa7b0e 100644 --- a/state/store/defaultstore/message.go +++ b/state/store/defaultstore/message.go @@ -81,7 +81,6 @@ func (s *Message) MessageSet(message discord.Message) error { msgs.mut.Lock() defer msgs.mut.Unlock() - // Check if we already have the message. for i, m := range msgs.messages { if m.ID == message.ID { DiffMessage(message, &m) @@ -92,9 +91,33 @@ func (s *Message) MessageSet(message discord.Message) error { // Order: latest to earliest, similar to the API. - var end = len(msgs.messages) - if max := s.MaxMessages(); end >= max { - // If the end (length) is larger than the maximum amount, then cap it. + // Check if we already have the message. Try to derive the order otherwise. + var insertAt int + + // Since we make the order guarantee ourselves, we can trust that we're + // iterating from latest to earliest. + for insertAt < len(msgs.messages) { + // Check if the new message is older. If it is, then we should insert it + // right after this message (or before this message in the list; i-1). + if message.ID > msgs.messages[insertAt].ID { + break + } + + insertAt++ + } + + end := len(msgs.messages) + max := s.MaxMessages() + + if end == max { + // If insertAt is larger than the length, then the message is older than + // every other messages we have. We have to discard this message here, + // since the store is already full. + if insertAt == end { + return nil + } + + // If the end (length) is approaching the maximum amount, then cap it. end = max } else { // Else, append an empty message to the end. @@ -103,11 +126,13 @@ func (s *Message) MessageSet(message discord.Message) error { end++ } - // Copy hack to prepend. This copies the 0th-(end-1)th entries to - // 1st-endth. - copy(msgs.messages[1:end], msgs.messages[0:end-1]) - // Then, set the 0th entry. - msgs.messages[0] = message + // Shift the slice right-wards if the current item is not the last. + if start := insertAt + 1; start < end { + copy(msgs.messages[insertAt+1:], msgs.messages[insertAt:end-1]) + } + + // Then, set the nth entry. + msgs.messages[insertAt] = message return nil } diff --git a/state/store/defaultstore/message_test.go b/state/store/defaultstore/message_test.go new file mode 100644 index 0000000..51c139d --- /dev/null +++ b/state/store/defaultstore/message_test.go @@ -0,0 +1,75 @@ +package defaultstore + +import ( + "testing" + + "github.com/diamondburned/arikawa/v2/discord" +) + +func populate12Store() *Message { + store := NewMessage(10) + + // Insert a regular list of messages. + store.MessageSet(discord.Message{ID: 11, ChannelID: 1}) + store.MessageSet(discord.Message{ID: 9, ChannelID: 1}) + store.MessageSet(discord.Message{ID: 7, ChannelID: 1}) + store.MessageSet(discord.Message{ID: 5, ChannelID: 1}) + store.MessageSet(discord.Message{ID: 3, ChannelID: 1}) + store.MessageSet(discord.Message{ID: 1, ChannelID: 1}) + + // Try to insert newer messages after inserting new messages. + store.MessageSet(discord.Message{ID: 12, ChannelID: 1}) + store.MessageSet(discord.Message{ID: 10, ChannelID: 1}) + store.MessageSet(discord.Message{ID: 8, ChannelID: 1}) + store.MessageSet(discord.Message{ID: 6, ChannelID: 1}) + store.MessageSet(discord.Message{ID: 4, ChannelID: 1}) + + // These messages should be discarded. + store.MessageSet(discord.Message{ID: 2, ChannelID: 1}) + store.MessageSet(discord.Message{ID: 0, ChannelID: 1}) + + return store +} + +func TestMessageSet(t *testing.T) { + store := populate12Store() + + messages, _ := store.Messages(1) + + const ( + start discord.MessageID = 2 + end discord.MessageID = 12 + ) + + for i := start; i < end; i++ { + index := i - start + expect := end - i + start + + if msgID := messages[index].ID; msgID != expect { + t.Errorf("message at %d has mismatch ID %d, expecting %d", i, msgID, expect) + } + } +} + +func TestMessagesUpdate(t *testing.T) { + store := populate12Store() + + store.MessageSet(discord.Message{ID: 5, ChannelID: 1, Content: "edited 1"}) + store.MessageSet(discord.Message{ID: 6, ChannelID: 1, Content: "edited 2"}) + store.MessageSet(discord.Message{ID: 5, ChannelID: 1, Content: "edited 3"}) + + expect := map[discord.MessageID]string{ + 5: "edited 3", + 6: "edited 2", + } + + messages, _ := store.Messages(1) + + for i := 0; i < store.MaxMessages(); i++ { + msg := messages[i] + content, ok := expect[msg.ID] + if ok && msg.Content != content { + t.Errorf("id %d expected %q, got %q", i, content, msg.Content) + } + } +}