1use std::cmp::{min, Reverse};
4use std::collections::{BinaryHeap, HashSet};
5use std::fmt;
6use std::path::{Path, PathBuf};
7use std::time::UNIX_EPOCH;
8
9use anyhow::{bail, ensure, Context as _, Result};
10use async_channel::{self as channel, Receiver, Sender};
11use base64::Engine as _;
12pub use deltachat_contact_tools::may_be_valid_addr;
13use deltachat_contact_tools::{
14 self as contact_tools, addr_cmp, addr_normalize, sanitize_name, sanitize_name_and_addr,
15 ContactAddress, VcardContact,
16};
17use deltachat_derive::{FromSql, ToSql};
18use rusqlite::OptionalExtension;
19use serde::{Deserialize, Serialize};
20use tokio::task;
21use tokio::time::{timeout, Duration};
22
23use crate::aheader::{Aheader, EncryptPreference};
24use crate::blob::BlobObject;
25use crate::chat::{ChatId, ChatIdBlocked, ProtectionStatus};
26use crate::color::str_to_color;
27use crate::config::Config;
28use crate::constants::{Blocked, Chattype, DC_GCL_ADD_SELF};
29use crate::context::Context;
30use crate::events::EventType;
31use crate::key::{load_self_public_key, DcKey, SignedPublicKey};
32use crate::log::LogExt;
33use crate::message::MessageState;
34use crate::mimeparser::AvatarAction;
35use crate::param::{Param, Params};
36use crate::peerstate::Peerstate;
37use crate::sync::{self, Sync::*};
38use crate::tools::{duration_to_str, get_abs_path, smeared_time, time, SystemTime};
39use crate::{chat, chatlist_events, stock_str};
40
41const SEEN_RECENTLY_SECONDS: i64 = 600;
43
44#[derive(
49 Debug, Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
50)]
51pub struct ContactId(u32);
52
53impl ContactId {
54 pub const UNDEFINED: ContactId = ContactId::new(0);
56
57 pub const SELF: ContactId = ContactId::new(1);
61
62 pub const INFO: ContactId = ContactId::new(2);
64
65 pub const DEVICE: ContactId = ContactId::new(5);
67 pub(crate) const LAST_SPECIAL: ContactId = ContactId::new(9);
68
69 pub const DEVICE_ADDR: &'static str = "device@localhost";
73
74 pub const fn new(id: u32) -> ContactId {
76 ContactId(id)
77 }
78
79 pub fn is_special(&self) -> bool {
86 self.0 <= Self::LAST_SPECIAL.0
87 }
88
89 pub const fn to_u32(&self) -> u32 {
95 self.0
96 }
97
98 pub async fn set_name(self, context: &Context, name: &str) -> Result<()> {
105 let addr = context
106 .sql
107 .transaction(|transaction| {
108 let is_changed = transaction.execute(
109 "UPDATE contacts SET name=?1 WHERE id=?2 AND name!=?1",
110 (name, self),
111 )? > 0;
112 if is_changed {
113 update_chat_names(context, transaction, self)?;
114 let addr = transaction.query_row(
115 "SELECT addr FROM contacts WHERE id=?",
116 (self,),
117 |row| {
118 let addr: String = row.get(0)?;
119 Ok(addr)
120 },
121 )?;
122 Ok(Some(addr))
123 } else {
124 Ok(None)
125 }
126 })
127 .await?;
128
129 if let Some(addr) = addr {
130 chat::sync(
131 context,
132 chat::SyncId::ContactAddr(addr.to_string()),
133 chat::SyncAction::Rename(name.to_string()),
134 )
135 .await
136 .log_err(context)
137 .ok();
138 }
139 Ok(())
140 }
141
142 pub(crate) async fn mark_bot(&self, context: &Context, is_bot: bool) -> Result<()> {
144 context
145 .sql
146 .execute("UPDATE contacts SET is_bot=? WHERE id=?;", (is_bot, self.0))
147 .await?;
148 Ok(())
149 }
150
151 pub(crate) async fn regossip_keys(&self, context: &Context) -> Result<()> {
153 context
154 .sql
155 .execute(
156 "UPDATE chats
157 SET gossiped_timestamp=0
158 WHERE EXISTS (SELECT 1 FROM chats_contacts
159 WHERE chats_contacts.chat_id=chats.id
160 AND chats_contacts.contact_id=?
161 AND chats_contacts.add_timestamp >= chats_contacts.remove_timestamp)",
162 (self,),
163 )
164 .await?;
165 Ok(())
166 }
167
168 pub(crate) async fn scaleup_origin(
170 context: &Context,
171 ids: &[Self],
172 origin: Origin,
173 ) -> Result<()> {
174 context
175 .sql
176 .transaction(|transaction| {
177 let mut stmt = transaction
178 .prepare("UPDATE contacts SET origin=?1 WHERE id = ?2 AND origin < ?1")?;
179 for id in ids {
180 stmt.execute((origin, id))?;
181 }
182 Ok(())
183 })
184 .await?;
185 Ok(())
186 }
187
188 pub async fn addr(&self, context: &Context) -> Result<String> {
190 let addr = context
191 .sql
192 .query_row("SELECT addr FROM contacts WHERE id=?", (self,), |row| {
193 let addr: String = row.get(0)?;
194 Ok(addr)
195 })
196 .await?;
197 Ok(addr)
198 }
199
200 pub async fn reset_encryption(self, context: &Context) -> Result<()> {
208 let now = time();
209
210 let addr = self.addr(context).await?;
211 if let Some(mut peerstate) = Peerstate::from_addr(context, &addr).await? {
212 peerstate.degrade_encryption(now);
213 peerstate.save_to_db(&context.sql).await?;
214 }
215
216 if let Some(chat_id) = ChatId::lookup_by_contact(context, self).await? {
218 chat_id
219 .set_protection(context, ProtectionStatus::Unprotected, now, Some(self))
220 .await?;
221 }
222 Ok(())
223 }
224}
225
226impl fmt::Display for ContactId {
227 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
228 if *self == ContactId::UNDEFINED {
229 write!(f, "Contact#Undefined")
230 } else if *self == ContactId::SELF {
231 write!(f, "Contact#Self")
232 } else if *self == ContactId::INFO {
233 write!(f, "Contact#Info")
234 } else if *self == ContactId::DEVICE {
235 write!(f, "Contact#Device")
236 } else if self.is_special() {
237 write!(f, "Contact#Special{}", self.0)
238 } else {
239 write!(f, "Contact#{}", self.0)
240 }
241 }
242}
243
244impl rusqlite::types::ToSql for ContactId {
246 fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput> {
247 let val = rusqlite::types::Value::Integer(i64::from(self.0));
248 let out = rusqlite::types::ToSqlOutput::Owned(val);
249 Ok(out)
250 }
251}
252
253impl rusqlite::types::FromSql for ContactId {
255 fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
256 i64::column_result(value).and_then(|val| {
257 val.try_into()
258 .map(ContactId::new)
259 .map_err(|_| rusqlite::types::FromSqlError::OutOfRange(val))
260 })
261 }
262}
263
264pub async fn make_vcard(context: &Context, contacts: &[ContactId]) -> Result<String> {
266 let now = time();
267 let mut vcard_contacts = Vec::with_capacity(contacts.len());
268 for id in contacts {
269 let c = Contact::get_by_id(context, *id).await?;
270 let key = match *id {
271 ContactId::SELF => Some(load_self_public_key(context).await?),
272 _ => Peerstate::from_addr(context, &c.addr)
273 .await?
274 .and_then(|peerstate| peerstate.take_key(false)),
275 };
276 let key = key.map(|k| k.to_base64());
277 let profile_image = match c.get_profile_image(context).await? {
278 None => None,
279 Some(path) => tokio::fs::read(path)
280 .await
281 .log_err(context)
282 .ok()
283 .map(|data| base64::engine::general_purpose::STANDARD.encode(data)),
284 };
285 vcard_contacts.push(VcardContact {
286 addr: c.addr,
287 authname: c.authname,
288 key,
289 profile_image,
290 biography: Some(c.status).filter(|s| !s.is_empty()),
291 timestamp: Ok(now),
293 });
294 }
295
296 Ok(contact_tools::make_vcard(&vcard_contacts)
303 .trim_end()
304 .to_string())
305}
306
307pub async fn import_vcard(context: &Context, vcard: &str) -> Result<Vec<ContactId>> {
312 let contacts = contact_tools::parse_vcard(vcard);
313 let mut contact_ids = Vec::with_capacity(contacts.len());
314 for c in &contacts {
315 let Ok(id) = import_vcard_contact(context, c)
316 .await
317 .with_context(|| format!("import_vcard_contact() failed for {}", c.addr))
318 .log_err(context)
319 else {
320 continue;
321 };
322 contact_ids.push(id);
323 }
324 Ok(contact_ids)
325}
326
327async fn import_vcard_contact(context: &Context, contact: &VcardContact) -> Result<ContactId> {
328 let addr = ContactAddress::new(&contact.addr).context("Invalid address")?;
329 let origin = Origin::CreateChat;
333 let (id, modified) =
334 match Contact::add_or_lookup(context, &contact.authname, &addr, origin).await {
335 Err(e) => return Err(e).context("Contact::add_or_lookup() failed"),
336 Ok((ContactId::SELF, _)) => return Ok(ContactId::SELF),
337 Ok(val) => val,
338 };
339 if modified != Modifier::None {
340 context.emit_event(EventType::ContactsChanged(Some(id)));
341 }
342 let key = contact.key.as_ref().and_then(|k| {
343 SignedPublicKey::from_base64(k)
344 .with_context(|| {
345 format!(
346 "import_vcard_contact: Cannot decode key for {}",
347 contact.addr
348 )
349 })
350 .log_err(context)
351 .ok()
352 });
353 if let Some(public_key) = key {
354 let timestamp = contact
355 .timestamp
356 .as_ref()
357 .map_or(0, |&t| min(t, smeared_time(context)));
358 let aheader = Aheader {
359 addr: contact.addr.clone(),
360 public_key,
361 prefer_encrypt: EncryptPreference::Mutual,
362 };
363 let peerstate = match Peerstate::from_addr(context, &aheader.addr).await {
364 Err(e) => {
365 warn!(
366 context,
367 "import_vcard_contact: Cannot create peerstate from {}: {e:#}.", contact.addr
368 );
369 return Ok(id);
370 }
371 Ok(p) => p,
372 };
373 let peerstate = if let Some(mut p) = peerstate {
374 p.apply_gossip(&aheader, timestamp);
375 p
376 } else {
377 Peerstate::from_gossip(&aheader, timestamp)
378 };
379 if let Err(e) = peerstate.save_to_db(&context.sql).await {
380 warn!(
381 context,
382 "import_vcard_contact: Could not save peerstate for {}: {e:#}.", contact.addr
383 );
384 return Ok(id);
385 }
386 if let Err(e) = peerstate
387 .handle_fingerprint_change(context, timestamp)
388 .await
389 {
390 warn!(
391 context,
392 "import_vcard_contact: handle_fingerprint_change() failed for {}: {e:#}.",
393 contact.addr
394 );
395 return Ok(id);
396 }
397 }
398 if modified != Modifier::Created {
399 return Ok(id);
400 }
401 let path = match &contact.profile_image {
402 Some(image) => match BlobObject::store_from_base64(context, image) {
403 Err(e) => {
404 warn!(
405 context,
406 "import_vcard_contact: Could not decode and save avatar for {}: {e:#}.",
407 contact.addr
408 );
409 None
410 }
411 Ok(path) => Some(path),
412 },
413 None => None,
414 };
415 if let Some(path) = path {
416 let was_encrypted = false;
418 if let Err(e) =
419 set_profile_image(context, id, &AvatarAction::Change(path), was_encrypted).await
420 {
421 warn!(
422 context,
423 "import_vcard_contact: Could not set avatar for {}: {e:#}.", contact.addr
424 );
425 }
426 }
427 if let Some(biography) = &contact.biography {
428 if let Err(e) = set_status(context, id, biography.to_owned(), false, false).await {
429 warn!(
430 context,
431 "import_vcard_contact: Could not set biography for {}: {e:#}.", contact.addr
432 );
433 }
434 }
435 Ok(id)
436}
437
438#[derive(Debug)]
451pub struct Contact {
452 pub id: ContactId,
454
455 name: String,
459
460 authname: String,
464
465 addr: String,
467
468 pub blocked: bool,
470
471 last_seen: i64,
473
474 pub origin: Origin,
476
477 pub param: Params,
479
480 status: String,
482
483 is_bot: bool,
485}
486
487#[derive(
489 Debug,
490 Default,
491 Clone,
492 Copy,
493 PartialEq,
494 Eq,
495 PartialOrd,
496 Ord,
497 FromPrimitive,
498 ToPrimitive,
499 FromSql,
500 ToSql,
501)]
502#[repr(u32)]
503pub enum Origin {
504 #[default]
507 Unknown = 0,
508
509 MailinglistAddress = 0x2,
511
512 Hidden = 0x8,
514
515 IncomingUnknownFrom = 0x10,
517
518 IncomingUnknownCc = 0x20,
520
521 IncomingUnknownTo = 0x40,
523
524 UnhandledQrScan = 0x80,
526
527 UnhandledSecurejoinQrScan = 0x81,
529
530 IncomingReplyTo = 0x100,
533
534 IncomingCc = 0x200,
536
537 IncomingTo = 0x400,
539
540 CreateChat = 0x800,
542
543 OutgoingBcc = 0x1000,
545
546 OutgoingCc = 0x2000,
548
549 OutgoingTo = 0x4000,
551
552 Internal = 0x40000,
554
555 AddressBook = 0x80000,
557
558 SecurejoinInvited = 0x0100_0000,
560
561 SecurejoinJoined = 0x0200_0000,
567
568 ManuallyCreated = 0x0400_0000,
570}
571
572impl Origin {
573 pub fn is_known(self) -> bool {
577 self >= Origin::IncomingReplyTo
578 }
579}
580
581#[derive(Debug, PartialEq, Eq, Clone, Copy)]
582pub(crate) enum Modifier {
583 None,
584 Modified,
585 Created,
586}
587
588impl Contact {
589 pub async fn get_by_id(context: &Context, contact_id: ContactId) -> Result<Self> {
600 let contact = Self::get_by_id_optional(context, contact_id)
601 .await?
602 .with_context(|| format!("contact {contact_id} not found"))?;
603 Ok(contact)
604 }
605
606 pub async fn get_by_id_optional(
610 context: &Context,
611 contact_id: ContactId,
612 ) -> Result<Option<Self>> {
613 if let Some(mut contact) = context
614 .sql
615 .query_row_optional(
616 "SELECT c.name, c.addr, c.origin, c.blocked, c.last_seen,
617 c.authname, c.param, c.status, c.is_bot
618 FROM contacts c
619 WHERE c.id=?;",
620 (contact_id,),
621 |row| {
622 let name: String = row.get(0)?;
623 let addr: String = row.get(1)?;
624 let origin: Origin = row.get(2)?;
625 let blocked: Option<bool> = row.get(3)?;
626 let last_seen: i64 = row.get(4)?;
627 let authname: String = row.get(5)?;
628 let param: String = row.get(6)?;
629 let status: Option<String> = row.get(7)?;
630 let is_bot: bool = row.get(8)?;
631 let contact = Self {
632 id: contact_id,
633 name,
634 authname,
635 addr,
636 blocked: blocked.unwrap_or_default(),
637 last_seen,
638 origin,
639 param: param.parse().unwrap_or_default(),
640 status: status.unwrap_or_default(),
641 is_bot,
642 };
643 Ok(contact)
644 },
645 )
646 .await?
647 {
648 if contact_id == ContactId::SELF {
649 contact.name = stock_str::self_msg(context).await;
650 contact.authname = context
651 .get_config(Config::Displayname)
652 .await?
653 .unwrap_or_default();
654 contact.addr = context
655 .get_config(Config::ConfiguredAddr)
656 .await?
657 .unwrap_or_default();
658 contact.status = context
659 .get_config(Config::Selfstatus)
660 .await?
661 .unwrap_or_default();
662 } else if contact_id == ContactId::DEVICE {
663 contact.name = stock_str::device_messages(context).await;
664 contact.addr = ContactId::DEVICE_ADDR.to_string();
665 contact.status = stock_str::device_messages_hint(context).await;
666 }
667 Ok(Some(contact))
668 } else {
669 Ok(None)
670 }
671 }
672
673 pub fn is_blocked(&self) -> bool {
675 self.blocked
676 }
677
678 pub fn last_seen(&self) -> i64 {
680 self.last_seen
681 }
682
683 pub fn was_seen_recently(&self) -> bool {
685 time() - self.last_seen <= SEEN_RECENTLY_SECONDS
686 }
687
688 pub async fn is_blocked_load(context: &Context, id: ContactId) -> Result<bool> {
690 let blocked = context
691 .sql
692 .query_row("SELECT blocked FROM contacts WHERE id=?", (id,), |row| {
693 let blocked: bool = row.get(0)?;
694 Ok(blocked)
695 })
696 .await?;
697 Ok(blocked)
698 }
699
700 pub async fn block(context: &Context, id: ContactId) -> Result<()> {
702 set_blocked(context, Sync, id, true).await
703 }
704
705 pub async fn unblock(context: &Context, id: ContactId) -> Result<()> {
707 set_blocked(context, Sync, id, false).await
708 }
709
710 pub async fn create(context: &Context, name: &str, addr: &str) -> Result<ContactId> {
720 Self::create_ex(context, Sync, name, addr).await
721 }
722
723 pub(crate) async fn create_ex(
724 context: &Context,
725 sync: sync::Sync,
726 name: &str,
727 addr: &str,
728 ) -> Result<ContactId> {
729 let (name, addr) = sanitize_name_and_addr(name, addr);
730 let addr = ContactAddress::new(&addr)?;
731
732 let (contact_id, sth_modified) =
733 Contact::add_or_lookup(context, &name, &addr, Origin::ManuallyCreated)
734 .await
735 .context("add_or_lookup")?;
736 let blocked = Contact::is_blocked_load(context, contact_id).await?;
737 match sth_modified {
738 Modifier::None => {}
739 Modifier::Modified | Modifier::Created => {
740 context.emit_event(EventType::ContactsChanged(Some(contact_id)))
741 }
742 }
743 if blocked {
744 set_blocked(context, Nosync, contact_id, false).await?;
745 }
746
747 if sync.into() && sth_modified != Modifier::None {
748 chat::sync(
749 context,
750 chat::SyncId::ContactAddr(addr.to_string()),
751 chat::SyncAction::Rename(name.to_string()),
752 )
753 .await
754 .log_err(context)
755 .ok();
756 }
757 Ok(contact_id)
758 }
759
760 pub async fn mark_noticed(context: &Context, id: ContactId) -> Result<()> {
762 context
763 .sql
764 .execute(
765 "UPDATE msgs SET state=? WHERE from_id=? AND state=?;",
766 (MessageState::InNoticed, id, MessageState::InFresh),
767 )
768 .await?;
769 Ok(())
770 }
771
772 pub fn is_bot(&self) -> bool {
774 self.is_bot
775 }
776
777 pub async fn lookup_id_by_addr(
787 context: &Context,
788 addr: &str,
789 min_origin: Origin,
790 ) -> Result<Option<ContactId>> {
791 Self::lookup_id_by_addr_ex(context, addr, min_origin, Some(Blocked::Not)).await
792 }
793
794 pub(crate) async fn lookup_id_by_addr_ex(
797 context: &Context,
798 addr: &str,
799 min_origin: Origin,
800 blocked: Option<Blocked>,
801 ) -> Result<Option<ContactId>> {
802 if addr.is_empty() {
803 bail!("lookup_id_by_addr: empty address");
804 }
805
806 let addr_normalized = addr_normalize(addr);
807
808 if context.is_self_addr(&addr_normalized).await? {
809 return Ok(Some(ContactId::SELF));
810 }
811
812 let id = context
813 .sql
814 .query_get_value(
815 "SELECT id FROM contacts \
816 WHERE addr=?1 COLLATE NOCASE \
817 AND id>?2 AND origin>=?3 AND (? OR blocked=?)",
818 (
819 &addr_normalized,
820 ContactId::LAST_SPECIAL,
821 min_origin as u32,
822 blocked.is_none(),
823 blocked.unwrap_or_default(),
824 ),
825 )
826 .await?;
827 Ok(id)
828 }
829
830 pub(crate) async fn add_or_lookup(
856 context: &Context,
857 name: &str,
858 addr: &ContactAddress,
859 mut origin: Origin,
860 ) -> Result<(ContactId, Modifier)> {
861 let mut sth_modified = Modifier::None;
862
863 ensure!(!addr.is_empty(), "Can not add_or_lookup empty address");
864 ensure!(origin != Origin::Unknown, "Missing valid origin");
865
866 if context.is_self_addr(addr).await? {
867 return Ok((ContactId::SELF, sth_modified));
868 }
869
870 let mut name = sanitize_name(name);
871 if origin <= Origin::OutgoingTo {
872 if addr.contains("noreply")
874 || addr.contains("no-reply")
875 || addr.starts_with("notifications@")
876 || (addr.len() > 50 && addr.contains('+'))
878 {
879 info!(context, "hiding contact {}", addr);
880 origin = Origin::Hidden;
881 name = "".to_string();
885 }
886 }
887
888 let manual = matches!(
893 origin,
894 Origin::ManuallyCreated | Origin::AddressBook | Origin::UnhandledQrScan
895 );
896
897 let mut update_addr = false;
898
899 let row_id = context
900 .sql
901 .transaction(|transaction| {
902 let row = transaction
903 .query_row(
904 "SELECT id, name, addr, origin, authname
905 FROM contacts WHERE addr=? COLLATE NOCASE",
906 (addr,),
907 |row| {
908 let row_id: u32 = row.get(0)?;
909 let row_name: String = row.get(1)?;
910 let row_addr: String = row.get(2)?;
911 let row_origin: Origin = row.get(3)?;
912 let row_authname: String = row.get(4)?;
913
914 Ok((row_id, row_name, row_addr, row_origin, row_authname))
915 },
916 )
917 .optional()?;
918
919 let row_id;
920 if let Some((id, row_name, row_addr, row_origin, row_authname)) = row {
921 let update_name = manual && name != row_name;
922 let update_authname = !manual
923 && name != row_authname
924 && !name.is_empty()
925 && (origin >= row_origin
926 || origin == Origin::IncomingUnknownFrom
927 || row_authname.is_empty());
928
929 row_id = id;
930 if origin >= row_origin && addr.as_ref() != row_addr {
931 update_addr = true;
932 }
933 if update_name || update_authname || update_addr || origin > row_origin {
934 let new_name = if update_name {
935 name.to_string()
936 } else {
937 row_name
938 };
939
940 transaction.execute(
941 "UPDATE contacts SET name=?, addr=?, origin=?, authname=? WHERE id=?;",
942 (
943 new_name,
944 if update_addr {
945 addr.to_string()
946 } else {
947 row_addr
948 },
949 if origin > row_origin {
950 origin
951 } else {
952 row_origin
953 },
954 if update_authname {
955 name.to_string()
956 } else {
957 row_authname
958 },
959 row_id,
960 ),
961 )?;
962
963 if update_name || update_authname {
964 let contact_id = ContactId::new(row_id);
965 update_chat_names(context, transaction, contact_id)?;
966 }
967 sth_modified = Modifier::Modified;
968 }
969 } else {
970 let update_name = manual;
971 let update_authname = !manual;
972
973 transaction.execute(
974 "INSERT INTO contacts (name, addr, origin, authname)
975 VALUES (?, ?, ?, ?);",
976 (
977 if update_name { &name } else { "" },
978 &addr,
979 origin,
980 if update_authname { &name } else { "" },
981 ),
982 )?;
983
984 sth_modified = Modifier::Created;
985 row_id = u32::try_from(transaction.last_insert_rowid())?;
986 info!(context, "Added contact id={row_id} addr={addr}.");
987 }
988 Ok(row_id)
989 })
990 .await?;
991
992 let contact_id = ContactId::new(row_id);
993
994 Ok((contact_id, sth_modified))
995 }
996
997 pub async fn add_address_book(context: &Context, addr_book: &str) -> Result<usize> {
1015 let mut modify_cnt = 0;
1016
1017 for (name, addr) in split_address_book(addr_book) {
1018 let (name, addr) = sanitize_name_and_addr(name, addr);
1019 match ContactAddress::new(&addr) {
1020 Ok(addr) => {
1021 match Contact::add_or_lookup(context, &name, &addr, Origin::AddressBook).await {
1022 Ok((_, modified)) => {
1023 if modified != Modifier::None {
1024 modify_cnt += 1
1025 }
1026 }
1027 Err(err) => {
1028 warn!(
1029 context,
1030 "Failed to add address {} from address book: {}", addr, err
1031 );
1032 }
1033 }
1034 }
1035 Err(err) => {
1036 warn!(context, "{:#}.", err);
1037 }
1038 }
1039 }
1040 if modify_cnt > 0 {
1041 context.emit_event(EventType::ContactsChanged(None));
1042 }
1043
1044 Ok(modify_cnt)
1045 }
1046
1047 pub async fn get_all(
1056 context: &Context,
1057 listflags: u32,
1058 query: Option<&str>,
1059 ) -> Result<Vec<ContactId>> {
1060 let self_addrs = context
1061 .get_all_self_addrs()
1062 .await?
1063 .into_iter()
1064 .collect::<HashSet<_>>();
1065 let mut add_self = false;
1066 let mut ret = Vec::new();
1067 let flag_add_self = (listflags & DC_GCL_ADD_SELF) != 0;
1068 let minimal_origin = if context.get_config_bool(Config::Bot).await? {
1069 Origin::Unknown
1070 } else {
1071 Origin::IncomingReplyTo
1072 };
1073 if query.is_some() {
1074 let s3str_like_cmd = format!("%{}%", query.unwrap_or(""));
1075 context
1076 .sql
1077 .query_map(
1078 "SELECT c.id, c.addr FROM contacts c
1079 LEFT JOIN acpeerstates ps ON c.addr=ps.addr \
1080 WHERE c.id>?
1081 AND c.origin>=? \
1082 AND c.blocked=0 \
1083 AND (iif(c.name='',c.authname,c.name) LIKE ? OR c.addr LIKE ?) \
1084 ORDER BY c.last_seen DESC, c.id DESC;",
1085 (
1086 ContactId::LAST_SPECIAL,
1087 minimal_origin,
1088 &s3str_like_cmd,
1089 &s3str_like_cmd,
1090 ),
1091 |row| {
1092 let id: ContactId = row.get(0)?;
1093 let addr: String = row.get(1)?;
1094 Ok((id, addr))
1095 },
1096 |rows| {
1097 for row in rows {
1098 let (id, addr) = row?;
1099 if !self_addrs.contains(&addr) {
1100 ret.push(id);
1101 }
1102 }
1103 Ok(())
1104 },
1105 )
1106 .await?;
1107
1108 if let Some(query) = query {
1109 let self_addr = context
1110 .get_config(Config::ConfiguredAddr)
1111 .await?
1112 .unwrap_or_default();
1113 let self_name = context
1114 .get_config(Config::Displayname)
1115 .await?
1116 .unwrap_or_default();
1117 let self_name2 = stock_str::self_msg(context);
1118
1119 if self_addr.contains(query)
1120 || self_name.contains(query)
1121 || self_name2.await.contains(query)
1122 {
1123 add_self = true;
1124 }
1125 } else {
1126 add_self = true;
1127 }
1128 } else {
1129 add_self = true;
1130
1131 context
1132 .sql
1133 .query_map(
1134 "SELECT id, addr FROM contacts
1135 WHERE id>?
1136 AND origin>=?
1137 AND blocked=0
1138 ORDER BY last_seen DESC, id DESC;",
1139 (ContactId::LAST_SPECIAL, minimal_origin),
1140 |row| {
1141 let id: ContactId = row.get(0)?;
1142 let addr: String = row.get(1)?;
1143 Ok((id, addr))
1144 },
1145 |rows| {
1146 for row in rows {
1147 let (id, addr) = row?;
1148 if !self_addrs.contains(&addr) {
1149 ret.push(id);
1150 }
1151 }
1152 Ok(())
1153 },
1154 )
1155 .await?;
1156 }
1157
1158 if flag_add_self && add_self {
1159 ret.push(ContactId::SELF);
1160 }
1161
1162 Ok(ret)
1163 }
1164
1165 async fn update_blocked_mailinglist_contacts(context: &Context) -> Result<()> {
1171 context
1172 .sql
1173 .transaction(move |transaction| {
1174 let mut stmt = transaction
1175 .prepare("SELECT name, grpid FROM chats WHERE type=? AND blocked=?")?;
1176 let rows = stmt.query_map((Chattype::Mailinglist, Blocked::Yes), |row| {
1177 let name: String = row.get(0)?;
1178 let grpid: String = row.get(1)?;
1179 Ok((name, grpid))
1180 })?;
1181 let blocked_mailinglists = rows.collect::<std::result::Result<Vec<_>, _>>()?;
1182 for (name, grpid) in blocked_mailinglists {
1183 let count = transaction.query_row(
1184 "SELECT COUNT(id) FROM contacts WHERE addr=?",
1185 [&grpid],
1186 |row| {
1187 let count: isize = row.get(0)?;
1188 Ok(count)
1189 },
1190 )?;
1191 if count == 0 {
1192 transaction.execute("INSERT INTO contacts (addr) VALUES (?)", [&grpid])?;
1193 }
1194
1195 transaction.execute(
1197 "UPDATE contacts SET name=?, origin=?, blocked=1 WHERE addr=?",
1198 (&name, Origin::MailinglistAddress, &grpid),
1199 )?;
1200 }
1201 Ok(())
1202 })
1203 .await?;
1204 Ok(())
1205 }
1206
1207 pub async fn get_blocked_cnt(context: &Context) -> Result<usize> {
1209 let count = context
1210 .sql
1211 .count(
1212 "SELECT COUNT(*) FROM contacts WHERE id>? AND blocked!=0",
1213 (ContactId::LAST_SPECIAL,),
1214 )
1215 .await?;
1216 Ok(count)
1217 }
1218
1219 pub async fn get_all_blocked(context: &Context) -> Result<Vec<ContactId>> {
1221 Contact::update_blocked_mailinglist_contacts(context)
1222 .await
1223 .context("cannot update blocked mailinglist contacts")?;
1224
1225 let list = context
1226 .sql
1227 .query_map(
1228 "SELECT id FROM contacts WHERE id>? AND blocked!=0 ORDER BY last_seen DESC, id DESC;",
1229 (ContactId::LAST_SPECIAL,),
1230 |row| row.get::<_, ContactId>(0),
1231 |ids| {
1232 ids.collect::<std::result::Result<Vec<_>, _>>()
1233 .map_err(Into::into)
1234 },
1235 )
1236 .await?;
1237 Ok(list)
1238 }
1239
1240 pub async fn get_encrinfo(context: &Context, contact_id: ContactId) -> Result<String> {
1246 ensure!(
1247 !contact_id.is_special(),
1248 "Can not provide encryption info for special contact"
1249 );
1250
1251 let contact = Contact::get_by_id(context, contact_id).await?;
1252 let addr = context
1253 .get_config(Config::ConfiguredAddr)
1254 .await?
1255 .unwrap_or_default();
1256 let peerstate = Peerstate::from_addr(context, &contact.addr).await?;
1257
1258 let Some(peerstate) = peerstate.filter(|peerstate| peerstate.peek_key(false).is_some())
1259 else {
1260 return Ok(stock_str::encr_none(context).await);
1261 };
1262
1263 let stock_message = match peerstate.prefer_encrypt {
1264 EncryptPreference::Mutual => stock_str::e2e_preferred(context).await,
1265 EncryptPreference::NoPreference => stock_str::e2e_available(context).await,
1266 EncryptPreference::Reset => stock_str::encr_none(context).await,
1267 };
1268
1269 let finger_prints = stock_str::finger_prints(context).await;
1270 let mut ret = format!("{stock_message}.\n{finger_prints}:");
1271
1272 let fingerprint_self = load_self_public_key(context)
1273 .await?
1274 .dc_fingerprint()
1275 .to_string();
1276 let fingerprint_other_verified = peerstate
1277 .peek_key(true)
1278 .map(|k| k.dc_fingerprint().to_string())
1279 .unwrap_or_default();
1280 let fingerprint_other_unverified = peerstate
1281 .peek_key(false)
1282 .map(|k| k.dc_fingerprint().to_string())
1283 .unwrap_or_default();
1284 if addr < peerstate.addr {
1285 cat_fingerprint(
1286 &mut ret,
1287 &stock_str::self_msg(context).await,
1288 &addr,
1289 &fingerprint_self,
1290 "",
1291 );
1292 cat_fingerprint(
1293 &mut ret,
1294 contact.get_display_name(),
1295 &peerstate.addr,
1296 &fingerprint_other_verified,
1297 &fingerprint_other_unverified,
1298 );
1299 } else {
1300 cat_fingerprint(
1301 &mut ret,
1302 contact.get_display_name(),
1303 &peerstate.addr,
1304 &fingerprint_other_verified,
1305 &fingerprint_other_unverified,
1306 );
1307 cat_fingerprint(
1308 &mut ret,
1309 &stock_str::self_msg(context).await,
1310 &addr,
1311 &fingerprint_self,
1312 "",
1313 );
1314 }
1315
1316 Ok(ret)
1317 }
1318
1319 pub async fn delete(context: &Context, contact_id: ContactId) -> Result<()> {
1325 ensure!(!contact_id.is_special(), "Can not delete special contact");
1326
1327 context
1328 .sql
1329 .transaction(move |transaction| {
1330 let deleted_contacts = transaction.execute(
1333 "DELETE FROM contacts WHERE id=?
1334 AND (SELECT COUNT(*) FROM chats_contacts WHERE contact_id=?)=0;",
1335 (contact_id, contact_id),
1336 )?;
1337 if deleted_contacts == 0 {
1338 transaction.execute(
1339 "UPDATE contacts SET origin=? WHERE id=?;",
1340 (Origin::Hidden, contact_id),
1341 )?;
1342 }
1343 Ok(())
1344 })
1345 .await?;
1346
1347 context.emit_event(EventType::ContactsChanged(None));
1348 Ok(())
1349 }
1350
1351 pub async fn update_param(&self, context: &Context) -> Result<()> {
1353 context
1354 .sql
1355 .execute(
1356 "UPDATE contacts SET param=? WHERE id=?",
1357 (self.param.to_string(), self.id),
1358 )
1359 .await?;
1360 Ok(())
1361 }
1362
1363 pub async fn update_status(&self, context: &Context) -> Result<()> {
1365 context
1366 .sql
1367 .execute(
1368 "UPDATE contacts SET status=? WHERE id=?",
1369 (&self.status, self.id),
1370 )
1371 .await?;
1372 Ok(())
1373 }
1374
1375 pub fn get_id(&self) -> ContactId {
1377 self.id
1378 }
1379
1380 pub fn get_addr(&self) -> &str {
1382 &self.addr
1383 }
1384
1385 pub fn get_authname(&self) -> &str {
1387 &self.authname
1388 }
1389
1390 pub fn get_name(&self) -> &str {
1396 &self.name
1397 }
1398
1399 pub fn get_display_name(&self) -> &str {
1405 if !self.name.is_empty() {
1406 return &self.name;
1407 }
1408 if !self.authname.is_empty() {
1409 return &self.authname;
1410 }
1411 &self.addr
1412 }
1413
1414 pub(crate) fn get_authname_or_addr(&self) -> String {
1419 if !self.authname.is_empty() {
1420 (&self.authname).into()
1421 } else {
1422 (&self.addr).into()
1423 }
1424 }
1425
1426 pub fn get_name_n_addr(&self) -> String {
1437 if !self.name.is_empty() {
1438 format!("{} ({})", self.name, self.addr)
1439 } else if !self.authname.is_empty() {
1440 format!("{} ({})", self.authname, self.addr)
1441 } else {
1442 (&self.addr).into()
1443 }
1444 }
1445
1446 pub async fn get_profile_image(&self, context: &Context) -> Result<Option<PathBuf>> {
1450 if self.id == ContactId::SELF {
1451 if let Some(p) = context.get_config(Config::Selfavatar).await? {
1452 return Ok(Some(PathBuf::from(p))); }
1454 } else if let Some(image_rel) = self.param.get(Param::ProfileImage) {
1455 if !image_rel.is_empty() {
1456 return Ok(Some(get_abs_path(context, Path::new(image_rel))));
1457 }
1458 }
1459 Ok(None)
1460 }
1461
1462 pub fn get_color(&self) -> u32 {
1467 str_to_color(&self.addr.to_lowercase())
1468 }
1469
1470 pub fn get_status(&self) -> &str {
1474 self.status.as_str()
1475 }
1476
1477 pub async fn e2ee_avail(&self, context: &Context) -> Result<bool> {
1479 if self.id == ContactId::SELF {
1480 return Ok(true);
1481 }
1482 let Some(peerstate) = Peerstate::from_addr(context, &self.addr).await? else {
1483 return Ok(false);
1484 };
1485 Ok(peerstate.peek_key(false).is_some())
1486 }
1487
1488 pub async fn is_verified(&self, context: &Context) -> Result<bool> {
1503 if self.id == ContactId::SELF {
1506 return Ok(true);
1507 }
1508
1509 let Some(peerstate) = Peerstate::from_addr(context, &self.addr).await? else {
1510 return Ok(false);
1511 };
1512
1513 let forward_verified = peerstate.is_using_verified_key();
1514 let backward_verified = peerstate.is_backward_verified(context).await?;
1515 Ok(forward_verified && backward_verified)
1516 }
1517
1518 pub async fn is_forward_verified(&self, context: &Context) -> Result<bool> {
1524 if self.id == ContactId::SELF {
1525 return Ok(true);
1526 }
1527
1528 let Some(peerstate) = Peerstate::from_addr(context, &self.addr).await? else {
1529 return Ok(false);
1530 };
1531
1532 Ok(peerstate.is_using_verified_key())
1533 }
1534
1535 pub async fn get_verifier_id(&self, context: &Context) -> Result<Option<ContactId>> {
1548 let Some(verifier_addr) = Peerstate::from_addr(context, self.get_addr())
1549 .await?
1550 .and_then(|peerstate| peerstate.get_verifier().map(|addr| addr.to_owned()))
1551 else {
1552 return Ok(None);
1553 };
1554
1555 if addr_cmp(&verifier_addr, &self.addr) {
1556 return Ok(Some(ContactId::SELF));
1558 }
1559
1560 match Contact::lookup_id_by_addr(context, &verifier_addr, Origin::Unknown).await? {
1561 Some(contact_id) => Ok(Some(contact_id)),
1562 None => {
1563 let addr = &self.addr;
1564 warn!(context, "Could not lookup contact with address {verifier_addr} which introduced {addr}.");
1565 Ok(None)
1566 }
1567 }
1568 }
1569
1570 pub async fn is_profile_verified(&self, context: &Context) -> Result<bool> {
1581 let contact_id = self.id;
1582
1583 if let Some(ChatIdBlocked { id: chat_id, .. }) =
1584 ChatIdBlocked::lookup_by_contact(context, contact_id).await?
1585 {
1586 Ok(chat_id.is_protected(context).await? == ProtectionStatus::Protected)
1587 } else {
1588 Ok(self.is_verified(context).await?)
1590 }
1591 }
1592
1593 pub async fn get_real_cnt(context: &Context) -> Result<usize> {
1595 if !context.sql.is_open().await {
1596 return Ok(0);
1597 }
1598
1599 let count = context
1600 .sql
1601 .count(
1602 "SELECT COUNT(*) FROM contacts WHERE id>?;",
1603 (ContactId::LAST_SPECIAL,),
1604 )
1605 .await?;
1606 Ok(count)
1607 }
1608
1609 pub async fn real_exists_by_id(context: &Context, contact_id: ContactId) -> Result<bool> {
1611 if contact_id.is_special() {
1612 return Ok(false);
1613 }
1614
1615 let exists = context
1616 .sql
1617 .exists("SELECT COUNT(*) FROM contacts WHERE id=?;", (contact_id,))
1618 .await?;
1619 Ok(exists)
1620 }
1621}
1622
1623fn update_chat_names(
1627 context: &Context,
1628 transaction: &rusqlite::Connection,
1629 contact_id: ContactId,
1630) -> Result<()> {
1631 let chat_id: Option<ChatId> = transaction.query_row(
1632 "SELECT id FROM chats WHERE type=? AND id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?)",
1633 (Chattype::Single, contact_id),
1634 |row| {
1635 let chat_id: ChatId = row.get(0)?;
1636 Ok(chat_id)
1637 }
1638 ).optional()?;
1639
1640 if let Some(chat_id) = chat_id {
1641 let (addr, name, authname) = transaction.query_row(
1642 "SELECT addr, name, authname
1643 FROM contacts
1644 WHERE id=?",
1645 (contact_id,),
1646 |row| {
1647 let addr: String = row.get(0)?;
1648 let name: String = row.get(1)?;
1649 let authname: String = row.get(2)?;
1650 Ok((addr, name, authname))
1651 },
1652 )?;
1653
1654 let chat_name = if !name.is_empty() {
1655 name
1656 } else if !authname.is_empty() {
1657 authname
1658 } else {
1659 addr
1660 };
1661
1662 let count = transaction.execute(
1663 "UPDATE chats SET name=?1 WHERE id=?2 AND name!=?1",
1664 (chat_name, chat_id),
1665 )?;
1666
1667 if count > 0 {
1668 context.emit_event(EventType::ChatModified(chat_id));
1670 chatlist_events::emit_chatlist_items_changed_for_contact(context, contact_id);
1671 }
1672 }
1673
1674 Ok(())
1675}
1676
1677pub(crate) async fn set_blocked(
1678 context: &Context,
1679 sync: sync::Sync,
1680 contact_id: ContactId,
1681 new_blocking: bool,
1682) -> Result<()> {
1683 ensure!(
1684 !contact_id.is_special(),
1685 "Can't block special contact {}",
1686 contact_id
1687 );
1688 let contact = Contact::get_by_id(context, contact_id).await?;
1689
1690 if contact.blocked != new_blocking {
1691 context
1692 .sql
1693 .execute(
1694 "UPDATE contacts SET blocked=? WHERE id=?;",
1695 (i32::from(new_blocking), contact_id),
1696 )
1697 .await?;
1698
1699 if context
1705 .sql
1706 .execute(
1707 r#"
1708UPDATE chats
1709SET blocked=?
1710WHERE type=? AND id IN (
1711 SELECT chat_id FROM chats_contacts WHERE contact_id=?
1712);
1713"#,
1714 (new_blocking, Chattype::Single, contact_id),
1715 )
1716 .await
1717 .is_ok()
1718 {
1719 Contact::mark_noticed(context, contact_id).await?;
1720 context.emit_event(EventType::ContactsChanged(Some(contact_id)));
1721 }
1722
1723 if !new_blocking && contact.origin == Origin::MailinglistAddress {
1726 if let Some((chat_id, _, _)) =
1727 chat::get_chat_id_by_grpid(context, &contact.addr).await?
1728 {
1729 chat_id.unblock_ex(context, Nosync).await?;
1730 }
1731 }
1732
1733 if sync.into() {
1734 let action = match new_blocking {
1735 true => chat::SyncAction::Block,
1736 false => chat::SyncAction::Unblock,
1737 };
1738 chat::sync(
1739 context,
1740 chat::SyncId::ContactAddr(contact.addr.clone()),
1741 action,
1742 )
1743 .await
1744 .log_err(context)
1745 .ok();
1746 }
1747 }
1748
1749 chatlist_events::emit_chatlist_changed(context);
1750 Ok(())
1751}
1752
1753pub(crate) async fn set_profile_image(
1761 context: &Context,
1762 contact_id: ContactId,
1763 profile_image: &AvatarAction,
1764 was_encrypted: bool,
1765) -> Result<()> {
1766 let mut contact = Contact::get_by_id(context, contact_id).await?;
1767 let changed = match profile_image {
1768 AvatarAction::Change(profile_image) => {
1769 if contact_id == ContactId::SELF {
1770 if was_encrypted {
1771 context
1772 .set_config_ex(Nosync, Config::Selfavatar, Some(profile_image))
1773 .await?;
1774 } else {
1775 info!(context, "Do not use unencrypted selfavatar.");
1776 }
1777 } else {
1778 contact.param.set(Param::ProfileImage, profile_image);
1779 }
1780 true
1781 }
1782 AvatarAction::Delete => {
1783 if contact_id == ContactId::SELF {
1784 if was_encrypted {
1785 context
1786 .set_config_ex(Nosync, Config::Selfavatar, None)
1787 .await?;
1788 } else {
1789 info!(context, "Do not use unencrypted selfavatar deletion.");
1790 }
1791 } else {
1792 contact.param.remove(Param::ProfileImage);
1793 }
1794 true
1795 }
1796 };
1797 if changed {
1798 contact.update_param(context).await?;
1799 context.emit_event(EventType::ContactsChanged(Some(contact_id)));
1800 chatlist_events::emit_chatlist_item_changed_for_contact_chat(context, contact_id).await;
1801 }
1802 Ok(())
1803}
1804
1805pub(crate) async fn set_status(
1811 context: &Context,
1812 contact_id: ContactId,
1813 status: String,
1814 encrypted: bool,
1815 has_chat_version: bool,
1816) -> Result<()> {
1817 if contact_id == ContactId::SELF {
1818 if encrypted && has_chat_version {
1819 context
1820 .set_config_ex(Nosync, Config::Selfstatus, Some(&status))
1821 .await?;
1822 }
1823 } else {
1824 let mut contact = Contact::get_by_id(context, contact_id).await?;
1825
1826 if contact.status != status {
1827 contact.status = status;
1828 contact.update_status(context).await?;
1829 context.emit_event(EventType::ContactsChanged(Some(contact_id)));
1830 }
1831 }
1832 Ok(())
1833}
1834
1835pub(crate) async fn update_last_seen(
1837 context: &Context,
1838 contact_id: ContactId,
1839 timestamp: i64,
1840) -> Result<()> {
1841 ensure!(
1842 !contact_id.is_special(),
1843 "Can not update special contact last seen timestamp"
1844 );
1845
1846 if context
1847 .sql
1848 .execute(
1849 "UPDATE contacts SET last_seen = ?1 WHERE last_seen < ?1 AND id = ?2",
1850 (timestamp, contact_id),
1851 )
1852 .await?
1853 > 0
1854 && timestamp > time() - SEEN_RECENTLY_SECONDS
1855 {
1856 context.emit_event(EventType::ContactsChanged(Some(contact_id)));
1857 context
1858 .scheduler
1859 .interrupt_recently_seen(contact_id, timestamp)
1860 .await;
1861 }
1862 Ok(())
1863}
1864
1865fn cat_fingerprint(
1866 ret: &mut String,
1867 name: &str,
1868 addr: &str,
1869 fingerprint_verified: &str,
1870 fingerprint_unverified: &str,
1871) {
1872 *ret += &format!(
1873 "\n\n{} ({}):\n{}",
1874 name,
1875 addr,
1876 if !fingerprint_verified.is_empty() {
1877 fingerprint_verified
1878 } else {
1879 fingerprint_unverified
1880 },
1881 );
1882 if !fingerprint_verified.is_empty()
1883 && !fingerprint_unverified.is_empty()
1884 && fingerprint_verified != fingerprint_unverified
1885 {
1886 *ret += &format!("\n\n{name} (alternative):\n{fingerprint_unverified}");
1887 }
1888}
1889
1890fn split_address_book(book: &str) -> Vec<(&str, &str)> {
1891 book.lines()
1892 .collect::<Vec<&str>>()
1893 .chunks(2)
1894 .filter_map(|chunk| {
1895 let name = chunk.first()?;
1896 let addr = chunk.get(1)?;
1897 Some((*name, *addr))
1898 })
1899 .collect()
1900}
1901
1902#[derive(Debug)]
1903pub(crate) struct RecentlySeenInterrupt {
1904 contact_id: ContactId,
1905 timestamp: i64,
1906}
1907
1908#[derive(Debug)]
1909pub(crate) struct RecentlySeenLoop {
1910 handle: task::JoinHandle<()>,
1912
1913 interrupt_send: Sender<RecentlySeenInterrupt>,
1914}
1915
1916impl RecentlySeenLoop {
1917 pub(crate) fn new(context: Context) -> Self {
1918 let (interrupt_send, interrupt_recv) = channel::bounded(1);
1919
1920 let handle = task::spawn(Self::run(context, interrupt_recv));
1921 Self {
1922 handle,
1923 interrupt_send,
1924 }
1925 }
1926
1927 async fn run(context: Context, interrupt: Receiver<RecentlySeenInterrupt>) {
1928 type MyHeapElem = (Reverse<i64>, ContactId);
1929
1930 let now = SystemTime::now();
1931 let now_ts = now
1932 .duration_since(SystemTime::UNIX_EPOCH)
1933 .unwrap_or_default()
1934 .as_secs() as i64;
1935
1936 let mut unseen_queue: BinaryHeap<MyHeapElem> = context
1942 .sql
1943 .query_map(
1944 "SELECT id, last_seen FROM contacts
1945 WHERE last_seen > ?",
1946 (now_ts - SEEN_RECENTLY_SECONDS,),
1947 |row| {
1948 let contact_id: ContactId = row.get("id")?;
1949 let last_seen: i64 = row.get("last_seen")?;
1950 Ok((Reverse(last_seen + SEEN_RECENTLY_SECONDS), contact_id))
1951 },
1952 |rows| {
1953 rows.collect::<std::result::Result<BinaryHeap<MyHeapElem>, _>>()
1954 .map_err(Into::into)
1955 },
1956 )
1957 .await
1958 .unwrap_or_default();
1959
1960 loop {
1961 let now = SystemTime::now();
1962 let (until, contact_id) =
1963 if let Some((Reverse(timestamp), contact_id)) = unseen_queue.peek() {
1964 (
1965 UNIX_EPOCH
1966 + Duration::from_secs((*timestamp).try_into().unwrap_or(u64::MAX))
1967 + Duration::from_secs(1),
1968 Some(contact_id),
1969 )
1970 } else {
1971 (now + Duration::from_secs(86400), None)
1973 };
1974
1975 if let Ok(duration) = until.duration_since(now) {
1976 info!(
1977 context,
1978 "Recently seen loop waiting for {} or interrupt",
1979 duration_to_str(duration)
1980 );
1981
1982 match timeout(duration, interrupt.recv()).await {
1983 Err(_) => {
1984 if let Some(contact_id) = contact_id {
1986 context.emit_event(EventType::ContactsChanged(Some(*contact_id)));
1987 chatlist_events::emit_chatlist_item_changed_for_contact_chat(
1988 &context,
1989 *contact_id,
1990 )
1991 .await;
1992 unseen_queue.pop();
1993 }
1994 }
1995 Ok(Err(err)) => {
1996 warn!(
1997 context,
1998 "Error receiving an interruption in recently seen loop: {}", err
1999 );
2000 return;
2003 }
2004 Ok(Ok(RecentlySeenInterrupt {
2005 contact_id,
2006 timestamp,
2007 })) => {
2008 if contact_id != ContactId::UNDEFINED {
2010 unseen_queue
2011 .push((Reverse(timestamp + SEEN_RECENTLY_SECONDS), contact_id));
2012 }
2013 }
2014 }
2015 } else {
2016 info!(
2017 context,
2018 "Recently seen loop is not waiting, event is already due."
2019 );
2020
2021 if let Some(contact_id) = contact_id {
2023 context.emit_event(EventType::ContactsChanged(Some(*contact_id)));
2024 chatlist_events::emit_chatlist_item_changed_for_contact_chat(
2025 &context,
2026 *contact_id,
2027 )
2028 .await;
2029 }
2030 unseen_queue.pop();
2031 }
2032 }
2033 }
2034
2035 pub(crate) fn try_interrupt(&self, contact_id: ContactId, timestamp: i64) {
2036 self.interrupt_send
2037 .try_send(RecentlySeenInterrupt {
2038 contact_id,
2039 timestamp,
2040 })
2041 .ok();
2042 }
2043
2044 #[cfg(test)]
2045 pub(crate) async fn interrupt(&self, contact_id: ContactId, timestamp: i64) {
2046 self.interrupt_send
2047 .send(RecentlySeenInterrupt {
2048 contact_id,
2049 timestamp,
2050 })
2051 .await
2052 .unwrap();
2053 }
2054
2055 pub(crate) async fn abort(self) {
2056 self.handle.abort();
2057
2058 self.handle.await.ok();
2062 }
2063}
2064
2065#[cfg(test)]
2066mod contact_tests;