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.origin>=? DESC, 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                        Origin::CreateChat,
1156                    ),
1157                    |row| {
1158                        let id: ContactId = row.get(0)?;
1159                        let addr: String = row.get(1)?;
1160                        Ok((id, addr))
1161                    },
1162                    |rows| {
1163                        for row in rows {
1164                            let (id, addr) = row?;
1165                            if !self_addrs.contains(&addr) {
1166                                ret.push(id);
1167                            }
1168                        }
1169                        Ok(())
1170                    },
1171                )
1172                .await?;
1173
1174            if let Some(query) = query {
1175                let self_addr = context
1176                    .get_config(Config::ConfiguredAddr)
1177                    .await?
1178                    .unwrap_or_default();
1179                let self_name = context
1180                    .get_config(Config::Displayname)
1181                    .await?
1182                    .unwrap_or_default();
1183                let self_name2 = stock_str::self_msg(context);
1184
1185                if self_addr.contains(query)
1186                    || self_name.contains(query)
1187                    || self_name2.await.contains(query)
1188                {
1189                    add_self = true;
1190                }
1191            } else {
1192                add_self = true;
1193            }
1194        } else {
1195            add_self = true;
1196
1197            context
1198                .sql
1199                .query_map(
1200                    "SELECT id, addr FROM contacts
1201                 WHERE id>?
1202                 AND (fingerprint='')=?
1203                 AND origin>=?
1204                 AND blocked=0
1205                 ORDER BY origin>=? DESC, last_seen DESC, id DESC",
1206                    (
1207                        ContactId::LAST_SPECIAL,
1208                        flag_address,
1209                        minimal_origin,
1210                        Origin::CreateChat,
1211                    ),
1212                    |row| {
1213                        let id: ContactId = row.get(0)?;
1214                        let addr: String = row.get(1)?;
1215                        Ok((id, addr))
1216                    },
1217                    |rows| {
1218                        for row in rows {
1219                            let (id, addr) = row?;
1220                            if !self_addrs.contains(&addr) {
1221                                ret.push(id);
1222                            }
1223                        }
1224                        Ok(())
1225                    },
1226                )
1227                .await?;
1228        }
1229
1230        if flag_add_self && add_self {
1231            ret.push(ContactId::SELF);
1232        }
1233
1234        Ok(ret)
1235    }
1236
1237    /// Adds blocked mailinglists and broadcast channels as pseudo-contacts
1238    /// to allow unblocking them as if they are contacts
1239    /// (this way, only one unblock-ffi is needed and only one set of ui-functions,
1240    /// from the users perspective,
1241    /// there is not much difference in an email- and a mailinglist-address)
1242    async fn update_blocked_mailinglist_contacts(context: &Context) -> Result<()> {
1243        context
1244            .sql
1245            .transaction(move |transaction| {
1246                let mut stmt = transaction.prepare(
1247                    "SELECT name, grpid, type FROM chats WHERE (type=? OR type=?) AND blocked=?",
1248                )?;
1249                let rows = stmt.query_map(
1250                    (Chattype::Mailinglist, Chattype::InBroadcast, Blocked::Yes),
1251                    |row| {
1252                        let name: String = row.get(0)?;
1253                        let grpid: String = row.get(1)?;
1254                        let typ: Chattype = row.get(2)?;
1255                        Ok((name, grpid, typ))
1256                    },
1257                )?;
1258                let blocked_mailinglists = rows.collect::<std::result::Result<Vec<_>, _>>()?;
1259                for (name, grpid, typ) in blocked_mailinglists {
1260                    let count = transaction.query_row(
1261                        "SELECT COUNT(id) FROM contacts WHERE addr=?",
1262                        [&grpid],
1263                        |row| {
1264                            let count: isize = row.get(0)?;
1265                            Ok(count)
1266                        },
1267                    )?;
1268                    if count == 0 {
1269                        transaction.execute("INSERT INTO contacts (addr) VALUES (?)", [&grpid])?;
1270                    }
1271
1272                    let fingerprint = if typ == Chattype::InBroadcast {
1273                        // Set some fingerprint so that is_pgp_contact() returns true,
1274                        // and the contact isn't marked with a letter icon.
1275                        "Blocked_broadcast"
1276                    } else {
1277                        ""
1278                    };
1279                    // Always do an update in case the blocking is reset or name is changed.
1280                    transaction.execute(
1281                        "
1282UPDATE contacts
1283SET name=?, name_normalized=IIF(?1='',name_normalized,?), origin=?, blocked=1, fingerprint=?
1284WHERE addr=?
1285                        ",
1286                        (
1287                            &name,
1288                            normalize_text(&name),
1289                            Origin::MailinglistAddress,
1290                            fingerprint,
1291                            &grpid,
1292                        ),
1293                    )?;
1294                }
1295                Ok(())
1296            })
1297            .await?;
1298        Ok(())
1299    }
1300
1301    /// Returns number of blocked contacts.
1302    pub async fn get_blocked_cnt(context: &Context) -> Result<usize> {
1303        let count = context
1304            .sql
1305            .count(
1306                "SELECT COUNT(*) FROM contacts WHERE id>? AND blocked!=0",
1307                (ContactId::LAST_SPECIAL,),
1308            )
1309            .await?;
1310        Ok(count)
1311    }
1312
1313    /// Get blocked contacts.
1314    pub async fn get_all_blocked(context: &Context) -> Result<Vec<ContactId>> {
1315        Contact::update_blocked_mailinglist_contacts(context)
1316            .await
1317            .context("cannot update blocked mailinglist contacts")?;
1318
1319        let list = context
1320            .sql
1321            .query_map_vec(
1322                "SELECT id FROM contacts WHERE id>? AND blocked!=0 ORDER BY last_seen DESC, id DESC;",
1323                (ContactId::LAST_SPECIAL,),
1324                |row| {
1325                    let contact_id: ContactId = row.get(0)?;
1326                    Ok(contact_id)
1327                }
1328            )
1329            .await?;
1330        Ok(list)
1331    }
1332
1333    /// Returns a textual summary of the encryption state for the contact.
1334    ///
1335    /// This function returns a string explaining the encryption state
1336    /// of the contact and if the connection is encrypted the
1337    /// fingerprints of the keys involved.
1338    pub async fn get_encrinfo(context: &Context, contact_id: ContactId) -> Result<String> {
1339        ensure!(
1340            !contact_id.is_special(),
1341            "Can not provide encryption info for special contact"
1342        );
1343
1344        let contact = Contact::get_by_id(context, contact_id).await?;
1345        let addr = context
1346            .get_config(Config::ConfiguredAddr)
1347            .await?
1348            .unwrap_or_default();
1349
1350        let Some(fingerprint_other) = contact.fingerprint() else {
1351            return Ok(stock_str::encr_none(context).await);
1352        };
1353        let fingerprint_other = fingerprint_other.to_string();
1354
1355        let stock_message = if contact.public_key(context).await?.is_some() {
1356            stock_str::e2e_available(context).await
1357        } else {
1358            stock_str::encr_none(context).await
1359        };
1360
1361        let finger_prints = stock_str::finger_prints(context).await;
1362        let mut ret = format!("{stock_message}.\n{finger_prints}:");
1363
1364        let fingerprint_self = load_self_public_key(context)
1365            .await?
1366            .dc_fingerprint()
1367            .to_string();
1368        if addr < contact.addr {
1369            cat_fingerprint(
1370                &mut ret,
1371                &stock_str::self_msg(context).await,
1372                &addr,
1373                &fingerprint_self,
1374            );
1375            cat_fingerprint(
1376                &mut ret,
1377                contact.get_display_name(),
1378                &contact.addr,
1379                &fingerprint_other,
1380            );
1381        } else {
1382            cat_fingerprint(
1383                &mut ret,
1384                contact.get_display_name(),
1385                &contact.addr,
1386                &fingerprint_other,
1387            );
1388            cat_fingerprint(
1389                &mut ret,
1390                &stock_str::self_msg(context).await,
1391                &addr,
1392                &fingerprint_self,
1393            );
1394        }
1395
1396        Ok(ret)
1397    }
1398
1399    /// Delete a contact so that it disappears from the corresponding lists.
1400    /// Depending on whether there are ongoing chats, deletion is done by physical deletion or hiding.
1401    /// The contact is deleted from the local device.
1402    ///
1403    /// May result in a `#DC_EVENT_CONTACTS_CHANGED` event.
1404    pub async fn delete(context: &Context, contact_id: ContactId) -> Result<()> {
1405        ensure!(!contact_id.is_special(), "Can not delete special contact");
1406
1407        context
1408            .sql
1409            .transaction(move |transaction| {
1410                // make sure, the transaction starts with a write command and becomes EXCLUSIVE by that -
1411                // upgrading later may be impossible by races.
1412                let deleted_contacts = transaction.execute(
1413                    "DELETE FROM contacts WHERE id=?
1414                     AND (SELECT COUNT(*) FROM chats_contacts WHERE contact_id=?)=0;",
1415                    (contact_id, contact_id),
1416                )?;
1417                if deleted_contacts == 0 {
1418                    transaction.execute(
1419                        "UPDATE contacts SET origin=? WHERE id=?;",
1420                        (Origin::Hidden, contact_id),
1421                    )?;
1422                }
1423                Ok(())
1424            })
1425            .await?;
1426
1427        context.emit_event(EventType::ContactsChanged(None));
1428        Ok(())
1429    }
1430
1431    /// Updates `param` column in the database.
1432    pub async fn update_param(&self, context: &Context) -> Result<()> {
1433        context
1434            .sql
1435            .execute(
1436                "UPDATE contacts SET param=? WHERE id=?",
1437                (self.param.to_string(), self.id),
1438            )
1439            .await?;
1440        Ok(())
1441    }
1442
1443    /// Updates `status` column in the database.
1444    pub async fn update_status(&self, context: &Context) -> Result<()> {
1445        context
1446            .sql
1447            .execute(
1448                "UPDATE contacts SET status=? WHERE id=?",
1449                (&self.status, self.id),
1450            )
1451            .await?;
1452        Ok(())
1453    }
1454
1455    /// Get the ID of the contact.
1456    pub fn get_id(&self) -> ContactId {
1457        self.id
1458    }
1459
1460    /// Get email address. The email address is always set for a contact.
1461    pub fn get_addr(&self) -> &str {
1462        &self.addr
1463    }
1464
1465    /// Returns true if the contact is a key-contact.
1466    /// Otherwise it is an addresss-contact.
1467    pub fn is_key_contact(&self) -> bool {
1468        self.fingerprint.is_some()
1469    }
1470
1471    /// Returns OpenPGP fingerprint of a contact.
1472    ///
1473    /// `None` for address-contacts.
1474    pub fn fingerprint(&self) -> Option<Fingerprint> {
1475        if let Some(fingerprint) = &self.fingerprint {
1476            fingerprint.parse().ok()
1477        } else {
1478            None
1479        }
1480    }
1481
1482    /// Returns OpenPGP public key of a contact.
1483    ///
1484    /// Returns `None` if the contact is not a key-contact
1485    /// or if the key is not available.
1486    /// It is possible for a key-contact to not have a key,
1487    /// e.g. if only the fingerprint is known from a QR-code.
1488    pub async fn public_key(&self, context: &Context) -> Result<Option<SignedPublicKey>> {
1489        if self.id == ContactId::SELF {
1490            return Ok(Some(load_self_public_key(context).await?));
1491        }
1492
1493        if let Some(fingerprint) = &self.fingerprint {
1494            if let Some(public_key_bytes) = context
1495                .sql
1496                .query_row_optional(
1497                    "SELECT public_key
1498                     FROM public_keys
1499                     WHERE fingerprint=?",
1500                    (fingerprint,),
1501                    |row| {
1502                        let bytes: Vec<u8> = row.get(0)?;
1503                        Ok(bytes)
1504                    },
1505                )
1506                .await?
1507            {
1508                let public_key = SignedPublicKey::from_slice(&public_key_bytes)?;
1509                Ok(Some(public_key))
1510            } else {
1511                Ok(None)
1512            }
1513        } else {
1514            Ok(None)
1515        }
1516    }
1517
1518    /// Get name authorized by the contact.
1519    pub fn get_authname(&self) -> &str {
1520        &self.authname
1521    }
1522
1523    /// Get the contact name. This is the name as modified by the local user.
1524    /// May be an empty string.
1525    ///
1526    /// This name is typically used in a form where the user can edit the name of a contact.
1527    /// To get a fine name to display in lists etc., use `Contact::get_display_name` or `Contact::get_name_n_addr`.
1528    pub fn get_name(&self) -> &str {
1529        &self.name
1530    }
1531
1532    /// Get display name. This is the name as defined by the contact himself,
1533    /// modified by the user or, if both are unset, the email address.
1534    ///
1535    /// This name is typically used in lists.
1536    /// To get the name editable in a formular, use `Contact::get_name`.
1537    pub fn get_display_name(&self) -> &str {
1538        if !self.name.is_empty() {
1539            return &self.name;
1540        }
1541        if !self.authname.is_empty() {
1542            return &self.authname;
1543        }
1544        &self.addr
1545    }
1546
1547    /// Get a summary of name and address.
1548    ///
1549    /// The returned string is either "Name (email@domain.com)" or just
1550    /// "email@domain.com" if the name is unset.
1551    ///
1552    /// The result should only be used locally and never sent over the network
1553    /// as it leaks the local contact name.
1554    ///
1555    /// The summary is typically used when asking the user something about the contact.
1556    /// The attached email address makes the question unique, eg. "Chat with Alan Miller (am@uniquedomain.com)?"
1557    pub fn get_name_n_addr(&self) -> String {
1558        if !self.name.is_empty() {
1559            format!("{} ({})", self.name, self.addr)
1560        } else if !self.authname.is_empty() {
1561            format!("{} ({})", self.authname, self.addr)
1562        } else {
1563            (&self.addr).into()
1564        }
1565    }
1566
1567    /// Get the contact's profile image.
1568    /// This is the image set by each remote user on their own
1569    /// using set_config(context, "selfavatar", image).
1570    pub async fn get_profile_image(&self, context: &Context) -> Result<Option<PathBuf>> {
1571        self.get_profile_image_ex(context, true).await
1572    }
1573
1574    /// Get the contact's profile image.
1575    /// This is the image set by each remote user on their own
1576    /// using set_config(context, "selfavatar", image).
1577    async fn get_profile_image_ex(
1578        &self,
1579        context: &Context,
1580        show_fallback_icon: bool,
1581    ) -> Result<Option<PathBuf>> {
1582        if self.id == ContactId::SELF {
1583            if let Some(p) = context.get_config(Config::Selfavatar).await? {
1584                return Ok(Some(PathBuf::from(p))); // get_config() calls get_abs_path() internally already
1585            }
1586        } else if self.id == ContactId::DEVICE {
1587            return Ok(Some(chat::get_device_icon(context).await?));
1588        }
1589        if show_fallback_icon && !self.id.is_special() && !self.is_key_contact() {
1590            return Ok(Some(chat::get_unencrypted_icon(context).await?));
1591        }
1592        if let Some(image_rel) = self.param.get(Param::ProfileImage)
1593            && !image_rel.is_empty()
1594        {
1595            return Ok(Some(get_abs_path(context, Path::new(image_rel))));
1596        }
1597        Ok(None)
1598    }
1599
1600    /// Returns a color for the contact.
1601    /// For self-contact this returns gray if own keypair doesn't exist yet.
1602    /// See also [`self::get_color`].
1603    pub fn get_color(&self) -> u32 {
1604        get_color(self.id == ContactId::SELF, &self.addr, &self.fingerprint())
1605    }
1606
1607    /// Returns a color for the contact.
1608    /// Ensures that the color isn't gray. For self-contact this generates own keypair if it doesn't
1609    /// exist yet.
1610    /// See also [`self::get_color`].
1611    pub async fn get_or_gen_color(&self, context: &Context) -> Result<u32> {
1612        let mut fpr = self.fingerprint();
1613        if fpr.is_none() && self.id == ContactId::SELF {
1614            fpr = Some(load_self_public_key(context).await?.dc_fingerprint());
1615        }
1616        Ok(get_color(self.id == ContactId::SELF, &self.addr, &fpr))
1617    }
1618
1619    /// Gets the contact's status.
1620    ///
1621    /// Status is the last signature received in a message from this contact.
1622    pub fn get_status(&self) -> &str {
1623        self.status.as_str()
1624    }
1625
1626    /// Returns whether end-to-end encryption to the contact is available.
1627    pub async fn e2ee_avail(&self, context: &Context) -> Result<bool> {
1628        if self.id == ContactId::SELF {
1629            // We don't need to check if we have our own key.
1630            return Ok(true);
1631        }
1632        Ok(self.public_key(context).await?.is_some())
1633    }
1634
1635    /// Returns true if the contact
1636    /// can be added to verified chats.
1637    ///
1638    /// If contact is verified
1639    /// UI should display green checkmark after the contact name
1640    /// in contact list items and
1641    /// in chat member list items.
1642    ///
1643    /// In contact profile view, use this function only if there is no chat with the contact,
1644    /// otherwise use is_chat_protected().
1645    /// Use [Self::get_verifier_id] to display the verifier contact
1646    /// in the info section of the contact profile.
1647    pub async fn is_verified(&self, context: &Context) -> Result<bool> {
1648        // We're always sort of secured-verified as we could verify the key on this device any time with the key
1649        // on this device
1650        if self.id == ContactId::SELF {
1651            return Ok(true);
1652        }
1653
1654        Ok(self.get_verifier_id(context).await?.is_some())
1655    }
1656
1657    /// Returns the `ContactId` that verified the contact.
1658    ///
1659    /// If this returns Some(_),
1660    /// display green checkmark in the profile and "Introduced by ..." line
1661    /// with the name and address of the contact
1662    /// formatted by [Self::get_name_n_addr].
1663    ///
1664    /// If this returns `Some(None)`, then the contact is verified,
1665    /// but it's unclear by whom.
1666    pub async fn get_verifier_id(&self, context: &Context) -> Result<Option<Option<ContactId>>> {
1667        let verifier_id: u32 = context
1668            .sql
1669            .query_get_value("SELECT verifier FROM contacts WHERE id=?", (self.id,))
1670            .await?
1671            .with_context(|| format!("Contact {} does not exist", self.id))?;
1672
1673        if verifier_id == 0 {
1674            Ok(None)
1675        } else if verifier_id == self.id.to_u32() {
1676            Ok(Some(None))
1677        } else {
1678            Ok(Some(Some(ContactId::new(verifier_id))))
1679        }
1680    }
1681
1682    /// Returns the number of real (i.e. non-special) contacts in the database.
1683    pub async fn get_real_cnt(context: &Context) -> Result<usize> {
1684        if !context.sql.is_open().await {
1685            return Ok(0);
1686        }
1687
1688        let count = context
1689            .sql
1690            .count(
1691                "SELECT COUNT(*) FROM contacts WHERE id>?;",
1692                (ContactId::LAST_SPECIAL,),
1693            )
1694            .await?;
1695        Ok(count)
1696    }
1697
1698    /// Returns true if a contact with this ID exists.
1699    pub async fn real_exists_by_id(context: &Context, contact_id: ContactId) -> Result<bool> {
1700        if contact_id.is_special() {
1701            return Ok(false);
1702        }
1703
1704        let exists = context
1705            .sql
1706            .exists("SELECT COUNT(*) FROM contacts WHERE id=?;", (contact_id,))
1707            .await?;
1708        Ok(exists)
1709    }
1710}
1711
1712/// Returns a color for a contact having given attributes.
1713///
1714/// The color is calculated from contact's fingerprint (for key-contacts) or email address (for
1715/// address-contacts; should be lowercased to avoid allocation) and can be used for an fallback
1716/// avatar with white initials as well as for headlines in bubbles of group chats.
1717pub fn get_color(is_self: bool, addr: &str, fingerprint: &Option<Fingerprint>) -> u32 {
1718    if let Some(fingerprint) = fingerprint {
1719        str_to_color(&fingerprint.hex())
1720    } else if is_self {
1721        0x808080
1722    } else {
1723        str_to_color(&to_lowercase(addr))
1724    }
1725}
1726
1727// Updates the names of the chats which use the contact name.
1728//
1729// This is one of the few duplicated data, however, getting the chat list is easier this way.
1730fn update_chat_names(
1731    context: &Context,
1732    transaction: &rusqlite::Connection,
1733    contact_id: ContactId,
1734) -> Result<()> {
1735    let chat_id: Option<ChatId> = transaction.query_row(
1736            "SELECT id FROM chats WHERE type=? AND id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?)",
1737            (Chattype::Single, contact_id),
1738            |row| {
1739                let chat_id: ChatId = row.get(0)?;
1740                Ok(chat_id)
1741            }
1742        ).optional()?;
1743
1744    if let Some(chat_id) = chat_id {
1745        let (addr, name, authname) = transaction.query_row(
1746            "SELECT addr, name, authname
1747                     FROM contacts
1748                     WHERE id=?",
1749            (contact_id,),
1750            |row| {
1751                let addr: String = row.get(0)?;
1752                let name: String = row.get(1)?;
1753                let authname: String = row.get(2)?;
1754                Ok((addr, name, authname))
1755            },
1756        )?;
1757
1758        let chat_name = if !name.is_empty() {
1759            name
1760        } else if !authname.is_empty() {
1761            authname
1762        } else {
1763            addr
1764        };
1765
1766        let count = transaction.execute(
1767            "UPDATE chats SET name=?1, name_normalized=?2 WHERE id=?3 AND name!=?1",
1768            (&chat_name, normalize_text(&chat_name), chat_id),
1769        )?;
1770
1771        if count > 0 {
1772            // Chat name updated
1773            context.emit_event(EventType::ChatModified(chat_id));
1774            chatlist_events::emit_chatlist_items_changed_for_contact(context, contact_id);
1775        }
1776    }
1777
1778    Ok(())
1779}
1780
1781pub(crate) async fn set_blocked(
1782    context: &Context,
1783    sync: sync::Sync,
1784    contact_id: ContactId,
1785    new_blocking: bool,
1786) -> Result<()> {
1787    ensure!(
1788        !contact_id.is_special(),
1789        "Can't block special contact {contact_id}"
1790    );
1791    let contact = Contact::get_by_id(context, contact_id).await?;
1792
1793    if contact.blocked != new_blocking {
1794        context
1795            .sql
1796            .execute(
1797                "UPDATE contacts SET blocked=? WHERE id=?;",
1798                (i32::from(new_blocking), contact_id),
1799            )
1800            .await?;
1801
1802        // also (un)block all chats with _only_ this contact - we do not delete them to allow a
1803        // non-destructive blocking->unblocking.
1804        // (Maybe, beside normal chats (type=100) we should also block group chats with only this user.
1805        // However, I'm not sure about this point; it may be confusing if the user wants to add other people;
1806        // this would result in recreating the same group...)
1807        if context
1808            .sql
1809            .execute(
1810                r#"
1811UPDATE chats
1812SET blocked=?
1813WHERE type=? AND id IN (
1814  SELECT chat_id FROM chats_contacts WHERE contact_id=?
1815);
1816"#,
1817                (new_blocking, Chattype::Single, contact_id),
1818            )
1819            .await
1820            .is_ok()
1821        {
1822            Contact::mark_noticed(context, contact_id).await?;
1823            context.emit_event(EventType::ContactsChanged(Some(contact_id)));
1824        }
1825
1826        // also unblock mailinglist
1827        // if the contact is a mailinglist address explicitly created to allow unblocking
1828        if !new_blocking
1829            && contact.origin == Origin::MailinglistAddress
1830            && let Some((chat_id, ..)) = chat::get_chat_id_by_grpid(context, &contact.addr).await?
1831        {
1832            chat_id.unblock_ex(context, Nosync).await?;
1833        }
1834
1835        if sync.into() {
1836            let action = match new_blocking {
1837                true => chat::SyncAction::Block,
1838                false => chat::SyncAction::Unblock,
1839            };
1840            let sync_id = if let Some(fingerprint) = contact.fingerprint() {
1841                chat::SyncId::ContactFingerprint(fingerprint.hex())
1842            } else {
1843                chat::SyncId::ContactAddr(contact.addr.clone())
1844            };
1845
1846            chat::sync(context, sync_id, action)
1847                .await
1848                .log_err(context)
1849                .ok();
1850        }
1851    }
1852
1853    chatlist_events::emit_chatlist_changed(context);
1854    Ok(())
1855}
1856
1857/// Set profile image for a contact.
1858///
1859/// The given profile image is expected to be already in the blob directory
1860/// as profile images can be set only by receiving messages, this should be always the case, however.
1861///
1862/// For contact SELF, the image is not saved in the contact-database but as Config::Selfavatar.
1863pub(crate) async fn set_profile_image(
1864    context: &Context,
1865    contact_id: ContactId,
1866    profile_image: &AvatarAction,
1867) -> Result<()> {
1868    let mut contact = Contact::get_by_id(context, contact_id).await?;
1869    let changed = match profile_image {
1870        AvatarAction::Change(profile_image) => {
1871            if contact_id == ContactId::SELF {
1872                context
1873                    .set_config_ex(Nosync, Config::Selfavatar, Some(profile_image))
1874                    .await?;
1875            } else {
1876                contact.param.set(Param::ProfileImage, profile_image);
1877            }
1878            true
1879        }
1880        AvatarAction::Delete => {
1881            if contact_id == ContactId::SELF {
1882                context
1883                    .set_config_ex(Nosync, Config::Selfavatar, None)
1884                    .await?;
1885            } else {
1886                contact.param.remove(Param::ProfileImage);
1887            }
1888            true
1889        }
1890    };
1891    if changed {
1892        contact.update_param(context).await?;
1893        context.emit_event(EventType::ContactsChanged(Some(contact_id)));
1894        chatlist_events::emit_chatlist_item_changed_for_contact_chat(context, contact_id).await;
1895    }
1896    Ok(())
1897}
1898
1899/// Sets contact status.
1900///
1901/// For contact SELF, the status is not saved in the contact table, but as Config::Selfstatus.
1902pub(crate) async fn set_status(
1903    context: &Context,
1904    contact_id: ContactId,
1905    status: String,
1906) -> Result<()> {
1907    if contact_id == ContactId::SELF {
1908        context
1909            .set_config_ex(Nosync, Config::Selfstatus, Some(&status))
1910            .await?;
1911    } else {
1912        let mut contact = Contact::get_by_id(context, contact_id).await?;
1913
1914        if contact.status != status {
1915            contact.status = status;
1916            contact.update_status(context).await?;
1917            context.emit_event(EventType::ContactsChanged(Some(contact_id)));
1918        }
1919    }
1920    Ok(())
1921}
1922
1923/// Updates last seen timestamp of the contact if it is earlier than the given `timestamp`.
1924pub(crate) async fn update_last_seen(
1925    context: &Context,
1926    contact_id: ContactId,
1927    timestamp: i64,
1928) -> Result<()> {
1929    ensure!(
1930        !contact_id.is_special(),
1931        "Can not update special contact last seen timestamp"
1932    );
1933
1934    if context
1935        .sql
1936        .execute(
1937            "UPDATE contacts SET last_seen = ?1 WHERE last_seen < ?1 AND id = ?2",
1938            (timestamp, contact_id),
1939        )
1940        .await?
1941        > 0
1942        && timestamp > time() - SEEN_RECENTLY_SECONDS
1943    {
1944        context.emit_event(EventType::ContactsChanged(Some(contact_id)));
1945        context
1946            .scheduler
1947            .interrupt_recently_seen(contact_id, timestamp)
1948            .await;
1949    }
1950    Ok(())
1951}
1952
1953/// Marks contact `contact_id` as verified by `verifier_id`.
1954///
1955/// `verifier_id == None` means that the verifier is unknown.
1956pub(crate) async fn mark_contact_id_as_verified(
1957    context: &Context,
1958    contact_id: ContactId,
1959    verifier_id: Option<ContactId>,
1960) -> Result<()> {
1961    ensure_and_debug_assert_ne!(contact_id, ContactId::SELF,);
1962    ensure_and_debug_assert_ne!(
1963        Some(contact_id),
1964        verifier_id,
1965        "Contact cannot be verified by self",
1966    );
1967    let by_self = verifier_id == Some(ContactId::SELF);
1968    let mut verifier_id = verifier_id.unwrap_or(contact_id);
1969    context
1970        .sql
1971        .transaction(|transaction| {
1972            let contact_fingerprint: String = transaction.query_row(
1973                "SELECT fingerprint FROM contacts WHERE id=?",
1974                (contact_id,),
1975                |row| row.get(0),
1976            )?;
1977            if contact_fingerprint.is_empty() {
1978                bail!("Non-key-contact {contact_id} cannot be verified");
1979            }
1980            if verifier_id != ContactId::SELF {
1981                let (verifier_fingerprint, verifier_verifier_id): (String, ContactId) = transaction
1982                    .query_row(
1983                        "SELECT fingerprint, verifier FROM contacts WHERE id=?",
1984                        (verifier_id,),
1985                        |row| Ok((row.get(0)?, row.get(1)?)),
1986                    )?;
1987                if verifier_fingerprint.is_empty() {
1988                    bail!(
1989                        "Contact {contact_id} cannot be verified by non-key-contact {verifier_id}"
1990                    );
1991                }
1992                ensure!(
1993                    verifier_id == contact_id || verifier_verifier_id != ContactId::UNDEFINED,
1994                    "Contact {contact_id} cannot be verified by unverified contact {verifier_id}",
1995                );
1996                if verifier_verifier_id == verifier_id {
1997                    // Avoid introducing incorrect reverse chains: if the verifier itself has an
1998                    // unknown verifier, it may be `contact_id` actually (directly or indirectly) on
1999                    // the other device (which is needed for getting "verified by unknown contact"
2000                    // in the first place).
2001                    verifier_id = contact_id;
2002                }
2003            }
2004            transaction.execute(
2005                "UPDATE contacts SET verifier=?1
2006                 WHERE id=?2 AND (verifier=0 OR verifier=id OR ?3)",
2007                (verifier_id, contact_id, by_self),
2008            )?;
2009            Ok(())
2010        })
2011        .await?;
2012    Ok(())
2013}
2014
2015fn cat_fingerprint(ret: &mut String, name: &str, addr: &str, fingerprint: &str) {
2016    *ret += &format!("\n\n{name} ({addr}):\n{fingerprint}");
2017}
2018
2019fn split_address_book(book: &str) -> Vec<(&str, &str)> {
2020    book.lines()
2021        .collect::<Vec<&str>>()
2022        .chunks(2)
2023        .filter_map(|chunk| {
2024            let name = chunk.first()?;
2025            let addr = chunk.get(1)?;
2026            Some((*name, *addr))
2027        })
2028        .collect()
2029}
2030
2031#[derive(Debug)]
2032pub(crate) struct RecentlySeenInterrupt {
2033    contact_id: ContactId,
2034    timestamp: i64,
2035}
2036
2037#[derive(Debug)]
2038pub(crate) struct RecentlySeenLoop {
2039    /// Task running "recently seen" loop.
2040    handle: task::JoinHandle<()>,
2041
2042    interrupt_send: Sender<RecentlySeenInterrupt>,
2043}
2044
2045impl RecentlySeenLoop {
2046    pub(crate) fn new(context: Context) -> Self {
2047        let (interrupt_send, interrupt_recv) = channel::bounded(1);
2048
2049        let handle = task::spawn(Self::run(context, interrupt_recv));
2050        Self {
2051            handle,
2052            interrupt_send,
2053        }
2054    }
2055
2056    async fn run(context: Context, interrupt: Receiver<RecentlySeenInterrupt>) {
2057        type MyHeapElem = (Reverse<i64>, ContactId);
2058
2059        let now = SystemTime::now();
2060        let now_ts = now
2061            .duration_since(SystemTime::UNIX_EPOCH)
2062            .unwrap_or_default()
2063            .as_secs() as i64;
2064
2065        // Priority contains all recently seen sorted by the timestamp
2066        // when they become not recently seen.
2067        //
2068        // Initialize with contacts which are currently seen, but will
2069        // become unseen in the future.
2070        let mut unseen_queue: BinaryHeap<MyHeapElem> = context
2071            .sql
2072            .query_map_collect(
2073                "SELECT id, last_seen FROM contacts
2074                 WHERE last_seen > ?",
2075                (now_ts - SEEN_RECENTLY_SECONDS,),
2076                |row| {
2077                    let contact_id: ContactId = row.get("id")?;
2078                    let last_seen: i64 = row.get("last_seen")?;
2079                    Ok((Reverse(last_seen + SEEN_RECENTLY_SECONDS), contact_id))
2080                },
2081            )
2082            .await
2083            .unwrap_or_default();
2084
2085        loop {
2086            let now = SystemTime::now();
2087            let (until, contact_id) =
2088                if let Some((Reverse(timestamp), contact_id)) = unseen_queue.peek() {
2089                    (
2090                        UNIX_EPOCH
2091                            + Duration::from_secs((*timestamp).try_into().unwrap_or(u64::MAX))
2092                            + Duration::from_secs(1),
2093                        Some(contact_id),
2094                    )
2095                } else {
2096                    // Sleep for 24 hours.
2097                    (now + Duration::from_secs(86400), None)
2098                };
2099
2100            if let Ok(duration) = until.duration_since(now) {
2101                info!(
2102                    context,
2103                    "Recently seen loop waiting for {} or interrupt",
2104                    duration_to_str(duration)
2105                );
2106
2107                match timeout(duration, interrupt.recv()).await {
2108                    Err(_) => {
2109                        // Timeout, notify about contact.
2110                        if let Some(contact_id) = contact_id {
2111                            context.emit_event(EventType::ContactsChanged(Some(*contact_id)));
2112                            chatlist_events::emit_chatlist_item_changed_for_contact_chat(
2113                                &context,
2114                                *contact_id,
2115                            )
2116                            .await;
2117                            unseen_queue.pop();
2118                        }
2119                    }
2120                    Ok(Err(err)) => {
2121                        warn!(
2122                            context,
2123                            "Error receiving an interruption in recently seen loop: {}", err
2124                        );
2125                        // Maybe the sender side is closed.
2126                        // Terminate the loop to avoid looping indefinitely.
2127                        return;
2128                    }
2129                    Ok(Ok(RecentlySeenInterrupt {
2130                        contact_id,
2131                        timestamp,
2132                    })) => {
2133                        // Received an interrupt.
2134                        if contact_id != ContactId::UNDEFINED {
2135                            unseen_queue
2136                                .push((Reverse(timestamp + SEEN_RECENTLY_SECONDS), contact_id));
2137                        }
2138                    }
2139                }
2140            } else {
2141                info!(
2142                    context,
2143                    "Recently seen loop is not waiting, event is already due."
2144                );
2145
2146                // Event is already in the past.
2147                if let Some(contact_id) = contact_id {
2148                    context.emit_event(EventType::ContactsChanged(Some(*contact_id)));
2149                    chatlist_events::emit_chatlist_item_changed_for_contact_chat(
2150                        &context,
2151                        *contact_id,
2152                    )
2153                    .await;
2154                }
2155                unseen_queue.pop();
2156            }
2157        }
2158    }
2159
2160    pub(crate) fn try_interrupt(&self, contact_id: ContactId, timestamp: i64) {
2161        self.interrupt_send
2162            .try_send(RecentlySeenInterrupt {
2163                contact_id,
2164                timestamp,
2165            })
2166            .ok();
2167    }
2168
2169    #[cfg(test)]
2170    pub(crate) async fn interrupt(&self, contact_id: ContactId, timestamp: i64) {
2171        self.interrupt_send
2172            .send(RecentlySeenInterrupt {
2173                contact_id,
2174                timestamp,
2175            })
2176            .await
2177            .unwrap();
2178    }
2179
2180    pub(crate) async fn abort(self) {
2181        self.handle.abort();
2182
2183        // Await aborted task to ensure the `Future` is dropped
2184        // with all resources moved inside such as the `Context`
2185        // reference to `InnerContext`.
2186        self.handle.await.ok();
2187    }
2188}
2189
2190#[cfg(test)]
2191mod contact_tests;