feature(@desktop/chat): implement search on sqlcipher (status-go side)

Searching messages by some term for a specific channel is added on the side of status-go and an
appropriate part on the side of nim is developed accordingly.

Fixes: #2912
This commit is contained in:
Sale Djenic 2021-07-21 16:33:43 +02:00 committed by Iuri Matias
parent 1573d7b928
commit f0c605936d
12 changed files with 110 additions and 19 deletions

View File

@ -20,9 +20,14 @@ QtObject:
result.setup(status)
proc setSourceMessages*(self: MessageListProxyModel, messages: seq[Message]) =
## Sets source messages which will be filtered.
self.sourceMessages = messages
proc setFilter*(self: MessageListProxyModel, filter: string, caseSensitive: bool) =
## This proc filters messages that contain passed "filter" from the previously
## set source model, respecting case sensitivity or not, based on the passed
## "caseSensitive" parameter.
self.clear(false)
if (filter.len == 0):
@ -39,4 +44,11 @@ QtObject:
if (matchedMessages.len == 0):
return
self.add(matchedMessages)
self.add(matchedMessages)
proc setFilteredMessages*(self: MessageListProxyModel, messages: seq[Message]) =
## Sets filtered messages. Messages passed to this proc will be immediately
## added to the view model and displayed to user. Method is convenient for
## displaying already filtered messages (for example filtered response from DB)
self.clear(false)
self.add(messages)

View File

@ -13,9 +13,6 @@ import ../../../status/tasks/marathon/mailserver/worker
import communities, chat_item, channels_list, communities, community_list, message_list, channel, message_item, message_list_proxy
# TODO: remove me
import ../../../status/libstatus/chat as libstatus_chat
logScope:
topics = "messages-view"
@ -338,7 +335,7 @@ QtObject:
let messages = rpcResponseObj{"messages"}
if(messages != nil and messages.kind != JNull):
let chatMessages = libstatus_chat.parseChatMessagesResponse(messages)
let chatMessages = status_chat.parseChatMessagesResponse(messages)
self.status.chat.chatMessages(chatId, true, chatMessages[0], chatMessages[1])
let rxns = rpcResponseObj{"reactions"}
@ -348,7 +345,7 @@ QtObject:
let pinnedMsgs = rpcResponseObj{"pinnedMessages"}
if(pinnedMsgs != nil and pinnedMsgs.kind != JNull):
let pinnedMessages = libstatus_chat.parseChatPinnedMessagesResponse(pinnedMsgs)
let pinnedMessages = status_chat.parseChatPinnedMessagesResponse(pinnedMsgs)
self.status.chat.pinnedMessagesByChatID(chatId, pinnedMessages[0], pinnedMessages[1])
proc hideLoadingIndicator*(self: MessageView) {.slot.} =
@ -503,10 +500,38 @@ QtObject:
QtProperty[QVariant] searchResultMessageModel:
read = getSearchResultMessageModel
proc onSearchMessages*(self: MessageView, response: string) {.slot.} =
let responseObj = response.parseJson
if (responseObj.kind != JObject):
error "search messages response is not an json object"
return
let chatId = if(responseObj.contains("chatId")): responseObj{"chatId"}.getStr else : ""
if (chatId.len == 0):
error "search messages response either doesn't contain chatId or it is empty"
return
let messagesObj = if(responseObj.contains("messages")): responseObj{"messages"} else: newJObject()
if (messagesObj.kind != JObject):
error "search messages response either doesn't contain messages object or it is empty"
return
let (cursor, messages) = status_chat.parseChatMessagesResponse(messagesObj)
self.searchResultMessageModel.setFilteredMessages(messages)
proc searchMessages*(self: MessageView, searchTerm: string) {.slot.} =
# channelId is used here only to support message search in currently selected channel
if (searchTerm.len == 0):
self.searchResultMessageModel.clear(false)
return
# chatId is used here only to support message search in currently selected channel
# later when we decide to apply message search over multiple channels MessageListProxyModel
# will be updated to support setting list of sourcer messages.
let channelId = self.channelView.activeChannel.id
self.searchResultMessageModel.setSourceMessages(self.messageList[channelId].messages)
self.searchResultMessageModel.setFilter(searchTerm, false)
let chatId = self.channelView.activeChannel.id
let slot = SlotArg(
vptr: cast[ByteAddress](self.vptr),
slot: "onSearchMessages"
)
self.status.chat.asyncSearchMessages(slot, chatId, searchTerm, false)

View File

@ -9,6 +9,7 @@ import ../eventemitter
import profile/profile
import contacts
import chat/[chat, message]
import tasks/[qt, task_runner_impl]
import signals/messages
import ens
@ -56,6 +57,7 @@ type
ChatModel* = ref object
publicKey*: string
events*: EventEmitter
tasks*: TaskRunner
communitiesToFetch*: seq[string]
mailserverReady*: bool
contacts*: Table[string, Profile]
@ -77,9 +79,10 @@ type
include chat/utils
proc newChatModel*(events: EventEmitter): ChatModel =
proc newChatModel*(events: EventEmitter, tasks: TaskRunner): ChatModel =
result = ChatModel()
result.events = events
result.tasks = tasks
result.mailserverReady = false
result.communitiesToFetch = @[]
result.contacts = initTable[string, Profile]()
@ -634,3 +637,44 @@ proc parseReactionsResponse*(chatId: string, rpcResult: JsonNode): (string, seq[
proc parseChatMessagesResponse*(rpcResult: JsonNode): (string, seq[Message]) =
result = status_chat.parseChatMessagesResponse(rpcResult)
# This is still not proper place for such methods, but anyway better then calling
# them directly from the view part. Such methods should be placed in appropriate
# utils file and may be used on many places from there.
proc parseChatPinnedMessagesResponse*(rpcResult: JsonNode): (string, seq[Message]) =
result = status_chat.parseChatPinnedMessagesResponse(rpcResult)
#################################################
# Async search messages by term
#################################################
type
AsyncSearchMessageTaskArg = ref object of QObjectTaskArg
chatId: string
searchTerm: string
caseSensitive: bool
const asyncSearchMessagesTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[AsyncSearchMessageTaskArg](argEncoded)
var messages: JsonNode
var success: bool
let response = status_chat.asyncSearchMessages(arg.chatId, arg.searchTerm, arg.caseSensitive, success)
if(success):
messages = response.parseJson()["result"]
let responseJson = %*{
"chatId": arg.chatId,
"messages": messages
}
arg.finish(responseJson)
proc asyncSearchMessages*(self: ChatModel, slot: SlotArg, chatId: string, searchTerm: string, caseSensitive: bool) =
let arg = AsyncSearchMessageTaskArg(
tptr: cast[ByteAddress](asyncSearchMessagesTask),
vptr: slot.vptr,
slot: slot.slot,
chatId: chatId,
searchTerm: searchTerm,
caseSensitive: caseSensitive
)
self.tasks.threadpool.start(arg)

View File

@ -630,3 +630,11 @@ proc unreadActivityCenterNotificationsCount*(): int =
if rpcResult{"result"}.kind != JNull:
return rpcResult["result"].getInt
proc asyncSearchMessages*(chatId: string, searchTerm: string, caseSensitive: bool, success: var bool): string =
success = true
try:
result = callPrivateRPC("allChatMessagesWhichMatchTerm".prefix, %* [chatId, searchTerm, caseSensitive])
except RpcException as e:
success = false
result = e.msg

View File

@ -33,7 +33,7 @@ proc newStatusInstance*(fleetConfig: string): Status =
result.tasks = newTaskRunner()
result.events = createEventEmitter()
result.fleet = fleet.newFleetModel(fleetConfig)
result.chat = chat.newChatModel(result.events)
result.chat = chat.newChatModel(result.events, result.tasks)
result.accounts = accounts.newAccountModel(result.events)
result.wallet = wallet.newWalletModel(result.events)
result.wallet.initEvents()

View File

@ -9,6 +9,10 @@ type
vptr*: ByteAddress
slot*: string
SlotArg* = ref object of RootObj
vptr*: ByteAddress
slot*: string
proc finish*[T](arg: QObjectTaskArg, payload: T) =
signal_handler(cast[pointer](arg.vptr), Json.encode(payload), arg.slot)

@ -1 +1 @@
Subproject commit 7e03daeaf9bf4e1614d5507400b3d9a0ac016bc0
Subproject commit 6c67aaf3a3031af92931dce9673418e81f80749f

View File

@ -218,8 +218,6 @@ Popup {
prevMessageIndex: -1
prevMsgTimestamp: ""
}
}
}
}

2
vendor/DOtherSide vendored

@ -1 +1 @@
Subproject commit a838753904615610667e704efec5864e0d27e7c2
Subproject commit eaa394d711f757b859030349aeaf41a10e1bad2b

@ -1 +1 @@
Subproject commit f4463f3955a96e162e9881b73ba02f819e0374a4
Subproject commit f1ec58561d7b48f6c8b664f3806fd8a113164fba

2
vendor/nimqml vendored

@ -1 +1 @@
Subproject commit 8ca765ca11a9fde6a43d60289afcea312d3e6ed8
Subproject commit 65fa68c244fa52037de652520755430ec65c77df

2
vendor/status-go vendored

@ -1 +1 @@
Subproject commit 0aab3e238bb903547e580793d8eca8740909cd57
Subproject commit c4a71f813a783e310b1c8ca59d2390a8d76f6a0c