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::Mailinglist
228 && param.get(Param::ListPost).is_none_or_empty()
229 {
230 None
231 } else {
232 Some(Ok((chat_id, msg_id)))
233 }
234 }
235 Err(e) => Some(Err(e)),
236 })
237 .collect::<std::result::Result<Vec<_>, _>>()
238 };
239 context.sql.query_map(
240 "SELECT c.id, c.type, c.param, m.id
241 FROM chats c
242 LEFT JOIN msgs m
243 ON c.id=m.chat_id
244 AND m.id=(
245 SELECT id
246 FROM msgs
247 WHERE chat_id=c.id
248 AND (hidden=0 OR state=?)
249 ORDER BY timestamp DESC, id DESC LIMIT 1)
250 WHERE c.id>9 AND c.id!=?
251 AND c.blocked=0
252 AND NOT c.archived=?
253 AND (c.type!=? OR c.id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=? AND add_timestamp >= remove_timestamp))
254 GROUP BY c.id
255 ORDER BY c.id=? DESC, c.archived=? DESC, IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;",
256 (
257 MessageState::OutDraft, skip_id, ChatVisibility::Archived,
258 Chattype::Group, ContactId::SELF,
259 sort_id_up, ChatVisibility::Pinned,
260 ),
261 process_row,
262 process_rows,
263 ).await?
264 } else {
265 context.sql.query_map_vec(
267 "SELECT c.id, m.id
268 FROM chats c
269 LEFT JOIN msgs m
270 ON c.id=m.chat_id
271 AND m.id=(
272 SELECT id
273 FROM msgs
274 WHERE chat_id=c.id
275 AND (hidden=0 OR state=?)
276 ORDER BY timestamp DESC, id DESC LIMIT 1)
277 WHERE c.id>9 AND c.id!=?
278 AND (c.blocked=0 OR c.blocked=2)
279 AND NOT c.archived=?
280 GROUP BY c.id
281 ORDER BY c.id=0 DESC, c.archived=? DESC, IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;",
282 (MessageState::OutDraft, skip_id, ChatVisibility::Archived, ChatVisibility::Pinned),
283 process_row,
284 ).await?
285 };
286 if !flag_no_specials && get_archived_cnt(context).await? > 0 {
287 if ids.is_empty() && flag_add_alldone_hint {
288 ids.push((DC_CHAT_ID_ALLDONE_HINT, None));
289 }
290 ids.insert(0, (DC_CHAT_ID_ARCHIVED_LINK, None));
291 }
292 ids
293 };
294
295 Ok(Chatlist { ids })
296 }
297
298 pub(crate) async fn from_chat_ids(context: &Context, chat_ids: &[ChatId]) -> Result<Self> {
300 let mut ids = Vec::new();
301 for &chat_id in chat_ids {
302 let msg_id: Option<MsgId> = context
303 .sql
304 .query_get_value(
305 "SELECT id
306 FROM msgs
307 WHERE chat_id=?1
308 AND (hidden=0 OR state=?2)
309 ORDER BY timestamp DESC, id DESC LIMIT 1",
310 (chat_id, MessageState::OutDraft),
311 )
312 .await
313 .with_context(|| format!("failed to get msg ID for chat {chat_id}"))?;
314 ids.push((chat_id, msg_id));
315 }
316 Ok(Chatlist { ids })
317 }
318
319 pub fn len(&self) -> usize {
321 self.ids.len()
322 }
323
324 pub fn is_empty(&self) -> bool {
326 self.ids.is_empty()
327 }
328
329 pub fn get_chat_id(&self, index: usize) -> Result<ChatId> {
333 let (chat_id, _msg_id) = self
334 .ids
335 .get(index)
336 .context("chatlist index is out of range")?;
337 Ok(*chat_id)
338 }
339
340 pub fn get_msg_id(&self, index: usize) -> Result<Option<MsgId>> {
344 let (_chat_id, msg_id) = self
345 .ids
346 .get(index)
347 .context("chatlist index is out of range")?;
348 Ok(*msg_id)
349 }
350
351 pub async fn get_summary(
353 &self,
354 context: &Context,
355 index: usize,
356 chat: Option<&Chat>,
357 ) -> Result<Summary> {
358 let (chat_id, lastmsg_id) = self
363 .ids
364 .get(index)
365 .context("chatlist index is out of range")?;
366 Chatlist::get_summary2(context, *chat_id, *lastmsg_id, chat).await
367 }
368
369 pub async fn get_summary2(
371 context: &Context,
372 chat_id: ChatId,
373 lastmsg_id: Option<MsgId>,
374 chat: Option<&Chat>,
375 ) -> Result<Summary> {
376 let chat_loaded: Chat;
377 let chat = if let Some(chat) = chat {
378 chat
379 } else {
380 let chat = Chat::load_from_db(context, chat_id).await?;
381 chat_loaded = chat;
382 &chat_loaded
383 };
384
385 let lastmsg = if let Some(lastmsg_id) = lastmsg_id {
386 Message::load_from_db_optional(context, lastmsg_id)
389 .await
390 .context("Loading message failed")?
391 } else {
392 None
393 };
394
395 let lastcontact = if let Some(lastmsg) = &lastmsg {
396 if lastmsg.from_id == ContactId::SELF {
397 None
398 } else if chat.typ == Chattype::Group
399 || chat.typ == Chattype::Mailinglist
400 || chat.is_self_talk()
401 {
402 let lastcontact = Contact::get_by_id(context, lastmsg.from_id)
403 .await
404 .context("loading contact failed")?;
405 Some(lastcontact)
406 } else {
407 None
408 }
409 } else {
410 None
411 };
412
413 if chat.id.is_archived_link() {
414 Ok(Default::default())
415 } else if let Some(lastmsg) = lastmsg.filter(|msg| msg.from_id != ContactId::UNDEFINED) {
416 Summary::new_with_reaction_details(context, &lastmsg, chat, lastcontact.as_ref()).await
417 } else {
418 Ok(Summary {
419 text: stock_str::no_messages(context).await,
420 ..Default::default()
421 })
422 }
423 }
424
425 pub fn get_index_for_id(&self, id: ChatId) -> Option<usize> {
427 self.ids.iter().position(|(chat_id, _)| chat_id == &id)
428 }
429
430 pub fn iter(&self) -> impl Iterator<Item = &(ChatId, Option<MsgId>)> {
432 self.ids.iter()
433 }
434}
435
436pub async fn get_archived_cnt(context: &Context) -> Result<usize> {
438 let count = context
439 .sql
440 .count(
441 "SELECT COUNT(*) FROM chats WHERE blocked!=? AND archived=?;",
442 (Blocked::Yes, ChatVisibility::Archived),
443 )
444 .await?;
445 Ok(count)
446}
447
448pub async fn get_last_message_for_chat(
451 context: &Context,
452 chat_id: ChatId,
453) -> Result<Option<MsgId>> {
454 context
455 .sql
456 .query_get_value(
457 "SELECT id
458 FROM msgs
459 WHERE chat_id=?2
460 AND (hidden=0 OR state=?1)
461 ORDER BY timestamp DESC, id DESC LIMIT 1",
462 (MessageState::OutDraft, chat_id),
463 )
464 .await
465}
466
467#[cfg(test)]
468mod tests {
469 use super::*;
470 use crate::chat::save_msgs;
471 use crate::chat::{
472 add_contact_to_chat, create_broadcast, create_group, get_chat_contacts,
473 remove_contact_from_chat, send_text_msg, set_chat_name,
474 };
475 use crate::receive_imf::receive_imf;
476 use crate::securejoin::get_securejoin_qr;
477 use crate::stock_str::StockMessage;
478 use crate::test_utils::TestContext;
479 use crate::test_utils::TestContextManager;
480 use crate::tools::SystemTime;
481 use std::time::Duration;
482
483 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
484 async fn test_try_load() -> Result<()> {
485 let mut tcm = TestContextManager::new();
486 let bob = &tcm.bob().await;
487 let chat_id1 = create_group(bob, "a chat").await.unwrap();
488 let chat_id2 = create_group(bob, "b chat").await.unwrap();
489 let chat_id3 = create_group(bob, "c chat").await.unwrap();
490
491 let chats = Chatlist::try_load(bob, 0, None, None).await.unwrap();
493 assert_eq!(chats.len(), 3);
494 assert_eq!(chats.get_chat_id(0).unwrap(), chat_id3);
495 assert_eq!(chats.get_chat_id(1).unwrap(), chat_id2);
496 assert_eq!(chats.get_chat_id(2).unwrap(), chat_id1);
497
498 SystemTime::shift(Duration::from_secs(5));
499
500 for chat_id in &[chat_id1, chat_id3, chat_id2] {
509 let mut msg = Message::new_text("hello".to_string());
510 chat_id.set_draft(bob, Some(&mut msg)).await.unwrap();
511 }
512
513 let chats = Chatlist::try_load(bob, 0, None, None).await.unwrap();
514 assert_eq!(chats.get_chat_id(0).unwrap(), chat_id2);
515
516 let chats = Chatlist::try_load(bob, 0, Some("b"), None).await.unwrap();
518 assert_eq!(chats.len(), 1);
519
520 let alice = &tcm.alice().await;
522 let alice_chat_id = create_group(alice, "alice chat").await.unwrap();
523 add_contact_to_chat(
524 alice,
525 alice_chat_id,
526 alice.add_or_lookup_contact_id(bob).await,
527 )
528 .await
529 .unwrap();
530 send_text_msg(alice, alice_chat_id, "hi".into())
531 .await
532 .unwrap();
533 let sent_msg = alice.pop_sent_msg().await;
534
535 bob.recv_msg(&sent_msg).await;
536 let chats = Chatlist::try_load(bob, 0, Some("is:unread"), None)
537 .await
538 .unwrap();
539 assert_eq!(chats.len(), 1);
540
541 let chats = Chatlist::try_load(bob, DC_GCL_ARCHIVED_ONLY, None, None)
542 .await
543 .unwrap();
544 assert_eq!(chats.len(), 0);
545
546 chat_id1
547 .set_visibility(bob, ChatVisibility::Archived)
548 .await
549 .ok();
550 let chats = Chatlist::try_load(bob, DC_GCL_ARCHIVED_ONLY, None, None)
551 .await
552 .unwrap();
553 assert_eq!(chats.len(), 1);
554
555 let chat_id = create_group(bob, "Δ-chat").await.unwrap();
556 let chats = Chatlist::try_load(bob, 0, Some("δ"), None).await?;
557 assert_eq!(chats.len(), 1);
558 assert_eq!(chats.ids[0].0, chat_id);
559 set_chat_name(bob, chat_id, "abcδe").await?;
560 let chats = Chatlist::try_load(bob, 0, Some("Δ"), None).await?;
561 assert_eq!(chats.len(), 1);
562 Ok(())
563 }
564
565 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
566 async fn test_sort_self_talk_up_on_forward() {
567 let t = TestContext::new_alice().await;
568 t.update_device_chats().await.unwrap();
569 create_group(&t, "a chat").await.unwrap();
570
571 let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap();
572 assert_eq!(chats.len(), 3);
573 assert!(
574 !Chat::load_from_db(&t, chats.get_chat_id(0).unwrap())
575 .await
576 .unwrap()
577 .is_self_talk()
578 );
579
580 let chats = Chatlist::try_load(&t, DC_GCL_FOR_FORWARDING, None, None)
581 .await
582 .unwrap();
583 assert_eq!(chats.len(), 2); assert!(
585 Chat::load_from_db(&t, chats.get_chat_id(0).unwrap())
586 .await
587 .unwrap()
588 .is_self_talk()
589 );
590
591 remove_contact_from_chat(&t, chats.get_chat_id(1).unwrap(), ContactId::SELF)
592 .await
593 .unwrap();
594 let chats = Chatlist::try_load(&t, DC_GCL_FOR_FORWARDING, None, None)
595 .await
596 .unwrap();
597 assert_eq!(chats.len(), 1);
598 }
599
600 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
601 async fn test_search_special_chat_names() {
602 let t = TestContext::new_alice().await;
603 t.update_device_chats().await.unwrap();
604
605 let chats = Chatlist::try_load(&t, 0, Some("t-1234-s"), None)
606 .await
607 .unwrap();
608 assert_eq!(chats.len(), 0);
609 let chats = Chatlist::try_load(&t, 0, Some("t-5678-b"), None)
610 .await
611 .unwrap();
612 assert_eq!(chats.len(), 0);
613
614 t.set_stock_translation(StockMessage::SavedMessages, "test-1234-save".to_string())
615 .await
616 .unwrap();
617 let chats = Chatlist::try_load(&t, 0, Some("t-1234-s"), None)
618 .await
619 .unwrap();
620 assert_eq!(chats.len(), 1);
621
622 t.set_stock_translation(StockMessage::DeviceMessages, "test-5678-babbel".to_string())
623 .await
624 .unwrap();
625 let chats = Chatlist::try_load(&t, 0, Some("t-5678-b"), None)
626 .await
627 .unwrap();
628 assert_eq!(chats.len(), 1);
629 }
630
631 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
632 async fn test_search_single_chat() -> anyhow::Result<()> {
633 let t = TestContext::new_alice().await;
634
635 receive_imf(
637 &t,
638 b"From: Bob Authname <bob@example.org>\n\
639 To: alice@example.org\n\
640 Subject: foo\n\
641 Message-ID: <msg1234@example.org>\n\
642 Chat-Version: 1.0\n\
643 Date: Sun, 22 Mar 2021 22:37:57 +0000\n\
644 \n\
645 hello foo\n",
646 false,
647 )
648 .await?;
649
650 let chats = Chatlist::try_load(&t, 0, Some("Bob Authname"), None).await?;
651 assert_eq!(chats.len(), 1);
653
654 let msg = t.get_last_msg().await;
655 let chat_id = msg.get_chat_id();
656 chat_id.accept(&t).await.unwrap();
657
658 let contacts = get_chat_contacts(&t, chat_id).await?;
659 let contact_id = *contacts.first().unwrap();
660 let chat = Chat::load_from_db(&t, chat_id).await?;
661 assert_eq!(chat.get_name(), "Bob Authname");
662
663 let chats = Chatlist::try_load(&t, 0, Some("bob authname"), None).await?;
665 assert_eq!(chats.len(), 1);
666 assert_eq!(chats.get_chat_id(0).unwrap(), chat_id);
667
668 let test_id = Contact::create(&t, "Bob Nickname", "bob@example.org").await?;
670 assert_eq!(contact_id, test_id);
671 let chat = Chat::load_from_db(&t, chat_id).await?;
672 assert_eq!(chat.get_name(), "Bob Nickname");
673 let chats = Chatlist::try_load(&t, 0, Some("bob authname"), None).await?;
674 assert_eq!(chats.len(), 0);
675 let chats = Chatlist::try_load(&t, 0, Some("bob nickname"), None).await?;
676 assert_eq!(chats.len(), 1);
677
678 let test_id = Contact::create(&t, "", "bob@example.org").await?;
680 assert_eq!(contact_id, test_id);
681 let chat = Chat::load_from_db(&t, chat_id).await?;
682 assert_eq!(chat.get_name(), "Bob Authname");
683 let chats = Chatlist::try_load(&t, 0, Some("bob authname"), None).await?;
684 assert_eq!(chats.len(), 1);
685 let chats = Chatlist::try_load(&t, 0, Some("bob nickname"), None).await?;
686 assert_eq!(chats.len(), 0);
687
688 Ok(())
689 }
690
691 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
692 async fn test_search_single_chat_without_authname() -> anyhow::Result<()> {
693 let t = TestContext::new_alice().await;
694
695 receive_imf(
697 &t,
698 b"From: bob@example.org\n\
699 To: alice@example.org\n\
700 Subject: foo\n\
701 Message-ID: <msg5678@example.org>\n\
702 Chat-Version: 1.0\n\
703 Date: Sun, 22 Mar 2021 22:38:57 +0000\n\
704 \n\
705 hello foo\n",
706 false,
707 )
708 .await?;
709
710 let msg = t.get_last_msg().await;
711 let chat_id = msg.get_chat_id();
712 chat_id.accept(&t).await.unwrap();
713 let contacts = get_chat_contacts(&t, chat_id).await?;
714 let contact_id = *contacts.first().unwrap();
715 let chat = Chat::load_from_db(&t, chat_id).await?;
716 assert_eq!(chat.get_name(), "bob@example.org");
717
718 let chats = Chatlist::try_load(&t, 0, Some("bob@example.org"), None).await?;
720 assert_eq!(chats.len(), 1);
721 assert_eq!(chats.get_chat_id(0)?, chat_id);
722
723 let test_id = Contact::create(&t, "Bob Nickname", "bob@example.org").await?;
725 assert_eq!(contact_id, test_id);
726 let chat = Chat::load_from_db(&t, chat_id).await?;
727 assert_eq!(chat.get_name(), "Bob Nickname");
728 let chats = Chatlist::try_load(&t, 0, Some("bob@example.org"), None).await?;
729 assert_eq!(chats.len(), 0); let chats = Chatlist::try_load(&t, 0, Some("Bob Nickname"), None).await?;
731 assert_eq!(chats.len(), 1);
732 assert_eq!(chats.get_chat_id(0)?, chat_id);
733
734 let test_id = Contact::create(&t, "", "bob@example.org").await?;
736 assert_eq!(contact_id, test_id);
737 let chat = Chat::load_from_db(&t, chat_id).await?;
738 assert_eq!(chat.get_name(), "bob@example.org");
739 let chats = Chatlist::try_load(&t, 0, Some("bob@example.org"), None).await?;
740 assert_eq!(chats.len(), 1);
741 let chats = Chatlist::try_load(&t, 0, Some("bob nickname"), None).await?;
742 assert_eq!(chats.len(), 0);
743
744 let chats = Chatlist::try_load(&t, 0, Some("b@exa"), None).await?;
746 assert_eq!(chats.len(), 1);
747 let chats = Chatlist::try_load(&t, 0, Some("b@exac"), None).await?;
748 assert_eq!(chats.len(), 0);
749
750 Ok(())
751 }
752
753 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
754 async fn test_get_summary_unwrap() {
755 let t = TestContext::new().await;
756 let chat_id1 = create_group(&t, "a chat").await.unwrap();
757
758 let mut msg = Message::new_text("foo:\nbar \r\n test".to_string());
759 chat_id1.set_draft(&t, Some(&mut msg)).await.unwrap();
760
761 let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap();
762 let summary = chats.get_summary(&t, 0, None).await.unwrap();
763 assert_eq!(summary.text, "foo: bar test"); }
765
766 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
769 async fn test_get_summary_deleted_draft() {
770 let t = TestContext::new().await;
771
772 let chat_id = create_group(&t, "a chat").await.unwrap();
773 let mut msg = Message::new_text("Foobar".to_string());
774 chat_id.set_draft(&t, Some(&mut msg)).await.unwrap();
775
776 let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap();
777 chat_id.set_draft(&t, None).await.unwrap();
778
779 let summary_res = chats.get_summary(&t, 0, None).await;
780 assert!(summary_res.is_ok());
781 }
782
783 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
784 async fn test_get_summary_for_saved_messages() -> Result<()> {
785 let mut tcm = TestContextManager::new();
786 let alice = tcm.alice().await;
787 let bob = tcm.bob().await;
788 let chat_alice = alice.create_chat(&bob).await;
789
790 send_text_msg(&alice, chat_alice.id, "hi".into()).await?;
791 let sent1 = alice.pop_sent_msg().await;
792 save_msgs(&alice, &[sent1.sender_msg_id]).await?;
793 let chatlist = Chatlist::try_load(&alice, 0, None, None).await?;
794 let summary = chatlist.get_summary(&alice, 0, None).await?;
795 assert_eq!(summary.prefix.unwrap().to_string(), "Me");
796 assert_eq!(summary.text, "hi");
797
798 let msg = bob.recv_msg(&sent1).await;
799 save_msgs(&bob, &[msg.id]).await?;
800 let chatlist = Chatlist::try_load(&bob, 0, None, None).await?;
801 let summary = chatlist.get_summary(&bob, 0, None).await?;
802 assert_eq!(summary.prefix.unwrap().to_string(), "alice@example.org");
803 assert_eq!(summary.text, "hi");
804
805 Ok(())
806 }
807
808 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
809 async fn test_no_summary_prefix_for_channel() -> Result<()> {
810 let mut tcm = TestContextManager::new();
811 let alice = tcm.alice().await;
812 let bob = tcm.bob().await;
813
814 let alice_chat_id = create_broadcast(&alice, "alice's channel".to_string()).await?;
815 let qr = get_securejoin_qr(&alice, Some(alice_chat_id)).await?;
816 tcm.exec_securejoin_qr(&bob, &alice, &qr).await;
817
818 send_text_msg(&alice, alice_chat_id, "hi".into()).await?;
819 let sent1 = alice.pop_sent_msg().await;
820 let chatlist = Chatlist::try_load(&alice, 0, None, None).await?;
821 let summary = chatlist.get_summary(&alice, 0, None).await?;
822 assert!(summary.prefix.is_none());
823 assert_eq!(summary.text, "hi");
824
825 bob.recv_msg(&sent1).await;
826 let chatlist = Chatlist::try_load(&bob, 0, None, None).await?;
827 let summary = chatlist.get_summary(&bob, 0, None).await?;
828 assert!(summary.prefix.is_none());
829 assert_eq!(summary.text, "hi");
830
831 Ok(())
832 }
833
834 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
835 async fn test_load_broken() {
836 let t = TestContext::new_bob().await;
837 let chat_id1 = create_group(&t, "a chat").await.unwrap();
838 create_group(&t, "b chat").await.unwrap();
839 create_group(&t, "c chat").await.unwrap();
840
841 let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap();
843 assert_eq!(chats.len(), 3);
844
845 t.sql
847 .execute("UPDATE chats SET type=10 WHERE id=?", (chat_id1,))
848 .await
849 .unwrap();
850
851 assert!(Chat::load_from_db(&t, chat_id1).await.is_err());
853
854 let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap();
856
857 assert!(chats.get_summary(&t, 0, None).await.is_ok());
859 assert!(chats.get_summary(&t, 1, None).await.is_ok());
860 assert!(chats.get_summary(&t, 2, None).await.is_err());
861 assert_eq!(chats.get_index_for_id(chat_id1).unwrap(), 2);
862 }
863}