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