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, 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, to_lowercase};
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 None => {
374 warn!(
375 context,
376 "import_vcard_contact: Could not decode avatar for {}.", contact.addr
377 );
378 None
379 }
380 Some(path) => Some(path),
381 },
382 None => None,
383 };
384 if let Some(path) = path {
385 let was_encrypted = false;
387 if let Err(e) =
388 set_profile_image(context, id, &AvatarAction::Change(path), was_encrypted).await
389 {
390 warn!(
391 context,
392 "import_vcard_contact: Could not set avatar for {}: {e:#}.", contact.addr
393 );
394 }
395 }
396 if let Some(biography) = &contact.biography {
397 if let Err(e) = set_status(context, id, biography.to_owned(), false, false).await {
398 warn!(
399 context,
400 "import_vcard_contact: Could not set biography for {}: {e:#}.", contact.addr
401 );
402 }
403 }
404 Ok(id)
405}
406
407#[derive(Debug)]
420pub struct Contact {
421 pub id: ContactId,
423
424 name: String,
428
429 authname: String,
433
434 addr: String,
436
437 fingerprint: Option<String>,
441
442 pub blocked: bool,
444
445 last_seen: i64,
447
448 pub origin: Origin,
450
451 pub param: Params,
453
454 status: String,
456
457 is_bot: bool,
459}
460
461#[derive(
463 Debug,
464 Default,
465 Clone,
466 Copy,
467 PartialEq,
468 Eq,
469 PartialOrd,
470 Ord,
471 FromPrimitive,
472 ToPrimitive,
473 FromSql,
474 ToSql,
475)]
476#[repr(u32)]
477pub enum Origin {
478 #[default]
481 Unknown = 0,
482
483 MailinglistAddress = 0x2,
485
486 Hidden = 0x8,
488
489 IncomingUnknownFrom = 0x10,
491
492 IncomingUnknownCc = 0x20,
494
495 IncomingUnknownTo = 0x40,
497
498 UnhandledQrScan = 0x80,
500
501 UnhandledSecurejoinQrScan = 0x81,
503
504 IncomingReplyTo = 0x100,
507
508 IncomingCc = 0x200,
510
511 IncomingTo = 0x400,
513
514 CreateChat = 0x800,
516
517 OutgoingBcc = 0x1000,
519
520 OutgoingCc = 0x2000,
522
523 OutgoingTo = 0x4000,
525
526 Internal = 0x40000,
528
529 AddressBook = 0x80000,
531
532 SecurejoinInvited = 0x0100_0000,
534
535 SecurejoinJoined = 0x0200_0000,
541
542 ManuallyCreated = 0x0400_0000,
544}
545
546impl Origin {
547 pub fn is_known(self) -> bool {
551 self >= Origin::IncomingReplyTo
552 }
553}
554
555#[derive(Debug, PartialEq, Eq, Clone, Copy)]
556pub(crate) enum Modifier {
557 None,
558 Modified,
559 Created,
560}
561
562impl Contact {
563 pub async fn get_by_id(context: &Context, contact_id: ContactId) -> Result<Self> {
574 let contact = Self::get_by_id_optional(context, contact_id)
575 .await?
576 .with_context(|| format!("contact {contact_id} not found"))?;
577 Ok(contact)
578 }
579
580 pub async fn get_by_id_optional(
584 context: &Context,
585 contact_id: ContactId,
586 ) -> Result<Option<Self>> {
587 if let Some(mut contact) = context
588 .sql
589 .query_row_optional(
590 "SELECT c.name, c.addr, c.origin, c.blocked, c.last_seen,
591 c.authname, c.param, c.status, c.is_bot, c.fingerprint
592 FROM contacts c
593 WHERE c.id=?;",
594 (contact_id,),
595 |row| {
596 let name: String = row.get(0)?;
597 let addr: String = row.get(1)?;
598 let origin: Origin = row.get(2)?;
599 let blocked: Option<bool> = row.get(3)?;
600 let last_seen: i64 = row.get(4)?;
601 let authname: String = row.get(5)?;
602 let param: String = row.get(6)?;
603 let status: Option<String> = row.get(7)?;
604 let is_bot: bool = row.get(8)?;
605 let fingerprint: Option<String> =
606 Some(row.get(9)?).filter(|s: &String| !s.is_empty());
607 let contact = Self {
608 id: contact_id,
609 name,
610 authname,
611 addr,
612 fingerprint,
613 blocked: blocked.unwrap_or_default(),
614 last_seen,
615 origin,
616 param: param.parse().unwrap_or_default(),
617 status: status.unwrap_or_default(),
618 is_bot,
619 };
620 Ok(contact)
621 },
622 )
623 .await?
624 {
625 if contact_id == ContactId::SELF {
626 contact.name = stock_str::self_msg(context).await;
627 contact.authname = context
628 .get_config(Config::Displayname)
629 .await?
630 .unwrap_or_default();
631 contact.addr = context
632 .get_config(Config::ConfiguredAddr)
633 .await?
634 .unwrap_or_default();
635 if let Some(self_fp) = self_fingerprint_opt(context).await? {
636 contact.fingerprint = Some(self_fp.to_string());
637 }
638 contact.status = context
639 .get_config(Config::Selfstatus)
640 .await?
641 .unwrap_or_default();
642 } else if contact_id == ContactId::DEVICE {
643 contact.name = stock_str::device_messages(context).await;
644 contact.addr = ContactId::DEVICE_ADDR.to_string();
645 contact.status = stock_str::device_messages_hint(context).await;
646 }
647 Ok(Some(contact))
648 } else {
649 Ok(None)
650 }
651 }
652
653 pub fn is_blocked(&self) -> bool {
655 self.blocked
656 }
657
658 pub fn last_seen(&self) -> i64 {
660 self.last_seen
661 }
662
663 pub fn was_seen_recently(&self) -> bool {
665 time() - self.last_seen <= SEEN_RECENTLY_SECONDS
666 }
667
668 pub async fn is_blocked_load(context: &Context, id: ContactId) -> Result<bool> {
670 let blocked = context
671 .sql
672 .query_row("SELECT blocked FROM contacts WHERE id=?", (id,), |row| {
673 let blocked: bool = row.get(0)?;
674 Ok(blocked)
675 })
676 .await?;
677 Ok(blocked)
678 }
679
680 pub async fn block(context: &Context, id: ContactId) -> Result<()> {
682 set_blocked(context, Sync, id, true).await
683 }
684
685 pub async fn unblock(context: &Context, id: ContactId) -> Result<()> {
687 set_blocked(context, Sync, id, false).await
688 }
689
690 pub async fn create(context: &Context, name: &str, addr: &str) -> Result<ContactId> {
700 Self::create_ex(context, Sync, name, addr).await
701 }
702
703 pub(crate) async fn create_ex(
704 context: &Context,
705 sync: sync::Sync,
706 name: &str,
707 addr: &str,
708 ) -> Result<ContactId> {
709 let (name, addr) = sanitize_name_and_addr(name, addr);
710 let addr = ContactAddress::new(&addr)?;
711
712 let (contact_id, sth_modified) =
713 Contact::add_or_lookup(context, &name, &addr, Origin::ManuallyCreated)
714 .await
715 .context("add_or_lookup")?;
716 let blocked = Contact::is_blocked_load(context, contact_id).await?;
717 match sth_modified {
718 Modifier::None => {}
719 Modifier::Modified | Modifier::Created => {
720 context.emit_event(EventType::ContactsChanged(Some(contact_id)))
721 }
722 }
723 if blocked {
724 set_blocked(context, Nosync, contact_id, false).await?;
725 }
726
727 if sync.into() && sth_modified != Modifier::None {
728 chat::sync(
729 context,
730 chat::SyncId::ContactAddr(addr.to_string()),
731 chat::SyncAction::Rename(name.to_string()),
732 )
733 .await
734 .log_err(context)
735 .ok();
736 }
737 Ok(contact_id)
738 }
739
740 pub async fn mark_noticed(context: &Context, id: ContactId) -> Result<()> {
742 context
743 .sql
744 .execute(
745 "UPDATE msgs SET state=? WHERE from_id=? AND state=?;",
746 (MessageState::InNoticed, id, MessageState::InFresh),
747 )
748 .await?;
749 Ok(())
750 }
751
752 pub fn is_bot(&self) -> bool {
754 self.is_bot
755 }
756
757 pub async fn lookup_id_by_addr(
779 context: &Context,
780 addr: &str,
781 min_origin: Origin,
782 ) -> Result<Option<ContactId>> {
783 Self::lookup_id_by_addr_ex(context, addr, min_origin, Some(Blocked::Not)).await
784 }
785
786 pub(crate) async fn lookup_id_by_addr_ex(
789 context: &Context,
790 addr: &str,
791 min_origin: Origin,
792 blocked: Option<Blocked>,
793 ) -> Result<Option<ContactId>> {
794 if addr.is_empty() {
795 bail!("lookup_id_by_addr: empty address");
796 }
797
798 let addr_normalized = addr_normalize(addr);
799
800 if context.is_configured().await? && context.is_self_addr(addr).await? {
801 return Ok(Some(ContactId::SELF));
802 }
803
804 let id = context
805 .sql
806 .query_get_value(
807 "SELECT id FROM contacts
808 WHERE addr=?1 COLLATE NOCASE
809 AND id>?2 AND origin>=?3 AND (? OR blocked=?)
810 ORDER BY
811 (
812 SELECT COUNT(*) FROM chats c
813 INNER JOIN chats_contacts cc
814 ON c.id=cc.chat_id
815 WHERE c.type=?
816 AND c.id>?
817 AND c.blocked=?
818 AND cc.contact_id=contacts.id
819 ) DESC,
820 last_seen DESC, fingerprint DESC
821 LIMIT 1",
822 (
823 &addr_normalized,
824 ContactId::LAST_SPECIAL,
825 min_origin as u32,
826 blocked.is_none(),
827 blocked.unwrap_or(Blocked::Not),
828 Chattype::Single,
829 constants::DC_CHAT_ID_LAST_SPECIAL,
830 blocked.unwrap_or(Blocked::Not),
831 ),
832 )
833 .await?;
834 Ok(id)
835 }
836
837 pub(crate) async fn add_or_lookup(
838 context: &Context,
839 name: &str,
840 addr: &ContactAddress,
841 origin: Origin,
842 ) -> Result<(ContactId, Modifier)> {
843 Self::add_or_lookup_ex(context, name, addr, "", origin).await
844 }
845
846 pub(crate) async fn add_or_lookup_ex(
874 context: &Context,
875 name: &str,
876 addr: &str,
877 fingerprint: &str,
878 mut origin: Origin,
879 ) -> Result<(ContactId, Modifier)> {
880 let mut sth_modified = Modifier::None;
881
882 ensure!(
883 !addr.is_empty() || !fingerprint.is_empty(),
884 "Can not add_or_lookup empty address"
885 );
886 ensure!(origin != Origin::Unknown, "Missing valid origin");
887
888 if context.is_configured().await? && context.is_self_addr(addr).await? {
889 return Ok((ContactId::SELF, sth_modified));
890 }
891
892 if !fingerprint.is_empty() && context.is_configured().await? {
893 let fingerprint_self = self_fingerprint(context)
894 .await
895 .context("self_fingerprint")?;
896 if fingerprint == fingerprint_self {
897 return Ok((ContactId::SELF, sth_modified));
898 }
899 }
900
901 let mut name = sanitize_name(name);
902 if origin <= Origin::OutgoingTo {
903 if addr.contains("noreply")
905 || addr.contains("no-reply")
906 || addr.starts_with("notifications@")
907 || (addr.len() > 50 && addr.contains('+'))
909 {
910 info!(context, "hiding contact {}", addr);
911 origin = Origin::Hidden;
912 name = "".to_string();
916 }
917 }
918
919 let manual = matches!(
924 origin,
925 Origin::ManuallyCreated | Origin::AddressBook | Origin::UnhandledQrScan
926 );
927
928 let mut update_addr = false;
929
930 let row_id = context
931 .sql
932 .transaction(|transaction| {
933 let row = transaction
934 .query_row(
935 "SELECT id, name, addr, origin, authname
936 FROM contacts
937 WHERE fingerprint=?1 AND
938 (?1<>'' OR addr=?2 COLLATE NOCASE)",
939 (fingerprint, addr),
940 |row| {
941 let row_id: u32 = row.get(0)?;
942 let row_name: String = row.get(1)?;
943 let row_addr: String = row.get(2)?;
944 let row_origin: Origin = row.get(3)?;
945 let row_authname: String = row.get(4)?;
946
947 Ok((row_id, row_name, row_addr, row_origin, row_authname))
948 },
949 )
950 .optional()?;
951
952 let row_id;
953 if let Some((id, row_name, row_addr, row_origin, row_authname)) = row {
954 let update_name = manual && name != row_name;
955 let update_authname = !manual
956 && name != row_authname
957 && !name.is_empty()
958 && (origin >= row_origin
959 || origin == Origin::IncomingUnknownFrom
960 || row_authname.is_empty());
961
962 row_id = id;
963 if origin >= row_origin && addr != row_addr {
964 update_addr = true;
965 }
966 if update_name || update_authname || update_addr || origin > row_origin {
967 let new_name = if update_name {
968 name.to_string()
969 } else {
970 row_name
971 };
972
973 transaction.execute(
974 "UPDATE contacts SET name=?, addr=?, origin=?, authname=? WHERE id=?;",
975 (
976 new_name,
977 if update_addr {
978 addr.to_string()
979 } else {
980 row_addr
981 },
982 if origin > row_origin {
983 origin
984 } else {
985 row_origin
986 },
987 if update_authname {
988 name.to_string()
989 } else {
990 row_authname
991 },
992 row_id,
993 ),
994 )?;
995
996 if update_name || update_authname {
997 let contact_id = ContactId::new(row_id);
998 update_chat_names(context, transaction, contact_id)?;
999 }
1000 sth_modified = Modifier::Modified;
1001 }
1002 } else {
1003 let update_name = manual;
1004 let update_authname = !manual;
1005
1006 transaction.execute(
1007 "INSERT INTO contacts (name, addr, fingerprint, origin, authname)
1008 VALUES (?, ?, ?, ?, ?);",
1009 (
1010 if update_name { &name } else { "" },
1011 &addr,
1012 fingerprint,
1013 origin,
1014 if update_authname { &name } else { "" },
1015 ),
1016 )?;
1017
1018 sth_modified = Modifier::Created;
1019 row_id = u32::try_from(transaction.last_insert_rowid())?;
1020 if fingerprint.is_empty() {
1021 info!(context, "Added contact id={row_id} addr={addr}.");
1022 } else {
1023 info!(
1024 context,
1025 "Added contact id={row_id} fpr={fingerprint} addr={addr}."
1026 );
1027 }
1028 }
1029 Ok(row_id)
1030 })
1031 .await?;
1032
1033 let contact_id = ContactId::new(row_id);
1034
1035 Ok((contact_id, sth_modified))
1036 }
1037
1038 pub async fn add_address_book(context: &Context, addr_book: &str) -> Result<usize> {
1056 let mut modify_cnt = 0;
1057
1058 for (name, addr) in split_address_book(addr_book) {
1059 let (name, addr) = sanitize_name_and_addr(name, addr);
1060 match ContactAddress::new(&addr) {
1061 Ok(addr) => {
1062 match Contact::add_or_lookup(context, &name, &addr, Origin::AddressBook).await {
1063 Ok((_, modified)) => {
1064 if modified != Modifier::None {
1065 modify_cnt += 1
1066 }
1067 }
1068 Err(err) => {
1069 warn!(
1070 context,
1071 "Failed to add address {} from address book: {}", addr, err
1072 );
1073 }
1074 }
1075 }
1076 Err(err) => {
1077 warn!(context, "{:#}.", err);
1078 }
1079 }
1080 }
1081 if modify_cnt > 0 {
1082 context.emit_event(EventType::ContactsChanged(None));
1083 }
1084
1085 Ok(modify_cnt)
1086 }
1087
1088 pub async fn get_all(
1098 context: &Context,
1099 listflags: u32,
1100 query: Option<&str>,
1101 ) -> Result<Vec<ContactId>> {
1102 let self_addrs = context
1103 .get_all_self_addrs()
1104 .await?
1105 .into_iter()
1106 .collect::<HashSet<_>>();
1107 let mut add_self = false;
1108 let mut ret = Vec::new();
1109 let flag_add_self = (listflags & constants::DC_GCL_ADD_SELF) != 0;
1110 let flag_address = (listflags & constants::DC_GCL_ADDRESS) != 0;
1111 let minimal_origin = if context.get_config_bool(Config::Bot).await? {
1112 Origin::Unknown
1113 } else {
1114 Origin::IncomingReplyTo
1115 };
1116 if query.is_some() {
1117 let s3str_like_cmd = format!("%{}%", query.unwrap_or(""));
1118 context
1119 .sql
1120 .query_map(
1121 "SELECT c.id, c.addr FROM contacts c
1122 WHERE c.id>?
1123 AND (c.fingerprint='')=?
1124 AND c.origin>=? \
1125 AND c.blocked=0 \
1126 AND (iif(c.name='',c.authname,c.name) LIKE ? OR c.addr LIKE ?) \
1127 ORDER BY c.last_seen DESC, c.id DESC;",
1128 (
1129 ContactId::LAST_SPECIAL,
1130 flag_address,
1131 minimal_origin,
1132 &s3str_like_cmd,
1133 &s3str_like_cmd,
1134 ),
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 if let Some(query) = query {
1153 let self_addr = context
1154 .get_config(Config::ConfiguredAddr)
1155 .await?
1156 .unwrap_or_default();
1157 let self_name = context
1158 .get_config(Config::Displayname)
1159 .await?
1160 .unwrap_or_default();
1161 let self_name2 = stock_str::self_msg(context);
1162
1163 if self_addr.contains(query)
1164 || self_name.contains(query)
1165 || self_name2.await.contains(query)
1166 {
1167 add_self = true;
1168 }
1169 } else {
1170 add_self = true;
1171 }
1172 } else {
1173 add_self = true;
1174
1175 context
1176 .sql
1177 .query_map(
1178 "SELECT id, addr FROM contacts
1179 WHERE id>?
1180 AND (fingerprint='')=?
1181 AND origin>=?
1182 AND blocked=0
1183 ORDER BY last_seen DESC, id DESC;",
1184 (ContactId::LAST_SPECIAL, flag_address, minimal_origin),
1185 |row| {
1186 let id: ContactId = row.get(0)?;
1187 let addr: String = row.get(1)?;
1188 Ok((id, addr))
1189 },
1190 |rows| {
1191 for row in rows {
1192 let (id, addr) = row?;
1193 if !self_addrs.contains(&addr) {
1194 ret.push(id);
1195 }
1196 }
1197 Ok(())
1198 },
1199 )
1200 .await?;
1201 }
1202
1203 if flag_add_self && add_self {
1204 ret.push(ContactId::SELF);
1205 }
1206
1207 Ok(ret)
1208 }
1209
1210 async fn update_blocked_mailinglist_contacts(context: &Context) -> Result<()> {
1216 context
1217 .sql
1218 .transaction(move |transaction| {
1219 let mut stmt = transaction.prepare(
1220 "SELECT name, grpid, type FROM chats WHERE (type=? OR type=?) AND blocked=?",
1221 )?;
1222 let rows = stmt.query_map(
1223 (Chattype::Mailinglist, Chattype::InBroadcast, Blocked::Yes),
1224 |row| {
1225 let name: String = row.get(0)?;
1226 let grpid: String = row.get(1)?;
1227 let typ: Chattype = row.get(2)?;
1228 Ok((name, grpid, typ))
1229 },
1230 )?;
1231 let blocked_mailinglists = rows.collect::<std::result::Result<Vec<_>, _>>()?;
1232 for (name, grpid, typ) in blocked_mailinglists {
1233 let count = transaction.query_row(
1234 "SELECT COUNT(id) FROM contacts WHERE addr=?",
1235 [&grpid],
1236 |row| {
1237 let count: isize = row.get(0)?;
1238 Ok(count)
1239 },
1240 )?;
1241 if count == 0 {
1242 transaction.execute("INSERT INTO contacts (addr) VALUES (?)", [&grpid])?;
1243 }
1244
1245 let fingerprint = if typ == Chattype::InBroadcast {
1246 "Blocked_broadcast"
1249 } else {
1250 ""
1251 };
1252 transaction.execute(
1254 "UPDATE contacts SET name=?, origin=?, blocked=1, fingerprint=? WHERE addr=?",
1255 (&name, Origin::MailinglistAddress, fingerprint, &grpid),
1256 )?;
1257 }
1258 Ok(())
1259 })
1260 .await?;
1261 Ok(())
1262 }
1263
1264 pub async fn get_blocked_cnt(context: &Context) -> Result<usize> {
1266 let count = context
1267 .sql
1268 .count(
1269 "SELECT COUNT(*) FROM contacts WHERE id>? AND blocked!=0",
1270 (ContactId::LAST_SPECIAL,),
1271 )
1272 .await?;
1273 Ok(count)
1274 }
1275
1276 pub async fn get_all_blocked(context: &Context) -> Result<Vec<ContactId>> {
1278 Contact::update_blocked_mailinglist_contacts(context)
1279 .await
1280 .context("cannot update blocked mailinglist contacts")?;
1281
1282 let list = context
1283 .sql
1284 .query_map_vec(
1285 "SELECT id FROM contacts WHERE id>? AND blocked!=0 ORDER BY last_seen DESC, id DESC;",
1286 (ContactId::LAST_SPECIAL,),
1287 |row| {
1288 let contact_id: ContactId = row.get(0)?;
1289 Ok(contact_id)
1290 }
1291 )
1292 .await?;
1293 Ok(list)
1294 }
1295
1296 pub async fn get_encrinfo(context: &Context, contact_id: ContactId) -> Result<String> {
1302 ensure!(
1303 !contact_id.is_special(),
1304 "Can not provide encryption info for special contact"
1305 );
1306
1307 let contact = Contact::get_by_id(context, contact_id).await?;
1308 let addr = context
1309 .get_config(Config::ConfiguredAddr)
1310 .await?
1311 .unwrap_or_default();
1312
1313 let Some(fingerprint_other) = contact.fingerprint() else {
1314 return Ok(stock_str::encr_none(context).await);
1315 };
1316 let fingerprint_other = fingerprint_other.to_string();
1317
1318 let stock_message = if contact.public_key(context).await?.is_some() {
1319 stock_str::e2e_available(context).await
1320 } else {
1321 stock_str::encr_none(context).await
1322 };
1323
1324 let finger_prints = stock_str::finger_prints(context).await;
1325 let mut ret = format!("{stock_message}.\n{finger_prints}:");
1326
1327 let fingerprint_self = load_self_public_key(context)
1328 .await?
1329 .dc_fingerprint()
1330 .to_string();
1331 if addr < contact.addr {
1332 cat_fingerprint(
1333 &mut ret,
1334 &stock_str::self_msg(context).await,
1335 &addr,
1336 &fingerprint_self,
1337 );
1338 cat_fingerprint(
1339 &mut ret,
1340 contact.get_display_name(),
1341 &contact.addr,
1342 &fingerprint_other,
1343 );
1344 } else {
1345 cat_fingerprint(
1346 &mut ret,
1347 contact.get_display_name(),
1348 &contact.addr,
1349 &fingerprint_other,
1350 );
1351 cat_fingerprint(
1352 &mut ret,
1353 &stock_str::self_msg(context).await,
1354 &addr,
1355 &fingerprint_self,
1356 );
1357 }
1358
1359 Ok(ret)
1360 }
1361
1362 pub async fn delete(context: &Context, contact_id: ContactId) -> Result<()> {
1368 ensure!(!contact_id.is_special(), "Can not delete special contact");
1369
1370 context
1371 .sql
1372 .transaction(move |transaction| {
1373 let deleted_contacts = transaction.execute(
1376 "DELETE FROM contacts WHERE id=?
1377 AND (SELECT COUNT(*) FROM chats_contacts WHERE contact_id=?)=0;",
1378 (contact_id, contact_id),
1379 )?;
1380 if deleted_contacts == 0 {
1381 transaction.execute(
1382 "UPDATE contacts SET origin=? WHERE id=?;",
1383 (Origin::Hidden, contact_id),
1384 )?;
1385 }
1386 Ok(())
1387 })
1388 .await?;
1389
1390 context.emit_event(EventType::ContactsChanged(None));
1391 Ok(())
1392 }
1393
1394 pub async fn update_param(&self, context: &Context) -> Result<()> {
1396 context
1397 .sql
1398 .execute(
1399 "UPDATE contacts SET param=? WHERE id=?",
1400 (self.param.to_string(), self.id),
1401 )
1402 .await?;
1403 Ok(())
1404 }
1405
1406 pub async fn update_status(&self, context: &Context) -> Result<()> {
1408 context
1409 .sql
1410 .execute(
1411 "UPDATE contacts SET status=? WHERE id=?",
1412 (&self.status, self.id),
1413 )
1414 .await?;
1415 Ok(())
1416 }
1417
1418 pub fn get_id(&self) -> ContactId {
1420 self.id
1421 }
1422
1423 pub fn get_addr(&self) -> &str {
1425 &self.addr
1426 }
1427
1428 pub fn is_key_contact(&self) -> bool {
1431 self.fingerprint.is_some()
1432 }
1433
1434 pub fn fingerprint(&self) -> Option<Fingerprint> {
1438 if let Some(fingerprint) = &self.fingerprint {
1439 fingerprint.parse().ok()
1440 } else {
1441 None
1442 }
1443 }
1444
1445 pub async fn public_key(&self, context: &Context) -> Result<Option<SignedPublicKey>> {
1452 if self.id == ContactId::SELF {
1453 return Ok(Some(load_self_public_key(context).await?));
1454 }
1455
1456 if let Some(fingerprint) = &self.fingerprint {
1457 if let Some(public_key_bytes) = context
1458 .sql
1459 .query_row_optional(
1460 "SELECT public_key
1461 FROM public_keys
1462 WHERE fingerprint=?",
1463 (fingerprint,),
1464 |row| {
1465 let bytes: Vec<u8> = row.get(0)?;
1466 Ok(bytes)
1467 },
1468 )
1469 .await?
1470 {
1471 let public_key = SignedPublicKey::from_slice(&public_key_bytes)?;
1472 Ok(Some(public_key))
1473 } else {
1474 Ok(None)
1475 }
1476 } else {
1477 Ok(None)
1478 }
1479 }
1480
1481 pub fn get_authname(&self) -> &str {
1483 &self.authname
1484 }
1485
1486 pub fn get_name(&self) -> &str {
1492 &self.name
1493 }
1494
1495 pub fn get_display_name(&self) -> &str {
1501 if !self.name.is_empty() {
1502 return &self.name;
1503 }
1504 if !self.authname.is_empty() {
1505 return &self.authname;
1506 }
1507 &self.addr
1508 }
1509
1510 pub(crate) fn get_authname_or_addr(&self) -> String {
1515 if !self.authname.is_empty() {
1516 (&self.authname).into()
1517 } else {
1518 (&self.addr).into()
1519 }
1520 }
1521
1522 pub fn get_name_n_addr(&self) -> String {
1533 if !self.name.is_empty() {
1534 format!("{} ({})", self.name, self.addr)
1535 } else if !self.authname.is_empty() {
1536 format!("{} ({})", self.authname, self.addr)
1537 } else {
1538 (&self.addr).into()
1539 }
1540 }
1541
1542 pub async fn get_profile_image(&self, context: &Context) -> Result<Option<PathBuf>> {
1546 self.get_profile_image_ex(context, true).await
1547 }
1548
1549 async fn get_profile_image_ex(
1553 &self,
1554 context: &Context,
1555 show_fallback_icon: bool,
1556 ) -> Result<Option<PathBuf>> {
1557 if self.id == ContactId::SELF {
1558 if let Some(p) = context.get_config(Config::Selfavatar).await? {
1559 return Ok(Some(PathBuf::from(p))); }
1561 } else if self.id == ContactId::DEVICE {
1562 return Ok(Some(chat::get_device_icon(context).await?));
1563 }
1564 if show_fallback_icon && !self.id.is_special() && !self.is_key_contact() {
1565 return Ok(Some(chat::get_unencrypted_icon(context).await?));
1566 }
1567 if let Some(image_rel) = self.param.get(Param::ProfileImage) {
1568 if !image_rel.is_empty() {
1569 return Ok(Some(get_abs_path(context, Path::new(image_rel))));
1570 }
1571 }
1572 Ok(None)
1573 }
1574
1575 pub fn get_color(&self) -> u32 {
1579 get_color(self.id == ContactId::SELF, &self.addr, &self.fingerprint())
1580 }
1581
1582 pub async fn get_or_gen_color(&self, context: &Context) -> Result<u32> {
1587 let mut fpr = self.fingerprint();
1588 if fpr.is_none() && self.id == ContactId::SELF {
1589 fpr = Some(load_self_public_key(context).await?.dc_fingerprint());
1590 }
1591 Ok(get_color(self.id == ContactId::SELF, &self.addr, &fpr))
1592 }
1593
1594 pub fn get_status(&self) -> &str {
1598 self.status.as_str()
1599 }
1600
1601 pub async fn e2ee_avail(&self, context: &Context) -> Result<bool> {
1603 if self.id == ContactId::SELF {
1604 return Ok(true);
1606 }
1607 Ok(self.public_key(context).await?.is_some())
1608 }
1609
1610 pub async fn is_verified(&self, context: &Context) -> Result<bool> {
1623 if self.id == ContactId::SELF {
1626 return Ok(true);
1627 }
1628
1629 Ok(self.get_verifier_id(context).await?.is_some())
1630 }
1631
1632 pub async fn get_verifier_id(&self, context: &Context) -> Result<Option<Option<ContactId>>> {
1642 let verifier_id: u32 = context
1643 .sql
1644 .query_get_value("SELECT verifier FROM contacts WHERE id=?", (self.id,))
1645 .await?
1646 .with_context(|| format!("Contact {} does not exist", self.id))?;
1647
1648 if verifier_id == 0 {
1649 Ok(None)
1650 } else if verifier_id == self.id.to_u32() {
1651 Ok(Some(None))
1652 } else {
1653 Ok(Some(Some(ContactId::new(verifier_id))))
1654 }
1655 }
1656
1657 pub async fn get_real_cnt(context: &Context) -> Result<usize> {
1659 if !context.sql.is_open().await {
1660 return Ok(0);
1661 }
1662
1663 let count = context
1664 .sql
1665 .count(
1666 "SELECT COUNT(*) FROM contacts WHERE id>?;",
1667 (ContactId::LAST_SPECIAL,),
1668 )
1669 .await?;
1670 Ok(count)
1671 }
1672
1673 pub async fn real_exists_by_id(context: &Context, contact_id: ContactId) -> Result<bool> {
1675 if contact_id.is_special() {
1676 return Ok(false);
1677 }
1678
1679 let exists = context
1680 .sql
1681 .exists("SELECT COUNT(*) FROM contacts WHERE id=?;", (contact_id,))
1682 .await?;
1683 Ok(exists)
1684 }
1685}
1686
1687pub fn get_color(is_self: bool, addr: &str, fingerprint: &Option<Fingerprint>) -> u32 {
1693 if let Some(fingerprint) = fingerprint {
1694 str_to_color(&fingerprint.hex())
1695 } else if is_self {
1696 0x808080
1697 } else {
1698 str_to_color(&to_lowercase(addr))
1699 }
1700}
1701
1702fn update_chat_names(
1706 context: &Context,
1707 transaction: &rusqlite::Connection,
1708 contact_id: ContactId,
1709) -> Result<()> {
1710 let chat_id: Option<ChatId> = transaction.query_row(
1711 "SELECT id FROM chats WHERE type=? AND id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?)",
1712 (Chattype::Single, contact_id),
1713 |row| {
1714 let chat_id: ChatId = row.get(0)?;
1715 Ok(chat_id)
1716 }
1717 ).optional()?;
1718
1719 if let Some(chat_id) = chat_id {
1720 let (addr, name, authname) = transaction.query_row(
1721 "SELECT addr, name, authname
1722 FROM contacts
1723 WHERE id=?",
1724 (contact_id,),
1725 |row| {
1726 let addr: String = row.get(0)?;
1727 let name: String = row.get(1)?;
1728 let authname: String = row.get(2)?;
1729 Ok((addr, name, authname))
1730 },
1731 )?;
1732
1733 let chat_name = if !name.is_empty() {
1734 name
1735 } else if !authname.is_empty() {
1736 authname
1737 } else {
1738 addr
1739 };
1740
1741 let count = transaction.execute(
1742 "UPDATE chats SET name=?1 WHERE id=?2 AND name!=?1",
1743 (chat_name, chat_id),
1744 )?;
1745
1746 if count > 0 {
1747 context.emit_event(EventType::ChatModified(chat_id));
1749 chatlist_events::emit_chatlist_items_changed_for_contact(context, contact_id);
1750 }
1751 }
1752
1753 Ok(())
1754}
1755
1756pub(crate) async fn set_blocked(
1757 context: &Context,
1758 sync: sync::Sync,
1759 contact_id: ContactId,
1760 new_blocking: bool,
1761) -> Result<()> {
1762 ensure!(
1763 !contact_id.is_special(),
1764 "Can't block special contact {contact_id}"
1765 );
1766 let contact = Contact::get_by_id(context, contact_id).await?;
1767
1768 if contact.blocked != new_blocking {
1769 context
1770 .sql
1771 .execute(
1772 "UPDATE contacts SET blocked=? WHERE id=?;",
1773 (i32::from(new_blocking), contact_id),
1774 )
1775 .await?;
1776
1777 if context
1783 .sql
1784 .execute(
1785 r#"
1786UPDATE chats
1787SET blocked=?
1788WHERE type=? AND id IN (
1789 SELECT chat_id FROM chats_contacts WHERE contact_id=?
1790);
1791"#,
1792 (new_blocking, Chattype::Single, contact_id),
1793 )
1794 .await
1795 .is_ok()
1796 {
1797 Contact::mark_noticed(context, contact_id).await?;
1798 context.emit_event(EventType::ContactsChanged(Some(contact_id)));
1799 }
1800
1801 if !new_blocking && contact.origin == Origin::MailinglistAddress {
1804 if let Some((chat_id, ..)) = chat::get_chat_id_by_grpid(context, &contact.addr).await? {
1805 chat_id.unblock_ex(context, Nosync).await?;
1806 }
1807 }
1808
1809 if sync.into() {
1810 let action = match new_blocking {
1811 true => chat::SyncAction::Block,
1812 false => chat::SyncAction::Unblock,
1813 };
1814 let sync_id = if let Some(fingerprint) = contact.fingerprint() {
1815 chat::SyncId::ContactFingerprint(fingerprint.hex())
1816 } else {
1817 chat::SyncId::ContactAddr(contact.addr.clone())
1818 };
1819
1820 chat::sync(context, sync_id, action)
1821 .await
1822 .log_err(context)
1823 .ok();
1824 }
1825 }
1826
1827 chatlist_events::emit_chatlist_changed(context);
1828 Ok(())
1829}
1830
1831pub(crate) async fn set_profile_image(
1839 context: &Context,
1840 contact_id: ContactId,
1841 profile_image: &AvatarAction,
1842 was_encrypted: bool,
1843) -> Result<()> {
1844 let mut contact = Contact::get_by_id(context, contact_id).await?;
1845 let changed = match profile_image {
1846 AvatarAction::Change(profile_image) => {
1847 if contact_id == ContactId::SELF {
1848 if was_encrypted {
1849 context
1850 .set_config_ex(Nosync, Config::Selfavatar, Some(profile_image))
1851 .await?;
1852 } else {
1853 info!(context, "Do not use unencrypted selfavatar.");
1854 }
1855 } else {
1856 contact.param.set(Param::ProfileImage, profile_image);
1857 }
1858 true
1859 }
1860 AvatarAction::Delete => {
1861 if contact_id == ContactId::SELF {
1862 if was_encrypted {
1863 context
1864 .set_config_ex(Nosync, Config::Selfavatar, None)
1865 .await?;
1866 } else {
1867 info!(context, "Do not use unencrypted selfavatar deletion.");
1868 }
1869 } else {
1870 contact.param.remove(Param::ProfileImage);
1871 }
1872 true
1873 }
1874 };
1875 if changed {
1876 contact.update_param(context).await?;
1877 context.emit_event(EventType::ContactsChanged(Some(contact_id)));
1878 chatlist_events::emit_chatlist_item_changed_for_contact_chat(context, contact_id).await;
1879 }
1880 Ok(())
1881}
1882
1883pub(crate) async fn set_status(
1889 context: &Context,
1890 contact_id: ContactId,
1891 status: String,
1892 encrypted: bool,
1893 has_chat_version: bool,
1894) -> Result<()> {
1895 if contact_id == ContactId::SELF {
1896 if encrypted && has_chat_version {
1897 context
1898 .set_config_ex(Nosync, Config::Selfstatus, Some(&status))
1899 .await?;
1900 }
1901 } else {
1902 let mut contact = Contact::get_by_id(context, contact_id).await?;
1903
1904 if contact.status != status {
1905 contact.status = status;
1906 contact.update_status(context).await?;
1907 context.emit_event(EventType::ContactsChanged(Some(contact_id)));
1908 }
1909 }
1910 Ok(())
1911}
1912
1913pub(crate) async fn update_last_seen(
1915 context: &Context,
1916 contact_id: ContactId,
1917 timestamp: i64,
1918) -> Result<()> {
1919 ensure!(
1920 !contact_id.is_special(),
1921 "Can not update special contact last seen timestamp"
1922 );
1923
1924 if context
1925 .sql
1926 .execute(
1927 "UPDATE contacts SET last_seen = ?1 WHERE last_seen < ?1 AND id = ?2",
1928 (timestamp, contact_id),
1929 )
1930 .await?
1931 > 0
1932 && timestamp > time() - SEEN_RECENTLY_SECONDS
1933 {
1934 context.emit_event(EventType::ContactsChanged(Some(contact_id)));
1935 context
1936 .scheduler
1937 .interrupt_recently_seen(contact_id, timestamp)
1938 .await;
1939 }
1940 Ok(())
1941}
1942
1943pub(crate) async fn mark_contact_id_as_verified(
1947 context: &Context,
1948 contact_id: ContactId,
1949 verifier_id: Option<ContactId>,
1950) -> Result<()> {
1951 ensure_and_debug_assert_ne!(contact_id, ContactId::SELF,);
1952 ensure_and_debug_assert_ne!(
1953 Some(contact_id),
1954 verifier_id,
1955 "Contact cannot be verified by self",
1956 );
1957 let by_self = verifier_id == Some(ContactId::SELF);
1958 let mut verifier_id = verifier_id.unwrap_or(contact_id);
1959 context
1960 .sql
1961 .transaction(|transaction| {
1962 let contact_fingerprint: String = transaction.query_row(
1963 "SELECT fingerprint FROM contacts WHERE id=?",
1964 (contact_id,),
1965 |row| row.get(0),
1966 )?;
1967 if contact_fingerprint.is_empty() {
1968 bail!("Non-key-contact {contact_id} cannot be verified");
1969 }
1970 if verifier_id != ContactId::SELF {
1971 let (verifier_fingerprint, verifier_verifier_id): (String, ContactId) = transaction
1972 .query_row(
1973 "SELECT fingerprint, verifier FROM contacts WHERE id=?",
1974 (verifier_id,),
1975 |row| Ok((row.get(0)?, row.get(1)?)),
1976 )?;
1977 if verifier_fingerprint.is_empty() {
1978 bail!(
1979 "Contact {contact_id} cannot be verified by non-key-contact {verifier_id}"
1980 );
1981 }
1982 ensure!(
1983 verifier_id == contact_id || verifier_verifier_id != ContactId::UNDEFINED,
1984 "Contact {contact_id} cannot be verified by unverified contact {verifier_id}",
1985 );
1986 if verifier_verifier_id == verifier_id {
1987 verifier_id = contact_id;
1992 }
1993 }
1994 transaction.execute(
1995 "UPDATE contacts SET verifier=?1
1996 WHERE id=?2 AND (verifier=0 OR verifier=id OR ?3)",
1997 (verifier_id, contact_id, by_self),
1998 )?;
1999 Ok(())
2000 })
2001 .await?;
2002 Ok(())
2003}
2004
2005fn cat_fingerprint(ret: &mut String, name: &str, addr: &str, fingerprint: &str) {
2006 *ret += &format!("\n\n{name} ({addr}):\n{fingerprint}");
2007}
2008
2009fn split_address_book(book: &str) -> Vec<(&str, &str)> {
2010 book.lines()
2011 .collect::<Vec<&str>>()
2012 .chunks(2)
2013 .filter_map(|chunk| {
2014 let name = chunk.first()?;
2015 let addr = chunk.get(1)?;
2016 Some((*name, *addr))
2017 })
2018 .collect()
2019}
2020
2021#[derive(Debug)]
2022pub(crate) struct RecentlySeenInterrupt {
2023 contact_id: ContactId,
2024 timestamp: i64,
2025}
2026
2027#[derive(Debug)]
2028pub(crate) struct RecentlySeenLoop {
2029 handle: task::JoinHandle<()>,
2031
2032 interrupt_send: Sender<RecentlySeenInterrupt>,
2033}
2034
2035impl RecentlySeenLoop {
2036 pub(crate) fn new(context: Context) -> Self {
2037 let (interrupt_send, interrupt_recv) = channel::bounded(1);
2038
2039 let handle = task::spawn(Self::run(context, interrupt_recv));
2040 Self {
2041 handle,
2042 interrupt_send,
2043 }
2044 }
2045
2046 async fn run(context: Context, interrupt: Receiver<RecentlySeenInterrupt>) {
2047 type MyHeapElem = (Reverse<i64>, ContactId);
2048
2049 let now = SystemTime::now();
2050 let now_ts = now
2051 .duration_since(SystemTime::UNIX_EPOCH)
2052 .unwrap_or_default()
2053 .as_secs() as i64;
2054
2055 let mut unseen_queue: BinaryHeap<MyHeapElem> = context
2061 .sql
2062 .query_map_collect(
2063 "SELECT id, last_seen FROM contacts
2064 WHERE last_seen > ?",
2065 (now_ts - SEEN_RECENTLY_SECONDS,),
2066 |row| {
2067 let contact_id: ContactId = row.get("id")?;
2068 let last_seen: i64 = row.get("last_seen")?;
2069 Ok((Reverse(last_seen + SEEN_RECENTLY_SECONDS), contact_id))
2070 },
2071 )
2072 .await
2073 .unwrap_or_default();
2074
2075 loop {
2076 let now = SystemTime::now();
2077 let (until, contact_id) =
2078 if let Some((Reverse(timestamp), contact_id)) = unseen_queue.peek() {
2079 (
2080 UNIX_EPOCH
2081 + Duration::from_secs((*timestamp).try_into().unwrap_or(u64::MAX))
2082 + Duration::from_secs(1),
2083 Some(contact_id),
2084 )
2085 } else {
2086 (now + Duration::from_secs(86400), None)
2088 };
2089
2090 if let Ok(duration) = until.duration_since(now) {
2091 info!(
2092 context,
2093 "Recently seen loop waiting for {} or interrupt",
2094 duration_to_str(duration)
2095 );
2096
2097 match timeout(duration, interrupt.recv()).await {
2098 Err(_) => {
2099 if let Some(contact_id) = contact_id {
2101 context.emit_event(EventType::ContactsChanged(Some(*contact_id)));
2102 chatlist_events::emit_chatlist_item_changed_for_contact_chat(
2103 &context,
2104 *contact_id,
2105 )
2106 .await;
2107 unseen_queue.pop();
2108 }
2109 }
2110 Ok(Err(err)) => {
2111 warn!(
2112 context,
2113 "Error receiving an interruption in recently seen loop: {}", err
2114 );
2115 return;
2118 }
2119 Ok(Ok(RecentlySeenInterrupt {
2120 contact_id,
2121 timestamp,
2122 })) => {
2123 if contact_id != ContactId::UNDEFINED {
2125 unseen_queue
2126 .push((Reverse(timestamp + SEEN_RECENTLY_SECONDS), contact_id));
2127 }
2128 }
2129 }
2130 } else {
2131 info!(
2132 context,
2133 "Recently seen loop is not waiting, event is already due."
2134 );
2135
2136 if let Some(contact_id) = contact_id {
2138 context.emit_event(EventType::ContactsChanged(Some(*contact_id)));
2139 chatlist_events::emit_chatlist_item_changed_for_contact_chat(
2140 &context,
2141 *contact_id,
2142 )
2143 .await;
2144 }
2145 unseen_queue.pop();
2146 }
2147 }
2148 }
2149
2150 pub(crate) fn try_interrupt(&self, contact_id: ContactId, timestamp: i64) {
2151 self.interrupt_send
2152 .try_send(RecentlySeenInterrupt {
2153 contact_id,
2154 timestamp,
2155 })
2156 .ok();
2157 }
2158
2159 #[cfg(test)]
2160 pub(crate) async fn interrupt(&self, contact_id: ContactId, timestamp: i64) {
2161 self.interrupt_send
2162 .send(RecentlySeenInterrupt {
2163 contact_id,
2164 timestamp,
2165 })
2166 .await
2167 .unwrap();
2168 }
2169
2170 pub(crate) async fn abort(self) {
2171 self.handle.abort();
2172
2173 self.handle.await.ok();
2177 }
2178}
2179
2180#[cfg(test)]
2181mod contact_tests;