1use anyhow::{Context as _, Result, ensure};
4use std::sync::LazyLock;
5
6use crate::chat::{Chat, ChatId, ChatVisibility, update_special_chat_names};
7use crate::constants::{
8 Blocked, Chattype, DC_CHAT_ID_ALLDONE_HINT, DC_CHAT_ID_ARCHIVED_LINK, DC_GCL_ADD_ALLDONE_HINT,
9 DC_GCL_ARCHIVED_ONLY, DC_GCL_FOR_FORWARDING, DC_GCL_NO_SPECIALS,
10};
11use crate::contact::{Contact, ContactId};
12use crate::context::Context;
13use crate::log::warn;
14use crate::message::{Message, MessageState, MsgId};
15use crate::param::{Param, Params};
16use crate::stock_str;
17use crate::summary::Summary;
18use crate::tools::IsNoneOrEmpty;
19
20pub static IS_UNREAD_FILTER: LazyLock<regex::Regex> =
22 LazyLock::new(|| regex::Regex::new(r"\bis:unread\b").unwrap());
23
24#[derive(Debug)]
46pub struct Chatlist {
47 ids: Vec<(ChatId, Option<MsgId>)>,
49}
50
51impl Chatlist {
52 pub async fn try_load(
94 context: &Context,
95 listflags: usize,
96 query: Option<&str>,
97 query_contact_id: Option<ContactId>,
98 ) -> Result<Self> {
99 let flag_archived_only = 0 != listflags & DC_GCL_ARCHIVED_ONLY;
100 let flag_for_forwarding = 0 != listflags & DC_GCL_FOR_FORWARDING;
101 let flag_no_specials = 0 != listflags & DC_GCL_NO_SPECIALS;
102 let flag_add_alldone_hint = 0 != listflags & DC_GCL_ADD_ALLDONE_HINT;
103
104 let process_row = |row: &rusqlite::Row| {
105 let chat_id: ChatId = row.get(0)?;
106 let msg_id: Option<MsgId> = row.get(1)?;
107 Ok((chat_id, msg_id))
108 };
109
110 let skip_id = if flag_for_forwarding {
111 ChatId::lookup_by_contact(context, ContactId::DEVICE)
112 .await?
113 .unwrap_or_default()
114 } else {
115 ChatId::new(0)
116 };
117
118 let ids = if let Some(query_contact_id) = query_contact_id {
129 context.sql.query_map_vec(
131 "SELECT c.id, m.id
132 FROM chats c
133 LEFT JOIN msgs m
134 ON c.id=m.chat_id
135 AND m.id=(
136 SELECT id
137 FROM msgs
138 WHERE chat_id=c.id
139 AND (hidden=0 OR state=?1)
140 ORDER BY timestamp DESC, id DESC LIMIT 1)
141 WHERE c.id>9
142 AND c.blocked!=1
143 AND c.id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?2 AND add_timestamp >= remove_timestamp)
144 GROUP BY c.id
145 ORDER BY c.archived=?3 DESC, IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;",
146 (MessageState::OutDraft, query_contact_id, ChatVisibility::Pinned),
147 process_row,
148 ).await?
149 } else if flag_archived_only {
150 context
155 .sql
156 .query_map_vec(
157 "SELECT c.id, m.id
158 FROM chats c
159 LEFT JOIN msgs m
160 ON c.id=m.chat_id
161 AND m.id=(
162 SELECT id
163 FROM msgs
164 WHERE chat_id=c.id
165 AND (hidden=0 OR state=?)
166 ORDER BY timestamp DESC, id DESC LIMIT 1)
167 WHERE c.id>9
168 AND c.blocked!=1
169 AND c.archived=1
170 GROUP BY c.id
171 ORDER BY IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;",
172 (MessageState::OutDraft,),
173 process_row,
174 )
175 .await?
176 } else if let Some(query) = query {
177 let mut query = query.trim().to_string();
178 ensure!(!query.is_empty(), "query mustn't be empty");
179 let only_unread = IS_UNREAD_FILTER.find(&query).is_some();
180 query = IS_UNREAD_FILTER.replace(&query, "").trim().to_string();
181
182 if let Err(err) = update_special_chat_names(context).await {
185 warn!(context, "Cannot update special chat names: {err:#}.")
186 }
187
188 let str_like_cmd = format!("%{}%", query.to_lowercase());
189 context
190 .sql
191 .query_map_vec(
192 "SELECT c.id, m.id
193 FROM chats c
194 LEFT JOIN msgs m
195 ON c.id=m.chat_id
196 AND m.id=(
197 SELECT id
198 FROM msgs
199 WHERE chat_id=c.id
200 AND (hidden=0 OR state=?1)
201 ORDER BY timestamp DESC, id DESC LIMIT 1)
202 WHERE c.id>9 AND c.id!=?2
203 AND c.blocked!=1
204 AND IFNULL(c.name_normalized,c.name) LIKE ?3
205 AND (NOT ?4 OR EXISTS (SELECT 1 FROM msgs m WHERE m.chat_id = c.id AND m.state == ?5 AND hidden=0))
206 GROUP BY c.id
207 ORDER BY IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;",
208 (MessageState::OutDraft, skip_id, str_like_cmd, only_unread, MessageState::InFresh),
209 process_row,
210 )
211 .await?
212 } else {
213 let mut ids = if flag_for_forwarding {
214 let sort_id_up = ChatId::lookup_by_contact(context, ContactId::SELF)
215 .await?
216 .unwrap_or_default();
217 let process_row = |row: &rusqlite::Row| {
218 let chat_id: ChatId = row.get(0)?;
219 let typ: Chattype = row.get(1)?;
220 let param: Params = row.get::<_, String>(2)?.parse().unwrap_or_default();
221 let msg_id: Option<MsgId> = row.get(3)?;
222 Ok((chat_id, typ, param, msg_id))
223 };
224 let process_rows = |rows: rusqlite::AndThenRows<_>| {
225 rows.filter_map(|row: std::result::Result<(_, _, Params, _), _>| match row {
226 Ok((chat_id, typ, param, msg_id)) => {
227 if typ == Chattype::InBroadcast
228 || (typ == Chattype::Mailinglist
229 && param.get(Param::ListPost).is_none_or_empty())
230 {
231 None
232 } else {
233 Some(Ok((chat_id, msg_id)))
234 }
235 }
236 Err(e) => Some(Err(e)),
237 })
238 .collect::<std::result::Result<Vec<_>, _>>()
239 };
240 context.sql.query_map(
241 "SELECT c.id, c.type, c.param, m.id
242 FROM chats c
243 LEFT JOIN msgs m
244 ON c.id=m.chat_id
245 AND m.id=(
246 SELECT id
247 FROM msgs
248 WHERE chat_id=c.id
249 AND (hidden=0 OR state=?)
250 ORDER BY timestamp DESC, id DESC LIMIT 1)
251 WHERE c.id>9 AND c.id!=?
252 AND c.blocked=0
253 AND NOT c.archived=?
254 AND (c.type!=? OR c.id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=? AND add_timestamp >= remove_timestamp))
255 GROUP BY c.id
256 ORDER BY c.id=? DESC, c.archived=? DESC, IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;",
257 (
258 MessageState::OutDraft, skip_id, ChatVisibility::Archived,
259 Chattype::Group, ContactId::SELF,
260 sort_id_up, ChatVisibility::Pinned,
261 ),
262 process_row,
263 process_rows,
264 ).await?
265 } else {
266 context.sql.query_map_vec(
268 "SELECT c.id, m.id
269 FROM chats c
270 LEFT JOIN msgs m
271 ON c.id=m.chat_id
272 AND m.id=(
273 SELECT id
274 FROM msgs
275 WHERE chat_id=c.id
276 AND (hidden=0 OR state=?)
277 ORDER BY timestamp DESC, id DESC LIMIT 1)
278 WHERE c.id>9 AND c.id!=?
279 AND (c.blocked=0 OR c.blocked=2)
280 AND NOT c.archived=?
281 GROUP BY c.id
282 ORDER BY c.id=0 DESC, c.archived=? DESC, IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;",
283 (MessageState::OutDraft, skip_id, ChatVisibility::Archived, ChatVisibility::Pinned),
284 process_row,
285 ).await?
286 };
287 if !flag_no_specials && get_archived_cnt(context).await? > 0 {
288 if ids.is_empty() && flag_add_alldone_hint {
289 ids.push((DC_CHAT_ID_ALLDONE_HINT, None));
290 }
291 ids.insert(0, (DC_CHAT_ID_ARCHIVED_LINK, None));
292 }
293 ids
294 };
295
296 Ok(Chatlist { ids })
297 }
298
299 pub(crate) async fn from_chat_ids(context: &Context, chat_ids: &[ChatId]) -> Result<Self> {
301 let mut ids = Vec::new();
302 for &chat_id in chat_ids {
303 let msg_id: Option<MsgId> = context
304 .sql
305 .query_get_value(
306 "SELECT id
307 FROM msgs
308 WHERE chat_id=?1
309 AND (hidden=0 OR state=?2)
310 ORDER BY timestamp DESC, id DESC LIMIT 1",
311 (chat_id, MessageState::OutDraft),
312 )
313 .await
314 .with_context(|| format!("failed to get msg ID for chat {chat_id}"))?;
315 ids.push((chat_id, msg_id));
316 }
317 Ok(Chatlist { ids })
318 }
319
320 pub fn len(&self) -> usize {
322 self.ids.len()
323 }
324
325 pub fn is_empty(&self) -> bool {
327 self.ids.is_empty()
328 }
329
330 pub fn get_chat_id(&self, index: usize) -> Result<ChatId> {
334 let (chat_id, _msg_id) = self
335 .ids
336 .get(index)
337 .context("chatlist index is out of range")?;
338 Ok(*chat_id)
339 }
340
341 pub fn get_msg_id(&self, index: usize) -> Result<Option<MsgId>> {
345 let (_chat_id, msg_id) = self
346 .ids
347 .get(index)
348 .context("chatlist index is out of range")?;
349 Ok(*msg_id)
350 }
351
352 pub async fn get_summary(
354 &self,
355 context: &Context,
356 index: usize,
357 chat: Option<&Chat>,
358 ) -> Result<Summary> {
359 let (chat_id, lastmsg_id) = self
364 .ids
365 .get(index)
366 .context("chatlist index is out of range")?;
367 Chatlist::get_summary2(context, *chat_id, *lastmsg_id, chat).await
368 }
369
370 pub async fn get_summary2(
372 context: &Context,
373 chat_id: ChatId,
374 lastmsg_id: Option<MsgId>,
375 chat: Option<&Chat>,
376 ) -> Result<Summary> {
377 let chat_loaded: Chat;
378 let chat = if let Some(chat) = chat {
379 chat
380 } else {
381 let chat = Chat::load_from_db(context, chat_id).await?;
382 chat_loaded = chat;
383 &chat_loaded
384 };
385
386 let lastmsg = if let Some(lastmsg_id) = lastmsg_id {
387 Message::load_from_db_optional(context, lastmsg_id)
390 .await
391 .context("Loading message failed")?
392 } else {
393 None
394 };
395
396 let lastcontact = if let Some(lastmsg) = &lastmsg {
397 if lastmsg.from_id == ContactId::SELF {
398 None
399 } else if chat.typ == Chattype::Group
400 || chat.typ == Chattype::Mailinglist
401 || chat.is_self_talk()
402 {
403 let lastcontact = Contact::get_by_id(context, lastmsg.from_id)
404 .await
405 .context("loading contact failed")?;
406 Some(lastcontact)
407 } else {
408 None
409 }
410 } else {
411 None
412 };
413
414 if chat.id.is_archived_link() {
415 Ok(Default::default())
416 } else if let Some(lastmsg) = lastmsg.filter(|msg| msg.from_id != ContactId::UNDEFINED) {
417 Summary::new_with_reaction_details(context, &lastmsg, chat, lastcontact.as_ref()).await
418 } else {
419 Ok(Summary {
420 text: stock_str::no_messages(context).await,
421 ..Default::default()
422 })
423 }
424 }
425
426 pub fn get_index_for_id(&self, id: ChatId) -> Option<usize> {
428 self.ids.iter().position(|(chat_id, _)| chat_id == &id)
429 }
430
431 pub fn iter(&self) -> impl Iterator<Item = &(ChatId, Option<MsgId>)> {
433 self.ids.iter()
434 }
435}
436
437pub async fn get_archived_cnt(context: &Context) -> Result<usize> {
439 let count = context
440 .sql
441 .count(
442 "SELECT COUNT(*) FROM chats WHERE blocked!=? AND archived=?;",
443 (Blocked::Yes, ChatVisibility::Archived),
444 )
445 .await?;
446 Ok(count)
447}
448
449pub async fn get_last_message_for_chat(
452 context: &Context,
453 chat_id: ChatId,
454) -> Result<Option<MsgId>> {
455 context
456 .sql
457 .query_get_value(
458 "SELECT id
459 FROM msgs
460 WHERE chat_id=?2
461 AND (hidden=0 OR state=?1)
462 ORDER BY timestamp DESC, id DESC LIMIT 1",
463 (MessageState::OutDraft, chat_id),
464 )
465 .await
466}
467
468#[cfg(test)]
469mod tests {
470 use super::*;
471 use crate::chat::save_msgs;
472 use crate::chat::{
473 add_contact_to_chat, create_broadcast, create_group, get_chat_contacts,
474 remove_contact_from_chat, send_text_msg, set_chat_name,
475 };
476 use crate::receive_imf::receive_imf;
477 use crate::securejoin::get_securejoin_qr;
478 use crate::stock_str::StockMessage;
479 use crate::test_utils::TestContext;
480 use crate::test_utils::TestContextManager;
481 use crate::tools::SystemTime;
482 use std::time::Duration;
483
484 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
485 async fn test_try_load() -> Result<()> {
486 let mut tcm = TestContextManager::new();
487 let bob = &tcm.bob().await;
488 let chat_id1 = create_group(bob, "a chat").await.unwrap();
489 let chat_id2 = create_group(bob, "b chat").await.unwrap();
490 let chat_id3 = create_group(bob, "c chat").await.unwrap();
491
492 let chats = Chatlist::try_load(bob, 0, None, None).await.unwrap();
494 assert_eq!(chats.len(), 3);
495 assert_eq!(chats.get_chat_id(0).unwrap(), chat_id3);
496 assert_eq!(chats.get_chat_id(1).unwrap(), chat_id2);
497 assert_eq!(chats.get_chat_id(2).unwrap(), chat_id1);
498
499 SystemTime::shift(Duration::from_secs(5));
500
501 for chat_id in &[chat_id1, chat_id3, chat_id2] {
510 let mut msg = Message::new_text("hello".to_string());
511 chat_id.set_draft(bob, Some(&mut msg)).await.unwrap();
512 }
513
514 let chats = Chatlist::try_load(bob, 0, None, None).await.unwrap();
515 assert_eq!(chats.get_chat_id(0).unwrap(), chat_id2);
516
517 let chats = Chatlist::try_load(bob, 0, Some("b"), None).await.unwrap();
519 assert_eq!(chats.len(), 1);
520
521 let alice = &tcm.alice().await;
523 let alice_chat_id = create_group(alice, "alice chat").await.unwrap();
524 add_contact_to_chat(
525 alice,
526 alice_chat_id,
527 alice.add_or_lookup_contact_id(bob).await,
528 )
529 .await
530 .unwrap();
531 send_text_msg(alice, alice_chat_id, "hi".into())
532 .await
533 .unwrap();
534 let sent_msg = alice.pop_sent_msg().await;
535
536 bob.recv_msg(&sent_msg).await;
537 let chats = Chatlist::try_load(bob, 0, Some("is:unread"), None)
538 .await
539 .unwrap();
540 assert_eq!(chats.len(), 1);
541
542 let chats = Chatlist::try_load(bob, DC_GCL_ARCHIVED_ONLY, None, None)
543 .await
544 .unwrap();
545 assert_eq!(chats.len(), 0);
546
547 chat_id1
548 .set_visibility(bob, ChatVisibility::Archived)
549 .await
550 .ok();
551 let chats = Chatlist::try_load(bob, DC_GCL_ARCHIVED_ONLY, None, None)
552 .await
553 .unwrap();
554 assert_eq!(chats.len(), 1);
555
556 let chat_id = create_group(bob, "Δ-chat").await.unwrap();
557 let chats = Chatlist::try_load(bob, 0, Some("δ"), None).await?;
558 assert_eq!(chats.len(), 1);
559 assert_eq!(chats.ids[0].0, chat_id);
560 set_chat_name(bob, chat_id, "abcδe").await?;
561 let chats = Chatlist::try_load(bob, 0, Some("Δ"), None).await?;
562 assert_eq!(chats.len(), 1);
563 Ok(())
564 }
565
566 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
567 async fn test_sort_self_talk_up_on_forward() {
568 let t = TestContext::new_alice().await;
569 t.update_device_chats().await.unwrap();
570 create_group(&t, "a chat").await.unwrap();
571
572 let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap();
573 assert_eq!(chats.len(), 3);
574 assert!(
575 !Chat::load_from_db(&t, chats.get_chat_id(0).unwrap())
576 .await
577 .unwrap()
578 .is_self_talk()
579 );
580
581 let chats = Chatlist::try_load(&t, DC_GCL_FOR_FORWARDING, None, None)
582 .await
583 .unwrap();
584 assert_eq!(chats.len(), 2); assert!(
586 Chat::load_from_db(&t, chats.get_chat_id(0).unwrap())
587 .await
588 .unwrap()
589 .is_self_talk()
590 );
591
592 remove_contact_from_chat(&t, chats.get_chat_id(1).unwrap(), ContactId::SELF)
593 .await
594 .unwrap();
595 let chats = Chatlist::try_load(&t, DC_GCL_FOR_FORWARDING, None, None)
596 .await
597 .unwrap();
598 assert_eq!(chats.len(), 1);
599 }
600
601 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
604 async fn test_broadcast_visiblity_on_forward() -> Result<()> {
605 let mut tcm = TestContextManager::new();
606 let alice = &tcm.alice().await;
607 let bob = &tcm.bob().await;
608
609 let alice_broadcast_a_id = create_broadcast(alice, "Channel Alice".to_string()).await?;
610 let qr = get_securejoin_qr(alice, Some(alice_broadcast_a_id))
611 .await
612 .unwrap();
613 let bob_broadcast_a_id = tcm.exec_securejoin_qr(bob, alice, &qr).await;
614 let bob_broadcast_b_id = create_broadcast(bob, "Channel Bob".to_string()).await?;
615
616 let chats = Chatlist::try_load(bob, DC_GCL_FOR_FORWARDING, None, None)
617 .await
618 .unwrap();
619
620 assert!(
621 !chats
622 .iter()
623 .any(|(chat_id, _)| chat_id == &bob_broadcast_a_id),
624 "alice broadcast is not shown in bobs forwarding chatlist"
625 );
626 assert!(
627 chats
628 .iter()
629 .any(|(chat_id, _)| chat_id == &bob_broadcast_b_id),
630 "bobs own broadcast is shown in his forwarding chatlist"
631 );
632
633 Ok(())
634 }
635
636 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
637 async fn test_search_special_chat_names() {
638 let t = TestContext::new_alice().await;
639 t.update_device_chats().await.unwrap();
640
641 let chats = Chatlist::try_load(&t, 0, Some("t-1234-s"), None)
642 .await
643 .unwrap();
644 assert_eq!(chats.len(), 0);
645 let chats = Chatlist::try_load(&t, 0, Some("t-5678-b"), None)
646 .await
647 .unwrap();
648 assert_eq!(chats.len(), 0);
649
650 t.set_stock_translation(StockMessage::SavedMessages, "test-1234-save".to_string())
651 .await
652 .unwrap();
653 let chats = Chatlist::try_load(&t, 0, Some("t-1234-s"), None)
654 .await
655 .unwrap();
656 assert_eq!(chats.len(), 1);
657
658 t.set_stock_translation(StockMessage::DeviceMessages, "test-5678-babbel".to_string())
659 .await
660 .unwrap();
661 let chats = Chatlist::try_load(&t, 0, Some("t-5678-b"), None)
662 .await
663 .unwrap();
664 assert_eq!(chats.len(), 1);
665 }
666
667 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
668 async fn test_search_single_chat() -> anyhow::Result<()> {
669 let t = TestContext::new_alice().await;
670
671 receive_imf(
673 &t,
674 b"From: Bob Authname <bob@example.org>\n\
675 To: alice@example.org\n\
676 Subject: foo\n\
677 Message-ID: <msg1234@example.org>\n\
678 Chat-Version: 1.0\n\
679 Date: Sun, 22 Mar 2021 22:37:57 +0000\n\
680 \n\
681 hello foo\n",
682 false,
683 )
684 .await?;
685
686 let chats = Chatlist::try_load(&t, 0, Some("Bob Authname"), None).await?;
687 assert_eq!(chats.len(), 1);
689
690 let msg = t.get_last_msg().await;
691 let chat_id = msg.get_chat_id();
692 chat_id.accept(&t).await.unwrap();
693
694 let contacts = get_chat_contacts(&t, chat_id).await?;
695 let contact_id = *contacts.first().unwrap();
696 let chat = Chat::load_from_db(&t, chat_id).await?;
697 assert_eq!(chat.get_name(), "Bob Authname");
698
699 let chats = Chatlist::try_load(&t, 0, Some("bob authname"), None).await?;
701 assert_eq!(chats.len(), 1);
702 assert_eq!(chats.get_chat_id(0).unwrap(), chat_id);
703
704 let test_id = Contact::create(&t, "Bob Nickname", "bob@example.org").await?;
706 assert_eq!(contact_id, test_id);
707 let chat = Chat::load_from_db(&t, chat_id).await?;
708 assert_eq!(chat.get_name(), "Bob Nickname");
709 let chats = Chatlist::try_load(&t, 0, Some("bob authname"), None).await?;
710 assert_eq!(chats.len(), 0);
711 let chats = Chatlist::try_load(&t, 0, Some("bob nickname"), None).await?;
712 assert_eq!(chats.len(), 1);
713
714 let test_id = Contact::create(&t, "", "bob@example.org").await?;
716 assert_eq!(contact_id, test_id);
717 let chat = Chat::load_from_db(&t, chat_id).await?;
718 assert_eq!(chat.get_name(), "Bob Authname");
719 let chats = Chatlist::try_load(&t, 0, Some("bob authname"), None).await?;
720 assert_eq!(chats.len(), 1);
721 let chats = Chatlist::try_load(&t, 0, Some("bob nickname"), None).await?;
722 assert_eq!(chats.len(), 0);
723
724 Ok(())
725 }
726
727 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
728 async fn test_search_single_chat_without_authname() -> anyhow::Result<()> {
729 let t = TestContext::new_alice().await;
730
731 receive_imf(
733 &t,
734 b"From: bob@example.org\n\
735 To: alice@example.org\n\
736 Subject: foo\n\
737 Message-ID: <msg5678@example.org>\n\
738 Chat-Version: 1.0\n\
739 Date: Sun, 22 Mar 2021 22:38:57 +0000\n\
740 \n\
741 hello foo\n",
742 false,
743 )
744 .await?;
745
746 let msg = t.get_last_msg().await;
747 let chat_id = msg.get_chat_id();
748 chat_id.accept(&t).await.unwrap();
749 let contacts = get_chat_contacts(&t, chat_id).await?;
750 let contact_id = *contacts.first().unwrap();
751 let chat = Chat::load_from_db(&t, chat_id).await?;
752 assert_eq!(chat.get_name(), "bob@example.org");
753
754 let chats = Chatlist::try_load(&t, 0, Some("bob@example.org"), None).await?;
756 assert_eq!(chats.len(), 1);
757 assert_eq!(chats.get_chat_id(0)?, chat_id);
758
759 let test_id = Contact::create(&t, "Bob Nickname", "bob@example.org").await?;
761 assert_eq!(contact_id, test_id);
762 let chat = Chat::load_from_db(&t, chat_id).await?;
763 assert_eq!(chat.get_name(), "Bob Nickname");
764 let chats = Chatlist::try_load(&t, 0, Some("bob@example.org"), None).await?;
765 assert_eq!(chats.len(), 0); let chats = Chatlist::try_load(&t, 0, Some("Bob Nickname"), None).await?;
767 assert_eq!(chats.len(), 1);
768 assert_eq!(chats.get_chat_id(0)?, chat_id);
769
770 let test_id = Contact::create(&t, "", "bob@example.org").await?;
772 assert_eq!(contact_id, test_id);
773 let chat = Chat::load_from_db(&t, chat_id).await?;
774 assert_eq!(chat.get_name(), "bob@example.org");
775 let chats = Chatlist::try_load(&t, 0, Some("bob@example.org"), None).await?;
776 assert_eq!(chats.len(), 1);
777 let chats = Chatlist::try_load(&t, 0, Some("bob nickname"), None).await?;
778 assert_eq!(chats.len(), 0);
779
780 let chats = Chatlist::try_load(&t, 0, Some("b@exa"), None).await?;
782 assert_eq!(chats.len(), 1);
783 let chats = Chatlist::try_load(&t, 0, Some("b@exac"), None).await?;
784 assert_eq!(chats.len(), 0);
785
786 Ok(())
787 }
788
789 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
790 async fn test_get_summary_unwrap() {
791 let t = TestContext::new().await;
792 let chat_id1 = create_group(&t, "a chat").await.unwrap();
793
794 let mut msg = Message::new_text("foo:\nbar \r\n test".to_string());
795 chat_id1.set_draft(&t, Some(&mut msg)).await.unwrap();
796
797 let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap();
798 let summary = chats.get_summary(&t, 0, None).await.unwrap();
799 assert_eq!(summary.text, "foo: bar test"); }
801
802 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
805 async fn test_get_summary_deleted_draft() {
806 let t = TestContext::new().await;
807
808 let chat_id = create_group(&t, "a chat").await.unwrap();
809 let mut msg = Message::new_text("Foobar".to_string());
810 chat_id.set_draft(&t, Some(&mut msg)).await.unwrap();
811
812 let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap();
813 chat_id.set_draft(&t, None).await.unwrap();
814
815 let summary_res = chats.get_summary(&t, 0, None).await;
816 assert!(summary_res.is_ok());
817 }
818
819 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
820 async fn test_get_summary_for_saved_messages() -> Result<()> {
821 let mut tcm = TestContextManager::new();
822 let alice = tcm.alice().await;
823 let bob = tcm.bob().await;
824 let chat_alice = alice.create_chat(&bob).await;
825
826 send_text_msg(&alice, chat_alice.id, "hi".into()).await?;
827 let sent1 = alice.pop_sent_msg().await;
828 save_msgs(&alice, &[sent1.sender_msg_id]).await?;
829 let chatlist = Chatlist::try_load(&alice, 0, None, None).await?;
830 let summary = chatlist.get_summary(&alice, 0, None).await?;
831 assert_eq!(summary.prefix.unwrap().to_string(), "Me");
832 assert_eq!(summary.text, "hi");
833
834 let msg = bob.recv_msg(&sent1).await;
835 save_msgs(&bob, &[msg.id]).await?;
836 let chatlist = Chatlist::try_load(&bob, 0, None, None).await?;
837 let summary = chatlist.get_summary(&bob, 0, None).await?;
838 assert_eq!(summary.prefix.unwrap().to_string(), "alice@example.org");
839 assert_eq!(summary.text, "hi");
840
841 Ok(())
842 }
843
844 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
845 async fn test_no_summary_prefix_for_channel() -> Result<()> {
846 let mut tcm = TestContextManager::new();
847 let alice = tcm.alice().await;
848 let bob = tcm.bob().await;
849
850 let alice_chat_id = create_broadcast(&alice, "alice's channel".to_string()).await?;
851 let qr = get_securejoin_qr(&alice, Some(alice_chat_id)).await?;
852 tcm.exec_securejoin_qr(&bob, &alice, &qr).await;
853
854 send_text_msg(&alice, alice_chat_id, "hi".into()).await?;
855 let sent1 = alice.pop_sent_msg().await;
856 let chatlist = Chatlist::try_load(&alice, 0, None, None).await?;
857 let summary = chatlist.get_summary(&alice, 0, None).await?;
858 assert!(summary.prefix.is_none());
859 assert_eq!(summary.text, "hi");
860
861 bob.recv_msg(&sent1).await;
862 let chatlist = Chatlist::try_load(&bob, 0, None, None).await?;
863 let summary = chatlist.get_summary(&bob, 0, None).await?;
864 assert!(summary.prefix.is_none());
865 assert_eq!(summary.text, "hi");
866
867 Ok(())
868 }
869
870 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
871 async fn test_load_broken() {
872 let t = TestContext::new_bob().await;
873 let chat_id1 = create_group(&t, "a chat").await.unwrap();
874 create_group(&t, "b chat").await.unwrap();
875 create_group(&t, "c chat").await.unwrap();
876
877 let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap();
879 assert_eq!(chats.len(), 3);
880
881 t.sql
883 .execute("UPDATE chats SET type=10 WHERE id=?", (chat_id1,))
884 .await
885 .unwrap();
886
887 assert!(Chat::load_from_db(&t, chat_id1).await.is_err());
889
890 let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap();
892
893 assert!(chats.get_summary(&t, 0, None).await.is_ok());
895 assert!(chats.get_summary(&t, 1, None).await.is_ok());
896 assert!(chats.get_summary(&t, 2, None).await.is_err());
897 assert_eq!(chats.get_index_for_id(chat_id1).unwrap(), 2);
898 }
899}