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, DC_GCL_VERIFIED_ONLY};
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 timestamp: Ok(now),
292 });
293 }
294
295 Ok(contact_tools::make_vcard(&vcard_contacts)
302 .trim_end()
303 .to_string())
304}
305
306pub async fn import_vcard(context: &Context, vcard: &str) -> Result<Vec<ContactId>> {
311 let contacts = contact_tools::parse_vcard(vcard);
312 let mut contact_ids = Vec::with_capacity(contacts.len());
313 for c in &contacts {
314 let Ok(id) = import_vcard_contact(context, c)
315 .await
316 .with_context(|| format!("import_vcard_contact() failed for {}", c.addr))
317 .log_err(context)
318 else {
319 continue;
320 };
321 contact_ids.push(id);
322 }
323 Ok(contact_ids)
324}
325
326async fn import_vcard_contact(context: &Context, contact: &VcardContact) -> Result<ContactId> {
327 let addr = ContactAddress::new(&contact.addr).context("Invalid address")?;
328 let origin = Origin::CreateChat;
332 let (id, modified) =
333 match Contact::add_or_lookup(context, &contact.authname, &addr, origin).await {
334 Err(e) => return Err(e).context("Contact::add_or_lookup() failed"),
335 Ok((ContactId::SELF, _)) => return Ok(ContactId::SELF),
336 Ok(val) => val,
337 };
338 if modified != Modifier::None {
339 context.emit_event(EventType::ContactsChanged(Some(id)));
340 }
341 let key = contact.key.as_ref().and_then(|k| {
342 SignedPublicKey::from_base64(k)
343 .with_context(|| {
344 format!(
345 "import_vcard_contact: Cannot decode key for {}",
346 contact.addr
347 )
348 })
349 .log_err(context)
350 .ok()
351 });
352 if let Some(public_key) = key {
353 let timestamp = contact
354 .timestamp
355 .as_ref()
356 .map_or(0, |&t| min(t, smeared_time(context)));
357 let aheader = Aheader {
358 addr: contact.addr.clone(),
359 public_key,
360 prefer_encrypt: EncryptPreference::Mutual,
361 };
362 let peerstate = match Peerstate::from_addr(context, &aheader.addr).await {
363 Err(e) => {
364 warn!(
365 context,
366 "import_vcard_contact: Cannot create peerstate from {}: {e:#}.", contact.addr
367 );
368 return Ok(id);
369 }
370 Ok(p) => p,
371 };
372 let peerstate = if let Some(mut p) = peerstate {
373 p.apply_gossip(&aheader, timestamp);
374 p
375 } else {
376 Peerstate::from_gossip(&aheader, timestamp)
377 };
378 if let Err(e) = peerstate.save_to_db(&context.sql).await {
379 warn!(
380 context,
381 "import_vcard_contact: Could not save peerstate for {}: {e:#}.", contact.addr
382 );
383 return Ok(id);
384 }
385 if let Err(e) = peerstate
386 .handle_fingerprint_change(context, timestamp)
387 .await
388 {
389 warn!(
390 context,
391 "import_vcard_contact: handle_fingerprint_change() failed for {}: {e:#}.",
392 contact.addr
393 );
394 return Ok(id);
395 }
396 }
397 if modified != Modifier::Created {
398 return Ok(id);
399 }
400 let path = match &contact.profile_image {
401 Some(image) => match BlobObject::store_from_base64(context, image) {
402 Err(e) => {
403 warn!(
404 context,
405 "import_vcard_contact: Could not decode and save avatar for {}: {e:#}.",
406 contact.addr
407 );
408 None
409 }
410 Ok(path) => Some(path),
411 },
412 None => None,
413 };
414 if let Some(path) = path {
415 let was_encrypted = false;
417 if let Err(e) =
418 set_profile_image(context, id, &AvatarAction::Change(path), was_encrypted).await
419 {
420 warn!(
421 context,
422 "import_vcard_contact: Could not set avatar for {}: {e:#}.", contact.addr
423 );
424 }
425 }
426 Ok(id)
427}
428
429#[derive(Debug)]
442pub struct Contact {
443 pub id: ContactId,
445
446 name: String,
450
451 authname: String,
455
456 addr: String,
458
459 pub blocked: bool,
461
462 last_seen: i64,
464
465 pub origin: Origin,
467
468 pub param: Params,
470
471 status: String,
473
474 is_bot: bool,
476}
477
478#[derive(
480 Debug,
481 Default,
482 Clone,
483 Copy,
484 PartialEq,
485 Eq,
486 PartialOrd,
487 Ord,
488 FromPrimitive,
489 ToPrimitive,
490 FromSql,
491 ToSql,
492)]
493#[repr(u32)]
494pub enum Origin {
495 #[default]
498 Unknown = 0,
499
500 MailinglistAddress = 0x2,
502
503 Hidden = 0x8,
505
506 IncomingUnknownFrom = 0x10,
508
509 IncomingUnknownCc = 0x20,
511
512 IncomingUnknownTo = 0x40,
514
515 UnhandledQrScan = 0x80,
517
518 UnhandledSecurejoinQrScan = 0x81,
520
521 IncomingReplyTo = 0x100,
524
525 IncomingCc = 0x200,
527
528 IncomingTo = 0x400,
530
531 CreateChat = 0x800,
533
534 OutgoingBcc = 0x1000,
536
537 OutgoingCc = 0x2000,
539
540 OutgoingTo = 0x4000,
542
543 Internal = 0x40000,
545
546 AddressBook = 0x80000,
548
549 SecurejoinInvited = 0x0100_0000,
551
552 SecurejoinJoined = 0x0200_0000,
558
559 ManuallyCreated = 0x0400_0000,
561}
562
563impl Origin {
564 pub fn is_known(self) -> bool {
568 self >= Origin::IncomingReplyTo
569 }
570}
571
572#[derive(Debug, PartialEq, Eq, Clone, Copy)]
573pub(crate) enum Modifier {
574 None,
575 Modified,
576 Created,
577}
578
579impl Contact {
580 pub async fn get_by_id(context: &Context, contact_id: ContactId) -> Result<Self> {
591 let contact = Self::get_by_id_optional(context, contact_id)
592 .await?
593 .with_context(|| format!("contact {contact_id} not found"))?;
594 Ok(contact)
595 }
596
597 pub async fn get_by_id_optional(
601 context: &Context,
602 contact_id: ContactId,
603 ) -> Result<Option<Self>> {
604 if let Some(mut contact) = context
605 .sql
606 .query_row_optional(
607 "SELECT c.name, c.addr, c.origin, c.blocked, c.last_seen,
608 c.authname, c.param, c.status, c.is_bot
609 FROM contacts c
610 WHERE c.id=?;",
611 (contact_id,),
612 |row| {
613 let name: String = row.get(0)?;
614 let addr: String = row.get(1)?;
615 let origin: Origin = row.get(2)?;
616 let blocked: Option<bool> = row.get(3)?;
617 let last_seen: i64 = row.get(4)?;
618 let authname: String = row.get(5)?;
619 let param: String = row.get(6)?;
620 let status: Option<String> = row.get(7)?;
621 let is_bot: bool = row.get(8)?;
622 let contact = Self {
623 id: contact_id,
624 name,
625 authname,
626 addr,
627 blocked: blocked.unwrap_or_default(),
628 last_seen,
629 origin,
630 param: param.parse().unwrap_or_default(),
631 status: status.unwrap_or_default(),
632 is_bot,
633 };
634 Ok(contact)
635 },
636 )
637 .await?
638 {
639 if contact_id == ContactId::SELF {
640 contact.name = stock_str::self_msg(context).await;
641 contact.authname = context
642 .get_config(Config::Displayname)
643 .await?
644 .unwrap_or_default();
645 contact.addr = context
646 .get_config(Config::ConfiguredAddr)
647 .await?
648 .unwrap_or_default();
649 contact.status = context
650 .get_config(Config::Selfstatus)
651 .await?
652 .unwrap_or_default();
653 } else if contact_id == ContactId::DEVICE {
654 contact.name = stock_str::device_messages(context).await;
655 contact.addr = ContactId::DEVICE_ADDR.to_string();
656 contact.status = stock_str::device_messages_hint(context).await;
657 }
658 Ok(Some(contact))
659 } else {
660 Ok(None)
661 }
662 }
663
664 pub fn is_blocked(&self) -> bool {
666 self.blocked
667 }
668
669 pub fn last_seen(&self) -> i64 {
671 self.last_seen
672 }
673
674 pub fn was_seen_recently(&self) -> bool {
676 time() - self.last_seen <= SEEN_RECENTLY_SECONDS
677 }
678
679 pub async fn is_blocked_load(context: &Context, id: ContactId) -> Result<bool> {
681 let blocked = context
682 .sql
683 .query_row("SELECT blocked FROM contacts WHERE id=?", (id,), |row| {
684 let blocked: bool = row.get(0)?;
685 Ok(blocked)
686 })
687 .await?;
688 Ok(blocked)
689 }
690
691 pub async fn block(context: &Context, id: ContactId) -> Result<()> {
693 set_blocked(context, Sync, id, true).await
694 }
695
696 pub async fn unblock(context: &Context, id: ContactId) -> Result<()> {
698 set_blocked(context, Sync, id, false).await
699 }
700
701 pub async fn create(context: &Context, name: &str, addr: &str) -> Result<ContactId> {
711 Self::create_ex(context, Sync, name, addr).await
712 }
713
714 pub(crate) async fn create_ex(
715 context: &Context,
716 sync: sync::Sync,
717 name: &str,
718 addr: &str,
719 ) -> Result<ContactId> {
720 let (name, addr) = sanitize_name_and_addr(name, addr);
721 let addr = ContactAddress::new(&addr)?;
722
723 let (contact_id, sth_modified) =
724 Contact::add_or_lookup(context, &name, &addr, Origin::ManuallyCreated)
725 .await
726 .context("add_or_lookup")?;
727 let blocked = Contact::is_blocked_load(context, contact_id).await?;
728 match sth_modified {
729 Modifier::None => {}
730 Modifier::Modified | Modifier::Created => {
731 context.emit_event(EventType::ContactsChanged(Some(contact_id)))
732 }
733 }
734 if blocked {
735 set_blocked(context, Nosync, contact_id, false).await?;
736 }
737
738 if sync.into() && sth_modified != Modifier::None {
739 chat::sync(
740 context,
741 chat::SyncId::ContactAddr(addr.to_string()),
742 chat::SyncAction::Rename(name.to_string()),
743 )
744 .await
745 .log_err(context)
746 .ok();
747 }
748 Ok(contact_id)
749 }
750
751 pub async fn mark_noticed(context: &Context, id: ContactId) -> Result<()> {
753 context
754 .sql
755 .execute(
756 "UPDATE msgs SET state=? WHERE from_id=? AND state=?;",
757 (MessageState::InNoticed, id, MessageState::InFresh),
758 )
759 .await?;
760 Ok(())
761 }
762
763 pub fn is_bot(&self) -> bool {
765 self.is_bot
766 }
767
768 pub async fn lookup_id_by_addr(
778 context: &Context,
779 addr: &str,
780 min_origin: Origin,
781 ) -> Result<Option<ContactId>> {
782 Self::lookup_id_by_addr_ex(context, addr, min_origin, Some(Blocked::Not)).await
783 }
784
785 pub(crate) async fn lookup_id_by_addr_ex(
788 context: &Context,
789 addr: &str,
790 min_origin: Origin,
791 blocked: Option<Blocked>,
792 ) -> Result<Option<ContactId>> {
793 if addr.is_empty() {
794 bail!("lookup_id_by_addr: empty address");
795 }
796
797 let addr_normalized = addr_normalize(addr);
798
799 if context.is_self_addr(&addr_normalized).await? {
800 return Ok(Some(ContactId::SELF));
801 }
802
803 let id = context
804 .sql
805 .query_get_value(
806 "SELECT id FROM contacts \
807 WHERE addr=?1 COLLATE NOCASE \
808 AND id>?2 AND origin>=?3 AND (? OR blocked=?)",
809 (
810 &addr_normalized,
811 ContactId::LAST_SPECIAL,
812 min_origin as u32,
813 blocked.is_none(),
814 blocked.unwrap_or_default(),
815 ),
816 )
817 .await?;
818 Ok(id)
819 }
820
821 pub(crate) async fn add_or_lookup(
847 context: &Context,
848 name: &str,
849 addr: &ContactAddress,
850 mut origin: Origin,
851 ) -> Result<(ContactId, Modifier)> {
852 let mut sth_modified = Modifier::None;
853
854 ensure!(!addr.is_empty(), "Can not add_or_lookup empty address");
855 ensure!(origin != Origin::Unknown, "Missing valid origin");
856
857 if context.is_self_addr(addr).await? {
858 return Ok((ContactId::SELF, sth_modified));
859 }
860
861 let mut name = sanitize_name(name);
862 if origin <= Origin::OutgoingTo {
863 if addr.contains("noreply")
865 || addr.contains("no-reply")
866 || addr.starts_with("notifications@")
867 || (addr.len() > 50 && addr.contains('+'))
869 {
870 info!(context, "hiding contact {}", addr);
871 origin = Origin::Hidden;
872 name = "".to_string();
876 }
877 }
878
879 let manual = matches!(
884 origin,
885 Origin::ManuallyCreated | Origin::AddressBook | Origin::UnhandledQrScan
886 );
887
888 let mut update_addr = false;
889
890 let row_id = context
891 .sql
892 .transaction(|transaction| {
893 let row = transaction
894 .query_row(
895 "SELECT id, name, addr, origin, authname
896 FROM contacts WHERE addr=? COLLATE NOCASE",
897 (addr,),
898 |row| {
899 let row_id: u32 = row.get(0)?;
900 let row_name: String = row.get(1)?;
901 let row_addr: String = row.get(2)?;
902 let row_origin: Origin = row.get(3)?;
903 let row_authname: String = row.get(4)?;
904
905 Ok((row_id, row_name, row_addr, row_origin, row_authname))
906 },
907 )
908 .optional()?;
909
910 let row_id;
911 if let Some((id, row_name, row_addr, row_origin, row_authname)) = row {
912 let update_name = manual && name != row_name;
913 let update_authname = !manual
914 && name != row_authname
915 && !name.is_empty()
916 && (origin >= row_origin
917 || origin == Origin::IncomingUnknownFrom
918 || row_authname.is_empty());
919
920 row_id = id;
921 if origin >= row_origin && addr.as_ref() != row_addr {
922 update_addr = true;
923 }
924 if update_name || update_authname || update_addr || origin > row_origin {
925 let new_name = if update_name {
926 name.to_string()
927 } else {
928 row_name
929 };
930
931 transaction.execute(
932 "UPDATE contacts SET name=?, addr=?, origin=?, authname=? WHERE id=?;",
933 (
934 new_name,
935 if update_addr {
936 addr.to_string()
937 } else {
938 row_addr
939 },
940 if origin > row_origin {
941 origin
942 } else {
943 row_origin
944 },
945 if update_authname {
946 name.to_string()
947 } else {
948 row_authname
949 },
950 row_id,
951 ),
952 )?;
953
954 if update_name || update_authname {
955 let contact_id = ContactId::new(row_id);
956 update_chat_names(context, transaction, contact_id)?;
957 }
958 sth_modified = Modifier::Modified;
959 }
960 } else {
961 let update_name = manual;
962 let update_authname = !manual;
963
964 transaction.execute(
965 "INSERT INTO contacts (name, addr, origin, authname)
966 VALUES (?, ?, ?, ?);",
967 (
968 if update_name { &name } else { "" },
969 &addr,
970 origin,
971 if update_authname { &name } else { "" },
972 ),
973 )?;
974
975 sth_modified = Modifier::Created;
976 row_id = u32::try_from(transaction.last_insert_rowid())?;
977 info!(context, "Added contact id={row_id} addr={addr}.");
978 }
979 Ok(row_id)
980 })
981 .await?;
982
983 let contact_id = ContactId::new(row_id);
984
985 Ok((contact_id, sth_modified))
986 }
987
988 pub async fn add_address_book(context: &Context, addr_book: &str) -> Result<usize> {
1006 let mut modify_cnt = 0;
1007
1008 for (name, addr) in split_address_book(addr_book) {
1009 let (name, addr) = sanitize_name_and_addr(name, addr);
1010 match ContactAddress::new(&addr) {
1011 Ok(addr) => {
1012 match Contact::add_or_lookup(context, &name, &addr, Origin::AddressBook).await {
1013 Ok((_, modified)) => {
1014 if modified != Modifier::None {
1015 modify_cnt += 1
1016 }
1017 }
1018 Err(err) => {
1019 warn!(
1020 context,
1021 "Failed to add address {} from address book: {}", addr, err
1022 );
1023 }
1024 }
1025 }
1026 Err(err) => {
1027 warn!(context, "{:#}.", err);
1028 }
1029 }
1030 }
1031 if modify_cnt > 0 {
1032 context.emit_event(EventType::ContactsChanged(None));
1033 }
1034
1035 Ok(modify_cnt)
1036 }
1037
1038 pub async fn get_all(
1048 context: &Context,
1049 listflags: u32,
1050 query: Option<&str>,
1051 ) -> Result<Vec<ContactId>> {
1052 let self_addrs = context
1053 .get_all_self_addrs()
1054 .await?
1055 .into_iter()
1056 .collect::<HashSet<_>>();
1057 let mut add_self = false;
1058 let mut ret = Vec::new();
1059 let flag_verified_only = (listflags & DC_GCL_VERIFIED_ONLY) != 0;
1060 let flag_add_self = (listflags & DC_GCL_ADD_SELF) != 0;
1061 let minimal_origin = if context.get_config_bool(Config::Bot).await? {
1062 Origin::Unknown
1063 } else {
1064 Origin::IncomingReplyTo
1065 };
1066 if flag_verified_only || query.is_some() {
1067 let s3str_like_cmd = format!("%{}%", query.unwrap_or(""));
1068 context
1069 .sql
1070 .query_map(
1071 "SELECT c.id, c.addr FROM contacts c
1072 LEFT JOIN acpeerstates ps ON c.addr=ps.addr \
1073 WHERE c.id>?
1074 AND c.origin>=? \
1075 AND c.blocked=0 \
1076 AND (iif(c.name='',c.authname,c.name) LIKE ? OR c.addr LIKE ?) \
1077 AND (1=? OR LENGTH(ps.verified_key_fingerprint)!=0) \
1078 ORDER BY c.last_seen DESC, c.id DESC;",
1079 (
1080 ContactId::LAST_SPECIAL,
1081 minimal_origin,
1082 &s3str_like_cmd,
1083 &s3str_like_cmd,
1084 if flag_verified_only { 0i32 } else { 1i32 },
1085 ),
1086 |row| {
1087 let id: ContactId = row.get(0)?;
1088 let addr: String = row.get(1)?;
1089 Ok((id, addr))
1090 },
1091 |rows| {
1092 for row in rows {
1093 let (id, addr) = row?;
1094 if !self_addrs.contains(&addr) {
1095 ret.push(id);
1096 }
1097 }
1098 Ok(())
1099 },
1100 )
1101 .await?;
1102
1103 if let Some(query) = query {
1104 let self_addr = context
1105 .get_config(Config::ConfiguredAddr)
1106 .await?
1107 .unwrap_or_default();
1108 let self_name = context
1109 .get_config(Config::Displayname)
1110 .await?
1111 .unwrap_or_default();
1112 let self_name2 = stock_str::self_msg(context);
1113
1114 if self_addr.contains(query)
1115 || self_name.contains(query)
1116 || self_name2.await.contains(query)
1117 {
1118 add_self = true;
1119 }
1120 } else {
1121 add_self = true;
1122 }
1123 } else {
1124 add_self = true;
1125
1126 context
1127 .sql
1128 .query_map(
1129 "SELECT id, addr FROM contacts
1130 WHERE id>?
1131 AND origin>=?
1132 AND blocked=0
1133 ORDER BY last_seen DESC, id DESC;",
1134 (ContactId::LAST_SPECIAL, minimal_origin),
1135 |row| {
1136 let id: ContactId = row.get(0)?;
1137 let addr: String = row.get(1)?;
1138 Ok((id, addr))
1139 },
1140 |rows| {
1141 for row in rows {
1142 let (id, addr) = row?;
1143 if !self_addrs.contains(&addr) {
1144 ret.push(id);
1145 }
1146 }
1147 Ok(())
1148 },
1149 )
1150 .await?;
1151 }
1152
1153 if flag_add_self && add_self {
1154 ret.push(ContactId::SELF);
1155 }
1156
1157 Ok(ret)
1158 }
1159
1160 async fn update_blocked_mailinglist_contacts(context: &Context) -> Result<()> {
1166 context
1167 .sql
1168 .transaction(move |transaction| {
1169 let mut stmt = transaction
1170 .prepare("SELECT name, grpid FROM chats WHERE type=? AND blocked=?")?;
1171 let rows = stmt.query_map((Chattype::Mailinglist, Blocked::Yes), |row| {
1172 let name: String = row.get(0)?;
1173 let grpid: String = row.get(1)?;
1174 Ok((name, grpid))
1175 })?;
1176 let blocked_mailinglists = rows.collect::<std::result::Result<Vec<_>, _>>()?;
1177 for (name, grpid) in blocked_mailinglists {
1178 let count = transaction.query_row(
1179 "SELECT COUNT(id) FROM contacts WHERE addr=?",
1180 [&grpid],
1181 |row| {
1182 let count: isize = row.get(0)?;
1183 Ok(count)
1184 },
1185 )?;
1186 if count == 0 {
1187 transaction.execute("INSERT INTO contacts (addr) VALUES (?)", [&grpid])?;
1188 }
1189
1190 transaction.execute(
1192 "UPDATE contacts SET name=?, origin=?, blocked=1 WHERE addr=?",
1193 (&name, Origin::MailinglistAddress, &grpid),
1194 )?;
1195 }
1196 Ok(())
1197 })
1198 .await?;
1199 Ok(())
1200 }
1201
1202 pub async fn get_blocked_cnt(context: &Context) -> Result<usize> {
1204 let count = context
1205 .sql
1206 .count(
1207 "SELECT COUNT(*) FROM contacts WHERE id>? AND blocked!=0",
1208 (ContactId::LAST_SPECIAL,),
1209 )
1210 .await?;
1211 Ok(count)
1212 }
1213
1214 pub async fn get_all_blocked(context: &Context) -> Result<Vec<ContactId>> {
1216 Contact::update_blocked_mailinglist_contacts(context)
1217 .await
1218 .context("cannot update blocked mailinglist contacts")?;
1219
1220 let list = context
1221 .sql
1222 .query_map(
1223 "SELECT id FROM contacts WHERE id>? AND blocked!=0 ORDER BY last_seen DESC, id DESC;",
1224 (ContactId::LAST_SPECIAL,),
1225 |row| row.get::<_, ContactId>(0),
1226 |ids| {
1227 ids.collect::<std::result::Result<Vec<_>, _>>()
1228 .map_err(Into::into)
1229 },
1230 )
1231 .await?;
1232 Ok(list)
1233 }
1234
1235 pub async fn get_encrinfo(context: &Context, contact_id: ContactId) -> Result<String> {
1241 ensure!(
1242 !contact_id.is_special(),
1243 "Can not provide encryption info for special contact"
1244 );
1245
1246 let contact = Contact::get_by_id(context, contact_id).await?;
1247 let addr = context
1248 .get_config(Config::ConfiguredAddr)
1249 .await?
1250 .unwrap_or_default();
1251 let peerstate = Peerstate::from_addr(context, &contact.addr).await?;
1252
1253 let Some(peerstate) = peerstate.filter(|peerstate| peerstate.peek_key(false).is_some())
1254 else {
1255 return Ok(stock_str::encr_none(context).await);
1256 };
1257
1258 let stock_message = match peerstate.prefer_encrypt {
1259 EncryptPreference::Mutual => stock_str::e2e_preferred(context).await,
1260 EncryptPreference::NoPreference => stock_str::e2e_available(context).await,
1261 EncryptPreference::Reset => stock_str::encr_none(context).await,
1262 };
1263
1264 let finger_prints = stock_str::finger_prints(context).await;
1265 let mut ret = format!("{stock_message}.\n{finger_prints}:");
1266
1267 let fingerprint_self = load_self_public_key(context)
1268 .await?
1269 .dc_fingerprint()
1270 .to_string();
1271 let fingerprint_other_verified = peerstate
1272 .peek_key(true)
1273 .map(|k| k.dc_fingerprint().to_string())
1274 .unwrap_or_default();
1275 let fingerprint_other_unverified = peerstate
1276 .peek_key(false)
1277 .map(|k| k.dc_fingerprint().to_string())
1278 .unwrap_or_default();
1279 if addr < peerstate.addr {
1280 cat_fingerprint(
1281 &mut ret,
1282 &stock_str::self_msg(context).await,
1283 &addr,
1284 &fingerprint_self,
1285 "",
1286 );
1287 cat_fingerprint(
1288 &mut ret,
1289 contact.get_display_name(),
1290 &peerstate.addr,
1291 &fingerprint_other_verified,
1292 &fingerprint_other_unverified,
1293 );
1294 } else {
1295 cat_fingerprint(
1296 &mut ret,
1297 contact.get_display_name(),
1298 &peerstate.addr,
1299 &fingerprint_other_verified,
1300 &fingerprint_other_unverified,
1301 );
1302 cat_fingerprint(
1303 &mut ret,
1304 &stock_str::self_msg(context).await,
1305 &addr,
1306 &fingerprint_self,
1307 "",
1308 );
1309 }
1310
1311 Ok(ret)
1312 }
1313
1314 pub async fn delete(context: &Context, contact_id: ContactId) -> Result<()> {
1320 ensure!(!contact_id.is_special(), "Can not delete special contact");
1321
1322 context
1323 .sql
1324 .transaction(move |transaction| {
1325 let deleted_contacts = transaction.execute(
1328 "DELETE FROM contacts WHERE id=?
1329 AND (SELECT COUNT(*) FROM chats_contacts WHERE contact_id=?)=0;",
1330 (contact_id, contact_id),
1331 )?;
1332 if deleted_contacts == 0 {
1333 transaction.execute(
1334 "UPDATE contacts SET origin=? WHERE id=?;",
1335 (Origin::Hidden, contact_id),
1336 )?;
1337 }
1338 Ok(())
1339 })
1340 .await?;
1341
1342 context.emit_event(EventType::ContactsChanged(None));
1343 Ok(())
1344 }
1345
1346 pub async fn update_param(&self, context: &Context) -> Result<()> {
1348 context
1349 .sql
1350 .execute(
1351 "UPDATE contacts SET param=? WHERE id=?",
1352 (self.param.to_string(), self.id),
1353 )
1354 .await?;
1355 Ok(())
1356 }
1357
1358 pub async fn update_status(&self, context: &Context) -> Result<()> {
1360 context
1361 .sql
1362 .execute(
1363 "UPDATE contacts SET status=? WHERE id=?",
1364 (&self.status, self.id),
1365 )
1366 .await?;
1367 Ok(())
1368 }
1369
1370 pub fn get_id(&self) -> ContactId {
1372 self.id
1373 }
1374
1375 pub fn get_addr(&self) -> &str {
1377 &self.addr
1378 }
1379
1380 pub fn get_authname(&self) -> &str {
1382 &self.authname
1383 }
1384
1385 pub fn get_name(&self) -> &str {
1391 &self.name
1392 }
1393
1394 pub fn get_display_name(&self) -> &str {
1400 if !self.name.is_empty() {
1401 return &self.name;
1402 }
1403 if !self.authname.is_empty() {
1404 return &self.authname;
1405 }
1406 &self.addr
1407 }
1408
1409 pub(crate) fn get_authname_or_addr(&self) -> String {
1414 if !self.authname.is_empty() {
1415 (&self.authname).into()
1416 } else {
1417 (&self.addr).into()
1418 }
1419 }
1420
1421 pub fn get_name_n_addr(&self) -> String {
1432 if !self.name.is_empty() {
1433 format!("{} ({})", self.name, self.addr)
1434 } else if !self.authname.is_empty() {
1435 format!("{} ({})", self.authname, self.addr)
1436 } else {
1437 (&self.addr).into()
1438 }
1439 }
1440
1441 pub async fn get_profile_image(&self, context: &Context) -> Result<Option<PathBuf>> {
1445 if self.id == ContactId::SELF {
1446 if let Some(p) = context.get_config(Config::Selfavatar).await? {
1447 return Ok(Some(PathBuf::from(p))); }
1449 } else if let Some(image_rel) = self.param.get(Param::ProfileImage) {
1450 if !image_rel.is_empty() {
1451 return Ok(Some(get_abs_path(context, Path::new(image_rel))));
1452 }
1453 }
1454 Ok(None)
1455 }
1456
1457 pub fn get_color(&self) -> u32 {
1462 str_to_color(&self.addr.to_lowercase())
1463 }
1464
1465 pub fn get_status(&self) -> &str {
1469 self.status.as_str()
1470 }
1471
1472 pub async fn e2ee_avail(&self, context: &Context) -> Result<bool> {
1474 if self.id == ContactId::SELF {
1475 return Ok(true);
1476 }
1477 let Some(peerstate) = Peerstate::from_addr(context, &self.addr).await? else {
1478 return Ok(false);
1479 };
1480 Ok(peerstate.peek_key(false).is_some())
1481 }
1482
1483 pub async fn is_verified(&self, context: &Context) -> Result<bool> {
1498 if self.id == ContactId::SELF {
1501 return Ok(true);
1502 }
1503
1504 let Some(peerstate) = Peerstate::from_addr(context, &self.addr).await? else {
1505 return Ok(false);
1506 };
1507
1508 let forward_verified = peerstate.is_using_verified_key();
1509 let backward_verified = peerstate.is_backward_verified(context).await?;
1510 Ok(forward_verified && backward_verified)
1511 }
1512
1513 pub async fn is_forward_verified(&self, context: &Context) -> Result<bool> {
1519 if self.id == ContactId::SELF {
1520 return Ok(true);
1521 }
1522
1523 let Some(peerstate) = Peerstate::from_addr(context, &self.addr).await? else {
1524 return Ok(false);
1525 };
1526
1527 Ok(peerstate.is_using_verified_key())
1528 }
1529
1530 pub async fn get_verifier_id(&self, context: &Context) -> Result<Option<ContactId>> {
1543 let Some(verifier_addr) = Peerstate::from_addr(context, self.get_addr())
1544 .await?
1545 .and_then(|peerstate| peerstate.get_verifier().map(|addr| addr.to_owned()))
1546 else {
1547 return Ok(None);
1548 };
1549
1550 if addr_cmp(&verifier_addr, &self.addr) {
1551 return Ok(Some(ContactId::SELF));
1553 }
1554
1555 match Contact::lookup_id_by_addr(context, &verifier_addr, Origin::Unknown).await? {
1556 Some(contact_id) => Ok(Some(contact_id)),
1557 None => {
1558 let addr = &self.addr;
1559 warn!(context, "Could not lookup contact with address {verifier_addr} which introduced {addr}.");
1560 Ok(None)
1561 }
1562 }
1563 }
1564
1565 pub async fn is_profile_verified(&self, context: &Context) -> Result<bool> {
1576 let contact_id = self.id;
1577
1578 if let Some(ChatIdBlocked { id: chat_id, .. }) =
1579 ChatIdBlocked::lookup_by_contact(context, contact_id).await?
1580 {
1581 Ok(chat_id.is_protected(context).await? == ProtectionStatus::Protected)
1582 } else {
1583 Ok(self.is_verified(context).await?)
1585 }
1586 }
1587
1588 pub async fn get_real_cnt(context: &Context) -> Result<usize> {
1590 if !context.sql.is_open().await {
1591 return Ok(0);
1592 }
1593
1594 let count = context
1595 .sql
1596 .count(
1597 "SELECT COUNT(*) FROM contacts WHERE id>?;",
1598 (ContactId::LAST_SPECIAL,),
1599 )
1600 .await?;
1601 Ok(count)
1602 }
1603
1604 pub async fn real_exists_by_id(context: &Context, contact_id: ContactId) -> Result<bool> {
1606 if contact_id.is_special() {
1607 return Ok(false);
1608 }
1609
1610 let exists = context
1611 .sql
1612 .exists("SELECT COUNT(*) FROM contacts WHERE id=?;", (contact_id,))
1613 .await?;
1614 Ok(exists)
1615 }
1616}
1617
1618fn update_chat_names(
1622 context: &Context,
1623 transaction: &rusqlite::Connection,
1624 contact_id: ContactId,
1625) -> Result<()> {
1626 let chat_id: Option<ChatId> = transaction.query_row(
1627 "SELECT id FROM chats WHERE type=? AND id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?)",
1628 (Chattype::Single, contact_id),
1629 |row| {
1630 let chat_id: ChatId = row.get(0)?;
1631 Ok(chat_id)
1632 }
1633 ).optional()?;
1634
1635 if let Some(chat_id) = chat_id {
1636 let (addr, name, authname) = transaction.query_row(
1637 "SELECT addr, name, authname
1638 FROM contacts
1639 WHERE id=?",
1640 (contact_id,),
1641 |row| {
1642 let addr: String = row.get(0)?;
1643 let name: String = row.get(1)?;
1644 let authname: String = row.get(2)?;
1645 Ok((addr, name, authname))
1646 },
1647 )?;
1648
1649 let chat_name = if !name.is_empty() {
1650 name
1651 } else if !authname.is_empty() {
1652 authname
1653 } else {
1654 addr
1655 };
1656
1657 let count = transaction.execute(
1658 "UPDATE chats SET name=?1 WHERE id=?2 AND name!=?1",
1659 (chat_name, chat_id),
1660 )?;
1661
1662 if count > 0 {
1663 context.emit_event(EventType::ChatModified(chat_id));
1665 chatlist_events::emit_chatlist_items_changed_for_contact(context, contact_id);
1666 }
1667 }
1668
1669 Ok(())
1670}
1671
1672pub(crate) async fn set_blocked(
1673 context: &Context,
1674 sync: sync::Sync,
1675 contact_id: ContactId,
1676 new_blocking: bool,
1677) -> Result<()> {
1678 ensure!(
1679 !contact_id.is_special(),
1680 "Can't block special contact {}",
1681 contact_id
1682 );
1683 let contact = Contact::get_by_id(context, contact_id).await?;
1684
1685 if contact.blocked != new_blocking {
1686 context
1687 .sql
1688 .execute(
1689 "UPDATE contacts SET blocked=? WHERE id=?;",
1690 (i32::from(new_blocking), contact_id),
1691 )
1692 .await?;
1693
1694 if context
1700 .sql
1701 .execute(
1702 r#"
1703UPDATE chats
1704SET blocked=?
1705WHERE type=? AND id IN (
1706 SELECT chat_id FROM chats_contacts WHERE contact_id=?
1707);
1708"#,
1709 (new_blocking, Chattype::Single, contact_id),
1710 )
1711 .await
1712 .is_ok()
1713 {
1714 Contact::mark_noticed(context, contact_id).await?;
1715 context.emit_event(EventType::ContactsChanged(Some(contact_id)));
1716 }
1717
1718 if !new_blocking && contact.origin == Origin::MailinglistAddress {
1721 if let Some((chat_id, _, _)) =
1722 chat::get_chat_id_by_grpid(context, &contact.addr).await?
1723 {
1724 chat_id.unblock_ex(context, Nosync).await?;
1725 }
1726 }
1727
1728 if sync.into() {
1729 let action = match new_blocking {
1730 true => chat::SyncAction::Block,
1731 false => chat::SyncAction::Unblock,
1732 };
1733 chat::sync(
1734 context,
1735 chat::SyncId::ContactAddr(contact.addr.clone()),
1736 action,
1737 )
1738 .await
1739 .log_err(context)
1740 .ok();
1741 }
1742 }
1743
1744 chatlist_events::emit_chatlist_changed(context);
1745 Ok(())
1746}
1747
1748pub(crate) async fn set_profile_image(
1756 context: &Context,
1757 contact_id: ContactId,
1758 profile_image: &AvatarAction,
1759 was_encrypted: bool,
1760) -> Result<()> {
1761 let mut contact = Contact::get_by_id(context, contact_id).await?;
1762 let changed = match profile_image {
1763 AvatarAction::Change(profile_image) => {
1764 if contact_id == ContactId::SELF {
1765 if was_encrypted {
1766 context
1767 .set_config_ex(Nosync, Config::Selfavatar, Some(profile_image))
1768 .await?;
1769 } else {
1770 info!(context, "Do not use unencrypted selfavatar.");
1771 }
1772 } else {
1773 contact.param.set(Param::ProfileImage, profile_image);
1774 }
1775 true
1776 }
1777 AvatarAction::Delete => {
1778 if contact_id == ContactId::SELF {
1779 if was_encrypted {
1780 context
1781 .set_config_ex(Nosync, Config::Selfavatar, None)
1782 .await?;
1783 } else {
1784 info!(context, "Do not use unencrypted selfavatar deletion.");
1785 }
1786 } else {
1787 contact.param.remove(Param::ProfileImage);
1788 }
1789 true
1790 }
1791 };
1792 if changed {
1793 contact.update_param(context).await?;
1794 context.emit_event(EventType::ContactsChanged(Some(contact_id)));
1795 chatlist_events::emit_chatlist_item_changed_for_contact_chat(context, contact_id).await;
1796 }
1797 Ok(())
1798}
1799
1800pub(crate) async fn set_status(
1806 context: &Context,
1807 contact_id: ContactId,
1808 status: String,
1809 encrypted: bool,
1810 has_chat_version: bool,
1811) -> Result<()> {
1812 if contact_id == ContactId::SELF {
1813 if encrypted && has_chat_version {
1814 context
1815 .set_config_ex(Nosync, Config::Selfstatus, Some(&status))
1816 .await?;
1817 }
1818 } else {
1819 let mut contact = Contact::get_by_id(context, contact_id).await?;
1820
1821 if contact.status != status {
1822 contact.status = status;
1823 contact.update_status(context).await?;
1824 context.emit_event(EventType::ContactsChanged(Some(contact_id)));
1825 }
1826 }
1827 Ok(())
1828}
1829
1830pub(crate) async fn update_last_seen(
1832 context: &Context,
1833 contact_id: ContactId,
1834 timestamp: i64,
1835) -> Result<()> {
1836 ensure!(
1837 !contact_id.is_special(),
1838 "Can not update special contact last seen timestamp"
1839 );
1840
1841 if context
1842 .sql
1843 .execute(
1844 "UPDATE contacts SET last_seen = ?1 WHERE last_seen < ?1 AND id = ?2",
1845 (timestamp, contact_id),
1846 )
1847 .await?
1848 > 0
1849 && timestamp > time() - SEEN_RECENTLY_SECONDS
1850 {
1851 context.emit_event(EventType::ContactsChanged(Some(contact_id)));
1852 context
1853 .scheduler
1854 .interrupt_recently_seen(contact_id, timestamp)
1855 .await;
1856 }
1857 Ok(())
1858}
1859
1860fn cat_fingerprint(
1861 ret: &mut String,
1862 name: &str,
1863 addr: &str,
1864 fingerprint_verified: &str,
1865 fingerprint_unverified: &str,
1866) {
1867 *ret += &format!(
1868 "\n\n{} ({}):\n{}",
1869 name,
1870 addr,
1871 if !fingerprint_verified.is_empty() {
1872 fingerprint_verified
1873 } else {
1874 fingerprint_unverified
1875 },
1876 );
1877 if !fingerprint_verified.is_empty()
1878 && !fingerprint_unverified.is_empty()
1879 && fingerprint_verified != fingerprint_unverified
1880 {
1881 *ret += &format!("\n\n{name} (alternative):\n{fingerprint_unverified}");
1882 }
1883}
1884
1885fn split_address_book(book: &str) -> Vec<(&str, &str)> {
1886 book.lines()
1887 .collect::<Vec<&str>>()
1888 .chunks(2)
1889 .filter_map(|chunk| {
1890 let name = chunk.first()?;
1891 let addr = chunk.get(1)?;
1892 Some((*name, *addr))
1893 })
1894 .collect()
1895}
1896
1897#[derive(Debug)]
1898pub(crate) struct RecentlySeenInterrupt {
1899 contact_id: ContactId,
1900 timestamp: i64,
1901}
1902
1903#[derive(Debug)]
1904pub(crate) struct RecentlySeenLoop {
1905 handle: task::JoinHandle<()>,
1907
1908 interrupt_send: Sender<RecentlySeenInterrupt>,
1909}
1910
1911impl RecentlySeenLoop {
1912 pub(crate) fn new(context: Context) -> Self {
1913 let (interrupt_send, interrupt_recv) = channel::bounded(1);
1914
1915 let handle = task::spawn(Self::run(context, interrupt_recv));
1916 Self {
1917 handle,
1918 interrupt_send,
1919 }
1920 }
1921
1922 async fn run(context: Context, interrupt: Receiver<RecentlySeenInterrupt>) {
1923 type MyHeapElem = (Reverse<i64>, ContactId);
1924
1925 let now = SystemTime::now();
1926 let now_ts = now
1927 .duration_since(SystemTime::UNIX_EPOCH)
1928 .unwrap_or_default()
1929 .as_secs() as i64;
1930
1931 let mut unseen_queue: BinaryHeap<MyHeapElem> = context
1937 .sql
1938 .query_map(
1939 "SELECT id, last_seen FROM contacts
1940 WHERE last_seen > ?",
1941 (now_ts - SEEN_RECENTLY_SECONDS,),
1942 |row| {
1943 let contact_id: ContactId = row.get("id")?;
1944 let last_seen: i64 = row.get("last_seen")?;
1945 Ok((Reverse(last_seen + SEEN_RECENTLY_SECONDS), contact_id))
1946 },
1947 |rows| {
1948 rows.collect::<std::result::Result<BinaryHeap<MyHeapElem>, _>>()
1949 .map_err(Into::into)
1950 },
1951 )
1952 .await
1953 .unwrap_or_default();
1954
1955 loop {
1956 let now = SystemTime::now();
1957 let (until, contact_id) =
1958 if let Some((Reverse(timestamp), contact_id)) = unseen_queue.peek() {
1959 (
1960 UNIX_EPOCH
1961 + Duration::from_secs((*timestamp).try_into().unwrap_or(u64::MAX))
1962 + Duration::from_secs(1),
1963 Some(contact_id),
1964 )
1965 } else {
1966 (now + Duration::from_secs(86400), None)
1968 };
1969
1970 if let Ok(duration) = until.duration_since(now) {
1971 info!(
1972 context,
1973 "Recently seen loop waiting for {} or interrupt",
1974 duration_to_str(duration)
1975 );
1976
1977 match timeout(duration, interrupt.recv()).await {
1978 Err(_) => {
1979 if let Some(contact_id) = contact_id {
1981 context.emit_event(EventType::ContactsChanged(Some(*contact_id)));
1982 chatlist_events::emit_chatlist_item_changed_for_contact_chat(
1983 &context,
1984 *contact_id,
1985 )
1986 .await;
1987 unseen_queue.pop();
1988 }
1989 }
1990 Ok(Err(err)) => {
1991 warn!(
1992 context,
1993 "Error receiving an interruption in recently seen loop: {}", err
1994 );
1995 return;
1998 }
1999 Ok(Ok(RecentlySeenInterrupt {
2000 contact_id,
2001 timestamp,
2002 })) => {
2003 if contact_id != ContactId::UNDEFINED {
2005 unseen_queue
2006 .push((Reverse(timestamp + SEEN_RECENTLY_SECONDS), contact_id));
2007 }
2008 }
2009 }
2010 } else {
2011 info!(
2012 context,
2013 "Recently seen loop is not waiting, event is already due."
2014 );
2015
2016 if let Some(contact_id) = contact_id {
2018 context.emit_event(EventType::ContactsChanged(Some(*contact_id)));
2019 chatlist_events::emit_chatlist_item_changed_for_contact_chat(
2020 &context,
2021 *contact_id,
2022 )
2023 .await;
2024 }
2025 unseen_queue.pop();
2026 }
2027 }
2028 }
2029
2030 pub(crate) fn try_interrupt(&self, contact_id: ContactId, timestamp: i64) {
2031 self.interrupt_send
2032 .try_send(RecentlySeenInterrupt {
2033 contact_id,
2034 timestamp,
2035 })
2036 .ok();
2037 }
2038
2039 #[cfg(test)]
2040 pub(crate) async fn interrupt(&self, contact_id: ContactId, timestamp: i64) {
2041 self.interrupt_send
2042 .send(RecentlySeenInterrupt {
2043 contact_id,
2044 timestamp,
2045 })
2046 .await
2047 .unwrap();
2048 }
2049
2050 pub(crate) async fn abort(self) {
2051 self.handle.abort();
2052
2053 self.handle.await.ok();
2057 }
2058}
2059
2060#[cfg(test)]
2061mod contact_tests;