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 query_lowercased = query.unwrap_or("").to_lowercase();
1137            let s3str_like_cmd = format!("%{}%", query_lowercased);
1138            context
1139                .sql
1140                .query_map(
1141                    "
1142SELECT c.id, c.addr FROM contacts c
1143WHERE c.id>?
1144    AND (c.fingerprint='')=?
1145    AND c.origin>=?
1146    AND c.blocked=0
1147    AND (IFNULL(c.name_normalized,IIF(c.name='',c.authname,c.name)) LIKE ? OR c.addr LIKE ?)
1148ORDER BY c.origin>=? DESC, c.last_seen DESC, c.id DESC
1149                    ",
1150                    (
1151                        ContactId::LAST_SPECIAL,
1152                        flag_address,
1153                        minimal_origin,
1154                        &s3str_like_cmd,
1155                        &query_lowercased,
1156                        Origin::CreateChat,
1157                    ),
1158                    |row| {
1159                        let id: ContactId = row.get(0)?;
1160                        let addr: String = row.get(1)?;
1161                        Ok((id, addr))
1162                    },
1163                    |rows| {
1164                        for row in rows {
1165                            let (id, addr) = row?;
1166                            if !self_addrs.contains(&addr) {
1167                                ret.push(id);
1168                            }
1169                        }
1170                        Ok(())
1171                    },
1172                )
1173                .await?;
1174
1175            if let Some(query) = query {
1176                let self_addr = context
1177                    .get_config(Config::ConfiguredAddr)
1178                    .await?
1179                    .unwrap_or_default();
1180                let self_name = context
1181                    .get_config(Config::Displayname)
1182                    .await?
1183                    .unwrap_or_default();
1184                let self_name2 = stock_str::self_msg(context);
1185
1186                if self_addr.contains(query)
1187                    || self_name.contains(query)
1188                    || self_name2.await.contains(query)
1189                {
1190                    add_self = true;
1191                }
1192            } else {
1193                add_self = true;
1194            }
1195        } else {
1196            add_self = true;
1197
1198            context
1199                .sql
1200                .query_map(
1201                    "SELECT id, addr FROM contacts
1202                 WHERE id>?
1203                 AND (fingerprint='')=?
1204                 AND origin>=?
1205                 AND blocked=0
1206                 ORDER BY origin>=? DESC, last_seen DESC, id DESC",
1207                    (
1208                        ContactId::LAST_SPECIAL,
1209                        flag_address,
1210                        minimal_origin,
1211                        Origin::CreateChat,
1212                    ),
1213                    |row| {
1214                        let id: ContactId = row.get(0)?;
1215                        let addr: String = row.get(1)?;
1216                        Ok((id, addr))
1217                    },
1218                    |rows| {
1219                        for row in rows {
1220                            let (id, addr) = row?;
1221                            if !self_addrs.contains(&addr) {
1222                                ret.push(id);
1223                            }
1224                        }
1225                        Ok(())
1226                    },
1227                )
1228                .await?;
1229        }
1230
1231        if flag_add_self && add_self {
1232            ret.push(ContactId::SELF);
1233        }
1234
1235        Ok(ret)
1236    }
1237
1238    /// Adds blocked mailinglists and broadcast channels as pseudo-contacts
1239    /// to allow unblocking them as if they are contacts
1240    /// (this way, only one unblock-ffi is needed and only one set of ui-functions,
1241    /// from the users perspective,
1242    /// there is not much difference in an email- and a mailinglist-address)
1243    async fn update_blocked_mailinglist_contacts(context: &Context) -> Result<()> {
1244        context
1245            .sql
1246            .transaction(move |transaction| {
1247                let mut stmt = transaction.prepare(
1248                    "SELECT name, grpid, type FROM chats WHERE (type=? OR type=?) AND blocked=?",
1249                )?;
1250                let rows = stmt.query_map(
1251                    (Chattype::Mailinglist, Chattype::InBroadcast, Blocked::Yes),
1252                    |row| {
1253                        let name: String = row.get(0)?;
1254                        let grpid: String = row.get(1)?;
1255                        let typ: Chattype = row.get(2)?;
1256                        Ok((name, grpid, typ))
1257                    },
1258                )?;
1259                let blocked_mailinglists = rows.collect::<std::result::Result<Vec<_>, _>>()?;
1260                for (name, grpid, typ) in blocked_mailinglists {
1261                    let count = transaction.query_row(
1262                        "SELECT COUNT(id) FROM contacts WHERE addr=?",
1263                        [&grpid],
1264                        |row| {
1265                            let count: isize = row.get(0)?;
1266                            Ok(count)
1267                        },
1268                    )?;
1269                    if count == 0 {
1270                        transaction.execute("INSERT INTO contacts (addr) VALUES (?)", [&grpid])?;
1271                    }
1272
1273                    let fingerprint = if typ == Chattype::InBroadcast {
1274                        // Set some fingerprint so that is_pgp_contact() returns true,
1275                        // and the contact isn't marked with a letter icon.
1276                        "Blocked_broadcast"
1277                    } else {
1278                        ""
1279                    };
1280                    // Always do an update in case the blocking is reset or name is changed.
1281                    transaction.execute(
1282                        "
1283UPDATE contacts
1284SET name=?, name_normalized=IIF(?1='',name_normalized,?), origin=?, blocked=1, fingerprint=?
1285WHERE addr=?
1286                        ",
1287                        (
1288                            &name,
1289                            normalize_text(&name),
1290                            Origin::MailinglistAddress,
1291                            fingerprint,
1292                            &grpid,
1293                        ),
1294                    )?;
1295                }
1296                Ok(())
1297            })
1298            .await?;
1299        Ok(())
1300    }
1301
1302    /// Returns number of blocked contacts.
1303    pub async fn get_blocked_cnt(context: &Context) -> Result<usize> {
1304        let count = context
1305            .sql
1306            .count(
1307                "SELECT COUNT(*) FROM contacts WHERE id>? AND blocked!=0",
1308                (ContactId::LAST_SPECIAL,),
1309            )
1310            .await?;
1311        Ok(count)
1312    }
1313
1314    /// Get blocked contacts.
1315    pub async fn get_all_blocked(context: &Context) -> Result<Vec<ContactId>> {
1316        Contact::update_blocked_mailinglist_contacts(context)
1317            .await
1318            .context("cannot update blocked mailinglist contacts")?;
1319
1320        let list = context
1321            .sql
1322            .query_map_vec(
1323                "SELECT id FROM contacts WHERE id>? AND blocked!=0 ORDER BY last_seen DESC, id DESC;",
1324                (ContactId::LAST_SPECIAL,),
1325                |row| {
1326                    let contact_id: ContactId = row.get(0)?;
1327                    Ok(contact_id)
1328                }
1329            )
1330            .await?;
1331        Ok(list)
1332    }
1333
1334    /// Returns a textual summary of the encryption state for the contact.
1335    ///
1336    /// This function returns a string explaining the encryption state
1337    /// of the contact and if the connection is encrypted the
1338    /// fingerprints of the keys involved.
1339    pub async fn get_encrinfo(context: &Context, contact_id: ContactId) -> Result<String> {
1340        ensure!(
1341            !contact_id.is_special(),
1342            "Can not provide encryption info for special contact"
1343        );
1344
1345        let contact = Contact::get_by_id(context, contact_id).await?;
1346        let addr = context
1347            .get_config(Config::ConfiguredAddr)
1348            .await?
1349            .unwrap_or_default();
1350
1351        let Some(fingerprint_other) = contact.fingerprint() else {
1352            return Ok(stock_str::encr_none(context).await);
1353        };
1354        let fingerprint_other = fingerprint_other.to_string();
1355
1356        let stock_message = if contact.public_key(context).await?.is_some() {
1357            stock_str::e2e_available(context).await
1358        } else {
1359            stock_str::encr_none(context).await
1360        };
1361
1362        let finger_prints = stock_str::finger_prints(context).await;
1363        let mut ret = format!("{stock_message}.\n{finger_prints}:");
1364
1365        let fingerprint_self = load_self_public_key(context)
1366            .await?
1367            .dc_fingerprint()
1368            .to_string();
1369        if addr < contact.addr {
1370            cat_fingerprint(
1371                &mut ret,
1372                &stock_str::self_msg(context).await,
1373                &addr,
1374                &fingerprint_self,
1375            );
1376            cat_fingerprint(
1377                &mut ret,
1378                contact.get_display_name(),
1379                &contact.addr,
1380                &fingerprint_other,
1381            );
1382        } else {
1383            cat_fingerprint(
1384                &mut ret,
1385                contact.get_display_name(),
1386                &contact.addr,
1387                &fingerprint_other,
1388            );
1389            cat_fingerprint(
1390                &mut ret,
1391                &stock_str::self_msg(context).await,
1392                &addr,
1393                &fingerprint_self,
1394            );
1395        }
1396
1397        Ok(ret)
1398    }
1399
1400    /// Delete a contact so that it disappears from the corresponding lists.
1401    /// Depending on whether there are ongoing chats, deletion is done by physical deletion or hiding.
1402    /// The contact is deleted from the local device.
1403    ///
1404    /// May result in a `#DC_EVENT_CONTACTS_CHANGED` event.
1405    pub async fn delete(context: &Context, contact_id: ContactId) -> Result<()> {
1406        ensure!(!contact_id.is_special(), "Can not delete special contact");
1407
1408        context
1409            .sql
1410            .transaction(move |transaction| {
1411                // make sure, the transaction starts with a write command and becomes EXCLUSIVE by that -
1412                // upgrading later may be impossible by races.
1413                let deleted_contacts = transaction.execute(
1414                    "DELETE FROM contacts WHERE id=?
1415                     AND (SELECT COUNT(*) FROM chats_contacts WHERE contact_id=?)=0;",
1416                    (contact_id, contact_id),
1417                )?;
1418                if deleted_contacts == 0 {
1419                    transaction.execute(
1420                        "UPDATE contacts SET origin=? WHERE id=?;",
1421                        (Origin::Hidden, contact_id),
1422                    )?;
1423                }
1424                Ok(())
1425            })
1426            .await?;
1427
1428        context.emit_event(EventType::ContactsChanged(None));
1429        Ok(())
1430    }
1431
1432    /// Updates `param` column in the database.
1433    pub async fn update_param(&self, context: &Context) -> Result<()> {
1434        context
1435            .sql
1436            .execute(
1437                "UPDATE contacts SET param=? WHERE id=?",
1438                (self.param.to_string(), self.id),
1439            )
1440            .await?;
1441        Ok(())
1442    }
1443
1444    /// Updates `status` column in the database.
1445    pub async fn update_status(&self, context: &Context) -> Result<()> {
1446        context
1447            .sql
1448            .execute(
1449                "UPDATE contacts SET status=? WHERE id=?",
1450                (&self.status, self.id),
1451            )
1452            .await?;
1453        Ok(())
1454    }
1455
1456    /// Get the ID of the contact.
1457    pub fn get_id(&self) -> ContactId {
1458        self.id
1459    }
1460
1461    /// Get email address. The email address is always set for a contact.
1462    pub fn get_addr(&self) -> &str {
1463        &self.addr
1464    }
1465
1466    /// Returns true if the contact is a key-contact.
1467    /// Otherwise it is an addresss-contact.
1468    pub fn is_key_contact(&self) -> bool {
1469        self.fingerprint.is_some()
1470    }
1471
1472    /// Returns OpenPGP fingerprint of a contact.
1473    ///
1474    /// `None` for address-contacts.
1475    pub fn fingerprint(&self) -> Option<Fingerprint> {
1476        if let Some(fingerprint) = &self.fingerprint {
1477            fingerprint.parse().ok()
1478        } else {
1479            None
1480        }
1481    }
1482
1483    /// Returns OpenPGP public key of a contact.
1484    ///
1485    /// Returns `None` if the contact is not a key-contact
1486    /// or if the key is not available.
1487    /// It is possible for a key-contact to not have a key,
1488    /// e.g. if only the fingerprint is known from a QR-code.
1489    pub async fn public_key(&self, context: &Context) -> Result<Option<SignedPublicKey>> {
1490        if self.id == ContactId::SELF {
1491            return Ok(Some(load_self_public_key(context).await?));
1492        }
1493
1494        if let Some(fingerprint) = &self.fingerprint {
1495            if let Some(public_key_bytes) = context
1496                .sql
1497                .query_row_optional(
1498                    "SELECT public_key
1499                     FROM public_keys
1500                     WHERE fingerprint=?",
1501                    (fingerprint,),
1502                    |row| {
1503                        let bytes: Vec<u8> = row.get(0)?;
1504                        Ok(bytes)
1505                    },
1506                )
1507                .await?
1508            {
1509                let public_key = SignedPublicKey::from_slice(&public_key_bytes)?;
1510                Ok(Some(public_key))
1511            } else {
1512                Ok(None)
1513            }
1514        } else {
1515            Ok(None)
1516        }
1517    }
1518
1519    /// Get name authorized by the contact.
1520    pub fn get_authname(&self) -> &str {
1521        &self.authname
1522    }
1523
1524    /// Get the contact name. This is the name as modified by the local user.
1525    /// May be an empty string.
1526    ///
1527    /// This name is typically used in a form where the user can edit the name of a contact.
1528    /// To get a fine name to display in lists etc., use `Contact::get_display_name` or `Contact::get_name_n_addr`.
1529    pub fn get_name(&self) -> &str {
1530        &self.name
1531    }
1532
1533    /// Get display name. This is the name as defined by the contact himself,
1534    /// modified by the user or, if both are unset, the email address.
1535    ///
1536    /// This name is typically used in lists.
1537    /// To get the name editable in a formular, use `Contact::get_name`.
1538    pub fn get_display_name(&self) -> &str {
1539        if !self.name.is_empty() {
1540            return &self.name;
1541        }
1542        if !self.authname.is_empty() {
1543            return &self.authname;
1544        }
1545        &self.addr
1546    }
1547
1548    /// Get a summary of name and address.
1549    ///
1550    /// The returned string is either "Name (email@domain.com)" or just
1551    /// "email@domain.com" if the name is unset.
1552    ///
1553    /// The result should only be used locally and never sent over the network
1554    /// as it leaks the local contact name.
1555    ///
1556    /// The summary is typically used when asking the user something about the contact.
1557    /// The attached email address makes the question unique, eg. "Chat with Alan Miller (am@uniquedomain.com)?"
1558    pub fn get_name_n_addr(&self) -> String {
1559        if !self.name.is_empty() {
1560            format!("{} ({})", self.name, self.addr)
1561        } else if !self.authname.is_empty() {
1562            format!("{} ({})", self.authname, self.addr)
1563        } else {
1564            (&self.addr).into()
1565        }
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    pub async fn get_profile_image(&self, context: &Context) -> Result<Option<PathBuf>> {
1572        self.get_profile_image_ex(context, true).await
1573    }
1574
1575    /// Get the contact's profile image.
1576    /// This is the image set by each remote user on their own
1577    /// using set_config(context, "selfavatar", image).
1578    async fn get_profile_image_ex(
1579        &self,
1580        context: &Context,
1581        show_fallback_icon: bool,
1582    ) -> Result<Option<PathBuf>> {
1583        if self.id == ContactId::SELF {
1584            if let Some(p) = context.get_config(Config::Selfavatar).await? {
1585                return Ok(Some(PathBuf::from(p))); // get_config() calls get_abs_path() internally already
1586            }
1587        } else if self.id == ContactId::DEVICE {
1588            return Ok(Some(chat::get_device_icon(context).await?));
1589        }
1590        if show_fallback_icon && !self.id.is_special() && !self.is_key_contact() {
1591            return Ok(Some(chat::get_unencrypted_icon(context).await?));
1592        }
1593        if let Some(image_rel) = self.param.get(Param::ProfileImage)
1594            && !image_rel.is_empty()
1595        {
1596            return Ok(Some(get_abs_path(context, Path::new(image_rel))));
1597        }
1598        Ok(None)
1599    }
1600
1601    /// Returns a color for the contact.
1602    /// For self-contact this returns gray if own keypair doesn't exist yet.
1603    /// See also [`self::get_color`].
1604    pub fn get_color(&self) -> u32 {
1605        get_color(self.id == ContactId::SELF, &self.addr, &self.fingerprint())
1606    }
1607
1608    /// Returns a color for the contact.
1609    /// Ensures that the color isn't gray. For self-contact this generates own keypair if it doesn't
1610    /// exist yet.
1611    /// See also [`self::get_color`].
1612    pub async fn get_or_gen_color(&self, context: &Context) -> Result<u32> {
1613        let mut fpr = self.fingerprint();
1614        if fpr.is_none() && self.id == ContactId::SELF {
1615            fpr = Some(load_self_public_key(context).await?.dc_fingerprint());
1616        }
1617        Ok(get_color(self.id == ContactId::SELF, &self.addr, &fpr))
1618    }
1619
1620    /// Gets the contact's status.
1621    ///
1622    /// Status is the last signature received in a message from this contact.
1623    pub fn get_status(&self) -> &str {
1624        self.status.as_str()
1625    }
1626
1627    /// Returns whether end-to-end encryption to the contact is available.
1628    pub async fn e2ee_avail(&self, context: &Context) -> Result<bool> {
1629        if self.id == ContactId::SELF {
1630            // We don't need to check if we have our own key.
1631            return Ok(true);
1632        }
1633        Ok(self.public_key(context).await?.is_some())
1634    }
1635
1636    /// Returns true if the contact
1637    /// can be added to verified chats.
1638    ///
1639    /// If contact is verified
1640    /// UI should display green checkmark after the contact name
1641    /// in contact list items and
1642    /// in chat member list items.
1643    ///
1644    /// In contact profile view, use this function only if there is no chat with the contact,
1645    /// otherwise use is_chat_protected().
1646    /// Use [Self::get_verifier_id] to display the verifier contact
1647    /// in the info section of the contact profile.
1648    pub async fn is_verified(&self, context: &Context) -> Result<bool> {
1649        // We're always sort of secured-verified as we could verify the key on this device any time with the key
1650        // on this device
1651        if self.id == ContactId::SELF {
1652            return Ok(true);
1653        }
1654
1655        Ok(self.get_verifier_id(context).await?.is_some())
1656    }
1657
1658    /// Returns the `ContactId` that verified the contact.
1659    ///
1660    /// If this returns Some(_),
1661    /// display green checkmark in the profile and "Introduced by ..." line
1662    /// with the name of the contact.
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;