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