deltachat/
contact.rs

1//! Contacts module
2
3use 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, ChatIdBlocked, ProtectionStatus};
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
42/// Time during which a contact is considered as seen recently.
43const SEEN_RECENTLY_SECONDS: i64 = 600;
44
45/// Contact ID, including reserved IDs.
46///
47/// Some contact IDs are reserved to identify special contacts.  This
48/// type can represent both the special as well as normal contacts.
49#[derive(
50    Debug, Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
51)]
52pub struct ContactId(u32);
53
54impl ContactId {
55    /// Undefined contact. Used as a placeholder for trashed messages.
56    pub const UNDEFINED: ContactId = ContactId::new(0);
57
58    /// The owner of the account.
59    ///
60    /// The email-address is set by `set_config` using "addr".
61    pub const SELF: ContactId = ContactId::new(1);
62
63    /// ID of the contact for info messages.
64    pub const INFO: ContactId = ContactId::new(2);
65
66    /// ID of the contact for device messages.
67    pub const DEVICE: ContactId = ContactId::new(5);
68    pub(crate) const LAST_SPECIAL: ContactId = ContactId::new(9);
69
70    /// Address to go with [`ContactId::DEVICE`].
71    ///
72    /// This is used by APIs which need to return an email address for this contact.
73    pub const DEVICE_ADDR: &'static str = "device@localhost";
74
75    /// Creates a new [`ContactId`].
76    pub const fn new(id: u32) -> ContactId {
77        ContactId(id)
78    }
79
80    /// Whether this is a special [`ContactId`].
81    ///
82    /// Some [`ContactId`]s are reserved for special contacts like [`ContactId::SELF`],
83    /// [`ContactId::INFO`] and [`ContactId::DEVICE`].  This function indicates whether this
84    /// [`ContactId`] is any of the reserved special [`ContactId`]s (`true`) or whether it
85    /// is the [`ContactId`] of a real contact (`false`).
86    pub fn is_special(&self) -> bool {
87        self.0 <= Self::LAST_SPECIAL.0
88    }
89
90    /// Numerical representation of the [`ContactId`].
91    ///
92    /// Each contact ID has a unique numerical representation which is used in the database
93    /// (via [`rusqlite::ToSql`]) and also for FFI purposes.  In Rust code you should never
94    /// need to use this directly.
95    pub const fn to_u32(&self) -> u32 {
96        self.0
97    }
98
99    /// Sets display name for existing contact.
100    ///
101    /// Display name may be an empty string,
102    /// in which case the name displayed in the UI
103    /// for this contact will switch to the
104    /// contact's authorized name.
105    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    /// Mark contact as bot.
168    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    /// Reset gossip timestamp in all chats with this contact.
177    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    /// Updates the origin of the contacts, but only if `origin` is higher than the current one.
194    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    /// Returns contact address.
214    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
244/// Allow converting [`ContactId`] to an SQLite type.
245impl 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
253/// Allow converting an SQLite integer directly into [`ContactId`].
254impl 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
264/// Returns a vCard containing contacts with the given ids.
265pub 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            // Use the current time to not reveal our or contact's online time.
286            timestamp: Ok(now),
287        });
288    }
289
290    // XXX: newline at the end of vCard is trimmed
291    // for compatibility with core <=1.155.3
292    // Newer core should be able to deal with
293    // trailing CRLF as the fix
294    // <https://github.com/deltachat/deltachat-core-rust/pull/6522>
295    // is merged.
296    Ok(contact_tools::make_vcard(&vcard_contacts)
297        .trim_end()
298        .to_string())
299}
300
301/// Imports contacts from the given vCard.
302///
303/// Returns the ids of successfully processed contacts in the order they appear in `vcard`,
304/// regardless of whether they are just created, modified or left untouched.
305pub 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    // Importing a vCard is also an explicit user action like creating a chat with the contact. We
324    // mustn't use `Origin::AddressBook` here because the vCard may be created not by us, also we
325    // want `contact.authname` to be saved as the authname and not a locally given name.
326    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        // Currently this value doesn't matter as we don't import the contact of self.
387        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/// An object representing a single contact in memory.
409///
410/// The contact object is not updated.
411/// If you want an update, you have to recreate the object.
412///
413/// The library makes sure
414/// only to use names _authorized_ by the contact in `To:` or `Cc:`.
415/// *Given-names* as "Daddy" or "Honey" are not used there.
416/// For this purpose, internally, two names are tracked -
417/// authorized name and given name.
418/// By default, these names are equal, but functions working with contact names
419/// only affect the given name.
420#[derive(Debug)]
421pub struct Contact {
422    /// The contact ID.
423    pub id: ContactId,
424
425    /// Contact name. It is recommended to use `Contact::get_name`,
426    /// `Contact::get_display_name` or `Contact::get_name_n_addr` to access this field.
427    /// May be empty, initially set to `authname`.
428    name: String,
429
430    /// Name authorized by the contact himself. Only this name may be spread to others,
431    /// e.g. in To:-lists. May be empty. It is recommended to use `Contact::get_authname`,
432    /// to access this field.
433    authname: String,
434
435    /// E-Mail-Address of the contact. It is recommended to use `Contact::get_addr` to access this field.
436    addr: String,
437
438    /// OpenPGP key fingerprint.
439    /// Non-empty iff the contact is a key-contact,
440    /// identified by this fingerprint.
441    fingerprint: Option<String>,
442
443    /// Blocked state. Use contact_is_blocked to access this field.
444    pub blocked: bool,
445
446    /// Time when the contact was seen last time, Unix time in seconds.
447    last_seen: i64,
448
449    /// The origin/source of the contact.
450    pub origin: Origin,
451
452    /// Parameters as Param::ProfileImage
453    pub param: Params,
454
455    /// Last seen message signature for this contact, to be displayed in the profile.
456    status: String,
457
458    /// If the contact is a bot.
459    is_bot: bool,
460}
461
462/// Possible origins of a contact.
463#[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    /// Unknown origin. Can be used as a minimum origin to specify that the caller does not care
480    /// about origin of the contact.
481    #[default]
482    Unknown = 0,
483
484    /// The contact is a mailing list address, needed to unblock mailing lists
485    MailinglistAddress = 0x2,
486
487    /// Hidden on purpose, e.g. addresses with the word "noreply" in it
488    Hidden = 0x8,
489
490    /// From: of incoming messages of unknown sender
491    IncomingUnknownFrom = 0x10,
492
493    /// Cc: of incoming messages of unknown sender
494    IncomingUnknownCc = 0x20,
495
496    /// To: of incoming messages of unknown sender
497    IncomingUnknownTo = 0x40,
498
499    /// Address scanned but not verified.
500    UnhandledQrScan = 0x80,
501
502    /// Address scanned from a SecureJoin QR code, but not verified yet.
503    UnhandledSecurejoinQrScan = 0x81,
504
505    /// Reply-To: of incoming message of known sender
506    /// Contacts with at least this origin value are shown in the contact list.
507    IncomingReplyTo = 0x100,
508
509    /// Cc: of incoming message of known sender
510    IncomingCc = 0x200,
511
512    /// additional To:'s of incoming message of known sender
513    IncomingTo = 0x400,
514
515    /// a chat was manually created for this user, but no message yet sent
516    CreateChat = 0x800,
517
518    /// message sent by us
519    OutgoingBcc = 0x1000,
520
521    /// message sent by us
522    OutgoingCc = 0x2000,
523
524    /// message sent by us
525    OutgoingTo = 0x4000,
526
527    /// internal use
528    Internal = 0x40000,
529
530    /// address is in our address book
531    AddressBook = 0x80000,
532
533    /// set on Alice's side for contacts like Bob that have scanned the QR code offered by her. Only means the contact has once been established using the "securejoin" procedure in the past, getting the current key verification status requires calling contact_is_verified() !
534    SecurejoinInvited = 0x0100_0000,
535
536    /// Set on Bob's side for contacts scanned from a QR code.
537    /// Only means the contact has been scanned from the QR code,
538    /// but does not mean that securejoin succeeded
539    /// or the key has not changed since the last scan.
540    /// Getting the current key verification status requires calling contact_is_verified() !
541    SecurejoinJoined = 0x0200_0000,
542
543    /// contact added manually by create_contact(), this should be the largest origin as otherwise the user cannot modify the names
544    ManuallyCreated = 0x0400_0000,
545}
546
547impl Origin {
548    /// Contacts that are known, i. e. they came in via accepted contacts or
549    /// themselves an accepted contact. Known contacts are shown in the
550    /// contact list when one creates a chat and wants to add members etc.
551    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    /// Loads a single contact object from the database.
565    ///
566    /// Returns an error if the contact does not exist.
567    ///
568    /// For contact ContactId::SELF (1), the function returns sth.
569    /// like "Me" in the selected language and the email address
570    /// defined by set_config().
571    ///
572    /// For contact ContactId::DEVICE, the function overrides
573    /// the contact name and status with localized address.
574    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    /// Loads a single contact object from the database.
582    ///
583    /// Similar to [`Contact::get_by_id()`] but returns `None` if the contact does not exist.
584    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    /// Returns `true` if this contact is blocked.
655    pub fn is_blocked(&self) -> bool {
656        self.blocked
657    }
658
659    /// Returns last seen timestamp.
660    pub fn last_seen(&self) -> i64 {
661        self.last_seen
662    }
663
664    /// Returns `true` if this contact was seen recently.
665    pub fn was_seen_recently(&self) -> bool {
666        time() - self.last_seen <= SEEN_RECENTLY_SECONDS
667    }
668
669    /// Check if a contact is blocked.
670    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    /// Block the given contact.
682    pub async fn block(context: &Context, id: ContactId) -> Result<()> {
683        set_blocked(context, Sync, id, true).await
684    }
685
686    /// Unblock the given contact.
687    pub async fn unblock(context: &Context, id: ContactId) -> Result<()> {
688        set_blocked(context, Sync, id, false).await
689    }
690
691    /// Add a single contact as a result of an _explicit_ user action.
692    ///
693    /// We assume, the contact name, if any, is entered by the user and is used "as is" therefore,
694    /// normalize() is *not* called for the name. If the contact is blocked, it is unblocked.
695    ///
696    /// To add a number of contacts, see `add_address_book()` which is much faster for adding
697    /// a bunch of addresses.
698    ///
699    /// May result in a `#DC_EVENT_CONTACTS_CHANGED` event.
700    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    /// Mark messages from a contact as noticed.
742    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    /// Returns whether contact is a bot.
754    pub fn is_bot(&self) -> bool {
755        self.is_bot
756    }
757
758    /// Looks up a known and unblocked contact with a given e-mail address.
759    /// To get a list of all known and unblocked contacts, use contacts_get_contacts().
760    ///
761    ///
762    /// **POTENTIAL SECURITY ISSUE**: If there are multiple contacts with this address
763    /// (e.g. an address-contact and a key-contact),
764    /// this looks up the most recently seen contact,
765    /// i.e. which contact is returned depends on which contact last sent a message.
766    /// If the user just clicked on a mailto: link, then this is the best thing you can do.
767    /// But **DO NOT** internally represent contacts by their email address
768    /// and do not use this function to look them up;
769    /// otherwise this function will sometimes look up the wrong contact.
770    /// Instead, you should internally represent contacts by their ids.
771    ///
772    /// Known and unblocked contacts will be returned by `get_contacts()`.
773    ///
774    /// To validate an e-mail address independently of the contact database
775    /// use `may_be_valid_addr()`.
776    ///
777    /// Returns the contact ID of the contact belonging to the e-mail address or 0 if there is no
778    /// contact that is or was introduced by an accepted contact.
779    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    /// The same as `lookup_id_by_addr()`, but internal function. Currently also allows looking up
788    /// not unblocked contacts.
789    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    /// Lookup a contact and create it if it does not exist yet.
848    /// If `fingerprint` is non-empty, a key-contact with this fingerprint is added / looked up.
849    /// Otherwise, an address-contact with `addr` is added / looked up.
850    /// A name and an "origin" can be given.
851    ///
852    /// The "origin" is where the address comes from -
853    /// from-header, cc-header, addressbook, qr, manual-edit etc.
854    /// In general, "better" origins overwrite the names of "worse" origins -
855    /// Eg. if we got a name in cc-header and later in from-header, the name will change -
856    /// this does not happen the other way round.
857    ///
858    /// The "best" origin are manually created contacts -
859    /// names given manually can only be overwritten by further manual edits
860    /// (until they are set empty again or reset to the name seen in the From-header).
861    ///
862    /// These manually edited names are _never_ used for sending on the wire -
863    /// this should avoid sending sth. as "Mama" or "Daddy" to some 3rd party.
864    /// Instead, for the wire, we use so called "authnames"
865    /// that can only be set and updated by a From-header.
866    ///
867    /// The different names used in the function are:
868    /// - "name": name passed as function argument, belonging to the given origin
869    /// - "row_name": current name used in the database, typically set to "name"
870    /// - "row_authname": name as authorized from a contact, set only through a From-header
871    ///   Depending on the origin, both, "row_name" and "row_authname" are updated from "name".
872    ///
873    /// Returns the contact_id and a `Modifier` value indicating if a modification occurred.
874    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            // The user may accidentally have written to a "noreply" address with another MUA:
905            if addr.contains("noreply")
906                || addr.contains("no-reply")
907                || addr.starts_with("notifications@")
908                // Filter out use-once addresses (like reply+AEJDGPOECLAP...@reply.github.com):
909                || (addr.len() > 50 && addr.contains('+'))
910            {
911                info!(context, "hiding contact {}", addr);
912                origin = Origin::Hidden;
913                // For these kind of email addresses, sender and address often don't belong together
914                // (like hocuri <notifications@github.com>). In this example, hocuri shouldn't
915                // be saved as the displayname for notifications@github.com.
916                name = "".to_string();
917            }
918        }
919
920        // If the origin indicates that user entered the contact manually, from the address book or
921        // from the QR-code scan (potentially from the address book of their other phone), then name
922        // should go into the "name" column and never into "authname" column, to avoid leaking it
923        // into the network.
924        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    /// Add a number of contacts.
1040    ///
1041    /// Typically used to add the whole address book from the OS. As names here are typically not
1042    /// well formatted, we call `normalize()` for each name given.
1043    ///
1044    /// No email-address is added twice.
1045    /// Trying to add email-addresses that are already in the contact list,
1046    /// results in updating the name unless the name was changed manually by the user.
1047    /// If any email-address or any name is really updated,
1048    /// the event `DC_EVENT_CONTACTS_CHANGED` is sent.
1049    ///
1050    /// To add a single contact entered by the user, you should prefer `Contact::create`,
1051    /// however, for adding a bunch of addresses, this function is much faster.
1052    ///
1053    /// The `addr_book` is a multiline string in the format `Name one\nAddress one\nName two\nAddress two`.
1054    ///
1055    /// Returns the number of modified contacts.
1056    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    /// Returns known and unblocked contacts.
1090    ///
1091    /// To get information about a single contact, see get_contact().
1092    /// By default, key-contacts are listed.
1093    ///
1094    /// * `listflags` - A combination of flags:
1095    ///   - `DC_GCL_ADD_SELF` - Add SELF unless filtered by other parameters.
1096    ///   - `DC_GCL_ADDRESS` - List address-contacts instead of key-contacts.
1097    /// * `query` - A string to filter the list.
1098    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    /// Adds blocked mailinglists and broadcast channels as pseudo-contacts
1212    /// to allow unblocking them as if they are contacts
1213    /// (this way, only one unblock-ffi is needed and only one set of ui-functions,
1214    /// from the users perspective,
1215    /// there is not much difference in an email- and a mailinglist-address)
1216    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                        // Set some fingerprint so that is_pgp_contact() returns true,
1248                        // and the contact isn't marked with a letter icon.
1249                        "Blocked_broadcast"
1250                    } else {
1251                        ""
1252                    };
1253                    // Always do an update in case the blocking is reset or name is changed.
1254                    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    /// Returns number of blocked contacts.
1266    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    /// Get blocked contacts.
1278    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    /// Returns a textual summary of the encryption state for the contact.
1299    ///
1300    /// This function returns a string explaining the encryption state
1301    /// of the contact and if the connection is encrypted the
1302    /// fingerprints of the keys involved.
1303    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    /// Delete a contact so that it disappears from the corresponding lists.
1365    /// Depending on whether there are ongoing chats, deletion is done by physical deletion or hiding.
1366    /// The contact is deleted from the local device.
1367    ///
1368    /// May result in a `#DC_EVENT_CONTACTS_CHANGED` event.
1369    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                // make sure, the transaction starts with a write command and becomes EXCLUSIVE by that -
1376                // upgrading later may be impossible by races.
1377                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    /// Updates `param` column in the database.
1397    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    /// Updates `status` column in the database.
1409    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    /// Get the ID of the contact.
1421    pub fn get_id(&self) -> ContactId {
1422        self.id
1423    }
1424
1425    /// Get email address. The email address is always set for a contact.
1426    pub fn get_addr(&self) -> &str {
1427        &self.addr
1428    }
1429
1430    /// Returns true if the contact is a key-contact.
1431    /// Otherwise it is an addresss-contact.
1432    pub fn is_key_contact(&self) -> bool {
1433        self.fingerprint.is_some()
1434    }
1435
1436    /// Returns OpenPGP fingerprint of a contact.
1437    ///
1438    /// `None` for address-contacts.
1439    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    /// Returns OpenPGP public key of a contact.
1448    ///
1449    /// Returns `None` if the contact is not a key-contact
1450    /// or if the key is not available.
1451    /// It is possible for a key-contact to not have a key,
1452    /// e.g. if only the fingerprint is known from a QR-code.
1453    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    /// Get name authorized by the contact.
1484    pub fn get_authname(&self) -> &str {
1485        &self.authname
1486    }
1487
1488    /// Get the contact name. This is the name as modified by the local user.
1489    /// May be an empty string.
1490    ///
1491    /// This name is typically used in a form where the user can edit the name of a contact.
1492    /// To get a fine name to display in lists etc., use `Contact::get_display_name` or `Contact::get_name_n_addr`.
1493    pub fn get_name(&self) -> &str {
1494        &self.name
1495    }
1496
1497    /// Get display name. This is the name as defined by the contact himself,
1498    /// modified by the user or, if both are unset, the email address.
1499    ///
1500    /// This name is typically used in lists.
1501    /// To get the name editable in a formular, use `Contact::get_name`.
1502    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    /// Get authorized name or address.
1513    ///
1514    /// This string is suitable for sending over email
1515    /// as it does not leak the locally set name.
1516    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    /// Get a summary of name and address.
1525    ///
1526    /// The returned string is either "Name (email@domain.com)" or just
1527    /// "email@domain.com" if the name is unset.
1528    ///
1529    /// The result should only be used locally and never sent over the network
1530    /// as it leaks the local contact name.
1531    ///
1532    /// The summary is typically used when asking the user something about the contact.
1533    /// The attached email address makes the question unique, eg. "Chat with Alan Miller (am@uniquedomain.com)?"
1534    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    /// Get the contact's profile image.
1545    /// This is the image set by each remote user on their own
1546    /// using set_config(context, "selfavatar", image).
1547    pub async fn get_profile_image(&self, context: &Context) -> Result<Option<PathBuf>> {
1548        self.get_profile_image_ex(context, true).await
1549    }
1550
1551    /// Get the contact's profile image.
1552    /// This is the image set by each remote user on their own
1553    /// using set_config(context, "selfavatar", image).
1554    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))); // get_config() calls get_abs_path() internally already
1562            }
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    /// Get a color for the contact.
1578    /// The color is calculated from the contact's email address
1579    /// and can be used for an fallback avatar with white initials
1580    /// as well as for headlines in bubbles of group chats.
1581    pub fn get_color(&self) -> u32 {
1582        str_to_color(&self.addr.to_lowercase())
1583    }
1584
1585    /// Gets the contact's status.
1586    ///
1587    /// Status is the last signature received in a message from this contact.
1588    pub fn get_status(&self) -> &str {
1589        self.status.as_str()
1590    }
1591
1592    /// Returns whether end-to-end encryption to the contact is available.
1593    pub async fn e2ee_avail(&self, context: &Context) -> Result<bool> {
1594        if self.id == ContactId::SELF {
1595            // We don't need to check if we have our own key.
1596            return Ok(true);
1597        }
1598        Ok(self.public_key(context).await?.is_some())
1599    }
1600
1601    /// Returns true if the contact
1602    /// can be added to verified chats.
1603    ///
1604    /// If contact is verified
1605    /// UI should display green checkmark after the contact name
1606    /// in contact list items and
1607    /// in chat member list items.
1608    ///
1609    /// In contact profile view, use this function only if there is no chat with the contact,
1610    /// otherwise use is_chat_protected().
1611    /// Use [Self::get_verifier_id] to display the verifier contact
1612    /// in the info section of the contact profile.
1613    pub async fn is_verified(&self, context: &Context) -> Result<bool> {
1614        // We're always sort of secured-verified as we could verify the key on this device any time with the key
1615        // on this device
1616        if self.id == ContactId::SELF {
1617            return Ok(true);
1618        }
1619
1620        Ok(self.get_verifier_id(context).await?.is_some())
1621    }
1622
1623    /// Returns the `ContactId` that verified the contact.
1624    ///
1625    /// If this returns Some(_),
1626    /// display green checkmark in the profile and "Introduced by ..." line
1627    /// with the name and address of the contact
1628    /// formatted by [Self::get_name_n_addr].
1629    ///
1630    /// If this returns `Some(None)`, then the contact is verified,
1631    /// but it's unclear by whom.
1632    pub async fn get_verifier_id(&self, context: &Context) -> Result<Option<Option<ContactId>>> {
1633        let verifier_id: u32 = context
1634            .sql
1635            .query_get_value("SELECT verifier FROM contacts WHERE id=?", (self.id,))
1636            .await?
1637            .with_context(|| format!("Contact {} does not exist", self.id))?;
1638
1639        if verifier_id == 0 {
1640            Ok(None)
1641        } else if verifier_id == self.id.to_u32() {
1642            Ok(Some(None))
1643        } else {
1644            Ok(Some(Some(ContactId::new(verifier_id))))
1645        }
1646    }
1647
1648    /// Returns if the contact profile title should display a green checkmark.
1649    ///
1650    /// This generally should be consistent with the 1:1 chat with the contact
1651    /// so 1:1 chat with the contact and the contact profile
1652    /// either both display the green checkmark or both don't display a green checkmark.
1653    ///
1654    /// UI often knows beforehand if a chat exists and can also call
1655    /// `chat.is_protected()` (if there is a chat)
1656    /// or `contact.is_verified()` (if there is no chat) directly.
1657    /// This is often easier and also skips some database calls.
1658    pub async fn is_profile_verified(&self, context: &Context) -> Result<bool> {
1659        let contact_id = self.id;
1660
1661        if let Some(ChatIdBlocked { id: chat_id, .. }) =
1662            ChatIdBlocked::lookup_by_contact(context, contact_id).await?
1663        {
1664            Ok(chat_id.is_protected(context).await? == ProtectionStatus::Protected)
1665        } else {
1666            // 1:1 chat does not exist.
1667            Ok(self.is_verified(context).await?)
1668        }
1669    }
1670
1671    /// Returns the number of real (i.e. non-special) contacts in the database.
1672    pub async fn get_real_cnt(context: &Context) -> Result<usize> {
1673        if !context.sql.is_open().await {
1674            return Ok(0);
1675        }
1676
1677        let count = context
1678            .sql
1679            .count(
1680                "SELECT COUNT(*) FROM contacts WHERE id>?;",
1681                (ContactId::LAST_SPECIAL,),
1682            )
1683            .await?;
1684        Ok(count)
1685    }
1686
1687    /// Returns true if a contact with this ID exists.
1688    pub async fn real_exists_by_id(context: &Context, contact_id: ContactId) -> Result<bool> {
1689        if contact_id.is_special() {
1690            return Ok(false);
1691        }
1692
1693        let exists = context
1694            .sql
1695            .exists("SELECT COUNT(*) FROM contacts WHERE id=?;", (contact_id,))
1696            .await?;
1697        Ok(exists)
1698    }
1699}
1700
1701// Updates the names of the chats which use the contact name.
1702//
1703// This is one of the few duplicated data, however, getting the chat list is easier this way.
1704fn update_chat_names(
1705    context: &Context,
1706    transaction: &rusqlite::Connection,
1707    contact_id: ContactId,
1708) -> Result<()> {
1709    let chat_id: Option<ChatId> = transaction.query_row(
1710            "SELECT id FROM chats WHERE type=? AND id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?)",
1711            (Chattype::Single, contact_id),
1712            |row| {
1713                let chat_id: ChatId = row.get(0)?;
1714                Ok(chat_id)
1715            }
1716        ).optional()?;
1717
1718    if let Some(chat_id) = chat_id {
1719        let (addr, name, authname) = transaction.query_row(
1720            "SELECT addr, name, authname
1721                     FROM contacts
1722                     WHERE id=?",
1723            (contact_id,),
1724            |row| {
1725                let addr: String = row.get(0)?;
1726                let name: String = row.get(1)?;
1727                let authname: String = row.get(2)?;
1728                Ok((addr, name, authname))
1729            },
1730        )?;
1731
1732        let chat_name = if !name.is_empty() {
1733            name
1734        } else if !authname.is_empty() {
1735            authname
1736        } else {
1737            addr
1738        };
1739
1740        let count = transaction.execute(
1741            "UPDATE chats SET name=?1 WHERE id=?2 AND name!=?1",
1742            (chat_name, chat_id),
1743        )?;
1744
1745        if count > 0 {
1746            // Chat name updated
1747            context.emit_event(EventType::ChatModified(chat_id));
1748            chatlist_events::emit_chatlist_items_changed_for_contact(context, contact_id);
1749        }
1750    }
1751
1752    Ok(())
1753}
1754
1755pub(crate) async fn set_blocked(
1756    context: &Context,
1757    sync: sync::Sync,
1758    contact_id: ContactId,
1759    new_blocking: bool,
1760) -> Result<()> {
1761    ensure!(
1762        !contact_id.is_special(),
1763        "Can't block special contact {}",
1764        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        // also (un)block all chats with _only_ this contact - we do not delete them to allow a
1778        // non-destructive blocking->unblocking.
1779        // (Maybe, beside normal chats (type=100) we should also block group chats with only this user.
1780        // However, I'm not sure about this point; it may be confusing if the user wants to add other people;
1781        // this would result in recreating the same group...)
1782        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        // also unblock mailinglist
1802        // if the contact is a mailinglist address explicitly created to allow unblocking
1803        if !new_blocking && contact.origin == Origin::MailinglistAddress {
1804            if let Some((chat_id, _, _)) =
1805                chat::get_chat_id_by_grpid(context, &contact.addr).await?
1806            {
1807                chat_id.unblock_ex(context, Nosync).await?;
1808            }
1809        }
1810
1811        if sync.into() {
1812            let action = match new_blocking {
1813                true => chat::SyncAction::Block,
1814                false => chat::SyncAction::Unblock,
1815            };
1816            let sync_id = if let Some(fingerprint) = contact.fingerprint() {
1817                chat::SyncId::ContactFingerprint(fingerprint.hex())
1818            } else {
1819                chat::SyncId::ContactAddr(contact.addr.clone())
1820            };
1821
1822            chat::sync(context, sync_id, action)
1823                .await
1824                .log_err(context)
1825                .ok();
1826        }
1827    }
1828
1829    chatlist_events::emit_chatlist_changed(context);
1830    Ok(())
1831}
1832
1833/// Set profile image for a contact.
1834///
1835/// The given profile image is expected to be already in the blob directory
1836/// as profile images can be set only by receiving messages, this should be always the case, however.
1837///
1838/// For contact SELF, the image is not saved in the contact-database but as Config::Selfavatar;
1839/// this typically happens if we see message with our own profile image.
1840pub(crate) async fn set_profile_image(
1841    context: &Context,
1842    contact_id: ContactId,
1843    profile_image: &AvatarAction,
1844    was_encrypted: bool,
1845) -> Result<()> {
1846    let mut contact = Contact::get_by_id(context, contact_id).await?;
1847    let changed = match profile_image {
1848        AvatarAction::Change(profile_image) => {
1849            if contact_id == ContactId::SELF {
1850                if was_encrypted {
1851                    context
1852                        .set_config_ex(Nosync, Config::Selfavatar, Some(profile_image))
1853                        .await?;
1854                } else {
1855                    info!(context, "Do not use unencrypted selfavatar.");
1856                }
1857            } else {
1858                contact.param.set(Param::ProfileImage, profile_image);
1859            }
1860            true
1861        }
1862        AvatarAction::Delete => {
1863            if contact_id == ContactId::SELF {
1864                if was_encrypted {
1865                    context
1866                        .set_config_ex(Nosync, Config::Selfavatar, None)
1867                        .await?;
1868                } else {
1869                    info!(context, "Do not use unencrypted selfavatar deletion.");
1870                }
1871            } else {
1872                contact.param.remove(Param::ProfileImage);
1873            }
1874            true
1875        }
1876    };
1877    if changed {
1878        contact.update_param(context).await?;
1879        context.emit_event(EventType::ContactsChanged(Some(contact_id)));
1880        chatlist_events::emit_chatlist_item_changed_for_contact_chat(context, contact_id).await;
1881    }
1882    Ok(())
1883}
1884
1885/// Sets contact status.
1886///
1887/// For contact SELF, the status is not saved in the contact table, but as Config::Selfstatus.  This
1888/// is only done if message is sent from Delta Chat and it is encrypted, to synchronize signature
1889/// between Delta Chat devices.
1890pub(crate) async fn set_status(
1891    context: &Context,
1892    contact_id: ContactId,
1893    status: String,
1894    encrypted: bool,
1895    has_chat_version: bool,
1896) -> Result<()> {
1897    if contact_id == ContactId::SELF {
1898        if encrypted && has_chat_version {
1899            context
1900                .set_config_ex(Nosync, Config::Selfstatus, Some(&status))
1901                .await?;
1902        }
1903    } else {
1904        let mut contact = Contact::get_by_id(context, contact_id).await?;
1905
1906        if contact.status != status {
1907            contact.status = status;
1908            contact.update_status(context).await?;
1909            context.emit_event(EventType::ContactsChanged(Some(contact_id)));
1910        }
1911    }
1912    Ok(())
1913}
1914
1915/// Updates last seen timestamp of the contact if it is earlier than the given `timestamp`.
1916pub(crate) async fn update_last_seen(
1917    context: &Context,
1918    contact_id: ContactId,
1919    timestamp: i64,
1920) -> Result<()> {
1921    ensure!(
1922        !contact_id.is_special(),
1923        "Can not update special contact last seen timestamp"
1924    );
1925
1926    if context
1927        .sql
1928        .execute(
1929            "UPDATE contacts SET last_seen = ?1 WHERE last_seen < ?1 AND id = ?2",
1930            (timestamp, contact_id),
1931        )
1932        .await?
1933        > 0
1934        && timestamp > time() - SEEN_RECENTLY_SECONDS
1935    {
1936        context.emit_event(EventType::ContactsChanged(Some(contact_id)));
1937        context
1938            .scheduler
1939            .interrupt_recently_seen(contact_id, timestamp)
1940            .await;
1941    }
1942    Ok(())
1943}
1944
1945/// Marks contact `contact_id` as verified by `verifier_id`.
1946pub(crate) async fn mark_contact_id_as_verified(
1947    context: &Context,
1948    contact_id: ContactId,
1949    verifier_id: ContactId,
1950) -> Result<()> {
1951    ensure_and_debug_assert_ne!(
1952        contact_id,
1953        verifier_id,
1954        "Contact cannot be verified by self",
1955    );
1956    context
1957        .sql
1958        .transaction(|transaction| {
1959            let contact_fingerprint: String = transaction.query_row(
1960                "SELECT fingerprint FROM contacts WHERE id=?",
1961                (contact_id,),
1962                |row| row.get(0),
1963            )?;
1964            if contact_fingerprint.is_empty() {
1965                bail!("Non-key-contact {contact_id} cannot be verified");
1966            }
1967            if verifier_id != ContactId::SELF {
1968                let verifier_fingerprint: String = transaction.query_row(
1969                    "SELECT fingerprint FROM contacts WHERE id=?",
1970                    (verifier_id,),
1971                    |row| row.get(0),
1972                )?;
1973                if verifier_fingerprint.is_empty() {
1974                    bail!(
1975                        "Contact {contact_id} cannot be verified by non-key-contact {verifier_id}"
1976                    );
1977                }
1978            }
1979            transaction.execute(
1980                "UPDATE contacts SET verifier=?1
1981                 WHERE id=?2 AND (verifier=0 OR ?1=?3)",
1982                (verifier_id, contact_id, ContactId::SELF),
1983            )?;
1984            Ok(())
1985        })
1986        .await?;
1987    Ok(())
1988}
1989
1990fn cat_fingerprint(ret: &mut String, name: &str, addr: &str, fingerprint: &str) {
1991    *ret += &format!("\n\n{name} ({addr}):\n{fingerprint}");
1992}
1993
1994fn split_address_book(book: &str) -> Vec<(&str, &str)> {
1995    book.lines()
1996        .collect::<Vec<&str>>()
1997        .chunks(2)
1998        .filter_map(|chunk| {
1999            let name = chunk.first()?;
2000            let addr = chunk.get(1)?;
2001            Some((*name, *addr))
2002        })
2003        .collect()
2004}
2005
2006#[derive(Debug)]
2007pub(crate) struct RecentlySeenInterrupt {
2008    contact_id: ContactId,
2009    timestamp: i64,
2010}
2011
2012#[derive(Debug)]
2013pub(crate) struct RecentlySeenLoop {
2014    /// Task running "recently seen" loop.
2015    handle: task::JoinHandle<()>,
2016
2017    interrupt_send: Sender<RecentlySeenInterrupt>,
2018}
2019
2020impl RecentlySeenLoop {
2021    pub(crate) fn new(context: Context) -> Self {
2022        let (interrupt_send, interrupt_recv) = channel::bounded(1);
2023
2024        let handle = task::spawn(Self::run(context, interrupt_recv));
2025        Self {
2026            handle,
2027            interrupt_send,
2028        }
2029    }
2030
2031    async fn run(context: Context, interrupt: Receiver<RecentlySeenInterrupt>) {
2032        type MyHeapElem = (Reverse<i64>, ContactId);
2033
2034        let now = SystemTime::now();
2035        let now_ts = now
2036            .duration_since(SystemTime::UNIX_EPOCH)
2037            .unwrap_or_default()
2038            .as_secs() as i64;
2039
2040        // Priority contains all recently seen sorted by the timestamp
2041        // when they become not recently seen.
2042        //
2043        // Initialize with contacts which are currently seen, but will
2044        // become unseen in the future.
2045        let mut unseen_queue: BinaryHeap<MyHeapElem> = context
2046            .sql
2047            .query_map(
2048                "SELECT id, last_seen FROM contacts
2049                 WHERE last_seen > ?",
2050                (now_ts - SEEN_RECENTLY_SECONDS,),
2051                |row| {
2052                    let contact_id: ContactId = row.get("id")?;
2053                    let last_seen: i64 = row.get("last_seen")?;
2054                    Ok((Reverse(last_seen + SEEN_RECENTLY_SECONDS), contact_id))
2055                },
2056                |rows| {
2057                    rows.collect::<std::result::Result<BinaryHeap<MyHeapElem>, _>>()
2058                        .map_err(Into::into)
2059                },
2060            )
2061            .await
2062            .unwrap_or_default();
2063
2064        loop {
2065            let now = SystemTime::now();
2066            let (until, contact_id) =
2067                if let Some((Reverse(timestamp), contact_id)) = unseen_queue.peek() {
2068                    (
2069                        UNIX_EPOCH
2070                            + Duration::from_secs((*timestamp).try_into().unwrap_or(u64::MAX))
2071                            + Duration::from_secs(1),
2072                        Some(contact_id),
2073                    )
2074                } else {
2075                    // Sleep for 24 hours.
2076                    (now + Duration::from_secs(86400), None)
2077                };
2078
2079            if let Ok(duration) = until.duration_since(now) {
2080                info!(
2081                    context,
2082                    "Recently seen loop waiting for {} or interrupt",
2083                    duration_to_str(duration)
2084                );
2085
2086                match timeout(duration, interrupt.recv()).await {
2087                    Err(_) => {
2088                        // Timeout, notify about contact.
2089                        if let Some(contact_id) = contact_id {
2090                            context.emit_event(EventType::ContactsChanged(Some(*contact_id)));
2091                            chatlist_events::emit_chatlist_item_changed_for_contact_chat(
2092                                &context,
2093                                *contact_id,
2094                            )
2095                            .await;
2096                            unseen_queue.pop();
2097                        }
2098                    }
2099                    Ok(Err(err)) => {
2100                        warn!(
2101                            context,
2102                            "Error receiving an interruption in recently seen loop: {}", err
2103                        );
2104                        // Maybe the sender side is closed.
2105                        // Terminate the loop to avoid looping indefinitely.
2106                        return;
2107                    }
2108                    Ok(Ok(RecentlySeenInterrupt {
2109                        contact_id,
2110                        timestamp,
2111                    })) => {
2112                        // Received an interrupt.
2113                        if contact_id != ContactId::UNDEFINED {
2114                            unseen_queue
2115                                .push((Reverse(timestamp + SEEN_RECENTLY_SECONDS), contact_id));
2116                        }
2117                    }
2118                }
2119            } else {
2120                info!(
2121                    context,
2122                    "Recently seen loop is not waiting, event is already due."
2123                );
2124
2125                // Event is already in the past.
2126                if let Some(contact_id) = contact_id {
2127                    context.emit_event(EventType::ContactsChanged(Some(*contact_id)));
2128                    chatlist_events::emit_chatlist_item_changed_for_contact_chat(
2129                        &context,
2130                        *contact_id,
2131                    )
2132                    .await;
2133                }
2134                unseen_queue.pop();
2135            }
2136        }
2137    }
2138
2139    pub(crate) fn try_interrupt(&self, contact_id: ContactId, timestamp: i64) {
2140        self.interrupt_send
2141            .try_send(RecentlySeenInterrupt {
2142                contact_id,
2143                timestamp,
2144            })
2145            .ok();
2146    }
2147
2148    #[cfg(test)]
2149    pub(crate) async fn interrupt(&self, contact_id: ContactId, timestamp: i64) {
2150        self.interrupt_send
2151            .send(RecentlySeenInterrupt {
2152                contact_id,
2153                timestamp,
2154            })
2155            .await
2156            .unwrap();
2157    }
2158
2159    pub(crate) async fn abort(self) {
2160        self.handle.abort();
2161
2162        // Await aborted task to ensure the `Future` is dropped
2163        // with all resources moved inside such as the `Context`
2164        // reference to `InnerContext`.
2165        self.handle.await.ok();
2166    }
2167}
2168
2169#[cfg(test)]
2170mod contact_tests;