Compare commits
4 Commits
stable
...
f/mark-rea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7bfb196e2a | ||
|
|
1127b11bba | ||
|
|
8d66750f20 | ||
|
|
50b71b3045 |
@@ -844,7 +844,7 @@ func apiRejectContactRequest(contactReqId: Int64) async throws {
|
|||||||
throw r
|
throw r
|
||||||
}
|
}
|
||||||
|
|
||||||
func apiChatRead(type: ChatType, id: Int64, itemRange: (Int64, Int64)) async throws {
|
func apiChatRead(type: ChatType, id: Int64, itemRange: (Int64, Int64)? = nil) async throws {
|
||||||
try await sendCommandOkResp(.apiChatRead(type: type, id: id, itemRange: itemRange))
|
try await sendCommandOkResp(.apiChatRead(type: type, id: id, itemRange: itemRange))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1044,7 +1044,24 @@ func markChatRead(_ chat: Chat, aboveItem: ChatItem? = nil) async {
|
|||||||
await markChatUnread(chat, unreadChat: false)
|
await markChatUnread(chat, unreadChat: false)
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
logger.error("markChatRead apiChatRead error: \(responseError(error))")
|
logger.error("markChatRead error: \(responseError(error))")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func markChatReadAll(_ chat: Chat) async {
|
||||||
|
do {
|
||||||
|
if chat.chatStats.unreadCount > 0 {
|
||||||
|
let cInfo = chat.chatInfo
|
||||||
|
try await apiChatRead(type: cInfo.chatType, id: cInfo.apiId)
|
||||||
|
await MainActor.run {
|
||||||
|
withAnimation { ChatModel.shared.markChatItemsRead(cInfo) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if chat.chatStats.unreadChat {
|
||||||
|
await markChatUnread(chat, unreadChat: false)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
logger.error("markChatReadAll error: \(responseError(error))")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -210,7 +210,7 @@ struct ChatListNavLink: View {
|
|||||||
@ViewBuilder private func markReadButton() -> some View {
|
@ViewBuilder private func markReadButton() -> some View {
|
||||||
if chat.chatStats.unreadCount > 0 || chat.chatStats.unreadChat {
|
if chat.chatStats.unreadCount > 0 || chat.chatStats.unreadChat {
|
||||||
Button {
|
Button {
|
||||||
Task { await markChatRead(chat) }
|
Task { await markChatReadAll(chat) }
|
||||||
} label: {
|
} label: {
|
||||||
Label("Read", systemImage: "checkmark")
|
Label("Read", systemImage: "checkmark")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ public enum ChatCommand {
|
|||||||
case apiGetCallInvitations
|
case apiGetCallInvitations
|
||||||
case apiCallStatus(contact: Contact, callStatus: WebRTCCallStatus)
|
case apiCallStatus(contact: Contact, callStatus: WebRTCCallStatus)
|
||||||
case apiGetNetworkStatuses
|
case apiGetNetworkStatuses
|
||||||
case apiChatRead(type: ChatType, id: Int64, itemRange: (Int64, Int64))
|
case apiChatRead(type: ChatType, id: Int64, itemRange: (Int64, Int64)?)
|
||||||
case apiChatUnread(type: ChatType, id: Int64, unreadChat: Bool)
|
case apiChatUnread(type: ChatType, id: Int64, unreadChat: Bool)
|
||||||
case receiveFile(fileId: Int64, encrypted: Bool?, inline: Bool?)
|
case receiveFile(fileId: Int64, encrypted: Bool?, inline: Bool?)
|
||||||
case setFileToReceive(fileId: Int64, encrypted: Bool?)
|
case setFileToReceive(fileId: Int64, encrypted: Bool?)
|
||||||
@@ -265,7 +265,11 @@ public enum ChatCommand {
|
|||||||
case .apiGetCallInvitations: return "/_call get"
|
case .apiGetCallInvitations: return "/_call get"
|
||||||
case let .apiCallStatus(contact, callStatus): return "/_call status @\(contact.apiId) \(callStatus.rawValue)"
|
case let .apiCallStatus(contact, callStatus): return "/_call status @\(contact.apiId) \(callStatus.rawValue)"
|
||||||
case .apiGetNetworkStatuses: return "/_network_statuses"
|
case .apiGetNetworkStatuses: return "/_network_statuses"
|
||||||
case let .apiChatRead(type, id, itemRange: (from, to)): return "/_read chat \(ref(type, id)) from=\(from) to=\(to)"
|
case let .apiChatRead(type, id, itemRange): if let (from, to) = itemRange {
|
||||||
|
return "/_read chat \(ref(type, id)) from=\(from) to=\(to)"
|
||||||
|
} else {
|
||||||
|
return "/_read chat \(ref(type, id))"
|
||||||
|
}
|
||||||
case let .apiChatUnread(type, id, unreadChat): return "/_unread chat \(ref(type, id)) \(onOff(unreadChat))"
|
case let .apiChatUnread(type, id, unreadChat): return "/_unread chat \(ref(type, id)) \(onOff(unreadChat))"
|
||||||
case let .receiveFile(fileId, encrypt, inline): return "/freceive \(fileId)\(onOffParam("encrypt", encrypt))\(onOffParam("inline", inline))"
|
case let .receiveFile(fileId, encrypt, inline): return "/freceive \(fileId)\(onOffParam("encrypt", encrypt))\(onOffParam("inline", inline))"
|
||||||
case let .setFileToReceive(fileId, encrypt): return "/_set_file_to_receive \(fileId)\(onOffParam("encrypt", encrypt))"
|
case let .setFileToReceive(fileId, encrypt): return "/_set_file_to_receive \(fileId)\(onOffParam("encrypt", encrypt))"
|
||||||
|
|||||||
20
src/Simplex/Chat/Migrations/M20240110_indexes.hs
Normal file
20
src/Simplex/Chat/Migrations/M20240110_indexes.hs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{-# LANGUAGE QuasiQuotes #-}
|
||||||
|
|
||||||
|
module Simplex.Chat.Migrations.M20240110_indexes where
|
||||||
|
|
||||||
|
import Database.SQLite.Simple (Query)
|
||||||
|
import Database.SQLite.Simple.QQ (sql)
|
||||||
|
|
||||||
|
m20240110_indexes :: Query
|
||||||
|
m20240110_indexes =
|
||||||
|
[sql|
|
||||||
|
CREATE INDEX idx_chat_items_direct_item_status ON chat_items(user_id, contact_id, item_status);
|
||||||
|
CREATE INDEX idx_chat_items_group_item_status ON chat_items(user_id, group_id, item_status);
|
||||||
|
|]
|
||||||
|
|
||||||
|
down_m20240110_indexes :: Query
|
||||||
|
down_m20240110_indexes =
|
||||||
|
[sql|
|
||||||
|
DROP INDEX idx_chat_items_group_item_status;
|
||||||
|
DROP INDEX idx_chat_items_direct_item_status;
|
||||||
|
|]
|
||||||
163
src/Simplex/Chat/Migrations/plan.sql
Normal file
163
src/Simplex/Chat/Migrations/plan.sql
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
plan
|
||||||
|
|
||||||
|
-- direct read
|
||||||
|
|
||||||
|
UPDATE chat_items SET item_status = ?, updated_at = ?
|
||||||
|
WHERE user_id = ? AND contact_id = ? AND chat_item_id >= ? AND chat_item_id <= ? AND item_status = ?
|
||||||
|
|
||||||
|
EXPLAIN QUERY PLAN UPDATE chat_items SET item_status = ?, updated_at = ? WHERE user_id = ? AND contact_id = ? AND chat_item_id >= ? AND chat_item_id <= ? AND item_status = ?;
|
||||||
|
|
||||||
|
|
||||||
|
UPDATE chat_items SET item_status = ?, updated_at = ?
|
||||||
|
WHERE user_id = ? AND contact_id = ? AND item_status = ?
|
||||||
|
|
||||||
|
EXPLAIN QUERY PLAN UPDATE chat_items SET item_status = ?, updated_at = ? WHERE user_id = ? AND contact_id = ? AND item_status = ?;
|
||||||
|
|
||||||
|
-- direct get chat
|
||||||
|
-- last
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
i.chat_item_id, i.item_ts, i.item_sent, i.item_content, i.item_text, i.item_status, i.shared_msg_id, i.item_deleted, i.item_deleted_ts, i.item_edited, i.created_at, i.updated_at, i.timed_ttl, i.timed_delete_at, i.item_live,
|
||||||
|
f.file_id, f.file_name, f.file_size, f.file_path, f.file_crypto_key, f.file_crypto_nonce, f.ci_file_status, f.protocol,
|
||||||
|
ri.chat_item_id, i.quoted_shared_msg_id, i.quoted_sent_at, i.quoted_content, i.quoted_sent
|
||||||
|
FROM chat_items i
|
||||||
|
LEFT JOIN files f ON f.chat_item_id = i.chat_item_id
|
||||||
|
LEFT JOIN chat_items ri ON ri.user_id = i.user_id AND ri.contact_id = i.contact_id AND ri.shared_msg_id = i.quoted_shared_msg_id
|
||||||
|
WHERE i.user_id = ? AND i.contact_id = ? AND i.item_text LIKE '%' || ? || '%'
|
||||||
|
ORDER BY i.created_at DESC, i.chat_item_id DESC
|
||||||
|
LIMIT ?
|
||||||
|
|
||||||
|
EXPLAIN QUERY PLAN SELECT i.chat_item_id, i.item_ts, i.item_sent, i.item_content, i.item_text, i.item_status, i.shared_msg_id, i.item_deleted, i.item_deleted_ts, i.item_edited, i.created_at, i.updated_at, i.timed_ttl, i.timed_delete_at, i.item_live, f.file_id, f.file_name, f.file_size, f.file_path, f.file_crypto_key, f.file_crypto_nonce, f.ci_file_status, f.protocol, ri.chat_item_id, i.quoted_shared_msg_id, i.quoted_sent_at, i.quoted_content, i.quoted_sent FROM chat_items i LEFT JOIN files f ON f.chat_item_id = i.chat_item_id LEFT JOIN chat_items ri ON ri.user_id = i.user_id AND ri.contact_id = i.contact_id AND ri.shared_msg_id = i.quoted_shared_msg_id WHERE i.user_id = ? AND i.contact_id = ? AND i.item_text LIKE '%' || ? || '%' ORDER BY i.created_at DESC, i.chat_item_id DESC LIMIT ?;
|
||||||
|
|
||||||
|
-- before
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
i.chat_item_id, i.item_ts, i.item_sent, i.item_content, i.item_text, i.item_status, i.shared_msg_id, i.item_deleted, i.item_deleted_ts, i.item_edited, i.created_at, i.updated_at, i.timed_ttl, i.timed_delete_at, i.item_live,
|
||||||
|
f.file_id, f.file_name, f.file_size, f.file_path, f.file_crypto_key, f.file_crypto_nonce, f.ci_file_status, f.protocol,
|
||||||
|
ri.chat_item_id, i.quoted_shared_msg_id, i.quoted_sent_at, i.quoted_content, i.quoted_sent
|
||||||
|
FROM chat_items i
|
||||||
|
LEFT JOIN files f ON f.chat_item_id = i.chat_item_id
|
||||||
|
LEFT JOIN chat_items ri ON ri.user_id = i.user_id AND ri.contact_id = i.contact_id AND ri.shared_msg_id = i.quoted_shared_msg_id
|
||||||
|
WHERE i.user_id = ? AND i.contact_id = ? AND i.item_text LIKE '%' || ? || '%'
|
||||||
|
AND i.chat_item_id < ?
|
||||||
|
ORDER BY i.created_at DESC, i.chat_item_id DESC
|
||||||
|
LIMIT ?
|
||||||
|
|
||||||
|
EXPLAIN QUERY PLAN SELECT i.chat_item_id, i.item_ts, i.item_sent, i.item_content, i.item_text, i.item_status, i.shared_msg_id, i.item_deleted, i.item_deleted_ts, i.item_edited, i.created_at, i.updated_at, i.timed_ttl, i.timed_delete_at, i.item_live, f.file_id, f.file_name, f.file_size, f.file_path, f.file_crypto_key, f.file_crypto_nonce, f.ci_file_status, f.protocol, ri.chat_item_id, i.quoted_shared_msg_id, i.quoted_sent_at, i.quoted_content, i.quoted_sent FROM chat_items i LEFT JOIN files f ON f.chat_item_id = i.chat_item_id LEFT JOIN chat_items ri ON ri.user_id = i.user_id AND ri.contact_id = i.contact_id AND ri.shared_msg_id = i.quoted_shared_msg_id WHERE i.user_id = ? AND i.contact_id = ? AND i.item_text LIKE '%' || ? || '%' AND i.chat_item_id < ? ORDER BY i.created_at DESC, i.chat_item_id DESC LIMIT ?;
|
||||||
|
|
||||||
|
-- direct get previews
|
||||||
|
-- last
|
||||||
|
|
||||||
|
SELECT ct.contact_id, ct.chat_ts as ts, LastItems.chat_item_id, COALESCE(ChatStats.UnreadCount, 0), COALESCE(ChatStats.MinUnread, 0), ct.unread_chat
|
||||||
|
FROM contacts ct
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT contact_id, chat_item_id, MAX(created_at)
|
||||||
|
FROM chat_items
|
||||||
|
GROUP BY contact_id
|
||||||
|
) LastItems ON LastItems.contact_id = ct.contact_id
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT contact_id, COUNT(1) AS UnreadCount, MIN(chat_item_id) AS MinUnread
|
||||||
|
FROM chat_items
|
||||||
|
WHERE item_status = :rcv_new
|
||||||
|
GROUP BY contact_id
|
||||||
|
) ChatStats ON ChatStats.contact_id = ct.contact_id
|
||||||
|
WHERE ct.user_id = :user_id AND ct.is_user = 0 AND ct.deleted = 0 AND ct.contact_used
|
||||||
|
ORDER BY ts DESC LIMIT :count
|
||||||
|
|
||||||
|
EXPLAIN QUERY PLAN SELECT ct.contact_id, ct.chat_ts as ts, LastItems.chat_item_id, COALESCE(ChatStats.UnreadCount, 0), COALESCE(ChatStats.MinUnread, 0), ct.unread_chat FROM contacts ct LEFT JOIN ( SELECT contact_id, chat_item_id, MAX(created_at) FROM chat_items GROUP BY contact_id ) LastItems ON LastItems.contact_id = ct.contact_id LEFT JOIN ( SELECT contact_id, COUNT(1) AS UnreadCount, MIN(chat_item_id) AS MinUnread FROM chat_items WHERE item_status = :rcv_new GROUP BY contact_id ) ChatStats ON ChatStats.contact_id = ct.contact_id WHERE ct.user_id = :user_id AND ct.is_user = 0 AND ct.deleted = 0 AND ct.contact_used ORDER BY ts DESC LIMIT :count;
|
||||||
|
|
||||||
|
-- before
|
||||||
|
|
||||||
|
SELECT ct.contact_id, ct.chat_ts as ts, LastItems.chat_item_id, COALESCE(ChatStats.UnreadCount, 0), COALESCE(ChatStats.MinUnread, 0), ct.unread_chat
|
||||||
|
FROM contacts ct
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT contact_id, chat_item_id, MAX(created_at)
|
||||||
|
FROM chat_items
|
||||||
|
GROUP BY contact_id
|
||||||
|
) LastItems ON LastItems.contact_id = ct.contact_id
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT contact_id, COUNT(1) AS UnreadCount, MIN(chat_item_id) AS MinUnread
|
||||||
|
FROM chat_items
|
||||||
|
WHERE item_status = :rcv_new
|
||||||
|
GROUP BY contact_id
|
||||||
|
) ChatStats ON ChatStats.contact_id = ct.contact_id
|
||||||
|
WHERE ct.user_id = :user_id AND ct.is_user = 0 AND ct.deleted = 0 AND ct.contact_used
|
||||||
|
AND ts < :ts ORDER BY ts DESC LIMIT :count
|
||||||
|
|
||||||
|
EXPLAIN QUERY PLAN SELECT ct.contact_id, ct.chat_ts as ts, LastItems.chat_item_id, COALESCE(ChatStats.UnreadCount, 0), COALESCE(ChatStats.MinUnread, 0), ct.unread_chat FROM contacts ct LEFT JOIN ( SELECT contact_id, chat_item_id, MAX(created_at) FROM chat_items GROUP BY contact_id ) LastItems ON LastItems.contact_id = ct.contact_id LEFT JOIN ( SELECT contact_id, COUNT(1) AS UnreadCount, MIN(chat_item_id) AS MinUnread FROM chat_items WHERE item_status = :rcv_new GROUP BY contact_id ) ChatStats ON ChatStats.contact_id = ct.contact_id WHERE ct.user_id = :user_id AND ct.is_user = 0 AND ct.deleted = 0 AND ct.contact_used AND ts < :ts ORDER BY ts DESC LIMIT :count;
|
||||||
|
|
||||||
|
-- group read
|
||||||
|
|
||||||
|
UPDATE chat_items SET item_status = ?, updated_at = ?
|
||||||
|
WHERE user_id = ? AND group_id = ? AND chat_item_id >= ? AND chat_item_id <= ? AND item_status = ?
|
||||||
|
|
||||||
|
EXPLAIN QUERY PLAN UPDATE chat_items SET item_status = ?, updated_at = ? WHERE user_id = ? AND group_id = ? AND chat_item_id >= ? AND chat_item_id <= ? AND item_status = ?;
|
||||||
|
|
||||||
|
|
||||||
|
UPDATE chat_items SET item_status = ?, updated_at = ?
|
||||||
|
WHERE user_id = ? AND group_id = ? AND item_status = ?
|
||||||
|
|
||||||
|
EXPLAIN QUERY PLAN UPDATE chat_items SET item_status = ?, updated_at = ? WHERE user_id = ? AND group_id = ? AND item_status = ?;
|
||||||
|
|
||||||
|
-- group get chat
|
||||||
|
-- last
|
||||||
|
|
||||||
|
SELECT chat_item_id
|
||||||
|
FROM chat_items
|
||||||
|
WHERE user_id = ? AND group_id = ? AND item_text LIKE '%' || ? || '%'
|
||||||
|
ORDER BY item_ts DESC, chat_item_id DESC
|
||||||
|
LIMIT ?
|
||||||
|
|
||||||
|
EXPLAIN QUERY PLAN SELECT chat_item_id FROM chat_items WHERE user_id = ? AND group_id = ? AND item_text LIKE '%' || ? || '%' ORDER BY item_ts DESC, chat_item_id DESC LIMIT ?;
|
||||||
|
|
||||||
|
-- before
|
||||||
|
|
||||||
|
SELECT chat_item_id
|
||||||
|
FROM chat_items
|
||||||
|
WHERE user_id = ? AND group_id = ? AND item_text LIKE '%' || ? || '%'
|
||||||
|
AND (item_ts < ? OR (item_ts = ? AND chat_item_id < ?))
|
||||||
|
ORDER BY item_ts DESC, chat_item_id DESC
|
||||||
|
LIMIT ?
|
||||||
|
|
||||||
|
EXPLAIN QUERY PLAN SELECT chat_item_id FROM chat_items WHERE user_id = ? AND group_id = ? AND item_text LIKE '%' || ? || '%' AND (item_ts < ? OR (item_ts = ? AND chat_item_id < ?)) ORDER BY item_ts DESC, chat_item_id DESC LIMIT ?;
|
||||||
|
|
||||||
|
-- get group previews
|
||||||
|
-- last
|
||||||
|
|
||||||
|
SELECT g.group_id, g.chat_ts as ts, LastItems.chat_item_id, COALESCE(ChatStats.UnreadCount, 0), COALESCE(ChatStats.MinUnread, 0), g.unread_chat
|
||||||
|
FROM groups g
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT group_id, chat_item_id, MAX(item_ts)
|
||||||
|
FROM chat_items
|
||||||
|
GROUP BY group_id
|
||||||
|
) LastItems ON LastItems.group_id = g.group_id
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT group_id, COUNT(1) AS UnreadCount, MIN(chat_item_id) AS MinUnread
|
||||||
|
FROM chat_items
|
||||||
|
WHERE item_status = :rcv_new
|
||||||
|
GROUP BY group_id
|
||||||
|
) ChatStats ON ChatStats.group_id = g.group_id
|
||||||
|
WHERE g.user_id = :user_id
|
||||||
|
ORDER BY ts DESC LIMIT :count
|
||||||
|
|
||||||
|
EXPLAIN QUERY PLAN SELECT g.group_id, g.chat_ts as ts, LastItems.chat_item_id, COALESCE(ChatStats.UnreadCount, 0), COALESCE(ChatStats.MinUnread, 0), g.unread_chat FROM groups g LEFT JOIN ( SELECT group_id, chat_item_id, MAX(item_ts) FROM chat_items GROUP BY group_id ) LastItems ON LastItems.group_id = g.group_id LEFT JOIN ( SELECT group_id, COUNT(1) AS UnreadCount, MIN(chat_item_id) AS MinUnread FROM chat_items WHERE item_status = :rcv_new GROUP BY group_id ) ChatStats ON ChatStats.group_id = g.group_id WHERE g.user_id = :user_id ORDER BY ts DESC LIMIT :count;
|
||||||
|
|
||||||
|
-- before
|
||||||
|
|
||||||
|
SELECT g.group_id, g.chat_ts as ts, LastItems.chat_item_id, COALESCE(ChatStats.UnreadCount, 0), COALESCE(ChatStats.MinUnread, 0), g.unread_chat
|
||||||
|
FROM groups g
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT group_id, chat_item_id, MAX(item_ts)
|
||||||
|
FROM chat_items
|
||||||
|
GROUP BY group_id
|
||||||
|
) LastItems ON LastItems.group_id = g.group_id
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT group_id, COUNT(1) AS UnreadCount, MIN(chat_item_id) AS MinUnread
|
||||||
|
FROM chat_items
|
||||||
|
WHERE item_status = :rcv_new
|
||||||
|
GROUP BY group_id
|
||||||
|
) ChatStats ON ChatStats.group_id = g.group_id
|
||||||
|
WHERE g.user_id = :user_id
|
||||||
|
AND ts < :ts ORDER BY ts DESC LIMIT :count
|
||||||
|
|
||||||
|
EXPLAIN QUERY PLAN SELECT g.group_id, g.chat_ts as ts, LastItems.chat_item_id, COALESCE(ChatStats.UnreadCount, 0), COALESCE(ChatStats.MinUnread, 0), g.unread_chat FROM groups g LEFT JOIN ( SELECT group_id, chat_item_id, MAX(item_ts) FROM chat_items GROUP BY group_id ) LastItems ON LastItems.group_id = g.group_id LEFT JOIN ( SELECT group_id, COUNT(1) AS UnreadCount, MIN(chat_item_id) AS MinUnread FROM chat_items WHERE item_status = :rcv_new GROUP BY group_id ) ChatStats ON ChatStats.group_id = g.group_id WHERE g.user_id = :user_id AND ts < :ts ORDER BY ts DESC LIMIT :count;
|
||||||
Reference in New Issue
Block a user