diff --git a/VERSION b/VERSION index 068337d83..a6f61ee40 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.63.3 +0.63.4 diff --git a/protocol/chat.go b/protocol/chat.go index 968f389a1..dda0b118e 100644 --- a/protocol/chat.go +++ b/protocol/chat.go @@ -29,6 +29,7 @@ const ( ChatTypePublic ChatTypePrivateGroupChat ChatTypeProfile + ChatTypeTimeline ) type Chat struct { @@ -99,6 +100,10 @@ func (c *Chat) ProfileUpdates() bool { return c.ChatType == ChatTypeProfile } +func (c *Chat) Timeline() bool { + return c.ChatType == ChatTypeTimeline +} + func (c *Chat) OneToOne() bool { return c.ChatType == ChatTypeOneToOne } diff --git a/protocol/message_persistence.go b/protocol/message_persistence.go index dc689c27c..44b667779 100644 --- a/protocol/message_persistence.go +++ b/protocol/message_persistence.go @@ -530,6 +530,78 @@ func (db sqlitePersistence) MessageByChatID(chatID string, currCursor string, li return result, newCursor, nil } +// MessageByChatIDs returns all messages for a given chatIDs in descending order. +// Ordering is accomplished using two concatenated values: ClockValue and ID. +// These two values are also used to compose a cursor which is returned to the result. +func (db sqlitePersistence) MessageByChatIDs(chatIDs []string, currCursor string, limit int) ([]*common.Message, string, error) { + cursorWhere := "" + if currCursor != "" { + cursorWhere = "AND cursor <= ?" + } + allFields := db.tableUserMessagesAllFieldsJoin() + args := make([]interface{}, len(chatIDs)) + for i, v := range chatIDs { + args[i] = v + } + if currCursor != "" { + args = append(args, currCursor) + } + // Build a new column `cursor` at the query time by having a fixed-sized clock value at the beginning + // concatenated with message ID. Results are sorted using this new column. + // This new column values can also be returned as a cursor for subsequent requests. + rows, err := db.db.Query( + fmt.Sprintf(` + SELECT + %s, + substr('0000000000000000000000000000000000000000000000000000000000000000' || m1.clock_value, -64, 64) || m1.id as cursor + FROM + user_messages m1 + LEFT JOIN + user_messages m2 + ON + m1.response_to = m2.id + + LEFT JOIN + contacts c + ON + + m1.source = c.id + WHERE + NOT(m1.hide) AND m1.local_chat_id IN %s %s + ORDER BY cursor DESC + LIMIT ? + `, allFields, "(?"+strings.Repeat(",?", len(chatIDs)-1)+")", cursorWhere), + append(args, limit+1)..., // take one more to figure our whether a cursor should be returned + ) + if err != nil { + return nil, "", err + } + defer rows.Close() + + var ( + result []*common.Message + cursors []string + ) + for rows.Next() { + var ( + message common.Message + cursor string + ) + if err := db.tableUserMessagesScanAllFields(rows, &message, &cursor); err != nil { + return nil, "", err + } + result = append(result, &message) + cursors = append(cursors, cursor) + } + + var newCursor string + if len(result) > limit { + newCursor = cursors[limit] + result = result[:limit] + } + return result, newCursor, nil +} + // EmojiReactionsByChatID returns the emoji reactions for the queried messages, up to a maximum of 100, as it's a potentially unbound number. // NOTE: This is not completely accurate, as the messages in the database might have change since the last call to `MessageByChatID`. func (db sqlitePersistence) EmojiReactionsByChatID(chatID string, currCursor string, limit int) ([]*EmojiReaction, error) { diff --git a/protocol/messenger.go b/protocol/messenger.go index 9e7739978..7049c40a3 100644 --- a/protocol/messenger.go +++ b/protocol/messenger.go @@ -673,7 +673,7 @@ func (m *Messenger) Join(chat Chat) error { return err } return m.transport.JoinGroup(members) - case ChatTypePublic, ChatTypeProfile: + case ChatTypePublic, ChatTypeProfile, ChatTypeTimeline: return m.transport.JoinPublic(chat.ID) default: return errors.New("chat is neither public nor private") @@ -696,7 +696,7 @@ func (m *Messenger) Leave(chat Chat) error { return err } return m.transport.LeaveGroup(members) - case ChatTypePublic, ChatTypeProfile: + case ChatTypePublic, ChatTypeProfile, ChatTypeTimeline: return m.transport.LeavePublic(chat.Name) default: return errors.New("chat is neither public nor private") @@ -2587,7 +2587,22 @@ func (m *Messenger) MessagesExist(ids []string) (map[string]bool, error) { } func (m *Messenger) MessageByChatID(chatID, cursor string, limit int) ([]*common.Message, string, error) { - return m.persistence.MessageByChatID(chatID, cursor, limit) + chat, err := m.persistence.Chat(chatID) + if err != nil { + return nil, "", err + } + + if chat.Timeline() { + var chatIDs = []string{"@" + contactIDFromPublicKey(&m.identity.PublicKey)} + for _, contact := range m.allContacts { + if contact.IsAdded() { + chatIDs = append(chatIDs, "@"+contact.ID) + } + } + return m.persistence.MessageByChatIDs(chatIDs, cursor, limit) + } else { + return m.persistence.MessageByChatID(chatID, cursor, limit) + } } func (m *Messenger) SaveMessages(messages []*common.Message) error {