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 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(
1047 context: &Context,
1048 listflags: u32,
1049 query: Option<&str>,
1050 ) -> Result<Vec<ContactId>> {
1051 let self_addrs = context
1052 .get_all_self_addrs()
1053 .await?
1054 .into_iter()
1055 .collect::<HashSet<_>>();
1056 let mut add_self = false;
1057 let mut ret = Vec::new();
1058 let flag_add_self = (listflags & DC_GCL_ADD_SELF) != 0;
1059 let minimal_origin = if context.get_config_bool(Config::Bot).await? {
1060 Origin::Unknown
1061 } else {
1062 Origin::IncomingReplyTo
1063 };
1064 if query.is_some() {
1065 let s3str_like_cmd = format!("%{}%", query.unwrap_or(""));
1066 context
1067 .sql
1068 .query_map(
1069 "SELECT c.id, c.addr FROM contacts c
1070 LEFT JOIN acpeerstates ps ON c.addr=ps.addr \
1071 WHERE c.id>?
1072 AND c.origin>=? \
1073 AND c.blocked=0 \
1074 AND (iif(c.name='',c.authname,c.name) LIKE ? OR c.addr LIKE ?) \
1075 ORDER BY c.last_seen DESC, c.id DESC;",
1076 (
1077 ContactId::LAST_SPECIAL,
1078 minimal_origin,
1079 &s3str_like_cmd,
1080 &s3str_like_cmd,
1081 ),
1082 |row| {
1083 let id: ContactId = row.get(0)?;
1084 let addr: String = row.get(1)?;
1085 Ok((id, addr))
1086 },
1087 |rows| {
1088 for row in rows {
1089 let (id, addr) = row?;
1090 if !self_addrs.contains(&addr) {
1091 ret.push(id);
1092 }
1093 }
1094 Ok(())
1095 },
1096 )
1097 .await?;
1098
1099 if let Some(query) = query {
1100 let self_addr = context
1101 .get_config(Config::ConfiguredAddr)
1102 .await?
1103 .unwrap_or_default();
1104 let self_name = context
1105 .get_config(Config::Displayname)
1106 .await?
1107 .unwrap_or_default();
1108 let self_name2 = stock_str::self_msg(context);
1109
1110 if self_addr.contains(query)
1111 || self_name.contains(query)
1112 || self_name2.await.contains(query)
1113 {
1114 add_self = true;
1115 }
1116 } else {
1117 add_self = true;
1118 }
1119 } else {
1120 add_self = true;
1121
1122 context
1123 .sql
1124 .query_map(
1125 "SELECT id, addr FROM contacts
1126 WHERE id>?
1127 AND origin>=?
1128 AND blocked=0
1129 ORDER BY last_seen DESC, id DESC;",
1130 (ContactId::LAST_SPECIAL, minimal_origin),
1131 |row| {
1132 let id: ContactId = row.get(0)?;
1133 let addr: String = row.get(1)?;
1134 Ok((id, addr))
1135 },
1136 |rows| {
1137 for row in rows {
1138 let (id, addr) = row?;
1139 if !self_addrs.contains(&addr) {
1140 ret.push(id);
1141 }
1142 }
1143 Ok(())
1144 },
1145 )
1146 .await?;
1147 }
1148
1149 if flag_add_self && add_self {
1150 ret.push(ContactId::SELF);
1151 }
1152
1153 Ok(ret)
1154 }
1155
1156 async fn update_blocked_mailinglist_contacts(context: &Context) -> Result<()> {
1162 context
1163 .sql
1164 .transaction(move |transaction| {
1165 let mut stmt = transaction
1166 .prepare("SELECT name, grpid FROM chats WHERE type=? AND blocked=?")?;
1167 let rows = stmt.query_map((Chattype::Mailinglist, Blocked::Yes), |row| {
1168 let name: String = row.get(0)?;
1169 let grpid: String = row.get(1)?;
1170 Ok((name, grpid))
1171 })?;
1172 let blocked_mailinglists = rows.collect::<std::result::Result<Vec<_>, _>>()?;
1173 for (name, grpid) in blocked_mailinglists {
1174 let count = transaction.query_row(
1175 "SELECT COUNT(id) FROM contacts WHERE addr=?",
1176 [&grpid],
1177 |row| {
1178 let count: isize = row.get(0)?;
1179 Ok(count)
1180 },
1181 )?;
1182 if count == 0 {
1183 transaction.execute("INSERT INTO contacts (addr) VALUES (?)", [&grpid])?;
1184 }
1185
1186 transaction.execute(
1188 "UPDATE contacts SET name=?, origin=?, blocked=1 WHERE addr=?",
1189 (&name, Origin::MailinglistAddress, &grpid),
1190 )?;
1191 }
1192 Ok(())
1193 })
1194 .await?;
1195 Ok(())
1196 }
1197
1198 pub async fn get_blocked_cnt(context: &Context) -> Result<usize> {
1200 let count = context
1201 .sql
1202 .count(
1203 "SELECT COUNT(*) FROM contacts WHERE id>? AND blocked!=0",
1204 (ContactId::LAST_SPECIAL,),
1205 )
1206 .await?;
1207 Ok(count)
1208 }
1209
1210 pub async fn get_all_blocked(context: &Context) -> Result<Vec<ContactId>> {
1212 Contact::update_blocked_mailinglist_contacts(context)
1213 .await
1214 .context("cannot update blocked mailinglist contacts")?;
1215
1216 let list = context
1217 .sql
1218 .query_map(
1219 "SELECT id FROM contacts WHERE id>? AND blocked!=0 ORDER BY last_seen DESC, id DESC;",
1220 (ContactId::LAST_SPECIAL,),
1221 |row| row.get::<_, ContactId>(0),
1222 |ids| {
1223 ids.collect::<std::result::Result<Vec<_>, _>>()
1224 .map_err(Into::into)
1225 },
1226 )
1227 .await?;
1228 Ok(list)
1229 }
1230
1231 pub async fn get_encrinfo(context: &Context, contact_id: ContactId) -> Result<String> {
1237 ensure!(
1238 !contact_id.is_special(),
1239 "Can not provide encryption info for special contact"
1240 );
1241
1242 let contact = Contact::get_by_id(context, contact_id).await?;
1243 let addr = context
1244 .get_config(Config::ConfiguredAddr)
1245 .await?
1246 .unwrap_or_default();
1247 let peerstate = Peerstate::from_addr(context, &contact.addr).await?;
1248
1249 let Some(peerstate) = peerstate.filter(|peerstate| peerstate.peek_key(false).is_some())
1250 else {
1251 return Ok(stock_str::encr_none(context).await);
1252 };
1253
1254 let stock_message = match peerstate.prefer_encrypt {
1255 EncryptPreference::Mutual => stock_str::e2e_preferred(context).await,
1256 EncryptPreference::NoPreference => stock_str::e2e_available(context).await,
1257 EncryptPreference::Reset => stock_str::encr_none(context).await,
1258 };
1259
1260 let finger_prints = stock_str::finger_prints(context).await;
1261 let mut ret = format!("{stock_message}.\n{finger_prints}:");
1262
1263 let fingerprint_self = load_self_public_key(context)
1264 .await?
1265 .dc_fingerprint()
1266 .to_string();
1267 let fingerprint_other_verified = peerstate
1268 .peek_key(true)
1269 .map(|k| k.dc_fingerprint().to_string())
1270 .unwrap_or_default();
1271 let fingerprint_other_unverified = peerstate
1272 .peek_key(false)
1273 .map(|k| k.dc_fingerprint().to_string())
1274 .unwrap_or_default();
1275 if addr < peerstate.addr {
1276 cat_fingerprint(
1277 &mut ret,
1278 &stock_str::self_msg(context).await,
1279 &addr,
1280 &fingerprint_self,
1281 "",
1282 );
1283 cat_fingerprint(
1284 &mut ret,
1285 contact.get_display_name(),
1286 &peerstate.addr,
1287 &fingerprint_other_verified,
1288 &fingerprint_other_unverified,
1289 );
1290 } else {
1291 cat_fingerprint(
1292 &mut ret,
1293 contact.get_display_name(),
1294 &peerstate.addr,
1295 &fingerprint_other_verified,
1296 &fingerprint_other_unverified,
1297 );
1298 cat_fingerprint(
1299 &mut ret,
1300 &stock_str::self_msg(context).await,
1301 &addr,
1302 &fingerprint_self,
1303 "",
1304 );
1305 }
1306
1307 Ok(ret)
1308 }
1309
1310 pub async fn delete(context: &Context, contact_id: ContactId) -> Result<()> {
1316 ensure!(!contact_id.is_special(), "Can not delete special contact");
1317
1318 context
1319 .sql
1320 .transaction(move |transaction| {
1321 let deleted_contacts = transaction.execute(
1324 "DELETE FROM contacts WHERE id=?
1325 AND (SELECT COUNT(*) FROM chats_contacts WHERE contact_id=?)=0;",
1326 (contact_id, contact_id),
1327 )?;
1328 if deleted_contacts == 0 {
1329 transaction.execute(
1330 "UPDATE contacts SET origin=? WHERE id=?;",
1331 (Origin::Hidden, contact_id),
1332 )?;
1333 }
1334 Ok(())
1335 })
1336 .await?;
1337
1338 context.emit_event(EventType::ContactsChanged(None));
1339 Ok(())
1340 }
1341
1342 pub async fn update_param(&self, context: &Context) -> Result<()> {
1344 context
1345 .sql
1346 .execute(
1347 "UPDATE contacts SET param=? WHERE id=?",
1348 (self.param.to_string(), self.id),
1349 )
1350 .await?;
1351 Ok(())
1352 }
1353
1354 pub async fn update_status(&self, context: &Context) -> Result<()> {
1356 context
1357 .sql
1358 .execute(
1359 "UPDATE contacts SET status=? WHERE id=?",
1360 (&self.status, self.id),
1361 )
1362 .await?;
1363 Ok(())
1364 }
1365
1366 pub fn get_id(&self) -> ContactId {
1368 self.id
1369 }
1370
1371 pub fn get_addr(&self) -> &str {
1373 &self.addr
1374 }
1375
1376 pub fn get_authname(&self) -> &str {
1378 &self.authname
1379 }
1380
1381 pub fn get_name(&self) -> &str {
1387 &self.name
1388 }
1389
1390 pub fn get_display_name(&self) -> &str {
1396 if !self.name.is_empty() {
1397 return &self.name;
1398 }
1399 if !self.authname.is_empty() {
1400 return &self.authname;
1401 }
1402 &self.addr
1403 }
1404
1405 pub(crate) fn get_authname_or_addr(&self) -> String {
1410 if !self.authname.is_empty() {
1411 (&self.authname).into()
1412 } else {
1413 (&self.addr).into()
1414 }
1415 }
1416
1417 pub fn get_name_n_addr(&self) -> String {
1428 if !self.name.is_empty() {
1429 format!("{} ({})", self.name, self.addr)
1430 } else if !self.authname.is_empty() {
1431 format!("{} ({})", self.authname, self.addr)
1432 } else {
1433 (&self.addr).into()
1434 }
1435 }
1436
1437 pub async fn get_profile_image(&self, context: &Context) -> Result<Option<PathBuf>> {
1441 if self.id == ContactId::SELF {
1442 if let Some(p) = context.get_config(Config::Selfavatar).await? {
1443 return Ok(Some(PathBuf::from(p))); }
1445 } else if let Some(image_rel) = self.param.get(Param::ProfileImage) {
1446 if !image_rel.is_empty() {
1447 return Ok(Some(get_abs_path(context, Path::new(image_rel))));
1448 }
1449 }
1450 Ok(None)
1451 }
1452
1453 pub fn get_color(&self) -> u32 {
1458 str_to_color(&self.addr.to_lowercase())
1459 }
1460
1461 pub fn get_status(&self) -> &str {
1465 self.status.as_str()
1466 }
1467
1468 pub async fn e2ee_avail(&self, context: &Context) -> Result<bool> {
1470 if self.id == ContactId::SELF {
1471 return Ok(true);
1472 }
1473 let Some(peerstate) = Peerstate::from_addr(context, &self.addr).await? else {
1474 return Ok(false);
1475 };
1476 Ok(peerstate.peek_key(false).is_some())
1477 }
1478
1479 pub async fn is_verified(&self, context: &Context) -> Result<bool> {
1494 if self.id == ContactId::SELF {
1497 return Ok(true);
1498 }
1499
1500 let Some(peerstate) = Peerstate::from_addr(context, &self.addr).await? else {
1501 return Ok(false);
1502 };
1503
1504 let forward_verified = peerstate.is_using_verified_key();
1505 let backward_verified = peerstate.is_backward_verified(context).await?;
1506 Ok(forward_verified && backward_verified)
1507 }
1508
1509 pub async fn is_forward_verified(&self, context: &Context) -> Result<bool> {
1515 if self.id == ContactId::SELF {
1516 return Ok(true);
1517 }
1518
1519 let Some(peerstate) = Peerstate::from_addr(context, &self.addr).await? else {
1520 return Ok(false);
1521 };
1522
1523 Ok(peerstate.is_using_verified_key())
1524 }
1525
1526 pub async fn get_verifier_id(&self, context: &Context) -> Result<Option<ContactId>> {
1539 let Some(verifier_addr) = Peerstate::from_addr(context, self.get_addr())
1540 .await?
1541 .and_then(|peerstate| peerstate.get_verifier().map(|addr| addr.to_owned()))
1542 else {
1543 return Ok(None);
1544 };
1545
1546 if addr_cmp(&verifier_addr, &self.addr) {
1547 return Ok(Some(ContactId::SELF));
1549 }
1550
1551 match Contact::lookup_id_by_addr(context, &verifier_addr, Origin::Unknown).await? {
1552 Some(contact_id) => Ok(Some(contact_id)),
1553 None => {
1554 let addr = &self.addr;
1555 warn!(context, "Could not lookup contact with address {verifier_addr} which introduced {addr}.");
1556 Ok(None)
1557 }
1558 }
1559 }
1560
1561 pub async fn is_profile_verified(&self, context: &Context) -> Result<bool> {
1572 let contact_id = self.id;
1573
1574 if let Some(ChatIdBlocked { id: chat_id, .. }) =
1575 ChatIdBlocked::lookup_by_contact(context, contact_id).await?
1576 {
1577 Ok(chat_id.is_protected(context).await? == ProtectionStatus::Protected)
1578 } else {
1579 Ok(self.is_verified(context).await?)
1581 }
1582 }
1583
1584 pub async fn get_real_cnt(context: &Context) -> Result<usize> {
1586 if !context.sql.is_open().await {
1587 return Ok(0);
1588 }
1589
1590 let count = context
1591 .sql
1592 .count(
1593 "SELECT COUNT(*) FROM contacts WHERE id>?;",
1594 (ContactId::LAST_SPECIAL,),
1595 )
1596 .await?;
1597 Ok(count)
1598 }
1599
1600 pub async fn real_exists_by_id(context: &Context, contact_id: ContactId) -> Result<bool> {
1602 if contact_id.is_special() {
1603 return Ok(false);
1604 }
1605
1606 let exists = context
1607 .sql
1608 .exists("SELECT COUNT(*) FROM contacts WHERE id=?;", (contact_id,))
1609 .await?;
1610 Ok(exists)
1611 }
1612}
1613
1614fn update_chat_names(
1618 context: &Context,
1619 transaction: &rusqlite::Connection,
1620 contact_id: ContactId,
1621) -> Result<()> {
1622 let chat_id: Option<ChatId> = transaction.query_row(
1623 "SELECT id FROM chats WHERE type=? AND id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?)",
1624 (Chattype::Single, contact_id),
1625 |row| {
1626 let chat_id: ChatId = row.get(0)?;
1627 Ok(chat_id)
1628 }
1629 ).optional()?;
1630
1631 if let Some(chat_id) = chat_id {
1632 let (addr, name, authname) = transaction.query_row(
1633 "SELECT addr, name, authname
1634 FROM contacts
1635 WHERE id=?",
1636 (contact_id,),
1637 |row| {
1638 let addr: String = row.get(0)?;
1639 let name: String = row.get(1)?;
1640 let authname: String = row.get(2)?;
1641 Ok((addr, name, authname))
1642 },
1643 )?;
1644
1645 let chat_name = if !name.is_empty() {
1646 name
1647 } else if !authname.is_empty() {
1648 authname
1649 } else {
1650 addr
1651 };
1652
1653 let count = transaction.execute(
1654 "UPDATE chats SET name=?1 WHERE id=?2 AND name!=?1",
1655 (chat_name, chat_id),
1656 )?;
1657
1658 if count > 0 {
1659 context.emit_event(EventType::ChatModified(chat_id));
1661 chatlist_events::emit_chatlist_items_changed_for_contact(context, contact_id);
1662 }
1663 }
1664
1665 Ok(())
1666}
1667
1668pub(crate) async fn set_blocked(
1669 context: &Context,
1670 sync: sync::Sync,
1671 contact_id: ContactId,
1672 new_blocking: bool,
1673) -> Result<()> {
1674 ensure!(
1675 !contact_id.is_special(),
1676 "Can't block special contact {}",
1677 contact_id
1678 );
1679 let contact = Contact::get_by_id(context, contact_id).await?;
1680
1681 if contact.blocked != new_blocking {
1682 context
1683 .sql
1684 .execute(
1685 "UPDATE contacts SET blocked=? WHERE id=?;",
1686 (i32::from(new_blocking), contact_id),
1687 )
1688 .await?;
1689
1690 if context
1696 .sql
1697 .execute(
1698 r#"
1699UPDATE chats
1700SET blocked=?
1701WHERE type=? AND id IN (
1702 SELECT chat_id FROM chats_contacts WHERE contact_id=?
1703);
1704"#,
1705 (new_blocking, Chattype::Single, contact_id),
1706 )
1707 .await
1708 .is_ok()
1709 {
1710 Contact::mark_noticed(context, contact_id).await?;
1711 context.emit_event(EventType::ContactsChanged(Some(contact_id)));
1712 }
1713
1714 if !new_blocking && contact.origin == Origin::MailinglistAddress {
1717 if let Some((chat_id, _, _)) =
1718 chat::get_chat_id_by_grpid(context, &contact.addr).await?
1719 {
1720 chat_id.unblock_ex(context, Nosync).await?;
1721 }
1722 }
1723
1724 if sync.into() {
1725 let action = match new_blocking {
1726 true => chat::SyncAction::Block,
1727 false => chat::SyncAction::Unblock,
1728 };
1729 chat::sync(
1730 context,
1731 chat::SyncId::ContactAddr(contact.addr.clone()),
1732 action,
1733 )
1734 .await
1735 .log_err(context)
1736 .ok();
1737 }
1738 }
1739
1740 chatlist_events::emit_chatlist_changed(context);
1741 Ok(())
1742}
1743
1744pub(crate) async fn set_profile_image(
1752 context: &Context,
1753 contact_id: ContactId,
1754 profile_image: &AvatarAction,
1755 was_encrypted: bool,
1756) -> Result<()> {
1757 let mut contact = Contact::get_by_id(context, contact_id).await?;
1758 let changed = match profile_image {
1759 AvatarAction::Change(profile_image) => {
1760 if contact_id == ContactId::SELF {
1761 if was_encrypted {
1762 context
1763 .set_config_ex(Nosync, Config::Selfavatar, Some(profile_image))
1764 .await?;
1765 } else {
1766 info!(context, "Do not use unencrypted selfavatar.");
1767 }
1768 } else {
1769 contact.param.set(Param::ProfileImage, profile_image);
1770 }
1771 true
1772 }
1773 AvatarAction::Delete => {
1774 if contact_id == ContactId::SELF {
1775 if was_encrypted {
1776 context
1777 .set_config_ex(Nosync, Config::Selfavatar, None)
1778 .await?;
1779 } else {
1780 info!(context, "Do not use unencrypted selfavatar deletion.");
1781 }
1782 } else {
1783 contact.param.remove(Param::ProfileImage);
1784 }
1785 true
1786 }
1787 };
1788 if changed {
1789 contact.update_param(context).await?;
1790 context.emit_event(EventType::ContactsChanged(Some(contact_id)));
1791 chatlist_events::emit_chatlist_item_changed_for_contact_chat(context, contact_id).await;
1792 }
1793 Ok(())
1794}
1795
1796pub(crate) async fn set_status(
1802 context: &Context,
1803 contact_id: ContactId,
1804 status: String,
1805 encrypted: bool,
1806 has_chat_version: bool,
1807) -> Result<()> {
1808 if contact_id == ContactId::SELF {
1809 if encrypted && has_chat_version {
1810 context
1811 .set_config_ex(Nosync, Config::Selfstatus, Some(&status))
1812 .await?;
1813 }
1814 } else {
1815 let mut contact = Contact::get_by_id(context, contact_id).await?;
1816
1817 if contact.status != status {
1818 contact.status = status;
1819 contact.update_status(context).await?;
1820 context.emit_event(EventType::ContactsChanged(Some(contact_id)));
1821 }
1822 }
1823 Ok(())
1824}
1825
1826pub(crate) async fn update_last_seen(
1828 context: &Context,
1829 contact_id: ContactId,
1830 timestamp: i64,
1831) -> Result<()> {
1832 ensure!(
1833 !contact_id.is_special(),
1834 "Can not update special contact last seen timestamp"
1835 );
1836
1837 if context
1838 .sql
1839 .execute(
1840 "UPDATE contacts SET last_seen = ?1 WHERE last_seen < ?1 AND id = ?2",
1841 (timestamp, contact_id),
1842 )
1843 .await?
1844 > 0
1845 && timestamp > time() - SEEN_RECENTLY_SECONDS
1846 {
1847 context.emit_event(EventType::ContactsChanged(Some(contact_id)));
1848 context
1849 .scheduler
1850 .interrupt_recently_seen(contact_id, timestamp)
1851 .await;
1852 }
1853 Ok(())
1854}
1855
1856fn cat_fingerprint(
1857 ret: &mut String,
1858 name: &str,
1859 addr: &str,
1860 fingerprint_verified: &str,
1861 fingerprint_unverified: &str,
1862) {
1863 *ret += &format!(
1864 "\n\n{} ({}):\n{}",
1865 name,
1866 addr,
1867 if !fingerprint_verified.is_empty() {
1868 fingerprint_verified
1869 } else {
1870 fingerprint_unverified
1871 },
1872 );
1873 if !fingerprint_verified.is_empty()
1874 && !fingerprint_unverified.is_empty()
1875 && fingerprint_verified != fingerprint_unverified
1876 {
1877 *ret += &format!("\n\n{name} (alternative):\n{fingerprint_unverified}");
1878 }
1879}
1880
1881fn split_address_book(book: &str) -> Vec<(&str, &str)> {
1882 book.lines()
1883 .collect::<Vec<&str>>()
1884 .chunks(2)
1885 .filter_map(|chunk| {
1886 let name = chunk.first()?;
1887 let addr = chunk.get(1)?;
1888 Some((*name, *addr))
1889 })
1890 .collect()
1891}
1892
1893#[derive(Debug)]
1894pub(crate) struct RecentlySeenInterrupt {
1895 contact_id: ContactId,
1896 timestamp: i64,
1897}
1898
1899#[derive(Debug)]
1900pub(crate) struct RecentlySeenLoop {
1901 handle: task::JoinHandle<()>,
1903
1904 interrupt_send: Sender<RecentlySeenInterrupt>,
1905}
1906
1907impl RecentlySeenLoop {
1908 pub(crate) fn new(context: Context) -> Self {
1909 let (interrupt_send, interrupt_recv) = channel::bounded(1);
1910
1911 let handle = task::spawn(Self::run(context, interrupt_recv));
1912 Self {
1913 handle,
1914 interrupt_send,
1915 }
1916 }
1917
1918 async fn run(context: Context, interrupt: Receiver<RecentlySeenInterrupt>) {
1919 type MyHeapElem = (Reverse<i64>, ContactId);
1920
1921 let now = SystemTime::now();
1922 let now_ts = now
1923 .duration_since(SystemTime::UNIX_EPOCH)
1924 .unwrap_or_default()
1925 .as_secs() as i64;
1926
1927 let mut unseen_queue: BinaryHeap<MyHeapElem> = context
1933 .sql
1934 .query_map(
1935 "SELECT id, last_seen FROM contacts
1936 WHERE last_seen > ?",
1937 (now_ts - SEEN_RECENTLY_SECONDS,),
1938 |row| {
1939 let contact_id: ContactId = row.get("id")?;
1940 let last_seen: i64 = row.get("last_seen")?;
1941 Ok((Reverse(last_seen + SEEN_RECENTLY_SECONDS), contact_id))
1942 },
1943 |rows| {
1944 rows.collect::<std::result::Result<BinaryHeap<MyHeapElem>, _>>()
1945 .map_err(Into::into)
1946 },
1947 )
1948 .await
1949 .unwrap_or_default();
1950
1951 loop {
1952 let now = SystemTime::now();
1953 let (until, contact_id) =
1954 if let Some((Reverse(timestamp), contact_id)) = unseen_queue.peek() {
1955 (
1956 UNIX_EPOCH
1957 + Duration::from_secs((*timestamp).try_into().unwrap_or(u64::MAX))
1958 + Duration::from_secs(1),
1959 Some(contact_id),
1960 )
1961 } else {
1962 (now + Duration::from_secs(86400), None)
1964 };
1965
1966 if let Ok(duration) = until.duration_since(now) {
1967 info!(
1968 context,
1969 "Recently seen loop waiting for {} or interrupt",
1970 duration_to_str(duration)
1971 );
1972
1973 match timeout(duration, interrupt.recv()).await {
1974 Err(_) => {
1975 if let Some(contact_id) = contact_id {
1977 context.emit_event(EventType::ContactsChanged(Some(*contact_id)));
1978 chatlist_events::emit_chatlist_item_changed_for_contact_chat(
1979 &context,
1980 *contact_id,
1981 )
1982 .await;
1983 unseen_queue.pop();
1984 }
1985 }
1986 Ok(Err(err)) => {
1987 warn!(
1988 context,
1989 "Error receiving an interruption in recently seen loop: {}", err
1990 );
1991 return;
1994 }
1995 Ok(Ok(RecentlySeenInterrupt {
1996 contact_id,
1997 timestamp,
1998 })) => {
1999 if contact_id != ContactId::UNDEFINED {
2001 unseen_queue
2002 .push((Reverse(timestamp + SEEN_RECENTLY_SECONDS), contact_id));
2003 }
2004 }
2005 }
2006 } else {
2007 info!(
2008 context,
2009 "Recently seen loop is not waiting, event is already due."
2010 );
2011
2012 if let Some(contact_id) = contact_id {
2014 context.emit_event(EventType::ContactsChanged(Some(*contact_id)));
2015 chatlist_events::emit_chatlist_item_changed_for_contact_chat(
2016 &context,
2017 *contact_id,
2018 )
2019 .await;
2020 }
2021 unseen_queue.pop();
2022 }
2023 }
2024 }
2025
2026 pub(crate) fn try_interrupt(&self, contact_id: ContactId, timestamp: i64) {
2027 self.interrupt_send
2028 .try_send(RecentlySeenInterrupt {
2029 contact_id,
2030 timestamp,
2031 })
2032 .ok();
2033 }
2034
2035 #[cfg(test)]
2036 pub(crate) async fn interrupt(&self, contact_id: ContactId, timestamp: i64) {
2037 self.interrupt_send
2038 .send(RecentlySeenInterrupt {
2039 contact_id,
2040 timestamp,
2041 })
2042 .await
2043 .unwrap();
2044 }
2045
2046 pub(crate) async fn abort(self) {
2047 self.handle.abort();
2048
2049 self.handle.await.ok();
2053 }
2054}
2055
2056#[cfg(test)]
2057mod contact_tests;