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;
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
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            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        // Currently this value doesn't matter as we don't import the contact of self.
386        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/// An object representing a single contact in memory.
408///
409/// The contact object is not updated.
410/// If you want an update, you have to recreate the object.
411///
412/// The library makes sure
413/// only to use names _authorized_ by the contact in `To:` or `Cc:`.
414/// *Given-names* as "Daddy" or "Honey" are not used there.
415/// For this purpose, internally, two names are tracked -
416/// authorized name and given name.
417/// By default, these names are equal, but functions working with contact names
418/// only affect the given name.
419#[derive(Debug)]
420pub struct Contact {
421    /// The contact ID.
422    pub id: ContactId,
423
424    /// Contact name. It is recommended to use `Contact::get_name`,
425    /// `Contact::get_display_name` or `Contact::get_name_n_addr` to access this field.
426    /// May be empty, initially set to `authname`.
427    name: String,
428
429    /// Name authorized by the contact himself. Only this name may be spread to others,
430    /// e.g. in To:-lists. May be empty. It is recommended to use `Contact::get_authname`,
431    /// to access this field.
432    authname: String,
433
434    /// E-Mail-Address of the contact. It is recommended to use `Contact::get_addr` to access this field.
435    addr: String,
436
437    /// OpenPGP key fingerprint.
438    /// Non-empty iff the contact is a key-contact,
439    /// identified by this fingerprint.
440    fingerprint: Option<String>,
441
442    /// Blocked state. Use contact_is_blocked to access this field.
443    pub blocked: bool,
444
445    /// Time when the contact was seen last time, Unix time in seconds.
446    last_seen: i64,
447
448    /// The origin/source of the contact.
449    pub origin: Origin,
450
451    /// Parameters as Param::ProfileImage
452    pub param: Params,
453
454    /// Last seen message signature for this contact, to be displayed in the profile.
455    status: String,
456
457    /// If the contact is a bot.
458    is_bot: bool,
459}
460
461/// Possible origins of a contact.
462#[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    /// Unknown origin. Can be used as a minimum origin to specify that the caller does not care
479    /// about origin of the contact.
480    #[default]
481    Unknown = 0,
482
483    /// The contact is a mailing list address, needed to unblock mailing lists
484    MailinglistAddress = 0x2,
485
486    /// Hidden on purpose, e.g. addresses with the word "noreply" in it
487    Hidden = 0x8,
488
489    /// From: of incoming messages of unknown sender
490    IncomingUnknownFrom = 0x10,
491
492    /// Cc: of incoming messages of unknown sender
493    IncomingUnknownCc = 0x20,
494
495    /// To: of incoming messages of unknown sender
496    IncomingUnknownTo = 0x40,
497
498    /// Address scanned but not verified.
499    UnhandledQrScan = 0x80,
500
501    /// Address scanned from a SecureJoin QR code, but not verified yet.
502    UnhandledSecurejoinQrScan = 0x81,
503
504    /// Reply-To: of incoming message of known sender
505    /// Contacts with at least this origin value are shown in the contact list.
506    IncomingReplyTo = 0x100,
507
508    /// Cc: of incoming message of known sender
509    IncomingCc = 0x200,
510
511    /// additional To:'s of incoming message of known sender
512    IncomingTo = 0x400,
513
514    /// a chat was manually created for this user, but no message yet sent
515    CreateChat = 0x800,
516
517    /// message sent by us
518    OutgoingBcc = 0x1000,
519
520    /// message sent by us
521    OutgoingCc = 0x2000,
522
523    /// message sent by us
524    OutgoingTo = 0x4000,
525
526    /// internal use
527    Internal = 0x40000,
528
529    /// address is in our address book
530    AddressBook = 0x80000,
531
532    /// 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() !
533    SecurejoinInvited = 0x0100_0000,
534
535    /// Set on Bob's side for contacts scanned from a QR code.
536    /// Only means the contact has been scanned from the QR code,
537    /// but does not mean that securejoin succeeded
538    /// or the key has not changed since the last scan.
539    /// Getting the current key verification status requires calling contact_is_verified() !
540    SecurejoinJoined = 0x0200_0000,
541
542    /// contact added manually by create_contact(), this should be the largest origin as otherwise the user cannot modify the names
543    ManuallyCreated = 0x0400_0000,
544}
545
546impl Origin {
547    /// Contacts that are known, i. e. they came in via accepted contacts or
548    /// themselves an accepted contact. Known contacts are shown in the
549    /// contact list when one creates a chat and wants to add members etc.
550    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    /// Loads a single contact object from the database.
564    ///
565    /// Returns an error if the contact does not exist.
566    ///
567    /// For contact ContactId::SELF (1), the function returns sth.
568    /// like "Me" in the selected language and the email address
569    /// defined by set_config().
570    ///
571    /// For contact ContactId::DEVICE, the function overrides
572    /// the contact name and status with localized address.
573    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    /// Loads a single contact object from the database.
581    ///
582    /// Similar to [`Contact::get_by_id()`] but returns `None` if the contact does not exist.
583    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    /// Returns `true` if this contact is blocked.
654    pub fn is_blocked(&self) -> bool {
655        self.blocked
656    }
657
658    /// Returns last seen timestamp.
659    pub fn last_seen(&self) -> i64 {
660        self.last_seen
661    }
662
663    /// Returns `true` if this contact was seen recently.
664    pub fn was_seen_recently(&self) -> bool {
665        time() - self.last_seen <= SEEN_RECENTLY_SECONDS
666    }
667
668    /// Check if a contact is blocked.
669    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    /// Block the given contact.
681    pub async fn block(context: &Context, id: ContactId) -> Result<()> {
682        set_blocked(context, Sync, id, true).await
683    }
684
685    /// Unblock the given contact.
686    pub async fn unblock(context: &Context, id: ContactId) -> Result<()> {
687        set_blocked(context, Sync, id, false).await
688    }
689
690    /// Add a single contact as a result of an _explicit_ user action.
691    ///
692    /// We assume, the contact name, if any, is entered by the user and is used "as is" therefore,
693    /// normalize() is *not* called for the name. If the contact is blocked, it is unblocked.
694    ///
695    /// To add a number of contacts, see `add_address_book()` which is much faster for adding
696    /// a bunch of addresses.
697    ///
698    /// May result in a `#DC_EVENT_CONTACTS_CHANGED` event.
699    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    /// Mark messages from a contact as noticed.
741    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    /// Returns whether contact is a bot.
753    pub fn is_bot(&self) -> bool {
754        self.is_bot
755    }
756
757    /// Looks up a known and unblocked contact with a given e-mail address.
758    /// To get a list of all known and unblocked contacts, use contacts_get_contacts().
759    ///
760    ///
761    /// **POTENTIAL SECURITY ISSUE**: If there are multiple contacts with this address
762    /// (e.g. an address-contact and a key-contact),
763    /// this looks up the most recently seen contact,
764    /// i.e. which contact is returned depends on which contact last sent a message.
765    /// If the user just clicked on a mailto: link, then this is the best thing you can do.
766    /// But **DO NOT** internally represent contacts by their email address
767    /// and do not use this function to look them up;
768    /// otherwise this function will sometimes look up the wrong contact.
769    /// Instead, you should internally represent contacts by their ids.
770    ///
771    /// Known and unblocked contacts will be returned by `get_contacts()`.
772    ///
773    /// To validate an e-mail address independently of the contact database
774    /// use `may_be_valid_addr()`.
775    ///
776    /// Returns the contact ID of the contact belonging to the e-mail address or 0 if there is no
777    /// contact that is or was introduced by an accepted contact.
778    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    /// The same as `lookup_id_by_addr()`, but internal function. Currently also allows looking up
787    /// not unblocked contacts.
788    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    /// Lookup a contact and create it if it does not exist yet.
847    /// If `fingerprint` is non-empty, a key-contact with this fingerprint is added / looked up.
848    /// Otherwise, an address-contact with `addr` is added / looked up.
849    /// A name and an "origin" can be given.
850    ///
851    /// The "origin" is where the address comes from -
852    /// from-header, cc-header, addressbook, qr, manual-edit etc.
853    /// In general, "better" origins overwrite the names of "worse" origins -
854    /// Eg. if we got a name in cc-header and later in from-header, the name will change -
855    /// this does not happen the other way round.
856    ///
857    /// The "best" origin are manually created contacts -
858    /// names given manually can only be overwritten by further manual edits
859    /// (until they are set empty again or reset to the name seen in the From-header).
860    ///
861    /// These manually edited names are _never_ used for sending on the wire -
862    /// this should avoid sending sth. as "Mama" or "Daddy" to some 3rd party.
863    /// Instead, for the wire, we use so called "authnames"
864    /// that can only be set and updated by a From-header.
865    ///
866    /// The different names used in the function are:
867    /// - "name": name passed as function argument, belonging to the given origin
868    /// - "row_name": current name used in the database, typically set to "name"
869    /// - "row_authname": name as authorized from a contact, set only through a From-header
870    ///   Depending on the origin, both, "row_name" and "row_authname" are updated from "name".
871    ///
872    /// Returns the contact_id and a `Modifier` value indicating if a modification occurred.
873    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            // The user may accidentally have written to a "noreply" address with another MUA:
904            if addr.contains("noreply")
905                || addr.contains("no-reply")
906                || addr.starts_with("notifications@")
907                // Filter out use-once addresses (like reply+AEJDGPOECLAP...@reply.github.com):
908                || (addr.len() > 50 && addr.contains('+'))
909            {
910                info!(context, "hiding contact {}", addr);
911                origin = Origin::Hidden;
912                // For these kind of email addresses, sender and address often don't belong together
913                // (like hocuri <notifications@github.com>). In this example, hocuri shouldn't
914                // be saved as the displayname for notifications@github.com.
915                name = "".to_string();
916            }
917        }
918
919        // If the origin indicates that user entered the contact manually, from the address book or
920        // from the QR-code scan (potentially from the address book of their other phone), then name
921        // should go into the "name" column and never into "authname" column, to avoid leaking it
922        // into the network.
923        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    /// Add a number of contacts.
1039    ///
1040    /// Typically used to add the whole address book from the OS. As names here are typically not
1041    /// well formatted, we call `normalize()` for each name given.
1042    ///
1043    /// No email-address is added twice.
1044    /// Trying to add email-addresses that are already in the contact list,
1045    /// results in updating the name unless the name was changed manually by the user.
1046    /// If any email-address or any name is really updated,
1047    /// the event `DC_EVENT_CONTACTS_CHANGED` is sent.
1048    ///
1049    /// To add a single contact entered by the user, you should prefer `Contact::create`,
1050    /// however, for adding a bunch of addresses, this function is much faster.
1051    ///
1052    /// The `addr_book` is a multiline string in the format `Name one\nAddress one\nName two\nAddress two`.
1053    ///
1054    /// Returns the number of modified contacts.
1055    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    /// Returns known and unblocked contacts.
1089    ///
1090    /// To get information about a single contact, see get_contact().
1091    /// By default, key-contacts are listed.
1092    ///
1093    /// * `listflags` - A combination of flags:
1094    ///   - `DC_GCL_ADD_SELF` - Add SELF unless filtered by other parameters.
1095    ///   - `DC_GCL_ADDRESS` - List address-contacts instead of key-contacts.
1096    /// * `query` - A string to filter the list.
1097    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    /// Adds blocked mailinglists and broadcast channels as pseudo-contacts
1211    /// to allow unblocking them as if they are contacts
1212    /// (this way, only one unblock-ffi is needed and only one set of ui-functions,
1213    /// from the users perspective,
1214    /// there is not much difference in an email- and a mailinglist-address)
1215    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                        // Set some fingerprint so that is_pgp_contact() returns true,
1247                        // and the contact isn't marked with a letter icon.
1248                        "Blocked_broadcast"
1249                    } else {
1250                        ""
1251                    };
1252                    // Always do an update in case the blocking is reset or name is changed.
1253                    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    /// Returns number of blocked contacts.
1265    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    /// Get blocked contacts.
1277    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    /// Returns a textual summary of the encryption state for the contact.
1297    ///
1298    /// This function returns a string explaining the encryption state
1299    /// of the contact and if the connection is encrypted the
1300    /// fingerprints of the keys involved.
1301    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    /// Delete a contact so that it disappears from the corresponding lists.
1363    /// Depending on whether there are ongoing chats, deletion is done by physical deletion or hiding.
1364    /// The contact is deleted from the local device.
1365    ///
1366    /// May result in a `#DC_EVENT_CONTACTS_CHANGED` event.
1367    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                // make sure, the transaction starts with a write command and becomes EXCLUSIVE by that -
1374                // upgrading later may be impossible by races.
1375                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    /// Updates `param` column in the database.
1395    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    /// Updates `status` column in the database.
1407    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    /// Get the ID of the contact.
1419    pub fn get_id(&self) -> ContactId {
1420        self.id
1421    }
1422
1423    /// Get email address. The email address is always set for a contact.
1424    pub fn get_addr(&self) -> &str {
1425        &self.addr
1426    }
1427
1428    /// Returns true if the contact is a key-contact.
1429    /// Otherwise it is an addresss-contact.
1430    pub fn is_key_contact(&self) -> bool {
1431        self.fingerprint.is_some()
1432    }
1433
1434    /// Returns OpenPGP fingerprint of a contact.
1435    ///
1436    /// `None` for address-contacts.
1437    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    /// Returns OpenPGP public key of a contact.
1446    ///
1447    /// Returns `None` if the contact is not a key-contact
1448    /// or if the key is not available.
1449    /// It is possible for a key-contact to not have a key,
1450    /// e.g. if only the fingerprint is known from a QR-code.
1451    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    /// Get name authorized by the contact.
1482    pub fn get_authname(&self) -> &str {
1483        &self.authname
1484    }
1485
1486    /// Get the contact name. This is the name as modified by the local user.
1487    /// May be an empty string.
1488    ///
1489    /// This name is typically used in a form where the user can edit the name of a contact.
1490    /// To get a fine name to display in lists etc., use `Contact::get_display_name` or `Contact::get_name_n_addr`.
1491    pub fn get_name(&self) -> &str {
1492        &self.name
1493    }
1494
1495    /// Get display name. This is the name as defined by the contact himself,
1496    /// modified by the user or, if both are unset, the email address.
1497    ///
1498    /// This name is typically used in lists.
1499    /// To get the name editable in a formular, use `Contact::get_name`.
1500    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    /// Get authorized name or address.
1511    ///
1512    /// This string is suitable for sending over email
1513    /// as it does not leak the locally set name.
1514    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    /// Get a summary of name and address.
1523    ///
1524    /// The returned string is either "Name (email@domain.com)" or just
1525    /// "email@domain.com" if the name is unset.
1526    ///
1527    /// The result should only be used locally and never sent over the network
1528    /// as it leaks the local contact name.
1529    ///
1530    /// The summary is typically used when asking the user something about the contact.
1531    /// The attached email address makes the question unique, eg. "Chat with Alan Miller (am@uniquedomain.com)?"
1532    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    /// Get the contact's profile image.
1543    /// This is the image set by each remote user on their own
1544    /// using set_config(context, "selfavatar", image).
1545    pub async fn get_profile_image(&self, context: &Context) -> Result<Option<PathBuf>> {
1546        self.get_profile_image_ex(context, true).await
1547    }
1548
1549    /// Get the contact's profile image.
1550    /// This is the image set by each remote user on their own
1551    /// using set_config(context, "selfavatar", image).
1552    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))); // get_config() calls get_abs_path() internally already
1560            }
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    /// Returns a color for the contact.
1576    /// For self-contact this returns gray if own keypair doesn't exist yet.
1577    /// See also [`self::get_color`].
1578    pub fn get_color(&self) -> u32 {
1579        get_color(self.id == ContactId::SELF, &self.addr, &self.fingerprint())
1580    }
1581
1582    /// Returns a color for the contact.
1583    /// Ensures that the color isn't gray. For self-contact this generates own keypair if it doesn't
1584    /// exist yet.
1585    /// See also [`self::get_color`].
1586    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    /// Gets the contact's status.
1595    ///
1596    /// Status is the last signature received in a message from this contact.
1597    pub fn get_status(&self) -> &str {
1598        self.status.as_str()
1599    }
1600
1601    /// Returns whether end-to-end encryption to the contact is available.
1602    pub async fn e2ee_avail(&self, context: &Context) -> Result<bool> {
1603        if self.id == ContactId::SELF {
1604            // We don't need to check if we have our own key.
1605            return Ok(true);
1606        }
1607        Ok(self.public_key(context).await?.is_some())
1608    }
1609
1610    /// Returns true if the contact
1611    /// can be added to verified chats.
1612    ///
1613    /// If contact is verified
1614    /// UI should display green checkmark after the contact name
1615    /// in contact list items and
1616    /// in chat member list items.
1617    ///
1618    /// In contact profile view, use this function only if there is no chat with the contact,
1619    /// otherwise use is_chat_protected().
1620    /// Use [Self::get_verifier_id] to display the verifier contact
1621    /// in the info section of the contact profile.
1622    pub async fn is_verified(&self, context: &Context) -> Result<bool> {
1623        // We're always sort of secured-verified as we could verify the key on this device any time with the key
1624        // on this device
1625        if self.id == ContactId::SELF {
1626            return Ok(true);
1627        }
1628
1629        Ok(self.get_verifier_id(context).await?.is_some())
1630    }
1631
1632    /// Returns the `ContactId` that verified the contact.
1633    ///
1634    /// If this returns Some(_),
1635    /// display green checkmark in the profile and "Introduced by ..." line
1636    /// with the name and address of the contact
1637    /// formatted by [Self::get_name_n_addr].
1638    ///
1639    /// If this returns `Some(None)`, then the contact is verified,
1640    /// but it's unclear by whom.
1641    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    /// Returns the number of real (i.e. non-special) contacts in the database.
1658    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    /// Returns true if a contact with this ID exists.
1674    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
1687/// Returns a color for a contact having given attributes.
1688///
1689/// The color is calculated from contact's fingerprint (for key-contacts) or email address (for
1690/// address-contacts; should be lowercased to avoid allocation) and can be used for an fallback
1691/// avatar with white initials as well as for headlines in bubbles of group chats.
1692pub 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
1702// Updates the names of the chats which use the contact name.
1703//
1704// This is one of the few duplicated data, however, getting the chat list is easier this way.
1705fn 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            // Chat name updated
1748            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        // 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, ..)) = 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
1831/// Set profile image for a contact.
1832///
1833/// The given profile image is expected to be already in the blob directory
1834/// as profile images can be set only by receiving messages, this should be always the case, however.
1835///
1836/// For contact SELF, the image is not saved in the contact-database but as Config::Selfavatar;
1837/// this typically happens if we see message with our own profile image.
1838pub(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
1883/// Sets contact status.
1884///
1885/// For contact SELF, the status is not saved in the contact table, but as Config::Selfstatus.  This
1886/// is only done if message is sent from Delta Chat and it is encrypted, to synchronize signature
1887/// between Delta Chat devices.
1888pub(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
1913/// Updates last seen timestamp of the contact if it is earlier than the given `timestamp`.
1914pub(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
1943/// Marks contact `contact_id` as verified by `verifier_id`.
1944///
1945/// `verifier_id == None` means that the verifier is unknown.
1946pub(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                    // Avoid introducing incorrect reverse chains: if the verifier itself has an
1988                    // unknown verifier, it may be `contact_id` actually (directly or indirectly) on
1989                    // the other device (which is needed for getting "verified by unknown contact"
1990                    // in the first place).
1991                    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    /// Task running "recently seen" loop.
2030    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        // Priority contains all recently seen sorted by the timestamp
2056        // when they become not recently seen.
2057        //
2058        // Initialize with contacts which are currently seen, but will
2059        // become unseen in the future.
2060        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                    // Sleep for 24 hours.
2087                    (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                        // Timeout, notify about contact.
2100                        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                        // Maybe the sender side is closed.
2116                        // Terminate the loop to avoid looping indefinitely.
2117                        return;
2118                    }
2119                    Ok(Ok(RecentlySeenInterrupt {
2120                        contact_id,
2121                        timestamp,
2122                    })) => {
2123                        // Received an interrupt.
2124                        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                // Event is already in the past.
2137                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        // Await aborted task to ensure the `Future` is dropped
2174        // with all resources moved inside such as the `Context`
2175        // reference to `InnerContext`.
2176        self.handle.await.ok();
2177    }
2178}
2179
2180#[cfg(test)]
2181mod contact_tests;