Skip to content

Commit d63c6c5

Browse files
committed
feat: implement messenger search
1 parent 537b729 commit d63c6c5

File tree

8 files changed

+473
-6
lines changed

8 files changed

+473
-6
lines changed

protobuf/generated/rust/qaul.rpc.chat.rs

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
#[derive(Clone, PartialEq, ::prost::Message)]
44
pub struct Chat {
55
/// message type
6-
#[prost(oneof = "chat::Message", tags = "3, 4, 5")]
6+
#[prost(oneof = "chat::Message", tags = "3, 4, 5, 6, 7")]
77
pub message: ::core::option::Option<chat::Message>,
88
}
99
/// Nested message and enum types in `Chat`.
@@ -20,6 +20,12 @@ pub mod chat {
2020
/// send a new chat message
2121
#[prost(message, tag = "5")]
2222
Send(super::ChatMessageSend),
23+
/// search chat messages
24+
#[prost(message, tag = "6")]
25+
SearchRequest(super::ChatSearchRequest),
26+
/// search results
27+
#[prost(message, tag = "7")]
28+
SearchResult(super::ChatSearchResult),
2329
}
2430
}
2531
/// request messages of a specific chat conversation
@@ -148,6 +154,42 @@ pub struct GroupEvent {
148154
#[prost(bytes = "vec", tag = "2")]
149155
pub user_id: ::prost::alloc::vec::Vec<u8>,
150156
}
157+
/// search chat messages request
158+
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
159+
pub struct ChatSearchRequest {
160+
/// search query string
161+
#[prost(string, tag = "1")]
162+
pub query: ::prost::alloc::string::String,
163+
}
164+
/// search results
165+
#[derive(Clone, PartialEq, ::prost::Message)]
166+
pub struct ChatSearchResult {
167+
/// the query that produced these results
168+
#[prost(string, tag = "1")]
169+
pub query: ::prost::alloc::string::String,
170+
/// matching messages
171+
#[prost(message, repeated, tag = "2")]
172+
pub items: ::prost::alloc::vec::Vec<ChatSearchResultItem>,
173+
}
174+
/// a single search result item
175+
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
176+
pub struct ChatSearchResultItem {
177+
/// message id
178+
#[prost(bytes = "vec", tag = "1")]
179+
pub message_id: ::prost::alloc::vec::Vec<u8>,
180+
/// group id of the conversation
181+
#[prost(bytes = "vec", tag = "2")]
182+
pub group_id: ::prost::alloc::vec::Vec<u8>,
183+
/// sender id
184+
#[prost(bytes = "vec", tag = "3")]
185+
pub sender_id: ::prost::alloc::vec::Vec<u8>,
186+
/// message text content
187+
#[prost(string, tag = "4")]
188+
pub content: ::prost::alloc::string::String,
189+
/// time when the message was sent
190+
#[prost(uint64, tag = "5")]
191+
pub sent_at: u64,
192+
}
151193
/// send chat message
152194
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
153195
pub struct ChatMessageSend {

protobuf/proto_definitions/services/chat/chat.proto

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ message Chat {
1212

1313
// send a new chat message
1414
ChatMessageSend send = 5;
15+
16+
// search chat messages
17+
ChatSearchRequest search_request = 6;
18+
// search results
19+
ChatSearchResult search_result = 7;
1520
}
1621
}
1722

@@ -155,6 +160,34 @@ enum GroupEventType {
155160
INVITE_ACCEPTED = 7;
156161
}
157162

163+
// search chat messages request
164+
message ChatSearchRequest {
165+
// search query string
166+
string query = 1;
167+
}
168+
169+
// search results
170+
message ChatSearchResult {
171+
// the query that produced these results
172+
string query = 1;
173+
// matching messages
174+
repeated ChatSearchResultItem items = 2;
175+
}
176+
177+
// a single search result item
178+
message ChatSearchResultItem {
179+
// message id
180+
bytes message_id = 1;
181+
// group id of the conversation
182+
bytes group_id = 2;
183+
// sender id
184+
bytes sender_id = 3;
185+
// message text content
186+
string content = 4;
187+
// time when the message was sent
188+
uint64 sent_at = 5;
189+
}
190+
158191
// send chat message
159192
message ChatMessageSend {
160193
// group id to which this message is sent

rust/clients/cli/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ The following commands are available:
7272
* chat
7373
* `chat send {Group ID} {Chat Message}` - sends the {Chat Message} to the user with the ID {Group ID}
7474
* `chat conversation {Group ID}` - displays all messages of the conversation with the ID {Group ID}
75+
* `chat search {query}` - searches chat messages matching {query} across all conversations
7576
* chat files
7677
* `file send {Group ID} {File Path} {File Description}` - sends a file to the user with the ID {Group ID} and a {File Description} text.
7778
* `file history [{offset} {limit}]` - displays a paginated file history

rust/clients/cli/src/chat.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,15 @@ impl Chat {
107107
}
108108
}
109109
}
110+
// search chat messages
111+
cmd if cmd.starts_with("search ") => {
112+
let query = cmd.strip_prefix("search ").unwrap().trim();
113+
if query.is_empty() {
114+
log::error!("usage: chat search <query>");
115+
return;
116+
}
117+
Self::send_search_request(query.to_string());
118+
}
110119
// unknown command
111120
_ => log::error!("unknown chat command"),
112121
}
@@ -182,6 +191,22 @@ impl Chat {
182191
Rpc::send_message(buf, super::rpc::proto::Modules::Chat.into(), "".to_string());
183192
}
184193

194+
/// Send a search request via rpc
195+
fn send_search_request(query: String) {
196+
let proto_message = proto::Chat {
197+
message: Some(proto::chat::Message::SearchRequest(
198+
proto::ChatSearchRequest { query },
199+
)),
200+
};
201+
202+
let mut buf = Vec::with_capacity(proto_message.encoded_len());
203+
proto_message
204+
.encode(&mut buf)
205+
.expect("Vec<u8> provides capacity as needed");
206+
207+
Rpc::send_message(buf, super::rpc::proto::Modules::Chat.into(), "".to_string());
208+
}
209+
185210
fn analyze_content(content: &Vec<u8>) -> Result<Vec<String>, String> {
186211
let mut res: Vec<String> = vec![];
187212

@@ -294,6 +319,31 @@ impl Chat {
294319
}
295320
}
296321

322+
Some(proto::chat::Message::SearchResult(results)) => {
323+
println!("");
324+
println!("Search results for \"{}\":", results.query);
325+
println!("");
326+
if results.items.is_empty() {
327+
println!(" (no results)");
328+
} else {
329+
for item in results.items {
330+
let group_id_str = match uuid::Uuid::from_slice(&item.group_id) {
331+
Ok(id) => id.to_string(),
332+
Err(_) => bs58::encode(&item.group_id).into_string(),
333+
};
334+
println!(
335+
" [{}] group:{} from:{} at:{}",
336+
bs58::encode(&item.message_id).into_string(),
337+
group_id_str,
338+
bs58::encode(&item.sender_id).into_string(),
339+
item.sent_at,
340+
);
341+
println!(" {}", item.content);
342+
println!("");
343+
}
344+
}
345+
}
346+
297347
_ => {
298348
log::error!("unprocessable RPC chat message");
299349
}

rust/libqaul/src/search/mod.rs

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ pub struct Search {
7171
// avoiding repeated string-based lookups into the schema.
7272
id_field: Field,
7373
content_field: Field,
74+
75+
// True if this index was freshly created (not opened from an existing directory).
76+
// Callers use this to decide whether a batch backfill of existing data is needed.
77+
is_fresh: bool,
7478
}
7579

7680
impl Search {
@@ -94,9 +98,11 @@ impl Search {
9498
std::fs::create_dir_all(index_path)?;
9599

96100
// create (or open, if existing) the Index for the corresponding Schema in the provided path
97-
let index = match Index::create_in_dir(index_path, schema.clone()) {
98-
Ok(index) => index,
99-
Err(tantivy::TantivyError::IndexAlreadyExists) => Index::open_in_dir(index_path)?,
101+
let (index, is_fresh) = match Index::create_in_dir(index_path, schema.clone()) {
102+
Ok(index) => (index, true),
103+
Err(tantivy::TantivyError::IndexAlreadyExists) => {
104+
(Index::open_in_dir(index_path)?, false)
105+
}
100106
Err(e) => return Err(e.into()),
101107
};
102108

@@ -114,17 +120,25 @@ impl Search {
114120
// "There must be only one writer at a time. This single IndexWriter is already multithreaded."
115121
//
116122
// That is why we keep it persistent per instance of Search, initializing it
117-
// with a 50Mb memory_arena budget (also suggested by the docs).
118-
let writer: IndexWriter = index.writer(50_000_000)?;
123+
// with a 15Mb memory_arena budget (suitable for mobile).
124+
let writer: IndexWriter = index.writer(15_000_000)?;
119125

120126
Ok(Self {
121127
writer,
122128
reader,
123129
id_field,
124130
content_field,
131+
is_fresh,
125132
})
126133
}
127134

135+
/// Returns `true` if this index was freshly created (not opened from an existing directory).
136+
///
137+
/// Callers use this to decide whether a batch backfill of existing data is needed.
138+
pub fn is_fresh(&self) -> bool {
139+
self.is_fresh
140+
}
141+
128142
/// Stages a single document for indexing, deduplicating by ID.
129143
///
130144
/// Note: this does NOT commit. Call `commit()` when you're ready
@@ -176,6 +190,7 @@ impl Search {
176190
///
177191
/// Note: Unlike `index` and `index_many`, this commits on its own.
178192
/// It's NOT designed to be called inside a batch operation.
193+
#[allow(dead_code)]
179194
pub fn remove(&mut self, id: &str) -> Result<(), SearchError> {
180195
self.delete_id(id);
181196
self.commit()?;

rust/libqaul/src/services/chat/mod.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,15 @@ use prost::Message;
1010

1111
pub mod file;
1212
pub mod message;
13+
pub mod search;
1314
pub mod storage;
1415

1516
use crate::connections::{internet::Internet, lan::Lan};
1617
use crate::node::user_accounts::UserAccounts;
1718
use crate::rpc::Rpc;
1819
pub use file::ChatFile;
1920
pub use message::ChatMessage;
21+
pub use search::ChatSearch;
2022
pub use storage::ChatStorage;
2123

2224
/// Import protobuf message definition
@@ -33,6 +35,9 @@ impl Chat {
3335

3436
// initialize the chat file management
3537
ChatFile::init();
38+
39+
// initialize the chat search indexes
40+
ChatSearch::init();
3641
}
3742

3843
/// Generate a Chat Message ID
@@ -127,6 +132,40 @@ impl Chat {
127132
log::error!("Outgoing chat message error: {}", error)
128133
}
129134
}
135+
Some(rpc_proto::chat::Message::SearchRequest(search_request)) => {
136+
let results = ChatSearch::search(&account_id, &search_request.query);
137+
138+
let items: Vec<rpc_proto::ChatSearchResultItem> = results
139+
.into_iter()
140+
.map(|r| rpc_proto::ChatSearchResultItem {
141+
message_id: r.message_id,
142+
group_id: r.group_id,
143+
sender_id: r.sender_id,
144+
content: r.content,
145+
sent_at: r.sent_at,
146+
})
147+
.collect();
148+
149+
let proto_message = rpc_proto::Chat {
150+
message: Some(rpc_proto::chat::Message::SearchResult(
151+
rpc_proto::ChatSearchResult {
152+
query: search_request.query,
153+
items,
154+
},
155+
)),
156+
};
157+
158+
let mut buf = Vec::with_capacity(proto_message.encoded_len());
159+
proto_message
160+
.encode(&mut buf)
161+
.expect("Vec<u8> provides capacity as needed");
162+
Rpc::send_message(
163+
buf,
164+
crate::rpc::proto::Modules::Chat.into(),
165+
request_id,
166+
Vec::new(),
167+
);
168+
}
130169
_ => {
131170
log::error!("Unhandled Protobuf Chat Message");
132171
}

0 commit comments

Comments
 (0)