1use std::cmp::Reverse;
4use std::collections::{BinaryHeap, HashSet};
5use std::fmt;
6use std::path::{Path, PathBuf};
7use std::time::UNIX_EPOCH;
8
9use anyhow::{Context as _, Result, bail, ensure};
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, ContactAddress, VcardContact, addr_normalize, sanitize_name,
15 sanitize_name_and_addr,
16};
17use deltachat_derive::{FromSql, ToSql};
18use rusqlite::OptionalExtension;
19use serde::{Deserialize, Serialize};
20use tokio::task;
21use tokio::time::{Duration, timeout};
22
23use crate::blob::BlobObject;
24use crate::chat::ChatId;
25use crate::color::str_to_color;
26use crate::config::Config;
27use crate::constants::{self, Blocked, Chattype};
28use crate::context::Context;
29use crate::events::EventType;
30use crate::key::{
31 DcKey, Fingerprint, SignedPublicKey, load_self_public_key, self_fingerprint,
32 self_fingerprint_opt,
33};
34use crate::log::{LogExt, info, warn};
35use crate::message::MessageState;
36use crate::mimeparser::AvatarAction;
37use crate::param::{Param, Params};
38use crate::sync::{self, Sync::*};
39use crate::tools::{SystemTime, duration_to_str, get_abs_path, time};
40use crate::{chat, chatlist_events, ensure_and_debug_assert_ne, stock_str};
41
42const SEEN_RECENTLY_SECONDS: i64 = 600;
44
45#[derive(
50 Debug, Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
51)]
52pub struct ContactId(u32);
53
54impl ContactId {
55 pub const UNDEFINED: ContactId = ContactId::new(0);
57
58 pub const SELF: ContactId = ContactId::new(1);
62
63 pub const INFO: ContactId = ContactId::new(2);
65
66 pub const DEVICE: ContactId = ContactId::new(5);
68 pub(crate) const LAST_SPECIAL: ContactId = ContactId::new(9);
69
70 pub const DEVICE_ADDR: &'static str = "device@localhost";
74
75 pub const fn new(id: u32) -> ContactId {
77 ContactId(id)
78 }
79
80 pub fn is_special(&self) -> bool {
87 self.0 <= Self::LAST_SPECIAL.0
88 }
89
90 pub const fn to_u32(&self) -> u32 {
96 self.0
97 }
98
99 pub async fn set_name(self, context: &Context, name: &str) -> Result<()> {
106 self.set_name_ex(context, Sync, name).await
107 }
108
109 pub(crate) async fn set_name_ex(
110 self,
111 context: &Context,
112 sync: sync::Sync,
113 name: &str,
114 ) -> Result<()> {
115 let row = context
116 .sql
117 .transaction(|transaction| {
118 let is_changed = transaction.execute(
119 "UPDATE contacts SET name=?1 WHERE id=?2 AND name!=?1",
120 (name, self),
121 )? > 0;
122 if is_changed {
123 update_chat_names(context, transaction, self)?;
124 let (addr, fingerprint) = transaction.query_row(
125 "SELECT addr, fingerprint FROM contacts WHERE id=?",
126 (self,),
127 |row| {
128 let addr: String = row.get(0)?;
129 let fingerprint: String = row.get(1)?;
130 Ok((addr, fingerprint))
131 },
132 )?;
133 context.emit_event(EventType::ContactsChanged(Some(self)));
134 Ok(Some((addr, fingerprint)))
135 } else {
136 Ok(None)
137 }
138 })
139 .await?;
140
141 if sync.into() {
142 if let Some((addr, fingerprint)) = row {
143 if fingerprint.is_empty() {
144 chat::sync(
145 context,
146 chat::SyncId::ContactAddr(addr),
147 chat::SyncAction::Rename(name.to_string()),
148 )
149 .await
150 .log_err(context)
151 .ok();
152 } else {
153 chat::sync(
154 context,
155 chat::SyncId::ContactFingerprint(fingerprint),
156 chat::SyncAction::Rename(name.to_string()),
157 )
158 .await
159 .log_err(context)
160 .ok();
161 }
162 }
163 }
164 Ok(())
165 }
166
167 pub(crate) async fn mark_bot(&self, context: &Context, is_bot: bool) -> Result<()> {
169 context
170 .sql
171 .execute("UPDATE contacts SET is_bot=? WHERE id=?;", (is_bot, self.0))
172 .await?;
173 Ok(())
174 }
175
176 pub(crate) async fn regossip_keys(&self, context: &Context) -> Result<()> {
178 context
179 .sql
180 .execute(
181 "UPDATE chats
182 SET gossiped_timestamp=0
183 WHERE EXISTS (SELECT 1 FROM chats_contacts
184 WHERE chats_contacts.chat_id=chats.id
185 AND chats_contacts.contact_id=?
186 AND chats_contacts.add_timestamp >= chats_contacts.remove_timestamp)",
187 (self,),
188 )
189 .await?;
190 Ok(())
191 }
192
193 pub(crate) async fn scaleup_origin(
195 context: &Context,
196 ids: &[Self],
197 origin: Origin,
198 ) -> Result<()> {
199 context
200 .sql
201 .transaction(|transaction| {
202 let mut stmt = transaction
203 .prepare("UPDATE contacts SET origin=?1 WHERE id = ?2 AND origin < ?1")?;
204 for id in ids {
205 stmt.execute((origin, id))?;
206 }
207 Ok(())
208 })
209 .await?;
210 Ok(())
211 }
212
213 pub async fn addr(&self, context: &Context) -> Result<String> {
215 let addr = context
216 .sql
217 .query_row("SELECT addr FROM contacts WHERE id=?", (self,), |row| {
218 let addr: String = row.get(0)?;
219 Ok(addr)
220 })
221 .await?;
222 Ok(addr)
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 = c.public_key(context).await?.map(|k| k.to_base64());
271 let profile_image = match c.get_profile_image_ex(context, false).await? {
272 None => None,
273 Some(path) => tokio::fs::read(path)
274 .await
275 .log_err(context)
276 .ok()
277 .map(|data| base64::engine::general_purpose::STANDARD.encode(data)),
278 };
279 vcard_contacts.push(VcardContact {
280 addr: c.addr,
281 authname: c.authname,
282 key,
283 profile_image,
284 biography: Some(c.status).filter(|s| !s.is_empty()),
285 timestamp: Ok(now),
287 });
288 }
289
290 Ok(contact_tools::make_vcard(&vcard_contacts)
297 .trim_end()
298 .to_string())
299}
300
301pub async fn import_vcard(context: &Context, vcard: &str) -> Result<Vec<ContactId>> {
306 let contacts = contact_tools::parse_vcard(vcard);
307 let mut contact_ids = Vec::with_capacity(contacts.len());
308 for c in &contacts {
309 let Ok(id) = import_vcard_contact(context, c)
310 .await
311 .with_context(|| format!("import_vcard_contact() failed for {}", c.addr))
312 .log_err(context)
313 else {
314 continue;
315 };
316 contact_ids.push(id);
317 }
318 Ok(contact_ids)
319}
320
321async fn import_vcard_contact(context: &Context, contact: &VcardContact) -> Result<ContactId> {
322 let addr = ContactAddress::new(&contact.addr).context("Invalid address")?;
323 let origin = Origin::CreateChat;
327 let key = contact.key.as_ref().and_then(|k| {
328 SignedPublicKey::from_base64(k)
329 .with_context(|| {
330 format!(
331 "import_vcard_contact: Cannot decode key for {}",
332 contact.addr
333 )
334 })
335 .log_err(context)
336 .ok()
337 });
338
339 let fingerprint;
340 if let Some(public_key) = key {
341 fingerprint = public_key.dc_fingerprint().hex();
342
343 context
344 .sql
345 .execute(
346 "INSERT INTO public_keys (fingerprint, public_key)
347 VALUES (?, ?)
348 ON CONFLICT (fingerprint)
349 DO NOTHING",
350 (&fingerprint, public_key.to_bytes()),
351 )
352 .await?;
353 } else {
354 fingerprint = String::new();
355 }
356
357 let (id, modified) =
358 match Contact::add_or_lookup_ex(context, &contact.authname, &addr, &fingerprint, origin)
359 .await
360 {
361 Err(e) => return Err(e).context("Contact::add_or_lookup() failed"),
362 Ok((ContactId::SELF, _)) => return Ok(ContactId::SELF),
363 Ok(val) => val,
364 };
365 if modified != Modifier::None {
366 context.emit_event(EventType::ContactsChanged(Some(id)));
367 }
368 if modified != Modifier::Created {
369 return Ok(id);
370 }
371 let path = match &contact.profile_image {
372 Some(image) => match BlobObject::store_from_base64(context, image) {
373 Err(e) => {
374 warn!(
375 context,
376 "import_vcard_contact: Could not decode and save avatar for {}: {e:#}.",
377 contact.addr
378 );
379 None
380 }
381 Ok(path) => Some(path),
382 },
383 None => None,
384 };
385 if let Some(path) = path {
386 let was_encrypted = false;
388 if let Err(e) =
389 set_profile_image(context, id, &AvatarAction::Change(path), was_encrypted).await
390 {
391 warn!(
392 context,
393 "import_vcard_contact: Could not set avatar for {}: {e:#}.", contact.addr
394 );
395 }
396 }
397 if let Some(biography) = &contact.biography {
398 if let Err(e) = set_status(context, id, biography.to_owned(), false, false).await {
399 warn!(
400 context,
401 "import_vcard_contact: Could not set biography for {}: {e:#}.", contact.addr
402 );
403 }
404 }
405 Ok(id)
406}
407
408#[derive(Debug)]
421pub struct Contact {
422 pub id: ContactId,
424
425 name: String,
429
430 authname: String,
434
435 addr: String,
437
438 fingerprint: Option<String>,
442
443 pub blocked: bool,
445
446 last_seen: i64,
448
449 pub origin: Origin,
451
452 pub param: Params,
454
455 status: String,
457
458 is_bot: bool,
460}
461
462#[derive(
464 Debug,
465 Default,
466 Clone,
467 Copy,
468 PartialEq,
469 Eq,
470 PartialOrd,
471 Ord,
472 FromPrimitive,
473 ToPrimitive,
474 FromSql,
475 ToSql,
476)]
477#[repr(u32)]
478pub enum Origin {
479 #[default]
482 Unknown = 0,
483
484 MailinglistAddress = 0x2,
486
487 Hidden = 0x8,
489
490 IncomingUnknownFrom = 0x10,
492
493 IncomingUnknownCc = 0x20,
495
496 IncomingUnknownTo = 0x40,
498
499 UnhandledQrScan = 0x80,
501
502 UnhandledSecurejoinQrScan = 0x81,
504
505 IncomingReplyTo = 0x100,
508
509 IncomingCc = 0x200,
511
512 IncomingTo = 0x400,
514
515 CreateChat = 0x800,
517
518 OutgoingBcc = 0x1000,
520
521 OutgoingCc = 0x2000,
523
524 OutgoingTo = 0x4000,
526
527 Internal = 0x40000,
529
530 AddressBook = 0x80000,
532
533 SecurejoinInvited = 0x0100_0000,
535
536 SecurejoinJoined = 0x0200_0000,
542
543 ManuallyCreated = 0x0400_0000,
545}
546
547impl Origin {
548 pub fn is_known(self) -> bool {
552 self >= Origin::IncomingReplyTo
553 }
554}
555
556#[derive(Debug, PartialEq, Eq, Clone, Copy)]
557pub(crate) enum Modifier {
558 None,
559 Modified,
560 Created,
561}
562
563impl Contact {
564 pub async fn get_by_id(context: &Context, contact_id: ContactId) -> Result<Self> {
575 let contact = Self::get_by_id_optional(context, contact_id)
576 .await?
577 .with_context(|| format!("contact {contact_id} not found"))?;
578 Ok(contact)
579 }
580
581 pub async fn get_by_id_optional(
585 context: &Context,
586 contact_id: ContactId,
587 ) -> Result<Option<Self>> {
588 if let Some(mut contact) = context
589 .sql
590 .query_row_optional(
591 "SELECT c.name, c.addr, c.origin, c.blocked, c.last_seen,
592 c.authname, c.param, c.status, c.is_bot, c.fingerprint
593 FROM contacts c
594 WHERE c.id=?;",
595 (contact_id,),
596 |row| {
597 let name: String = row.get(0)?;
598 let addr: String = row.get(1)?;
599 let origin: Origin = row.get(2)?;
600 let blocked: Option<bool> = row.get(3)?;
601 let last_seen: i64 = row.get(4)?;
602 let authname: String = row.get(5)?;
603 let param: String = row.get(6)?;
604 let status: Option<String> = row.get(7)?;
605 let is_bot: bool = row.get(8)?;
606 let fingerprint: Option<String> =
607 Some(row.get(9)?).filter(|s: &String| !s.is_empty());
608 let contact = Self {
609 id: contact_id,
610 name,
611 authname,
612 addr,
613 fingerprint,
614 blocked: blocked.unwrap_or_default(),
615 last_seen,
616 origin,
617 param: param.parse().unwrap_or_default(),
618 status: status.unwrap_or_default(),
619 is_bot,
620 };
621 Ok(contact)
622 },
623 )
624 .await?
625 {
626 if contact_id == ContactId::SELF {
627 contact.name = stock_str::self_msg(context).await;
628 contact.authname = context
629 .get_config(Config::Displayname)
630 .await?
631 .unwrap_or_default();
632 contact.addr = context
633 .get_config(Config::ConfiguredAddr)
634 .await?
635 .unwrap_or_default();
636 if let Some(self_fp) = self_fingerprint_opt(context).await? {
637 contact.fingerprint = Some(self_fp.to_string());
638 }
639 contact.status = context
640 .get_config(Config::Selfstatus)
641 .await?
642 .unwrap_or_default();
643 } else if contact_id == ContactId::DEVICE {
644 contact.name = stock_str::device_messages(context).await;
645 contact.addr = ContactId::DEVICE_ADDR.to_string();
646 contact.status = stock_str::device_messages_hint(context).await;
647 }
648 Ok(Some(contact))
649 } else {
650 Ok(None)
651 }
652 }
653
654 pub fn is_blocked(&self) -> bool {
656 self.blocked
657 }
658
659 pub fn last_seen(&self) -> i64 {
661 self.last_seen
662 }
663
664 pub fn was_seen_recently(&self) -> bool {
666 time() - self.last_seen <= SEEN_RECENTLY_SECONDS
667 }
668
669 pub async fn is_blocked_load(context: &Context, id: ContactId) -> Result<bool> {
671 let blocked = context
672 .sql
673 .query_row("SELECT blocked FROM contacts WHERE id=?", (id,), |row| {
674 let blocked: bool = row.get(0)?;
675 Ok(blocked)
676 })
677 .await?;
678 Ok(blocked)
679 }
680
681 pub async fn block(context: &Context, id: ContactId) -> Result<()> {
683 set_blocked(context, Sync, id, true).await
684 }
685
686 pub async fn unblock(context: &Context, id: ContactId) -> Result<()> {
688 set_blocked(context, Sync, id, false).await
689 }
690
691 pub async fn create(context: &Context, name: &str, addr: &str) -> Result<ContactId> {
701 Self::create_ex(context, Sync, name, addr).await
702 }
703
704 pub(crate) async fn create_ex(
705 context: &Context,
706 sync: sync::Sync,
707 name: &str,
708 addr: &str,
709 ) -> Result<ContactId> {
710 let (name, addr) = sanitize_name_and_addr(name, addr);
711 let addr = ContactAddress::new(&addr)?;
712
713 let (contact_id, sth_modified) =
714 Contact::add_or_lookup(context, &name, &addr, Origin::ManuallyCreated)
715 .await
716 .context("add_or_lookup")?;
717 let blocked = Contact::is_blocked_load(context, contact_id).await?;
718 match sth_modified {
719 Modifier::None => {}
720 Modifier::Modified | Modifier::Created => {
721 context.emit_event(EventType::ContactsChanged(Some(contact_id)))
722 }
723 }
724 if blocked {
725 set_blocked(context, Nosync, contact_id, false).await?;
726 }
727
728 if sync.into() && sth_modified != Modifier::None {
729 chat::sync(
730 context,
731 chat::SyncId::ContactAddr(addr.to_string()),
732 chat::SyncAction::Rename(name.to_string()),
733 )
734 .await
735 .log_err(context)
736 .ok();
737 }
738 Ok(contact_id)
739 }
740
741 pub async fn mark_noticed(context: &Context, id: ContactId) -> Result<()> {
743 context
744 .sql
745 .execute(
746 "UPDATE msgs SET state=? WHERE from_id=? AND state=?;",
747 (MessageState::InNoticed, id, MessageState::InFresh),
748 )
749 .await?;
750 Ok(())
751 }
752
753 pub fn is_bot(&self) -> bool {
755 self.is_bot
756 }
757
758 pub async fn lookup_id_by_addr(
780 context: &Context,
781 addr: &str,
782 min_origin: Origin,
783 ) -> Result<Option<ContactId>> {
784 Self::lookup_id_by_addr_ex(context, addr, min_origin, Some(Blocked::Not)).await
785 }
786
787 pub(crate) async fn lookup_id_by_addr_ex(
790 context: &Context,
791 addr: &str,
792 min_origin: Origin,
793 blocked: Option<Blocked>,
794 ) -> Result<Option<ContactId>> {
795 if addr.is_empty() {
796 bail!("lookup_id_by_addr: empty address");
797 }
798
799 let addr_normalized = addr_normalize(addr);
800
801 if context.is_configured().await? && context.is_self_addr(addr).await? {
802 return Ok(Some(ContactId::SELF));
803 }
804
805 let id = context
806 .sql
807 .query_get_value(
808 "SELECT id FROM contacts
809 WHERE addr=?1 COLLATE NOCASE
810 AND id>?2 AND origin>=?3 AND (? OR blocked=?)
811 ORDER BY
812 (
813 SELECT COUNT(*) FROM chats c
814 INNER JOIN chats_contacts cc
815 ON c.id=cc.chat_id
816 WHERE c.type=?
817 AND c.id>?
818 AND c.blocked=?
819 AND cc.contact_id=contacts.id
820 ) DESC,
821 last_seen DESC, fingerprint DESC
822 LIMIT 1",
823 (
824 &addr_normalized,
825 ContactId::LAST_SPECIAL,
826 min_origin as u32,
827 blocked.is_none(),
828 blocked.unwrap_or(Blocked::Not),
829 Chattype::Single,
830 constants::DC_CHAT_ID_LAST_SPECIAL,
831 blocked.unwrap_or(Blocked::Not),
832 ),
833 )
834 .await?;
835 Ok(id)
836 }
837
838 pub(crate) async fn add_or_lookup(
839 context: &Context,
840 name: &str,
841 addr: &ContactAddress,
842 origin: Origin,
843 ) -> Result<(ContactId, Modifier)> {
844 Self::add_or_lookup_ex(context, name, addr, "", origin).await
845 }
846
847 pub(crate) async fn add_or_lookup_ex(
875 context: &Context,
876 name: &str,
877 addr: &str,
878 fingerprint: &str,
879 mut origin: Origin,
880 ) -> Result<(ContactId, Modifier)> {
881 let mut sth_modified = Modifier::None;
882
883 ensure!(
884 !addr.is_empty() || !fingerprint.is_empty(),
885 "Can not add_or_lookup empty address"
886 );
887 ensure!(origin != Origin::Unknown, "Missing valid origin");
888
889 if context.is_configured().await? && context.is_self_addr(addr).await? {
890 return Ok((ContactId::SELF, sth_modified));
891 }
892
893 if !fingerprint.is_empty() && context.is_configured().await? {
894 let fingerprint_self = self_fingerprint(context)
895 .await
896 .context("self_fingerprint")?;
897 if fingerprint == fingerprint_self {
898 return Ok((ContactId::SELF, sth_modified));
899 }
900 }
901
902 let mut name = sanitize_name(name);
903 if origin <= Origin::OutgoingTo {
904 if addr.contains("noreply")
906 || addr.contains("no-reply")
907 || addr.starts_with("notifications@")
908 || (addr.len() > 50 && addr.contains('+'))
910 {
911 info!(context, "hiding contact {}", addr);
912 origin = Origin::Hidden;
913 name = "".to_string();
917 }
918 }
919
920 let manual = matches!(
925 origin,
926 Origin::ManuallyCreated | Origin::AddressBook | Origin::UnhandledQrScan
927 );
928
929 let mut update_addr = false;
930
931 let row_id = context
932 .sql
933 .transaction(|transaction| {
934 let row = transaction
935 .query_row(
936 "SELECT id, name, addr, origin, authname
937 FROM contacts
938 WHERE fingerprint=?1 AND
939 (?1<>'' OR addr=?2 COLLATE NOCASE)",
940 (fingerprint, addr),
941 |row| {
942 let row_id: u32 = row.get(0)?;
943 let row_name: String = row.get(1)?;
944 let row_addr: String = row.get(2)?;
945 let row_origin: Origin = row.get(3)?;
946 let row_authname: String = row.get(4)?;
947
948 Ok((row_id, row_name, row_addr, row_origin, row_authname))
949 },
950 )
951 .optional()?;
952
953 let row_id;
954 if let Some((id, row_name, row_addr, row_origin, row_authname)) = row {
955 let update_name = manual && name != row_name;
956 let update_authname = !manual
957 && name != row_authname
958 && !name.is_empty()
959 && (origin >= row_origin
960 || origin == Origin::IncomingUnknownFrom
961 || row_authname.is_empty());
962
963 row_id = id;
964 if origin >= row_origin && addr != row_addr {
965 update_addr = true;
966 }
967 if update_name || update_authname || update_addr || origin > row_origin {
968 let new_name = if update_name {
969 name.to_string()
970 } else {
971 row_name
972 };
973
974 transaction.execute(
975 "UPDATE contacts SET name=?, addr=?, origin=?, authname=? WHERE id=?;",
976 (
977 new_name,
978 if update_addr {
979 addr.to_string()
980 } else {
981 row_addr
982 },
983 if origin > row_origin {
984 origin
985 } else {
986 row_origin
987 },
988 if update_authname {
989 name.to_string()
990 } else {
991 row_authname
992 },
993 row_id,
994 ),
995 )?;
996
997 if update_name || update_authname {
998 let contact_id = ContactId::new(row_id);
999 update_chat_names(context, transaction, contact_id)?;
1000 }
1001 sth_modified = Modifier::Modified;
1002 }
1003 } else {
1004 let update_name = manual;
1005 let update_authname = !manual;
1006
1007 transaction.execute(
1008 "INSERT INTO contacts (name, addr, fingerprint, origin, authname)
1009 VALUES (?, ?, ?, ?, ?);",
1010 (
1011 if update_name { &name } else { "" },
1012 &addr,
1013 fingerprint,
1014 origin,
1015 if update_authname { &name } else { "" },
1016 ),
1017 )?;
1018
1019 sth_modified = Modifier::Created;
1020 row_id = u32::try_from(transaction.last_insert_rowid())?;
1021 if fingerprint.is_empty() {
1022 info!(context, "Added contact id={row_id} addr={addr}.");
1023 } else {
1024 info!(
1025 context,
1026 "Added contact id={row_id} fpr={fingerprint} addr={addr}."
1027 );
1028 }
1029 }
1030 Ok(row_id)
1031 })
1032 .await?;
1033
1034 let contact_id = ContactId::new(row_id);
1035
1036 Ok((contact_id, sth_modified))
1037 }
1038
1039 pub async fn add_address_book(context: &Context, addr_book: &str) -> Result<usize> {
1057 let mut modify_cnt = 0;
1058
1059 for (name, addr) in split_address_book(addr_book) {
1060 let (name, addr) = sanitize_name_and_addr(name, addr);
1061 match ContactAddress::new(&addr) {
1062 Ok(addr) => {
1063 match Contact::add_or_lookup(context, &name, &addr, Origin::AddressBook).await {
1064 Ok((_, modified)) => {
1065 if modified != Modifier::None {
1066 modify_cnt += 1
1067 }
1068 }
1069 Err(err) => {
1070 warn!(
1071 context,
1072 "Failed to add address {} from address book: {}", addr, err
1073 );
1074 }
1075 }
1076 }
1077 Err(err) => {
1078 warn!(context, "{:#}.", err);
1079 }
1080 }
1081 }
1082 if modify_cnt > 0 {
1083 context.emit_event(EventType::ContactsChanged(None));
1084 }
1085
1086 Ok(modify_cnt)
1087 }
1088
1089 pub async fn get_all(
1099 context: &Context,
1100 listflags: u32,
1101 query: Option<&str>,
1102 ) -> Result<Vec<ContactId>> {
1103 let self_addrs = context
1104 .get_all_self_addrs()
1105 .await?
1106 .into_iter()
1107 .collect::<HashSet<_>>();
1108 let mut add_self = false;
1109 let mut ret = Vec::new();
1110 let flag_add_self = (listflags & constants::DC_GCL_ADD_SELF) != 0;
1111 let flag_address = (listflags & constants::DC_GCL_ADDRESS) != 0;
1112 let minimal_origin = if context.get_config_bool(Config::Bot).await? {
1113 Origin::Unknown
1114 } else {
1115 Origin::IncomingReplyTo
1116 };
1117 if query.is_some() {
1118 let s3str_like_cmd = format!("%{}%", query.unwrap_or(""));
1119 context
1120 .sql
1121 .query_map(
1122 "SELECT c.id, c.addr FROM contacts c
1123 WHERE c.id>?
1124 AND (c.fingerprint='')=?
1125 AND c.origin>=? \
1126 AND c.blocked=0 \
1127 AND (iif(c.name='',c.authname,c.name) LIKE ? OR c.addr LIKE ?) \
1128 ORDER BY c.last_seen DESC, c.id DESC;",
1129 (
1130 ContactId::LAST_SPECIAL,
1131 flag_address,
1132 minimal_origin,
1133 &s3str_like_cmd,
1134 &s3str_like_cmd,
1135 ),
1136 |row| {
1137 let id: ContactId = row.get(0)?;
1138 let addr: String = row.get(1)?;
1139 Ok((id, addr))
1140 },
1141 |rows| {
1142 for row in rows {
1143 let (id, addr) = row?;
1144 if !self_addrs.contains(&addr) {
1145 ret.push(id);
1146 }
1147 }
1148 Ok(())
1149 },
1150 )
1151 .await?;
1152
1153 if let Some(query) = query {
1154 let self_addr = context
1155 .get_config(Config::ConfiguredAddr)
1156 .await?
1157 .unwrap_or_default();
1158 let self_name = context
1159 .get_config(Config::Displayname)
1160 .await?
1161 .unwrap_or_default();
1162 let self_name2 = stock_str::self_msg(context);
1163
1164 if self_addr.contains(query)
1165 || self_name.contains(query)
1166 || self_name2.await.contains(query)
1167 {
1168 add_self = true;
1169 }
1170 } else {
1171 add_self = true;
1172 }
1173 } else {
1174 add_self = true;
1175
1176 context
1177 .sql
1178 .query_map(
1179 "SELECT id, addr FROM contacts
1180 WHERE id>?
1181 AND (fingerprint='')=?
1182 AND origin>=?
1183 AND blocked=0
1184 ORDER BY last_seen DESC, id DESC;",
1185 (ContactId::LAST_SPECIAL, flag_address, minimal_origin),
1186 |row| {
1187 let id: ContactId = row.get(0)?;
1188 let addr: String = row.get(1)?;
1189 Ok((id, addr))
1190 },
1191 |rows| {
1192 for row in rows {
1193 let (id, addr) = row?;
1194 if !self_addrs.contains(&addr) {
1195 ret.push(id);
1196 }
1197 }
1198 Ok(())
1199 },
1200 )
1201 .await?;
1202 }
1203
1204 if flag_add_self && add_self {
1205 ret.push(ContactId::SELF);
1206 }
1207
1208 Ok(ret)
1209 }
1210
1211 async fn update_blocked_mailinglist_contacts(context: &Context) -> Result<()> {
1217 context
1218 .sql
1219 .transaction(move |transaction| {
1220 let mut stmt = transaction.prepare(
1221 "SELECT name, grpid, type FROM chats WHERE (type=? OR type=?) AND blocked=?",
1222 )?;
1223 let rows = stmt.query_map(
1224 (Chattype::Mailinglist, Chattype::InBroadcast, Blocked::Yes),
1225 |row| {
1226 let name: String = row.get(0)?;
1227 let grpid: String = row.get(1)?;
1228 let typ: Chattype = row.get(2)?;
1229 Ok((name, grpid, typ))
1230 },
1231 )?;
1232 let blocked_mailinglists = rows.collect::<std::result::Result<Vec<_>, _>>()?;
1233 for (name, grpid, typ) in blocked_mailinglists {
1234 let count = transaction.query_row(
1235 "SELECT COUNT(id) FROM contacts WHERE addr=?",
1236 [&grpid],
1237 |row| {
1238 let count: isize = row.get(0)?;
1239 Ok(count)
1240 },
1241 )?;
1242 if count == 0 {
1243 transaction.execute("INSERT INTO contacts (addr) VALUES (?)", [&grpid])?;
1244 }
1245
1246 let fingerprint = if typ == Chattype::InBroadcast {
1247 "Blocked_broadcast"
1250 } else {
1251 ""
1252 };
1253 transaction.execute(
1255 "UPDATE contacts SET name=?, origin=?, blocked=1, fingerprint=? WHERE addr=?",
1256 (&name, Origin::MailinglistAddress, fingerprint, &grpid),
1257 )?;
1258 }
1259 Ok(())
1260 })
1261 .await?;
1262 Ok(())
1263 }
1264
1265 pub async fn get_blocked_cnt(context: &Context) -> Result<usize> {
1267 let count = context
1268 .sql
1269 .count(
1270 "SELECT COUNT(*) FROM contacts WHERE id>? AND blocked!=0",
1271 (ContactId::LAST_SPECIAL,),
1272 )
1273 .await?;
1274 Ok(count)
1275 }
1276
1277 pub async fn get_all_blocked(context: &Context) -> Result<Vec<ContactId>> {
1279 Contact::update_blocked_mailinglist_contacts(context)
1280 .await
1281 .context("cannot update blocked mailinglist contacts")?;
1282
1283 let list = context
1284 .sql
1285 .query_map(
1286 "SELECT id FROM contacts WHERE id>? AND blocked!=0 ORDER BY last_seen DESC, id DESC;",
1287 (ContactId::LAST_SPECIAL,),
1288 |row| row.get::<_, ContactId>(0),
1289 |ids| {
1290 ids.collect::<std::result::Result<Vec<_>, _>>()
1291 .map_err(Into::into)
1292 },
1293 )
1294 .await?;
1295 Ok(list)
1296 }
1297
1298 pub async fn get_encrinfo(context: &Context, contact_id: ContactId) -> Result<String> {
1304 ensure!(
1305 !contact_id.is_special(),
1306 "Can not provide encryption info for special contact"
1307 );
1308
1309 let contact = Contact::get_by_id(context, contact_id).await?;
1310 let addr = context
1311 .get_config(Config::ConfiguredAddr)
1312 .await?
1313 .unwrap_or_default();
1314
1315 let Some(fingerprint_other) = contact.fingerprint() else {
1316 return Ok(stock_str::encr_none(context).await);
1317 };
1318 let fingerprint_other = fingerprint_other.to_string();
1319
1320 let stock_message = if contact.public_key(context).await?.is_some() {
1321 stock_str::e2e_available(context).await
1322 } else {
1323 stock_str::encr_none(context).await
1324 };
1325
1326 let finger_prints = stock_str::finger_prints(context).await;
1327 let mut ret = format!("{stock_message}.\n{finger_prints}:");
1328
1329 let fingerprint_self = load_self_public_key(context)
1330 .await?
1331 .dc_fingerprint()
1332 .to_string();
1333 if addr < contact.addr {
1334 cat_fingerprint(
1335 &mut ret,
1336 &stock_str::self_msg(context).await,
1337 &addr,
1338 &fingerprint_self,
1339 );
1340 cat_fingerprint(
1341 &mut ret,
1342 contact.get_display_name(),
1343 &contact.addr,
1344 &fingerprint_other,
1345 );
1346 } else {
1347 cat_fingerprint(
1348 &mut ret,
1349 contact.get_display_name(),
1350 &contact.addr,
1351 &fingerprint_other,
1352 );
1353 cat_fingerprint(
1354 &mut ret,
1355 &stock_str::self_msg(context).await,
1356 &addr,
1357 &fingerprint_self,
1358 );
1359 }
1360
1361 Ok(ret)
1362 }
1363
1364 pub async fn delete(context: &Context, contact_id: ContactId) -> Result<()> {
1370 ensure!(!contact_id.is_special(), "Can not delete special contact");
1371
1372 context
1373 .sql
1374 .transaction(move |transaction| {
1375 let deleted_contacts = transaction.execute(
1378 "DELETE FROM contacts WHERE id=?
1379 AND (SELECT COUNT(*) FROM chats_contacts WHERE contact_id=?)=0;",
1380 (contact_id, contact_id),
1381 )?;
1382 if deleted_contacts == 0 {
1383 transaction.execute(
1384 "UPDATE contacts SET origin=? WHERE id=?;",
1385 (Origin::Hidden, contact_id),
1386 )?;
1387 }
1388 Ok(())
1389 })
1390 .await?;
1391
1392 context.emit_event(EventType::ContactsChanged(None));
1393 Ok(())
1394 }
1395
1396 pub async fn update_param(&self, context: &Context) -> Result<()> {
1398 context
1399 .sql
1400 .execute(
1401 "UPDATE contacts SET param=? WHERE id=?",
1402 (self.param.to_string(), self.id),
1403 )
1404 .await?;
1405 Ok(())
1406 }
1407
1408 pub async fn update_status(&self, context: &Context) -> Result<()> {
1410 context
1411 .sql
1412 .execute(
1413 "UPDATE contacts SET status=? WHERE id=?",
1414 (&self.status, self.id),
1415 )
1416 .await?;
1417 Ok(())
1418 }
1419
1420 pub fn get_id(&self) -> ContactId {
1422 self.id
1423 }
1424
1425 pub fn get_addr(&self) -> &str {
1427 &self.addr
1428 }
1429
1430 pub fn is_key_contact(&self) -> bool {
1433 self.fingerprint.is_some()
1434 }
1435
1436 pub fn fingerprint(&self) -> Option<Fingerprint> {
1440 if let Some(fingerprint) = &self.fingerprint {
1441 fingerprint.parse().ok()
1442 } else {
1443 None
1444 }
1445 }
1446
1447 pub async fn public_key(&self, context: &Context) -> Result<Option<SignedPublicKey>> {
1454 if self.id == ContactId::SELF {
1455 return Ok(Some(load_self_public_key(context).await?));
1456 }
1457
1458 if let Some(fingerprint) = &self.fingerprint {
1459 if let Some(public_key_bytes) = context
1460 .sql
1461 .query_row_optional(
1462 "SELECT public_key
1463 FROM public_keys
1464 WHERE fingerprint=?",
1465 (fingerprint,),
1466 |row| {
1467 let bytes: Vec<u8> = row.get(0)?;
1468 Ok(bytes)
1469 },
1470 )
1471 .await?
1472 {
1473 let public_key = SignedPublicKey::from_slice(&public_key_bytes)?;
1474 Ok(Some(public_key))
1475 } else {
1476 Ok(None)
1477 }
1478 } else {
1479 Ok(None)
1480 }
1481 }
1482
1483 pub fn get_authname(&self) -> &str {
1485 &self.authname
1486 }
1487
1488 pub fn get_name(&self) -> &str {
1494 &self.name
1495 }
1496
1497 pub fn get_display_name(&self) -> &str {
1503 if !self.name.is_empty() {
1504 return &self.name;
1505 }
1506 if !self.authname.is_empty() {
1507 return &self.authname;
1508 }
1509 &self.addr
1510 }
1511
1512 pub(crate) fn get_authname_or_addr(&self) -> String {
1517 if !self.authname.is_empty() {
1518 (&self.authname).into()
1519 } else {
1520 (&self.addr).into()
1521 }
1522 }
1523
1524 pub fn get_name_n_addr(&self) -> String {
1535 if !self.name.is_empty() {
1536 format!("{} ({})", self.name, self.addr)
1537 } else if !self.authname.is_empty() {
1538 format!("{} ({})", self.authname, self.addr)
1539 } else {
1540 (&self.addr).into()
1541 }
1542 }
1543
1544 pub async fn get_profile_image(&self, context: &Context) -> Result<Option<PathBuf>> {
1548 self.get_profile_image_ex(context, true).await
1549 }
1550
1551 async fn get_profile_image_ex(
1555 &self,
1556 context: &Context,
1557 show_fallback_icon: bool,
1558 ) -> Result<Option<PathBuf>> {
1559 if self.id == ContactId::SELF {
1560 if let Some(p) = context.get_config(Config::Selfavatar).await? {
1561 return Ok(Some(PathBuf::from(p))); }
1563 } else if self.id == ContactId::DEVICE {
1564 return Ok(Some(chat::get_device_icon(context).await?));
1565 }
1566 if show_fallback_icon && !self.id.is_special() && !self.is_key_contact() {
1567 return Ok(Some(chat::get_unencrypted_icon(context).await?));
1568 }
1569 if let Some(image_rel) = self.param.get(Param::ProfileImage) {
1570 if !image_rel.is_empty() {
1571 return Ok(Some(get_abs_path(context, Path::new(image_rel))));
1572 }
1573 }
1574 Ok(None)
1575 }
1576
1577 pub fn get_color(&self) -> u32 {
1583 if let Some(fingerprint) = self.fingerprint() {
1584 str_to_color(&fingerprint.hex())
1585 } else {
1586 str_to_color(&self.addr.to_lowercase())
1587 }
1588 }
1589
1590 pub fn get_status(&self) -> &str {
1594 self.status.as_str()
1595 }
1596
1597 pub async fn e2ee_avail(&self, context: &Context) -> Result<bool> {
1599 if self.id == ContactId::SELF {
1600 return Ok(true);
1602 }
1603 Ok(self.public_key(context).await?.is_some())
1604 }
1605
1606 pub async fn is_verified(&self, context: &Context) -> Result<bool> {
1619 if self.id == ContactId::SELF {
1622 return Ok(true);
1623 }
1624
1625 Ok(self.get_verifier_id(context).await?.is_some())
1626 }
1627
1628 pub async fn get_verifier_id(&self, context: &Context) -> Result<Option<Option<ContactId>>> {
1638 let verifier_id: u32 = context
1639 .sql
1640 .query_get_value("SELECT verifier FROM contacts WHERE id=?", (self.id,))
1641 .await?
1642 .with_context(|| format!("Contact {} does not exist", self.id))?;
1643
1644 if verifier_id == 0 {
1645 Ok(None)
1646 } else if verifier_id == self.id.to_u32() {
1647 Ok(Some(None))
1648 } else {
1649 Ok(Some(Some(ContactId::new(verifier_id))))
1650 }
1651 }
1652
1653 pub async fn get_real_cnt(context: &Context) -> Result<usize> {
1655 if !context.sql.is_open().await {
1656 return Ok(0);
1657 }
1658
1659 let count = context
1660 .sql
1661 .count(
1662 "SELECT COUNT(*) FROM contacts WHERE id>?;",
1663 (ContactId::LAST_SPECIAL,),
1664 )
1665 .await?;
1666 Ok(count)
1667 }
1668
1669 pub async fn real_exists_by_id(context: &Context, contact_id: ContactId) -> Result<bool> {
1671 if contact_id.is_special() {
1672 return Ok(false);
1673 }
1674
1675 let exists = context
1676 .sql
1677 .exists("SELECT COUNT(*) FROM contacts WHERE id=?;", (contact_id,))
1678 .await?;
1679 Ok(exists)
1680 }
1681}
1682
1683fn update_chat_names(
1687 context: &Context,
1688 transaction: &rusqlite::Connection,
1689 contact_id: ContactId,
1690) -> Result<()> {
1691 let chat_id: Option<ChatId> = transaction.query_row(
1692 "SELECT id FROM chats WHERE type=? AND id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?)",
1693 (Chattype::Single, contact_id),
1694 |row| {
1695 let chat_id: ChatId = row.get(0)?;
1696 Ok(chat_id)
1697 }
1698 ).optional()?;
1699
1700 if let Some(chat_id) = chat_id {
1701 let (addr, name, authname) = transaction.query_row(
1702 "SELECT addr, name, authname
1703 FROM contacts
1704 WHERE id=?",
1705 (contact_id,),
1706 |row| {
1707 let addr: String = row.get(0)?;
1708 let name: String = row.get(1)?;
1709 let authname: String = row.get(2)?;
1710 Ok((addr, name, authname))
1711 },
1712 )?;
1713
1714 let chat_name = if !name.is_empty() {
1715 name
1716 } else if !authname.is_empty() {
1717 authname
1718 } else {
1719 addr
1720 };
1721
1722 let count = transaction.execute(
1723 "UPDATE chats SET name=?1 WHERE id=?2 AND name!=?1",
1724 (chat_name, chat_id),
1725 )?;
1726
1727 if count > 0 {
1728 context.emit_event(EventType::ChatModified(chat_id));
1730 chatlist_events::emit_chatlist_items_changed_for_contact(context, contact_id);
1731 }
1732 }
1733
1734 Ok(())
1735}
1736
1737pub(crate) async fn set_blocked(
1738 context: &Context,
1739 sync: sync::Sync,
1740 contact_id: ContactId,
1741 new_blocking: bool,
1742) -> Result<()> {
1743 ensure!(
1744 !contact_id.is_special(),
1745 "Can't block special contact {}",
1746 contact_id
1747 );
1748 let contact = Contact::get_by_id(context, contact_id).await?;
1749
1750 if contact.blocked != new_blocking {
1751 context
1752 .sql
1753 .execute(
1754 "UPDATE contacts SET blocked=? WHERE id=?;",
1755 (i32::from(new_blocking), contact_id),
1756 )
1757 .await?;
1758
1759 if context
1765 .sql
1766 .execute(
1767 r#"
1768UPDATE chats
1769SET blocked=?
1770WHERE type=? AND id IN (
1771 SELECT chat_id FROM chats_contacts WHERE contact_id=?
1772);
1773"#,
1774 (new_blocking, Chattype::Single, contact_id),
1775 )
1776 .await
1777 .is_ok()
1778 {
1779 Contact::mark_noticed(context, contact_id).await?;
1780 context.emit_event(EventType::ContactsChanged(Some(contact_id)));
1781 }
1782
1783 if !new_blocking && contact.origin == Origin::MailinglistAddress {
1786 if let Some((chat_id, _, _)) =
1787 chat::get_chat_id_by_grpid(context, &contact.addr).await?
1788 {
1789 chat_id.unblock_ex(context, Nosync).await?;
1790 }
1791 }
1792
1793 if sync.into() {
1794 let action = match new_blocking {
1795 true => chat::SyncAction::Block,
1796 false => chat::SyncAction::Unblock,
1797 };
1798 let sync_id = if let Some(fingerprint) = contact.fingerprint() {
1799 chat::SyncId::ContactFingerprint(fingerprint.hex())
1800 } else {
1801 chat::SyncId::ContactAddr(contact.addr.clone())
1802 };
1803
1804 chat::sync(context, sync_id, action)
1805 .await
1806 .log_err(context)
1807 .ok();
1808 }
1809 }
1810
1811 chatlist_events::emit_chatlist_changed(context);
1812 Ok(())
1813}
1814
1815pub(crate) async fn set_profile_image(
1823 context: &Context,
1824 contact_id: ContactId,
1825 profile_image: &AvatarAction,
1826 was_encrypted: bool,
1827) -> Result<()> {
1828 let mut contact = Contact::get_by_id(context, contact_id).await?;
1829 let changed = match profile_image {
1830 AvatarAction::Change(profile_image) => {
1831 if contact_id == ContactId::SELF {
1832 if was_encrypted {
1833 context
1834 .set_config_ex(Nosync, Config::Selfavatar, Some(profile_image))
1835 .await?;
1836 } else {
1837 info!(context, "Do not use unencrypted selfavatar.");
1838 }
1839 } else {
1840 contact.param.set(Param::ProfileImage, profile_image);
1841 }
1842 true
1843 }
1844 AvatarAction::Delete => {
1845 if contact_id == ContactId::SELF {
1846 if was_encrypted {
1847 context
1848 .set_config_ex(Nosync, Config::Selfavatar, None)
1849 .await?;
1850 } else {
1851 info!(context, "Do not use unencrypted selfavatar deletion.");
1852 }
1853 } else {
1854 contact.param.remove(Param::ProfileImage);
1855 }
1856 true
1857 }
1858 };
1859 if changed {
1860 contact.update_param(context).await?;
1861 context.emit_event(EventType::ContactsChanged(Some(contact_id)));
1862 chatlist_events::emit_chatlist_item_changed_for_contact_chat(context, contact_id).await;
1863 }
1864 Ok(())
1865}
1866
1867pub(crate) async fn set_status(
1873 context: &Context,
1874 contact_id: ContactId,
1875 status: String,
1876 encrypted: bool,
1877 has_chat_version: bool,
1878) -> Result<()> {
1879 if contact_id == ContactId::SELF {
1880 if encrypted && has_chat_version {
1881 context
1882 .set_config_ex(Nosync, Config::Selfstatus, Some(&status))
1883 .await?;
1884 }
1885 } else {
1886 let mut contact = Contact::get_by_id(context, contact_id).await?;
1887
1888 if contact.status != status {
1889 contact.status = status;
1890 contact.update_status(context).await?;
1891 context.emit_event(EventType::ContactsChanged(Some(contact_id)));
1892 }
1893 }
1894 Ok(())
1895}
1896
1897pub(crate) async fn update_last_seen(
1899 context: &Context,
1900 contact_id: ContactId,
1901 timestamp: i64,
1902) -> Result<()> {
1903 ensure!(
1904 !contact_id.is_special(),
1905 "Can not update special contact last seen timestamp"
1906 );
1907
1908 if context
1909 .sql
1910 .execute(
1911 "UPDATE contacts SET last_seen = ?1 WHERE last_seen < ?1 AND id = ?2",
1912 (timestamp, contact_id),
1913 )
1914 .await?
1915 > 0
1916 && timestamp > time() - SEEN_RECENTLY_SECONDS
1917 {
1918 context.emit_event(EventType::ContactsChanged(Some(contact_id)));
1919 context
1920 .scheduler
1921 .interrupt_recently_seen(contact_id, timestamp)
1922 .await;
1923 }
1924 Ok(())
1925}
1926
1927pub(crate) async fn mark_contact_id_as_verified(
1931 context: &Context,
1932 contact_id: ContactId,
1933 verifier_id: Option<ContactId>,
1934) -> Result<()> {
1935 ensure_and_debug_assert_ne!(contact_id, ContactId::SELF,);
1936 ensure_and_debug_assert_ne!(
1937 Some(contact_id),
1938 verifier_id,
1939 "Contact cannot be verified by self",
1940 );
1941 let by_self = verifier_id == Some(ContactId::SELF);
1942 let mut verifier_id = verifier_id.unwrap_or(contact_id);
1943 context
1944 .sql
1945 .transaction(|transaction| {
1946 let contact_fingerprint: String = transaction.query_row(
1947 "SELECT fingerprint FROM contacts WHERE id=?",
1948 (contact_id,),
1949 |row| row.get(0),
1950 )?;
1951 if contact_fingerprint.is_empty() {
1952 bail!("Non-key-contact {contact_id} cannot be verified");
1953 }
1954 if verifier_id != ContactId::SELF {
1955 let (verifier_fingerprint, verifier_verifier_id): (String, ContactId) = transaction
1956 .query_row(
1957 "SELECT fingerprint, verifier FROM contacts WHERE id=?",
1958 (verifier_id,),
1959 |row| Ok((row.get(0)?, row.get(1)?)),
1960 )?;
1961 if verifier_fingerprint.is_empty() {
1962 bail!(
1963 "Contact {contact_id} cannot be verified by non-key-contact {verifier_id}"
1964 );
1965 }
1966 ensure!(
1967 verifier_id == contact_id || verifier_verifier_id != ContactId::UNDEFINED,
1968 "Contact {contact_id} cannot be verified by unverified contact {verifier_id}",
1969 );
1970 if verifier_verifier_id == verifier_id {
1971 verifier_id = contact_id;
1976 }
1977 }
1978 transaction.execute(
1979 "UPDATE contacts SET verifier=?1
1980 WHERE id=?2 AND (verifier=0 OR verifier=id OR ?3)",
1981 (verifier_id, contact_id, by_self),
1982 )?;
1983 Ok(())
1984 })
1985 .await?;
1986 Ok(())
1987}
1988
1989fn cat_fingerprint(ret: &mut String, name: &str, addr: &str, fingerprint: &str) {
1990 *ret += &format!("\n\n{name} ({addr}):\n{fingerprint}");
1991}
1992
1993fn split_address_book(book: &str) -> Vec<(&str, &str)> {
1994 book.lines()
1995 .collect::<Vec<&str>>()
1996 .chunks(2)
1997 .filter_map(|chunk| {
1998 let name = chunk.first()?;
1999 let addr = chunk.get(1)?;
2000 Some((*name, *addr))
2001 })
2002 .collect()
2003}
2004
2005#[derive(Debug)]
2006pub(crate) struct RecentlySeenInterrupt {
2007 contact_id: ContactId,
2008 timestamp: i64,
2009}
2010
2011#[derive(Debug)]
2012pub(crate) struct RecentlySeenLoop {
2013 handle: task::JoinHandle<()>,
2015
2016 interrupt_send: Sender<RecentlySeenInterrupt>,
2017}
2018
2019impl RecentlySeenLoop {
2020 pub(crate) fn new(context: Context) -> Self {
2021 let (interrupt_send, interrupt_recv) = channel::bounded(1);
2022
2023 let handle = task::spawn(Self::run(context, interrupt_recv));
2024 Self {
2025 handle,
2026 interrupt_send,
2027 }
2028 }
2029
2030 async fn run(context: Context, interrupt: Receiver<RecentlySeenInterrupt>) {
2031 type MyHeapElem = (Reverse<i64>, ContactId);
2032
2033 let now = SystemTime::now();
2034 let now_ts = now
2035 .duration_since(SystemTime::UNIX_EPOCH)
2036 .unwrap_or_default()
2037 .as_secs() as i64;
2038
2039 let mut unseen_queue: BinaryHeap<MyHeapElem> = context
2045 .sql
2046 .query_map(
2047 "SELECT id, last_seen FROM contacts
2048 WHERE last_seen > ?",
2049 (now_ts - SEEN_RECENTLY_SECONDS,),
2050 |row| {
2051 let contact_id: ContactId = row.get("id")?;
2052 let last_seen: i64 = row.get("last_seen")?;
2053 Ok((Reverse(last_seen + SEEN_RECENTLY_SECONDS), contact_id))
2054 },
2055 |rows| {
2056 rows.collect::<std::result::Result<BinaryHeap<MyHeapElem>, _>>()
2057 .map_err(Into::into)
2058 },
2059 )
2060 .await
2061 .unwrap_or_default();
2062
2063 loop {
2064 let now = SystemTime::now();
2065 let (until, contact_id) =
2066 if let Some((Reverse(timestamp), contact_id)) = unseen_queue.peek() {
2067 (
2068 UNIX_EPOCH
2069 + Duration::from_secs((*timestamp).try_into().unwrap_or(u64::MAX))
2070 + Duration::from_secs(1),
2071 Some(contact_id),
2072 )
2073 } else {
2074 (now + Duration::from_secs(86400), None)
2076 };
2077
2078 if let Ok(duration) = until.duration_since(now) {
2079 info!(
2080 context,
2081 "Recently seen loop waiting for {} or interrupt",
2082 duration_to_str(duration)
2083 );
2084
2085 match timeout(duration, interrupt.recv()).await {
2086 Err(_) => {
2087 if let Some(contact_id) = contact_id {
2089 context.emit_event(EventType::ContactsChanged(Some(*contact_id)));
2090 chatlist_events::emit_chatlist_item_changed_for_contact_chat(
2091 &context,
2092 *contact_id,
2093 )
2094 .await;
2095 unseen_queue.pop();
2096 }
2097 }
2098 Ok(Err(err)) => {
2099 warn!(
2100 context,
2101 "Error receiving an interruption in recently seen loop: {}", err
2102 );
2103 return;
2106 }
2107 Ok(Ok(RecentlySeenInterrupt {
2108 contact_id,
2109 timestamp,
2110 })) => {
2111 if contact_id != ContactId::UNDEFINED {
2113 unseen_queue
2114 .push((Reverse(timestamp + SEEN_RECENTLY_SECONDS), contact_id));
2115 }
2116 }
2117 }
2118 } else {
2119 info!(
2120 context,
2121 "Recently seen loop is not waiting, event is already due."
2122 );
2123
2124 if let Some(contact_id) = contact_id {
2126 context.emit_event(EventType::ContactsChanged(Some(*contact_id)));
2127 chatlist_events::emit_chatlist_item_changed_for_contact_chat(
2128 &context,
2129 *contact_id,
2130 )
2131 .await;
2132 }
2133 unseen_queue.pop();
2134 }
2135 }
2136 }
2137
2138 pub(crate) fn try_interrupt(&self, contact_id: ContactId, timestamp: i64) {
2139 self.interrupt_send
2140 .try_send(RecentlySeenInterrupt {
2141 contact_id,
2142 timestamp,
2143 })
2144 .ok();
2145 }
2146
2147 #[cfg(test)]
2148 pub(crate) async fn interrupt(&self, contact_id: ContactId, timestamp: i64) {
2149 self.interrupt_send
2150 .send(RecentlySeenInterrupt {
2151 contact_id,
2152 timestamp,
2153 })
2154 .await
2155 .unwrap();
2156 }
2157
2158 pub(crate) async fn abort(self) {
2159 self.handle.abort();
2160
2161 self.handle.await.ok();
2165 }
2166}
2167
2168#[cfg(test)]
2169mod contact_tests;