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, normalize_text, 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 authname;
119                let name_or_authname = if !name.is_empty() {
120                    name
121                } else {
122                    authname = transaction.query_row(
123                        "SELECT authname FROM contacts WHERE id=?",
124                        (self,),
125                        |row| {
126                            let authname: String = row.get(0)?;
127                            Ok(authname)
128                        },
129                    )?;
130                    &authname
131                };
132                let is_changed = transaction.execute(
133                    "UPDATE contacts SET name=?1, name_normalized=?2 WHERE id=?3 AND name!=?1",
134                    (name, normalize_text(name_or_authname), self),
135                )? > 0;
136                if is_changed {
137                    update_chat_names(context, transaction, self)?;
138                    let (addr, fingerprint) = transaction.query_row(
139                        "SELECT addr, fingerprint FROM contacts WHERE id=?",
140                        (self,),
141                        |row| {
142                            let addr: String = row.get(0)?;
143                            let fingerprint: String = row.get(1)?;
144                            Ok((addr, fingerprint))
145                        },
146                    )?;
147                    Ok(Some((addr, fingerprint)))
148                } else {
149                    Ok(None)
150                }
151            })
152            .await?;
153        if row.is_some() {
154            context.emit_event(EventType::ContactsChanged(Some(self)));
155        }
156
157        if sync.into()
158            && let Some((addr, fingerprint)) = row
159        {
160            if fingerprint.is_empty() {
161                chat::sync(
162                    context,
163                    chat::SyncId::ContactAddr(addr),
164                    chat::SyncAction::Rename(name.to_string()),
165                )
166                .await
167                .log_err(context)
168                .ok();
169            } else {
170                chat::sync(
171                    context,
172                    chat::SyncId::ContactFingerprint(fingerprint),
173                    chat::SyncAction::Rename(name.to_string()),
174                )
175                .await
176                .log_err(context)
177                .ok();
178            }
179        }
180        Ok(())
181    }
182
183    /// Mark contact as bot.
184    pub(crate) async fn mark_bot(&self, context: &Context, is_bot: bool) -> Result<()> {
185        context
186            .sql
187            .execute("UPDATE contacts SET is_bot=? WHERE id=?;", (is_bot, self.0))
188            .await?;
189        Ok(())
190    }
191
192    /// Reset gossip timestamp in all chats with this contact.
193    pub(crate) async fn regossip_keys(&self, context: &Context) -> Result<()> {
194        context
195            .sql
196            .execute(
197                "UPDATE chats
198                 SET gossiped_timestamp=0
199                 WHERE EXISTS (SELECT 1 FROM chats_contacts
200                               WHERE chats_contacts.chat_id=chats.id
201                               AND chats_contacts.contact_id=?
202                               AND chats_contacts.add_timestamp >= chats_contacts.remove_timestamp)",
203                (self,),
204            )
205            .await?;
206        Ok(())
207    }
208
209    /// Updates the origin of the contacts, but only if `origin` is higher than the current one.
210    pub(crate) async fn scaleup_origin(
211        context: &Context,
212        ids: &[Self],
213        origin: Origin,
214    ) -> Result<()> {
215        context
216            .sql
217            .transaction(|transaction| {
218                let mut stmt = transaction
219                    .prepare("UPDATE contacts SET origin=?1 WHERE id = ?2 AND origin < ?1")?;
220                for id in ids {
221                    stmt.execute((origin, id))?;
222                }
223                Ok(())
224            })
225            .await?;
226        Ok(())
227    }
228
229    /// Returns contact address.
230    pub async fn addr(&self, context: &Context) -> Result<String> {
231        let addr = context
232            .sql
233            .query_row("SELECT addr FROM contacts WHERE id=?", (self,), |row| {
234                let addr: String = row.get(0)?;
235                Ok(addr)
236            })
237            .await?;
238        Ok(addr)
239    }
240}
241
242impl fmt::Display for ContactId {
243    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
244        if *self == ContactId::UNDEFINED {
245            write!(f, "Contact#Undefined")
246        } else if *self == ContactId::SELF {
247            write!(f, "Contact#Self")
248        } else if *self == ContactId::INFO {
249            write!(f, "Contact#Info")
250        } else if *self == ContactId::DEVICE {
251            write!(f, "Contact#Device")
252        } else if self.is_special() {
253            write!(f, "Contact#Special{}", self.0)
254        } else {
255            write!(f, "Contact#{}", self.0)
256        }
257    }
258}
259
260/// Allow converting [`ContactId`] to an SQLite type.
261impl rusqlite::types::ToSql for ContactId {
262    fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
263        let val = rusqlite::types::Value::Integer(i64::from(self.0));
264        let out = rusqlite::types::ToSqlOutput::Owned(val);
265        Ok(out)
266    }
267}
268
269/// Allow converting an SQLite integer directly into [`ContactId`].
270impl rusqlite::types::FromSql for ContactId {
271    fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
272        i64::column_result(value).and_then(|val| {
273            val.try_into()
274                .map(ContactId::new)
275                .map_err(|_| rusqlite::types::FromSqlError::OutOfRange(val))
276        })
277    }
278}
279
280/// Returns a vCard containing contacts with the given ids.
281pub async fn make_vcard(context: &Context, contacts: &[ContactId]) -> Result<String> {
282    let now = time();
283    let mut vcard_contacts = Vec::with_capacity(contacts.len());
284    for id in contacts {
285        let c = Contact::get_by_id(context, *id).await?;
286        let key = c.public_key(context).await?.map(|k| k.to_base64());
287        let profile_image = match c.get_profile_image_ex(context, false).await? {
288            None => None,
289            Some(path) => tokio::fs::read(path)
290                .await
291                .log_err(context)
292                .ok()
293                .map(|data| base64::engine::general_purpose::STANDARD.encode(data)),
294        };
295        vcard_contacts.push(VcardContact {
296            addr: c.addr,
297            authname: c.authname,
298            key,
299            profile_image,
300            biography: Some(c.status).filter(|s| !s.is_empty()),
301            // Use the current time to not reveal our or contact's online time.
302            timestamp: Ok(now),
303        });
304    }
305
306    // XXX: newline at the end of vCard is trimmed
307    // for compatibility with core <=1.155.3
308    // Newer core should be able to deal with
309    // trailing CRLF as the fix
310    // <https://github.com/deltachat/deltachat-core-rust/pull/6522>
311    // is merged.
312    Ok(contact_tools::make_vcard(&vcard_contacts)
313        .trim_end()
314        .to_string())
315}
316
317/// Imports contacts from the given vCard.
318///
319/// Returns the ids of successfully processed contacts in the order they appear in `vcard`,
320/// regardless of whether they are just created, modified or left untouched.
321pub async fn import_vcard(context: &Context, vcard: &str) -> Result<Vec<ContactId>> {
322    let contacts = contact_tools::parse_vcard(vcard);
323    let mut contact_ids = Vec::with_capacity(contacts.len());
324    for c in &contacts {
325        let Ok(id) = import_vcard_contact(context, c)
326            .await
327            .with_context(|| format!("import_vcard_contact() failed for {}", c.addr))
328            .log_err(context)
329        else {
330            continue;
331        };
332        contact_ids.push(id);
333    }
334    Ok(contact_ids)
335}
336
337async fn import_vcard_contact(context: &Context, contact: &VcardContact) -> Result<ContactId> {
338    let addr = ContactAddress::new(&contact.addr).context("Invalid address")?;
339    // Importing a vCard is also an explicit user action like creating a chat with the contact. We
340    // mustn't use `Origin::AddressBook` here because the vCard may be created not by us, also we
341    // want `contact.authname` to be saved as the authname and not a locally given name.
342    let origin = Origin::CreateChat;
343    let key = contact.key.as_ref().and_then(|k| {
344        SignedPublicKey::from_base64(k)
345            .with_context(|| {
346                format!(
347                    "import_vcard_contact: Cannot decode key for {}",
348                    contact.addr
349                )
350            })
351            .log_err(context)
352            .ok()
353    });
354
355    let fingerprint;
356    if let Some(public_key) = key {
357        fingerprint = public_key.dc_fingerprint().hex();
358
359        context
360            .sql
361            .execute(
362                "INSERT INTO public_keys (fingerprint, public_key)
363                 VALUES (?, ?)
364                 ON CONFLICT (fingerprint)
365                 DO NOTHING",
366                (&fingerprint, public_key.to_bytes()),
367            )
368            .await?;
369    } else {
370        fingerprint = String::new();
371    }
372
373    let (id, modified) =
374        match Contact::add_or_lookup_ex(context, &contact.authname, &addr, &fingerprint, origin)
375            .await
376        {
377            Err(e) => return Err(e).context("Contact::add_or_lookup() failed"),
378            Ok((ContactId::SELF, _)) => return Ok(ContactId::SELF),
379            Ok(val) => val,
380        };
381    if modified != Modifier::None {
382        context.emit_event(EventType::ContactsChanged(Some(id)));
383    }
384    if modified != Modifier::Created {
385        return Ok(id);
386    }
387    let path = match &contact.profile_image {
388        Some(image) => match BlobObject::store_from_base64(context, image)? {
389            None => {
390                warn!(
391                    context,
392                    "import_vcard_contact: Could not decode avatar for {}.", contact.addr
393                );
394                None
395            }
396            Some(path) => Some(path),
397        },
398        None => None,
399    };
400    if let Some(path) = path
401        && let Err(e) = set_profile_image(context, id, &AvatarAction::Change(path)).await
402    {
403        warn!(
404            context,
405            "import_vcard_contact: Could not set avatar for {}: {e:#}.", contact.addr
406        );
407    }
408    if let Some(biography) = &contact.biography
409        && let Err(e) = set_status(context, id, biography.to_owned()).await
410    {
411        warn!(
412            context,
413            "import_vcard_contact: Could not set biography for {}: {e:#}.", contact.addr
414        );
415    }
416    Ok(id)
417}
418
419/// An object representing a single contact in memory.
420///
421/// The contact object is not updated.
422/// If you want an update, you have to recreate the object.
423///
424/// The library makes sure
425/// only to use names _authorized_ by the contact in `To:` or `Cc:`.
426/// *Given-names* as "Daddy" or "Honey" are not used there.
427/// For this purpose, internally, two names are tracked -
428/// authorized name and given name.
429/// By default, these names are equal, but functions working with contact names
430/// only affect the given name.
431#[derive(Debug)]
432pub struct Contact {
433    /// The contact ID.
434    pub id: ContactId,
435
436    /// Contact name. It is recommended to use `Contact::get_name`,
437    /// `Contact::get_display_name` or `Contact::get_name_n_addr` to access this field.
438    /// May be empty, initially set to `authname`.
439    name: String,
440
441    /// Name authorized by the contact himself. Only this name may be spread to others,
442    /// e.g. in To:-lists. May be empty. It is recommended to use `Contact::get_authname`,
443    /// to access this field.
444    authname: String,
445
446    /// E-Mail-Address of the contact. It is recommended to use `Contact::get_addr` to access this field.
447    addr: String,
448
449    /// OpenPGP key fingerprint.
450    /// Non-empty iff the contact is a key-contact,
451    /// identified by this fingerprint.
452    fingerprint: Option<String>,
453
454    /// Blocked state. Use contact_is_blocked to access this field.
455    pub blocked: bool,
456
457    /// Time when the contact was seen last time, Unix time in seconds.
458    last_seen: i64,
459
460    /// The origin/source of the contact.
461    pub origin: Origin,
462
463    /// Parameters as Param::ProfileImage
464    pub param: Params,
465
466    /// Last seen message signature for this contact, to be displayed in the profile.
467    status: String,
468
469    /// If the contact is a bot.
470    is_bot: bool,
471}
472
473/// Possible origins of a contact.
474#[derive(
475    Debug,
476    Default,
477    Clone,
478    Copy,
479    PartialEq,
480    Eq,
481    PartialOrd,
482    Ord,
483    FromPrimitive,
484    ToPrimitive,
485    FromSql,
486    ToSql,
487)]
488#[repr(u32)]
489pub enum Origin {
490    /// Unknown origin. Can be used as a minimum origin to specify that the caller does not care
491    /// about origin of the contact.
492    #[default]
493    Unknown = 0,
494
495    /// The contact is a mailing list address, needed to unblock mailing lists
496    MailinglistAddress = 0x2,
497
498    /// Hidden on purpose, e.g. addresses with the word "noreply" in it
499    Hidden = 0x8,
500
501    /// From: of incoming messages of unknown sender
502    IncomingUnknownFrom = 0x10,
503
504    /// Cc: of incoming messages of unknown sender
505    IncomingUnknownCc = 0x20,
506
507    /// To: of incoming messages of unknown sender
508    IncomingUnknownTo = 0x40,
509
510    /// Address scanned but not verified.
511    UnhandledQrScan = 0x80,
512
513    /// Address scanned from a SecureJoin QR code, but not verified yet.
514    UnhandledSecurejoinQrScan = 0x81,
515
516    /// Reply-To: of incoming message of known sender
517    /// Contacts with at least this origin value are shown in the contact list.
518    IncomingReplyTo = 0x100,
519
520    /// Cc: of incoming message of known sender
521    IncomingCc = 0x200,
522
523    /// additional To:'s of incoming message of known sender
524    IncomingTo = 0x400,
525
526    /// a chat was manually created for this user, but no message yet sent
527    CreateChat = 0x800,
528
529    /// message sent by us
530    OutgoingBcc = 0x1000,
531
532    /// message sent by us
533    OutgoingCc = 0x2000,
534
535    /// message sent by us
536    OutgoingTo = 0x4000,
537
538    /// internal use
539    Internal = 0x40000,
540
541    /// address is in our address book
542    AddressBook = 0x80000,
543
544    /// 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() !
545    SecurejoinInvited = 0x0100_0000,
546
547    /// Set on Bob's side for contacts scanned from a QR code.
548    /// Only means the contact has been scanned from the QR code,
549    /// but does not mean that securejoin succeeded
550    /// or the key has not changed since the last scan.
551    /// Getting the current key verification status requires calling contact_is_verified() !
552    SecurejoinJoined = 0x0200_0000,
553
554    /// contact added manually by create_contact(), this should be the largest origin as otherwise the user cannot modify the names
555    ManuallyCreated = 0x0400_0000,
556}
557
558impl Origin {
559    /// Contacts that are known, i. e. they came in via accepted contacts or
560    /// themselves an accepted contact. Known contacts are shown in the
561    /// contact list when one creates a chat and wants to add members etc.
562    pub fn is_known(self) -> bool {
563        self >= Origin::IncomingReplyTo
564    }
565}
566
567#[derive(Debug, PartialEq, Eq, Clone, Copy)]
568pub(crate) enum Modifier {
569    None,
570    Modified,
571    Created,
572}
573
574impl Contact {
575    /// Loads a single contact object from the database.
576    ///
577    /// Returns an error if the contact does not exist.
578    ///
579    /// For contact ContactId::SELF (1), the function returns sth.
580    /// like "Me" in the selected language and the email address
581    /// defined by set_config().
582    ///
583    /// For contact ContactId::DEVICE, the function overrides
584    /// the contact name and status with localized address.
585    pub async fn get_by_id(context: &Context, contact_id: ContactId) -> Result<Self> {
586        let contact = Self::get_by_id_optional(context, contact_id)
587            .await?
588            .with_context(|| format!("contact {contact_id} not found"))?;
589        Ok(contact)
590    }
591
592    /// Loads a single contact object from the database.
593    ///
594    /// Similar to [`Contact::get_by_id()`] but returns `None` if the contact does not exist.
595    pub async fn get_by_id_optional(
596        context: &Context,
597        contact_id: ContactId,
598    ) -> Result<Option<Self>> {
599        if let Some(mut contact) = context
600            .sql
601            .query_row_optional(
602                "SELECT c.name, c.addr, c.origin, c.blocked, c.last_seen,
603                c.authname, c.param, c.status, c.is_bot, c.fingerprint
604               FROM contacts c
605              WHERE c.id=?;",
606                (contact_id,),
607                |row| {
608                    let name: String = row.get(0)?;
609                    let addr: String = row.get(1)?;
610                    let origin: Origin = row.get(2)?;
611                    let blocked: Option<bool> = row.get(3)?;
612                    let last_seen: i64 = row.get(4)?;
613                    let authname: String = row.get(5)?;
614                    let param: String = row.get(6)?;
615                    let status: Option<String> = row.get(7)?;
616                    let is_bot: bool = row.get(8)?;
617                    let fingerprint: Option<String> =
618                        Some(row.get(9)?).filter(|s: &String| !s.is_empty());
619                    let contact = Self {
620                        id: contact_id,
621                        name,
622                        authname,
623                        addr,
624                        fingerprint,
625                        blocked: blocked.unwrap_or_default(),
626                        last_seen,
627                        origin,
628                        param: param.parse().unwrap_or_default(),
629                        status: status.unwrap_or_default(),
630                        is_bot,
631                    };
632                    Ok(contact)
633                },
634            )
635            .await?
636        {
637            if contact_id == ContactId::SELF {
638                contact.name = stock_str::self_msg(context).await;
639                contact.authname = context
640                    .get_config(Config::Displayname)
641                    .await?
642                    .unwrap_or_default();
643                contact.addr = context
644                    .get_config(Config::ConfiguredAddr)
645                    .await?
646                    .unwrap_or_default();
647                if let Some(self_fp) = self_fingerprint_opt(context).await? {
648                    contact.fingerprint = Some(self_fp.to_string());
649                }
650                contact.status = context
651                    .get_config(Config::Selfstatus)
652                    .await?
653                    .unwrap_or_default();
654            } else if contact_id == ContactId::DEVICE {
655                contact.name = stock_str::device_messages(context).await;
656                contact.addr = ContactId::DEVICE_ADDR.to_string();
657                contact.status = stock_str::device_messages_hint(context).await;
658            }
659            Ok(Some(contact))
660        } else {
661            Ok(None)
662        }
663    }
664
665    /// Returns `true` if this contact is blocked.
666    pub fn is_blocked(&self) -> bool {
667        self.blocked
668    }
669
670    /// Returns last seen timestamp.
671    pub fn last_seen(&self) -> i64 {
672        self.last_seen
673    }
674
675    /// Returns `true` if this contact was seen recently.
676    pub fn was_seen_recently(&self) -> bool {
677        time() - self.last_seen <= SEEN_RECENTLY_SECONDS
678    }
679
680    /// Check if a contact is blocked.
681    pub async fn is_blocked_load(context: &Context, id: ContactId) -> Result<bool> {
682        let blocked = context
683            .sql
684            .query_row("SELECT blocked FROM contacts WHERE id=?", (id,), |row| {
685                let blocked: bool = row.get(0)?;
686                Ok(blocked)
687            })
688            .await?;
689        Ok(blocked)
690    }
691
692    /// Block the given contact.
693    pub async fn block(context: &Context, id: ContactId) -> Result<()> {
694        set_blocked(context, Sync, id, true).await
695    }
696
697    /// Unblock the given contact.
698    pub async fn unblock(context: &Context, id: ContactId) -> Result<()> {
699        set_blocked(context, Sync, id, false).await
700    }
701
702    /// Add a single contact as a result of an _explicit_ user action.
703    ///
704    /// We assume, the contact name, if any, is entered by the user and is used "as is" therefore,
705    /// normalize() is *not* called for the name. If the contact is blocked, it is unblocked.
706    ///
707    /// To add a number of contacts, see `add_address_book()` which is much faster for adding
708    /// a bunch of addresses.
709    ///
710    /// May result in a `#DC_EVENT_CONTACTS_CHANGED` event.
711    pub async fn create(context: &Context, name: &str, addr: &str) -> Result<ContactId> {
712        Self::create_ex(context, Sync, name, addr).await
713    }
714
715    pub(crate) async fn create_ex(
716        context: &Context,
717        sync: sync::Sync,
718        name: &str,
719        addr: &str,
720    ) -> Result<ContactId> {
721        let (name, addr) = sanitize_name_and_addr(name, addr);
722        let addr = ContactAddress::new(&addr)?;
723
724        let (contact_id, sth_modified) =
725            Contact::add_or_lookup(context, &name, &addr, Origin::ManuallyCreated)
726                .await
727                .context("add_or_lookup")?;
728        let blocked = Contact::is_blocked_load(context, contact_id).await?;
729        match sth_modified {
730            Modifier::None => {}
731            Modifier::Modified | Modifier::Created => {
732                context.emit_event(EventType::ContactsChanged(Some(contact_id)))
733            }
734        }
735        if blocked {
736            set_blocked(context, Nosync, contact_id, false).await?;
737        }
738
739        if sync.into() && sth_modified != Modifier::None {
740            chat::sync(
741                context,
742                chat::SyncId::ContactAddr(addr.to_string()),
743                chat::SyncAction::Rename(name.to_string()),
744            )
745            .await
746            .log_err(context)
747            .ok();
748        }
749        Ok(contact_id)
750    }
751
752    /// Mark messages from a contact as noticed.
753    pub async fn mark_noticed(context: &Context, id: ContactId) -> Result<()> {
754        context
755            .sql
756            .execute(
757                "UPDATE msgs SET state=? WHERE from_id=? AND state=?;",
758                (MessageState::InNoticed, id, MessageState::InFresh),
759            )
760            .await?;
761        Ok(())
762    }
763
764    /// Returns whether contact is a bot.
765    pub fn is_bot(&self) -> bool {
766        self.is_bot
767    }
768
769    /// Looks up a known and unblocked contact with a given e-mail address.
770    /// To get a list of all known and unblocked contacts, use contacts_get_contacts().
771    ///
772    ///
773    /// **POTENTIAL SECURITY ISSUE**: If there are multiple contacts with this address
774    /// (e.g. an address-contact and a key-contact),
775    /// this looks up the most recently seen contact,
776    /// i.e. which contact is returned depends on which contact last sent a message.
777    /// If the user just clicked on a mailto: link, then this is the best thing you can do.
778    /// But **DO NOT** internally represent contacts by their email address
779    /// and do not use this function to look them up;
780    /// otherwise this function will sometimes look up the wrong contact.
781    /// Instead, you should internally represent contacts by their ids.
782    ///
783    /// Known and unblocked contacts will be returned by `get_contacts()`.
784    ///
785    /// To validate an e-mail address independently of the contact database
786    /// use `may_be_valid_addr()`.
787    ///
788    /// Returns the contact ID of the contact belonging to the e-mail address or 0 if there is no
789    /// contact that is or was introduced by an accepted contact.
790    pub async fn lookup_id_by_addr(
791        context: &Context,
792        addr: &str,
793        min_origin: Origin,
794    ) -> Result<Option<ContactId>> {
795        Self::lookup_id_by_addr_ex(context, addr, min_origin, Some(Blocked::Not)).await
796    }
797
798    /// The same as `lookup_id_by_addr()`, but internal function. Currently also allows looking up
799    /// not unblocked contacts.
800    pub(crate) async fn lookup_id_by_addr_ex(
801        context: &Context,
802        addr: &str,
803        min_origin: Origin,
804        blocked: Option<Blocked>,
805    ) -> Result<Option<ContactId>> {
806        if addr.is_empty() {
807            bail!("lookup_id_by_addr: empty address");
808        }
809
810        let addr_normalized = addr_normalize(addr);
811
812        if context.is_configured().await? && context.is_self_addr(addr).await? {
813            return Ok(Some(ContactId::SELF));
814        }
815
816        let id = context
817            .sql
818            .query_get_value(
819                "SELECT id FROM contacts
820                 WHERE addr=?1 COLLATE NOCASE
821                     AND id>?2 AND origin>=?3 AND (? OR blocked=?)
822                 ORDER BY
823                     (
824                         SELECT COUNT(*) FROM chats c
825                         INNER JOIN chats_contacts cc
826                         ON c.id=cc.chat_id
827                         WHERE c.type=?
828                             AND c.id>?
829                             AND c.blocked=?
830                             AND cc.contact_id=contacts.id
831                     ) DESC,
832                     last_seen DESC, fingerprint DESC
833                 LIMIT 1",
834                (
835                    &addr_normalized,
836                    ContactId::LAST_SPECIAL,
837                    min_origin as u32,
838                    blocked.is_none(),
839                    blocked.unwrap_or(Blocked::Not),
840                    Chattype::Single,
841                    constants::DC_CHAT_ID_LAST_SPECIAL,
842                    blocked.unwrap_or(Blocked::Not),
843                ),
844            )
845            .await?;
846        Ok(id)
847    }
848
849    pub(crate) async fn add_or_lookup(
850        context: &Context,
851        name: &str,
852        addr: &ContactAddress,
853        origin: Origin,
854    ) -> Result<(ContactId, Modifier)> {
855        Self::add_or_lookup_ex(context, name, addr, "", origin).await
856    }
857
858    /// Lookup a contact and create it if it does not exist yet.
859    /// If `fingerprint` is non-empty, a key-contact with this fingerprint is added / looked up.
860    /// Otherwise, an address-contact with `addr` is added / looked up.
861    /// A name and an "origin" can be given.
862    ///
863    /// The "origin" is where the address comes from -
864    /// from-header, cc-header, addressbook, qr, manual-edit etc.
865    /// In general, "better" origins overwrite the names of "worse" origins -
866    /// Eg. if we got a name in cc-header and later in from-header, the name will change -
867    /// this does not happen the other way round.
868    ///
869    /// The "best" origin are manually created contacts -
870    /// names given manually can only be overwritten by further manual edits
871    /// (until they are set empty again or reset to the name seen in the From-header).
872    ///
873    /// These manually edited names are _never_ used for sending on the wire -
874    /// this should avoid sending sth. as "Mama" or "Daddy" to some 3rd party.
875    /// Instead, for the wire, we use so called "authnames"
876    /// that can only be set and updated by a From-header.
877    ///
878    /// The different names used in the function are:
879    /// - "name": name passed as function argument, belonging to the given origin
880    /// - "row_name": current name used in the database, typically set to "name"
881    /// - "row_authname": name as authorized from a contact, set only through a From-header
882    ///   Depending on the origin, both, "row_name" and "row_authname" are updated from "name".
883    ///
884    /// Returns the contact_id and a `Modifier` value indicating if a modification occurred.
885    pub(crate) async fn add_or_lookup_ex(
886        context: &Context,
887        name: &str,
888        addr: &str,
889        fingerprint: &str,
890        mut origin: Origin,
891    ) -> Result<(ContactId, Modifier)> {
892        let mut sth_modified = Modifier::None;
893
894        ensure!(
895            !addr.is_empty() || !fingerprint.is_empty(),
896            "Can not add_or_lookup empty address"
897        );
898        ensure!(origin != Origin::Unknown, "Missing valid origin");
899
900        if context.is_configured().await? && context.is_self_addr(addr).await? {
901            return Ok((ContactId::SELF, sth_modified));
902        }
903
904        if !fingerprint.is_empty() && context.is_configured().await? {
905            let fingerprint_self = self_fingerprint(context)
906                .await
907                .context("self_fingerprint")?;
908            if fingerprint == fingerprint_self {
909                return Ok((ContactId::SELF, sth_modified));
910            }
911        }
912
913        let mut name = sanitize_name(name);
914        if origin <= Origin::OutgoingTo {
915            // The user may accidentally have written to a "noreply" address with another MUA:
916            if addr.contains("noreply")
917                || addr.contains("no-reply")
918                || addr.starts_with("notifications@")
919                // Filter out use-once addresses (like reply+AEJDGPOECLAP...@reply.github.com):
920                || (addr.len() > 50 && addr.contains('+'))
921            {
922                info!(context, "hiding contact {}", addr);
923                origin = Origin::Hidden;
924                // For these kind of email addresses, sender and address often don't belong together
925                // (like hocuri <notifications@github.com>). In this example, hocuri shouldn't
926                // be saved as the displayname for notifications@github.com.
927                name = "".to_string();
928            }
929        }
930
931        // If the origin indicates that user entered the contact manually, from the address book or
932        // from the QR-code scan (potentially from the address book of their other phone), then name
933        // should go into the "name" column and never into "authname" column, to avoid leaking it
934        // into the network.
935        let manual = matches!(
936            origin,
937            Origin::ManuallyCreated | Origin::AddressBook | Origin::UnhandledQrScan
938        );
939
940        let mut update_addr = false;
941
942        let row_id = context
943            .sql
944            .transaction(|transaction| {
945                let row = transaction
946                    .query_row(
947                        "SELECT id, name, addr, origin, authname
948                         FROM contacts
949                         WHERE fingerprint=?1 AND
950                         (?1<>'' OR addr=?2 COLLATE NOCASE)",
951                        (fingerprint, addr),
952                        |row| {
953                            let row_id: u32 = row.get(0)?;
954                            let row_name: String = row.get(1)?;
955                            let row_addr: String = row.get(2)?;
956                            let row_origin: Origin = row.get(3)?;
957                            let row_authname: String = row.get(4)?;
958
959                            Ok((row_id, row_name, row_addr, row_origin, row_authname))
960                        },
961                    )
962                    .optional()?;
963
964                let row_id;
965                if let Some((id, row_name, row_addr, row_origin, row_authname)) = row {
966                    let update_name = manual && name != row_name;
967                    let update_authname = !manual
968                        && name != row_authname
969                        && !name.is_empty()
970                        && (origin >= row_origin
971                            || origin == Origin::IncomingUnknownFrom
972                            || row_authname.is_empty());
973
974                    row_id = id;
975                    if origin >= row_origin && addr != row_addr {
976                        update_addr = true;
977                    }
978                    if update_name || update_authname || update_addr || origin > row_origin {
979                        let new_name = if update_name {
980                            name.to_string()
981                        } else {
982                            row_name
983                        };
984                        let new_authname = if update_authname {
985                            name.to_string()
986                        } else {
987                            row_authname
988                        };
989
990                        transaction.execute(
991                            "UPDATE contacts SET name=?, name_normalized=?, addr=?, origin=?, authname=? WHERE id=?",
992                            (
993                                &new_name,
994                                normalize_text(
995                                    if !new_name.is_empty() {
996                                        &new_name
997                                    } else {
998                                        &new_authname
999                                    }),
1000                                if update_addr {
1001                                    addr.to_string()
1002                                } else {
1003                                    row_addr
1004                                },
1005                                if origin > row_origin {
1006                                    origin
1007                                } else {
1008                                    row_origin
1009                                },
1010                                &new_authname,
1011                                row_id,
1012                            ),
1013                        )?;
1014
1015                        if update_name || update_authname {
1016                            let contact_id = ContactId::new(row_id);
1017                            update_chat_names(context, transaction, contact_id)?;
1018                        }
1019                        sth_modified = Modifier::Modified;
1020                    }
1021                } else {
1022                    transaction.execute(
1023                        "
1024INSERT INTO contacts (name, name_normalized, addr, fingerprint, origin, authname)
1025VALUES (?, ?, ?, ?, ?, ?)
1026                        ",
1027                        (
1028                            if manual { &name } else { "" },
1029                            normalize_text(&name),
1030                            &addr,
1031                            fingerprint,
1032                            origin,
1033                            if manual { "" } else { &name },
1034                        ),
1035                    )?;
1036
1037                    sth_modified = Modifier::Created;
1038                    row_id = u32::try_from(transaction.last_insert_rowid())?;
1039                    if fingerprint.is_empty() {
1040                        info!(context, "Added contact id={row_id} addr={addr}.");
1041                    } else {
1042                        info!(
1043                            context,
1044                            "Added contact id={row_id} fpr={fingerprint} addr={addr}."
1045                        );
1046                    }
1047                }
1048                Ok(row_id)
1049            })
1050            .await?;
1051
1052        let contact_id = ContactId::new(row_id);
1053
1054        Ok((contact_id, sth_modified))
1055    }
1056
1057    /// Add a number of contacts.
1058    ///
1059    /// Typically used to add the whole address book from the OS. As names here are typically not
1060    /// well formatted, we call `normalize()` for each name given.
1061    ///
1062    /// No email-address is added twice.
1063    /// Trying to add email-addresses that are already in the contact list,
1064    /// results in updating the name unless the name was changed manually by the user.
1065    /// If any email-address or any name is really updated,
1066    /// the event `DC_EVENT_CONTACTS_CHANGED` is sent.
1067    ///
1068    /// To add a single contact entered by the user, you should prefer `Contact::create`,
1069    /// however, for adding a bunch of addresses, this function is much faster.
1070    ///
1071    /// The `addr_book` is a multiline string in the format `Name one\nAddress one\nName two\nAddress two`.
1072    ///
1073    /// Returns the number of modified contacts.
1074    pub async fn add_address_book(context: &Context, addr_book: &str) -> Result<usize> {
1075        let mut modify_cnt = 0;
1076
1077        for (name, addr) in split_address_book(addr_book) {
1078            let (name, addr) = sanitize_name_and_addr(name, addr);
1079            match ContactAddress::new(&addr) {
1080                Ok(addr) => {
1081                    match Contact::add_or_lookup(context, &name, &addr, Origin::AddressBook).await {
1082                        Ok((_, modified)) => {
1083                            if modified != Modifier::None {
1084                                modify_cnt += 1
1085                            }
1086                        }
1087                        Err(err) => {
1088                            warn!(
1089                                context,
1090                                "Failed to add address {} from address book: {}", addr, err
1091                            );
1092                        }
1093                    }
1094                }
1095                Err(err) => {
1096                    warn!(context, "{:#}.", err);
1097                }
1098            }
1099        }
1100        if modify_cnt > 0 {
1101            context.emit_event(EventType::ContactsChanged(None));
1102        }
1103
1104        Ok(modify_cnt)
1105    }
1106
1107    /// Returns known and unblocked contacts.
1108    ///
1109    /// To get information about a single contact, see get_contact().
1110    /// By default, key-contacts are listed.
1111    ///
1112    /// * `listflags` - A combination of flags:
1113    ///   - `DC_GCL_ADD_SELF` - Add SELF unless filtered by other parameters.
1114    ///   - `DC_GCL_ADDRESS` - List address-contacts instead of key-contacts.
1115    /// * `query` - A string to filter the list.
1116    pub async fn get_all(
1117        context: &Context,
1118        listflags: u32,
1119        query: Option<&str>,
1120    ) -> Result<Vec<ContactId>> {
1121        let self_addrs = context
1122            .get_all_self_addrs()
1123            .await?
1124            .into_iter()
1125            .collect::<HashSet<_>>();
1126        let mut add_self = false;
1127        let mut ret = Vec::new();
1128        let flag_add_self = (listflags & constants::DC_GCL_ADD_SELF) != 0;
1129        let flag_address = (listflags & constants::DC_GCL_ADDRESS) != 0;
1130        let minimal_origin = if context.get_config_bool(Config::Bot).await? {
1131            Origin::Unknown
1132        } else {
1133            Origin::IncomingReplyTo
1134        };
1135        if query.is_some() {
1136            let s3str_like_cmd = format!("%{}%", query.unwrap_or("").to_lowercase());
1137            context
1138                .sql
1139                .query_map(
1140                    "
1141SELECT c.id, c.addr FROM contacts c
1142WHERE c.id>?
1143    AND (c.fingerprint='')=?
1144    AND c.origin>=?
1145    AND c.blocked=0
1146    AND (IFNULL(c.name_normalized,IIF(c.name='',c.authname,c.name)) LIKE ? OR c.addr LIKE ?)
1147ORDER BY c.last_seen DESC, c.id DESC
1148                    ",
1149                    (
1150                        ContactId::LAST_SPECIAL,
1151                        flag_address,
1152                        minimal_origin,
1153                        &s3str_like_cmd,
1154                        &s3str_like_cmd,
1155                    ),
1156                    |row| {
1157                        let id: ContactId = row.get(0)?;
1158                        let addr: String = row.get(1)?;
1159                        Ok((id, addr))
1160                    },
1161                    |rows| {
1162                        for row in rows {
1163                            let (id, addr) = row?;
1164                            if !self_addrs.contains(&addr) {
1165                                ret.push(id);
1166                            }
1167                        }
1168                        Ok(())
1169                    },
1170                )
1171                .await?;
1172
1173            if let Some(query) = query {
1174                let self_addr = context
1175                    .get_config(Config::ConfiguredAddr)
1176                    .await?
1177                    .unwrap_or_default();
1178                let self_name = context
1179                    .get_config(Config::Displayname)
1180                    .await?
1181                    .unwrap_or_default();
1182                let self_name2 = stock_str::self_msg(context);
1183
1184                if self_addr.contains(query)
1185                    || self_name.contains(query)
1186                    || self_name2.await.contains(query)
1187                {
1188                    add_self = true;
1189                }
1190            } else {
1191                add_self = true;
1192            }
1193        } else {
1194            add_self = true;
1195
1196            context
1197                .sql
1198                .query_map(
1199                    "SELECT id, addr FROM contacts
1200                 WHERE id>?
1201                 AND (fingerprint='')=?
1202                 AND origin>=?
1203                 AND blocked=0
1204                 ORDER BY last_seen DESC, id DESC;",
1205                    (ContactId::LAST_SPECIAL, flag_address, minimal_origin),
1206                    |row| {
1207                        let id: ContactId = row.get(0)?;
1208                        let addr: String = row.get(1)?;
1209                        Ok((id, addr))
1210                    },
1211                    |rows| {
1212                        for row in rows {
1213                            let (id, addr) = row?;
1214                            if !self_addrs.contains(&addr) {
1215                                ret.push(id);
1216                            }
1217                        }
1218                        Ok(())
1219                    },
1220                )
1221                .await?;
1222        }
1223
1224        if flag_add_self && add_self {
1225            ret.push(ContactId::SELF);
1226        }
1227
1228        Ok(ret)
1229    }
1230
1231    /// Adds blocked mailinglists and broadcast channels as pseudo-contacts
1232    /// to allow unblocking them as if they are contacts
1233    /// (this way, only one unblock-ffi is needed and only one set of ui-functions,
1234    /// from the users perspective,
1235    /// there is not much difference in an email- and a mailinglist-address)
1236    async fn update_blocked_mailinglist_contacts(context: &Context) -> Result<()> {
1237        context
1238            .sql
1239            .transaction(move |transaction| {
1240                let mut stmt = transaction.prepare(
1241                    "SELECT name, grpid, type FROM chats WHERE (type=? OR type=?) AND blocked=?",
1242                )?;
1243                let rows = stmt.query_map(
1244                    (Chattype::Mailinglist, Chattype::InBroadcast, Blocked::Yes),
1245                    |row| {
1246                        let name: String = row.get(0)?;
1247                        let grpid: String = row.get(1)?;
1248                        let typ: Chattype = row.get(2)?;
1249                        Ok((name, grpid, typ))
1250                    },
1251                )?;
1252                let blocked_mailinglists = rows.collect::<std::result::Result<Vec<_>, _>>()?;
1253                for (name, grpid, typ) in blocked_mailinglists {
1254                    let count = transaction.query_row(
1255                        "SELECT COUNT(id) FROM contacts WHERE addr=?",
1256                        [&grpid],
1257                        |row| {
1258                            let count: isize = row.get(0)?;
1259                            Ok(count)
1260                        },
1261                    )?;
1262                    if count == 0 {
1263                        transaction.execute("INSERT INTO contacts (addr) VALUES (?)", [&grpid])?;
1264                    }
1265
1266                    let fingerprint = if typ == Chattype::InBroadcast {
1267                        // Set some fingerprint so that is_pgp_contact() returns true,
1268                        // and the contact isn't marked with a letter icon.
1269                        "Blocked_broadcast"
1270                    } else {
1271                        ""
1272                    };
1273                    // Always do an update in case the blocking is reset or name is changed.
1274                    transaction.execute(
1275                        "
1276UPDATE contacts
1277SET name=?, name_normalized=IIF(?1='',name_normalized,?), origin=?, blocked=1, fingerprint=?
1278WHERE addr=?
1279                        ",
1280                        (
1281                            &name,
1282                            normalize_text(&name),
1283                            Origin::MailinglistAddress,
1284                            fingerprint,
1285                            &grpid,
1286                        ),
1287                    )?;
1288                }
1289                Ok(())
1290            })
1291            .await?;
1292        Ok(())
1293    }
1294
1295    /// Returns number of blocked contacts.
1296    pub async fn get_blocked_cnt(context: &Context) -> Result<usize> {
1297        let count = context
1298            .sql
1299            .count(
1300                "SELECT COUNT(*) FROM contacts WHERE id>? AND blocked!=0",
1301                (ContactId::LAST_SPECIAL,),
1302            )
1303            .await?;
1304        Ok(count)
1305    }
1306
1307    /// Get blocked contacts.
1308    pub async fn get_all_blocked(context: &Context) -> Result<Vec<ContactId>> {
1309        Contact::update_blocked_mailinglist_contacts(context)
1310            .await
1311            .context("cannot update blocked mailinglist contacts")?;
1312
1313        let list = context
1314            .sql
1315            .query_map_vec(
1316                "SELECT id FROM contacts WHERE id>? AND blocked!=0 ORDER BY last_seen DESC, id DESC;",
1317                (ContactId::LAST_SPECIAL,),
1318                |row| {
1319                    let contact_id: ContactId = row.get(0)?;
1320                    Ok(contact_id)
1321                }
1322            )
1323            .await?;
1324        Ok(list)
1325    }
1326
1327    /// Returns a textual summary of the encryption state for the contact.
1328    ///
1329    /// This function returns a string explaining the encryption state
1330    /// of the contact and if the connection is encrypted the
1331    /// fingerprints of the keys involved.
1332    pub async fn get_encrinfo(context: &Context, contact_id: ContactId) -> Result<String> {
1333        ensure!(
1334            !contact_id.is_special(),
1335            "Can not provide encryption info for special contact"
1336        );
1337
1338        let contact = Contact::get_by_id(context, contact_id).await?;
1339        let addr = context
1340            .get_config(Config::ConfiguredAddr)
1341            .await?
1342            .unwrap_or_default();
1343
1344        let Some(fingerprint_other) = contact.fingerprint() else {
1345            return Ok(stock_str::encr_none(context).await);
1346        };
1347        let fingerprint_other = fingerprint_other.to_string();
1348
1349        let stock_message = if contact.public_key(context).await?.is_some() {
1350            stock_str::e2e_available(context).await
1351        } else {
1352            stock_str::encr_none(context).await
1353        };
1354
1355        let finger_prints = stock_str::finger_prints(context).await;
1356        let mut ret = format!("{stock_message}.\n{finger_prints}:");
1357
1358        let fingerprint_self = load_self_public_key(context)
1359            .await?
1360            .dc_fingerprint()
1361            .to_string();
1362        if addr < contact.addr {
1363            cat_fingerprint(
1364                &mut ret,
1365                &stock_str::self_msg(context).await,
1366                &addr,
1367                &fingerprint_self,
1368            );
1369            cat_fingerprint(
1370                &mut ret,
1371                contact.get_display_name(),
1372                &contact.addr,
1373                &fingerprint_other,
1374            );
1375        } else {
1376            cat_fingerprint(
1377                &mut ret,
1378                contact.get_display_name(),
1379                &contact.addr,
1380                &fingerprint_other,
1381            );
1382            cat_fingerprint(
1383                &mut ret,
1384                &stock_str::self_msg(context).await,
1385                &addr,
1386                &fingerprint_self,
1387            );
1388        }
1389
1390        Ok(ret)
1391    }
1392
1393    /// Delete a contact so that it disappears from the corresponding lists.
1394    /// Depending on whether there are ongoing chats, deletion is done by physical deletion or hiding.
1395    /// The contact is deleted from the local device.
1396    ///
1397    /// May result in a `#DC_EVENT_CONTACTS_CHANGED` event.
1398    pub async fn delete(context: &Context, contact_id: ContactId) -> Result<()> {
1399        ensure!(!contact_id.is_special(), "Can not delete special contact");
1400
1401        context
1402            .sql
1403            .transaction(move |transaction| {
1404                // make sure, the transaction starts with a write command and becomes EXCLUSIVE by that -
1405                // upgrading later may be impossible by races.
1406                let deleted_contacts = transaction.execute(
1407                    "DELETE FROM contacts WHERE id=?
1408                     AND (SELECT COUNT(*) FROM chats_contacts WHERE contact_id=?)=0;",
1409                    (contact_id, contact_id),
1410                )?;
1411                if deleted_contacts == 0 {
1412                    transaction.execute(
1413                        "UPDATE contacts SET origin=? WHERE id=?;",
1414                        (Origin::Hidden, contact_id),
1415                    )?;
1416                }
1417                Ok(())
1418            })
1419            .await?;
1420
1421        context.emit_event(EventType::ContactsChanged(None));
1422        Ok(())
1423    }
1424
1425    /// Updates `param` column in the database.
1426    pub async fn update_param(&self, context: &Context) -> Result<()> {
1427        context
1428            .sql
1429            .execute(
1430                "UPDATE contacts SET param=? WHERE id=?",
1431                (self.param.to_string(), self.id),
1432            )
1433            .await?;
1434        Ok(())
1435    }
1436
1437    /// Updates `status` column in the database.
1438    pub async fn update_status(&self, context: &Context) -> Result<()> {
1439        context
1440            .sql
1441            .execute(
1442                "UPDATE contacts SET status=? WHERE id=?",
1443                (&self.status, self.id),
1444            )
1445            .await?;
1446        Ok(())
1447    }
1448
1449    /// Get the ID of the contact.
1450    pub fn get_id(&self) -> ContactId {
1451        self.id
1452    }
1453
1454    /// Get email address. The email address is always set for a contact.
1455    pub fn get_addr(&self) -> &str {
1456        &self.addr
1457    }
1458
1459    /// Returns true if the contact is a key-contact.
1460    /// Otherwise it is an addresss-contact.
1461    pub fn is_key_contact(&self) -> bool {
1462        self.fingerprint.is_some()
1463    }
1464
1465    /// Returns OpenPGP fingerprint of a contact.
1466    ///
1467    /// `None` for address-contacts.
1468    pub fn fingerprint(&self) -> Option<Fingerprint> {
1469        if let Some(fingerprint) = &self.fingerprint {
1470            fingerprint.parse().ok()
1471        } else {
1472            None
1473        }
1474    }
1475
1476    /// Returns OpenPGP public key of a contact.
1477    ///
1478    /// Returns `None` if the contact is not a key-contact
1479    /// or if the key is not available.
1480    /// It is possible for a key-contact to not have a key,
1481    /// e.g. if only the fingerprint is known from a QR-code.
1482    pub async fn public_key(&self, context: &Context) -> Result<Option<SignedPublicKey>> {
1483        if self.id == ContactId::SELF {
1484            return Ok(Some(load_self_public_key(context).await?));
1485        }
1486
1487        if let Some(fingerprint) = &self.fingerprint {
1488            if let Some(public_key_bytes) = context
1489                .sql
1490                .query_row_optional(
1491                    "SELECT public_key
1492                     FROM public_keys
1493                     WHERE fingerprint=?",
1494                    (fingerprint,),
1495                    |row| {
1496                        let bytes: Vec<u8> = row.get(0)?;
1497                        Ok(bytes)
1498                    },
1499                )
1500                .await?
1501            {
1502                let public_key = SignedPublicKey::from_slice(&public_key_bytes)?;
1503                Ok(Some(public_key))
1504            } else {
1505                Ok(None)
1506            }
1507        } else {
1508            Ok(None)
1509        }
1510    }
1511
1512    /// Get name authorized by the contact.
1513    pub fn get_authname(&self) -> &str {
1514        &self.authname
1515    }
1516
1517    /// Get the contact name. This is the name as modified by the local user.
1518    /// May be an empty string.
1519    ///
1520    /// This name is typically used in a form where the user can edit the name of a contact.
1521    /// To get a fine name to display in lists etc., use `Contact::get_display_name` or `Contact::get_name_n_addr`.
1522    pub fn get_name(&self) -> &str {
1523        &self.name
1524    }
1525
1526    /// Get display name. This is the name as defined by the contact himself,
1527    /// modified by the user or, if both are unset, the email address.
1528    ///
1529    /// This name is typically used in lists.
1530    /// To get the name editable in a formular, use `Contact::get_name`.
1531    pub fn get_display_name(&self) -> &str {
1532        if !self.name.is_empty() {
1533            return &self.name;
1534        }
1535        if !self.authname.is_empty() {
1536            return &self.authname;
1537        }
1538        &self.addr
1539    }
1540
1541    /// Get a summary of name and address.
1542    ///
1543    /// The returned string is either "Name (email@domain.com)" or just
1544    /// "email@domain.com" if the name is unset.
1545    ///
1546    /// The result should only be used locally and never sent over the network
1547    /// as it leaks the local contact name.
1548    ///
1549    /// The summary is typically used when asking the user something about the contact.
1550    /// The attached email address makes the question unique, eg. "Chat with Alan Miller (am@uniquedomain.com)?"
1551    pub fn get_name_n_addr(&self) -> String {
1552        if !self.name.is_empty() {
1553            format!("{} ({})", self.name, self.addr)
1554        } else if !self.authname.is_empty() {
1555            format!("{} ({})", self.authname, self.addr)
1556        } else {
1557            (&self.addr).into()
1558        }
1559    }
1560
1561    /// Get the contact's profile image.
1562    /// This is the image set by each remote user on their own
1563    /// using set_config(context, "selfavatar", image).
1564    pub async fn get_profile_image(&self, context: &Context) -> Result<Option<PathBuf>> {
1565        self.get_profile_image_ex(context, true).await
1566    }
1567
1568    /// Get the contact's profile image.
1569    /// This is the image set by each remote user on their own
1570    /// using set_config(context, "selfavatar", image).
1571    async fn get_profile_image_ex(
1572        &self,
1573        context: &Context,
1574        show_fallback_icon: bool,
1575    ) -> Result<Option<PathBuf>> {
1576        if self.id == ContactId::SELF {
1577            if let Some(p) = context.get_config(Config::Selfavatar).await? {
1578                return Ok(Some(PathBuf::from(p))); // get_config() calls get_abs_path() internally already
1579            }
1580        } else if self.id == ContactId::DEVICE {
1581            return Ok(Some(chat::get_device_icon(context).await?));
1582        }
1583        if show_fallback_icon && !self.id.is_special() && !self.is_key_contact() {
1584            return Ok(Some(chat::get_unencrypted_icon(context).await?));
1585        }
1586        if let Some(image_rel) = self.param.get(Param::ProfileImage)
1587            && !image_rel.is_empty()
1588        {
1589            return Ok(Some(get_abs_path(context, Path::new(image_rel))));
1590        }
1591        Ok(None)
1592    }
1593
1594    /// Returns a color for the contact.
1595    /// For self-contact this returns gray if own keypair doesn't exist yet.
1596    /// See also [`self::get_color`].
1597    pub fn get_color(&self) -> u32 {
1598        get_color(self.id == ContactId::SELF, &self.addr, &self.fingerprint())
1599    }
1600
1601    /// Returns a color for the contact.
1602    /// Ensures that the color isn't gray. For self-contact this generates own keypair if it doesn't
1603    /// exist yet.
1604    /// See also [`self::get_color`].
1605    pub async fn get_or_gen_color(&self, context: &Context) -> Result<u32> {
1606        let mut fpr = self.fingerprint();
1607        if fpr.is_none() && self.id == ContactId::SELF {
1608            fpr = Some(load_self_public_key(context).await?.dc_fingerprint());
1609        }
1610        Ok(get_color(self.id == ContactId::SELF, &self.addr, &fpr))
1611    }
1612
1613    /// Gets the contact's status.
1614    ///
1615    /// Status is the last signature received in a message from this contact.
1616    pub fn get_status(&self) -> &str {
1617        self.status.as_str()
1618    }
1619
1620    /// Returns whether end-to-end encryption to the contact is available.
1621    pub async fn e2ee_avail(&self, context: &Context) -> Result<bool> {
1622        if self.id == ContactId::SELF {
1623            // We don't need to check if we have our own key.
1624            return Ok(true);
1625        }
1626        Ok(self.public_key(context).await?.is_some())
1627    }
1628
1629    /// Returns true if the contact
1630    /// can be added to verified chats.
1631    ///
1632    /// If contact is verified
1633    /// UI should display green checkmark after the contact name
1634    /// in contact list items and
1635    /// in chat member list items.
1636    ///
1637    /// In contact profile view, use this function only if there is no chat with the contact,
1638    /// otherwise use is_chat_protected().
1639    /// Use [Self::get_verifier_id] to display the verifier contact
1640    /// in the info section of the contact profile.
1641    pub async fn is_verified(&self, context: &Context) -> Result<bool> {
1642        // We're always sort of secured-verified as we could verify the key on this device any time with the key
1643        // on this device
1644        if self.id == ContactId::SELF {
1645            return Ok(true);
1646        }
1647
1648        Ok(self.get_verifier_id(context).await?.is_some())
1649    }
1650
1651    /// Returns the `ContactId` that verified the contact.
1652    ///
1653    /// If this returns Some(_),
1654    /// display green checkmark in the profile and "Introduced by ..." line
1655    /// with the name and address of the contact
1656    /// formatted by [Self::get_name_n_addr].
1657    ///
1658    /// If this returns `Some(None)`, then the contact is verified,
1659    /// but it's unclear by whom.
1660    pub async fn get_verifier_id(&self, context: &Context) -> Result<Option<Option<ContactId>>> {
1661        let verifier_id: u32 = context
1662            .sql
1663            .query_get_value("SELECT verifier FROM contacts WHERE id=?", (self.id,))
1664            .await?
1665            .with_context(|| format!("Contact {} does not exist", self.id))?;
1666
1667        if verifier_id == 0 {
1668            Ok(None)
1669        } else if verifier_id == self.id.to_u32() {
1670            Ok(Some(None))
1671        } else {
1672            Ok(Some(Some(ContactId::new(verifier_id))))
1673        }
1674    }
1675
1676    /// Returns the number of real (i.e. non-special) contacts in the database.
1677    pub async fn get_real_cnt(context: &Context) -> Result<usize> {
1678        if !context.sql.is_open().await {
1679            return Ok(0);
1680        }
1681
1682        let count = context
1683            .sql
1684            .count(
1685                "SELECT COUNT(*) FROM contacts WHERE id>?;",
1686                (ContactId::LAST_SPECIAL,),
1687            )
1688            .await?;
1689        Ok(count)
1690    }
1691
1692    /// Returns true if a contact with this ID exists.
1693    pub async fn real_exists_by_id(context: &Context, contact_id: ContactId) -> Result<bool> {
1694        if contact_id.is_special() {
1695            return Ok(false);
1696        }
1697
1698        let exists = context
1699            .sql
1700            .exists("SELECT COUNT(*) FROM contacts WHERE id=?;", (contact_id,))
1701            .await?;
1702        Ok(exists)
1703    }
1704}
1705
1706/// Returns a color for a contact having given attributes.
1707///
1708/// The color is calculated from contact's fingerprint (for key-contacts) or email address (for
1709/// address-contacts; should be lowercased to avoid allocation) and can be used for an fallback
1710/// avatar with white initials as well as for headlines in bubbles of group chats.
1711pub fn get_color(is_self: bool, addr: &str, fingerprint: &Option<Fingerprint>) -> u32 {
1712    if let Some(fingerprint) = fingerprint {
1713        str_to_color(&fingerprint.hex())
1714    } else if is_self {
1715        0x808080
1716    } else {
1717        str_to_color(&to_lowercase(addr))
1718    }
1719}
1720
1721// Updates the names of the chats which use the contact name.
1722//
1723// This is one of the few duplicated data, however, getting the chat list is easier this way.
1724fn update_chat_names(
1725    context: &Context,
1726    transaction: &rusqlite::Connection,
1727    contact_id: ContactId,
1728) -> Result<()> {
1729    let chat_id: Option<ChatId> = transaction.query_row(
1730            "SELECT id FROM chats WHERE type=? AND id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?)",
1731            (Chattype::Single, contact_id),
1732            |row| {
1733                let chat_id: ChatId = row.get(0)?;
1734                Ok(chat_id)
1735            }
1736        ).optional()?;
1737
1738    if let Some(chat_id) = chat_id {
1739        let (addr, name, authname) = transaction.query_row(
1740            "SELECT addr, name, authname
1741                     FROM contacts
1742                     WHERE id=?",
1743            (contact_id,),
1744            |row| {
1745                let addr: String = row.get(0)?;
1746                let name: String = row.get(1)?;
1747                let authname: String = row.get(2)?;
1748                Ok((addr, name, authname))
1749            },
1750        )?;
1751
1752        let chat_name = if !name.is_empty() {
1753            name
1754        } else if !authname.is_empty() {
1755            authname
1756        } else {
1757            addr
1758        };
1759
1760        let count = transaction.execute(
1761            "UPDATE chats SET name=?1, name_normalized=?2 WHERE id=?3 AND name!=?1",
1762            (&chat_name, normalize_text(&chat_name), chat_id),
1763        )?;
1764
1765        if count > 0 {
1766            // Chat name updated
1767            context.emit_event(EventType::ChatModified(chat_id));
1768            chatlist_events::emit_chatlist_items_changed_for_contact(context, contact_id);
1769        }
1770    }
1771
1772    Ok(())
1773}
1774
1775pub(crate) async fn set_blocked(
1776    context: &Context,
1777    sync: sync::Sync,
1778    contact_id: ContactId,
1779    new_blocking: bool,
1780) -> Result<()> {
1781    ensure!(
1782        !contact_id.is_special(),
1783        "Can't block special contact {contact_id}"
1784    );
1785    let contact = Contact::get_by_id(context, contact_id).await?;
1786
1787    if contact.blocked != new_blocking {
1788        context
1789            .sql
1790            .execute(
1791                "UPDATE contacts SET blocked=? WHERE id=?;",
1792                (i32::from(new_blocking), contact_id),
1793            )
1794            .await?;
1795
1796        // also (un)block all chats with _only_ this contact - we do not delete them to allow a
1797        // non-destructive blocking->unblocking.
1798        // (Maybe, beside normal chats (type=100) we should also block group chats with only this user.
1799        // However, I'm not sure about this point; it may be confusing if the user wants to add other people;
1800        // this would result in recreating the same group...)
1801        if context
1802            .sql
1803            .execute(
1804                r#"
1805UPDATE chats
1806SET blocked=?
1807WHERE type=? AND id IN (
1808  SELECT chat_id FROM chats_contacts WHERE contact_id=?
1809);
1810"#,
1811                (new_blocking, Chattype::Single, contact_id),
1812            )
1813            .await
1814            .is_ok()
1815        {
1816            Contact::mark_noticed(context, contact_id).await?;
1817            context.emit_event(EventType::ContactsChanged(Some(contact_id)));
1818        }
1819
1820        // also unblock mailinglist
1821        // if the contact is a mailinglist address explicitly created to allow unblocking
1822        if !new_blocking
1823            && contact.origin == Origin::MailinglistAddress
1824            && let Some((chat_id, ..)) = chat::get_chat_id_by_grpid(context, &contact.addr).await?
1825        {
1826            chat_id.unblock_ex(context, Nosync).await?;
1827        }
1828
1829        if sync.into() {
1830            let action = match new_blocking {
1831                true => chat::SyncAction::Block,
1832                false => chat::SyncAction::Unblock,
1833            };
1834            let sync_id = if let Some(fingerprint) = contact.fingerprint() {
1835                chat::SyncId::ContactFingerprint(fingerprint.hex())
1836            } else {
1837                chat::SyncId::ContactAddr(contact.addr.clone())
1838            };
1839
1840            chat::sync(context, sync_id, action)
1841                .await
1842                .log_err(context)
1843                .ok();
1844        }
1845    }
1846
1847    chatlist_events::emit_chatlist_changed(context);
1848    Ok(())
1849}
1850
1851/// Set profile image for a contact.
1852///
1853/// The given profile image is expected to be already in the blob directory
1854/// as profile images can be set only by receiving messages, this should be always the case, however.
1855///
1856/// For contact SELF, the image is not saved in the contact-database but as Config::Selfavatar.
1857pub(crate) async fn set_profile_image(
1858    context: &Context,
1859    contact_id: ContactId,
1860    profile_image: &AvatarAction,
1861) -> Result<()> {
1862    let mut contact = Contact::get_by_id(context, contact_id).await?;
1863    let changed = match profile_image {
1864        AvatarAction::Change(profile_image) => {
1865            if contact_id == ContactId::SELF {
1866                context
1867                    .set_config_ex(Nosync, Config::Selfavatar, Some(profile_image))
1868                    .await?;
1869            } else {
1870                contact.param.set(Param::ProfileImage, profile_image);
1871            }
1872            true
1873        }
1874        AvatarAction::Delete => {
1875            if contact_id == ContactId::SELF {
1876                context
1877                    .set_config_ex(Nosync, Config::Selfavatar, None)
1878                    .await?;
1879            } else {
1880                contact.param.remove(Param::ProfileImage);
1881            }
1882            true
1883        }
1884    };
1885    if changed {
1886        contact.update_param(context).await?;
1887        context.emit_event(EventType::ContactsChanged(Some(contact_id)));
1888        chatlist_events::emit_chatlist_item_changed_for_contact_chat(context, contact_id).await;
1889    }
1890    Ok(())
1891}
1892
1893/// Sets contact status.
1894///
1895/// For contact SELF, the status is not saved in the contact table, but as Config::Selfstatus.
1896pub(crate) async fn set_status(
1897    context: &Context,
1898    contact_id: ContactId,
1899    status: String,
1900) -> Result<()> {
1901    if contact_id == ContactId::SELF {
1902        context
1903            .set_config_ex(Nosync, Config::Selfstatus, Some(&status))
1904            .await?;
1905    } else {
1906        let mut contact = Contact::get_by_id(context, contact_id).await?;
1907
1908        if contact.status != status {
1909            contact.status = status;
1910            contact.update_status(context).await?;
1911            context.emit_event(EventType::ContactsChanged(Some(contact_id)));
1912        }
1913    }
1914    Ok(())
1915}
1916
1917/// Updates last seen timestamp of the contact if it is earlier than the given `timestamp`.
1918pub(crate) async fn update_last_seen(
1919    context: &Context,
1920    contact_id: ContactId,
1921    timestamp: i64,
1922) -> Result<()> {
1923    ensure!(
1924        !contact_id.is_special(),
1925        "Can not update special contact last seen timestamp"
1926    );
1927
1928    if context
1929        .sql
1930        .execute(
1931            "UPDATE contacts SET last_seen = ?1 WHERE last_seen < ?1 AND id = ?2",
1932            (timestamp, contact_id),
1933        )
1934        .await?
1935        > 0
1936        && timestamp > time() - SEEN_RECENTLY_SECONDS
1937    {
1938        context.emit_event(EventType::ContactsChanged(Some(contact_id)));
1939        context
1940            .scheduler
1941            .interrupt_recently_seen(contact_id, timestamp)
1942            .await;
1943    }
1944    Ok(())
1945}
1946
1947/// Marks contact `contact_id` as verified by `verifier_id`.
1948///
1949/// `verifier_id == None` means that the verifier is unknown.
1950pub(crate) async fn mark_contact_id_as_verified(
1951    context: &Context,
1952    contact_id: ContactId,
1953    verifier_id: Option<ContactId>,
1954) -> Result<()> {
1955    ensure_and_debug_assert_ne!(contact_id, ContactId::SELF,);
1956    ensure_and_debug_assert_ne!(
1957        Some(contact_id),
1958        verifier_id,
1959        "Contact cannot be verified by self",
1960    );
1961    let by_self = verifier_id == Some(ContactId::SELF);
1962    let mut verifier_id = verifier_id.unwrap_or(contact_id);
1963    context
1964        .sql
1965        .transaction(|transaction| {
1966            let contact_fingerprint: String = transaction.query_row(
1967                "SELECT fingerprint FROM contacts WHERE id=?",
1968                (contact_id,),
1969                |row| row.get(0),
1970            )?;
1971            if contact_fingerprint.is_empty() {
1972                bail!("Non-key-contact {contact_id} cannot be verified");
1973            }
1974            if verifier_id != ContactId::SELF {
1975                let (verifier_fingerprint, verifier_verifier_id): (String, ContactId) = transaction
1976                    .query_row(
1977                        "SELECT fingerprint, verifier FROM contacts WHERE id=?",
1978                        (verifier_id,),
1979                        |row| Ok((row.get(0)?, row.get(1)?)),
1980                    )?;
1981                if verifier_fingerprint.is_empty() {
1982                    bail!(
1983                        "Contact {contact_id} cannot be verified by non-key-contact {verifier_id}"
1984                    );
1985                }
1986                ensure!(
1987                    verifier_id == contact_id || verifier_verifier_id != ContactId::UNDEFINED,
1988                    "Contact {contact_id} cannot be verified by unverified contact {verifier_id}",
1989                );
1990                if verifier_verifier_id == verifier_id {
1991                    // Avoid introducing incorrect reverse chains: if the verifier itself has an
1992                    // unknown verifier, it may be `contact_id` actually (directly or indirectly) on
1993                    // the other device (which is needed for getting "verified by unknown contact"
1994                    // in the first place).
1995                    verifier_id = contact_id;
1996                }
1997            }
1998            transaction.execute(
1999                "UPDATE contacts SET verifier=?1
2000                 WHERE id=?2 AND (verifier=0 OR verifier=id OR ?3)",
2001                (verifier_id, contact_id, by_self),
2002            )?;
2003            Ok(())
2004        })
2005        .await?;
2006    Ok(())
2007}
2008
2009fn cat_fingerprint(ret: &mut String, name: &str, addr: &str, fingerprint: &str) {
2010    *ret += &format!("\n\n{name} ({addr}):\n{fingerprint}");
2011}
2012
2013fn split_address_book(book: &str) -> Vec<(&str, &str)> {
2014    book.lines()
2015        .collect::<Vec<&str>>()
2016        .chunks(2)
2017        .filter_map(|chunk| {
2018            let name = chunk.first()?;
2019            let addr = chunk.get(1)?;
2020            Some((*name, *addr))
2021        })
2022        .collect()
2023}
2024
2025#[derive(Debug)]
2026pub(crate) struct RecentlySeenInterrupt {
2027    contact_id: ContactId,
2028    timestamp: i64,
2029}
2030
2031#[derive(Debug)]
2032pub(crate) struct RecentlySeenLoop {
2033    /// Task running "recently seen" loop.
2034    handle: task::JoinHandle<()>,
2035
2036    interrupt_send: Sender<RecentlySeenInterrupt>,
2037}
2038
2039impl RecentlySeenLoop {
2040    pub(crate) fn new(context: Context) -> Self {
2041        let (interrupt_send, interrupt_recv) = channel::bounded(1);
2042
2043        let handle = task::spawn(Self::run(context, interrupt_recv));
2044        Self {
2045            handle,
2046            interrupt_send,
2047        }
2048    }
2049
2050    async fn run(context: Context, interrupt: Receiver<RecentlySeenInterrupt>) {
2051        type MyHeapElem = (Reverse<i64>, ContactId);
2052
2053        let now = SystemTime::now();
2054        let now_ts = now
2055            .duration_since(SystemTime::UNIX_EPOCH)
2056            .unwrap_or_default()
2057            .as_secs() as i64;
2058
2059        // Priority contains all recently seen sorted by the timestamp
2060        // when they become not recently seen.
2061        //
2062        // Initialize with contacts which are currently seen, but will
2063        // become unseen in the future.
2064        let mut unseen_queue: BinaryHeap<MyHeapElem> = context
2065            .sql
2066            .query_map_collect(
2067                "SELECT id, last_seen FROM contacts
2068                 WHERE last_seen > ?",
2069                (now_ts - SEEN_RECENTLY_SECONDS,),
2070                |row| {
2071                    let contact_id: ContactId = row.get("id")?;
2072                    let last_seen: i64 = row.get("last_seen")?;
2073                    Ok((Reverse(last_seen + SEEN_RECENTLY_SECONDS), contact_id))
2074                },
2075            )
2076            .await
2077            .unwrap_or_default();
2078
2079        loop {
2080            let now = SystemTime::now();
2081            let (until, contact_id) =
2082                if let Some((Reverse(timestamp), contact_id)) = unseen_queue.peek() {
2083                    (
2084                        UNIX_EPOCH
2085                            + Duration::from_secs((*timestamp).try_into().unwrap_or(u64::MAX))
2086                            + Duration::from_secs(1),
2087                        Some(contact_id),
2088                    )
2089                } else {
2090                    // Sleep for 24 hours.
2091                    (now + Duration::from_secs(86400), None)
2092                };
2093
2094            if let Ok(duration) = until.duration_since(now) {
2095                info!(
2096                    context,
2097                    "Recently seen loop waiting for {} or interrupt",
2098                    duration_to_str(duration)
2099                );
2100
2101                match timeout(duration, interrupt.recv()).await {
2102                    Err(_) => {
2103                        // Timeout, notify about contact.
2104                        if let Some(contact_id) = contact_id {
2105                            context.emit_event(EventType::ContactsChanged(Some(*contact_id)));
2106                            chatlist_events::emit_chatlist_item_changed_for_contact_chat(
2107                                &context,
2108                                *contact_id,
2109                            )
2110                            .await;
2111                            unseen_queue.pop();
2112                        }
2113                    }
2114                    Ok(Err(err)) => {
2115                        warn!(
2116                            context,
2117                            "Error receiving an interruption in recently seen loop: {}", err
2118                        );
2119                        // Maybe the sender side is closed.
2120                        // Terminate the loop to avoid looping indefinitely.
2121                        return;
2122                    }
2123                    Ok(Ok(RecentlySeenInterrupt {
2124                        contact_id,
2125                        timestamp,
2126                    })) => {
2127                        // Received an interrupt.
2128                        if contact_id != ContactId::UNDEFINED {
2129                            unseen_queue
2130                                .push((Reverse(timestamp + SEEN_RECENTLY_SECONDS), contact_id));
2131                        }
2132                    }
2133                }
2134            } else {
2135                info!(
2136                    context,
2137                    "Recently seen loop is not waiting, event is already due."
2138                );
2139
2140                // Event is already in the past.
2141                if let Some(contact_id) = contact_id {
2142                    context.emit_event(EventType::ContactsChanged(Some(*contact_id)));
2143                    chatlist_events::emit_chatlist_item_changed_for_contact_chat(
2144                        &context,
2145                        *contact_id,
2146                    )
2147                    .await;
2148                }
2149                unseen_queue.pop();
2150            }
2151        }
2152    }
2153
2154    pub(crate) fn try_interrupt(&self, contact_id: ContactId, timestamp: i64) {
2155        self.interrupt_send
2156            .try_send(RecentlySeenInterrupt {
2157                contact_id,
2158                timestamp,
2159            })
2160            .ok();
2161    }
2162
2163    #[cfg(test)]
2164    pub(crate) async fn interrupt(&self, contact_id: ContactId, timestamp: i64) {
2165        self.interrupt_send
2166            .send(RecentlySeenInterrupt {
2167                contact_id,
2168                timestamp,
2169            })
2170            .await
2171            .unwrap();
2172    }
2173
2174    pub(crate) async fn abort(self) {
2175        self.handle.abort();
2176
2177        // Await aborted task to ensure the `Future` is dropped
2178        // with all resources moved inside such as the `Context`
2179        // reference to `InnerContext`.
2180        self.handle.await.ok();
2181    }
2182}
2183
2184#[cfg(test)]
2185mod contact_tests;