deltachat/
context.rs

1//! Context module.
2
3use std::collections::{BTreeMap, HashMap};
4use std::ffi::OsString;
5use std::ops::Deref;
6use std::path::{Path, PathBuf};
7use std::sync::atomic::AtomicBool;
8use std::sync::{Arc, OnceLock, Weak};
9use std::time::Duration;
10
11use anyhow::{Context as _, Result, bail, ensure};
12use async_channel::{self as channel, Receiver, Sender};
13use ratelimit::Ratelimit;
14use tokio::sync::{Mutex, Notify, RwLock};
15
16use crate::chat::{ChatId, get_chat_cnt};
17use crate::config::Config;
18use crate::constants::{self, DC_BACKGROUND_FETCH_QUOTA_CHECK_RATELIMIT, DC_VERSION_STR};
19use crate::contact::{Contact, ContactId};
20use crate::debug_logging::DebugLogging;
21use crate::events::{Event, EventEmitter, EventType, Events};
22use crate::imap::{FolderMeaning, Imap, ServerMetadata};
23use crate::key::self_fingerprint;
24use crate::log::warn;
25use crate::logged_debug_assert;
26use crate::message::{self, MessageState, MsgId};
27use crate::net::tls::TlsSessionStore;
28use crate::peer_channels::Iroh;
29use crate::push::PushSubscriber;
30use crate::quota::QuotaInfo;
31use crate::scheduler::{ConnectivityStore, SchedulerState, convert_folder_meaning};
32use crate::sql::Sql;
33use crate::stock_str::StockStrings;
34use crate::timesmearing::SmearedTimestamp;
35use crate::tools::{self, duration_to_str, time, time_elapsed};
36use crate::transport::ConfiguredLoginParam;
37use crate::{chatlist_events, stats};
38
39pub use crate::scheduler::connectivity::Connectivity;
40
41/// Builder for the [`Context`].
42///
43/// Many arguments to the [`Context`] are kind of optional and only needed to handle
44/// multiple contexts, for which the [account manager](crate::accounts::Accounts) should be
45/// used.  This builder makes creating a new context simpler, especially for the
46/// standalone-context case.
47///
48/// # Examples
49///
50/// Creating a new database:
51///
52/// ```
53/// # let rt = tokio::runtime::Runtime::new().unwrap();
54/// # rt.block_on(async move {
55/// use deltachat::context::ContextBuilder;
56///
57/// let dir = tempfile::tempdir().unwrap();
58/// let context = ContextBuilder::new(dir.path().join("db"))
59///      .open()
60///      .await
61///      .unwrap();
62/// drop(context);
63/// # });
64/// ```
65#[derive(Clone, Debug)]
66pub struct ContextBuilder {
67    dbfile: PathBuf,
68    id: u32,
69    events: Events,
70    stock_strings: StockStrings,
71    password: Option<String>,
72
73    push_subscriber: Option<PushSubscriber>,
74}
75
76impl ContextBuilder {
77    /// Create the builder using the given database file.
78    ///
79    /// The *dbfile* should be in a dedicated directory and this directory must exist.  The
80    /// [`Context`] will create other files and folders in the same directory as the
81    /// database file used.
82    pub fn new(dbfile: PathBuf) -> Self {
83        ContextBuilder {
84            dbfile,
85            id: rand::random(),
86            events: Events::new(),
87            stock_strings: StockStrings::new(),
88            password: None,
89            push_subscriber: None,
90        }
91    }
92
93    /// Sets the context ID.
94    ///
95    /// This identifier is used e.g. in [`Event`]s to identify which [`Context`] an event
96    /// belongs to.  The only real limit on it is that it should not conflict with any other
97    /// [`Context`]s you currently have open.  So if you handle multiple [`Context`]s you
98    /// may want to use this.
99    ///
100    /// Note that the [account manager](crate::accounts::Accounts) is designed to handle the
101    /// common case for using multiple [`Context`] instances.
102    pub fn with_id(mut self, id: u32) -> Self {
103        self.id = id;
104        self
105    }
106
107    /// Sets the event channel for this [`Context`].
108    ///
109    /// Mostly useful when using multiple [`Context`]s, this allows creating one [`Events`]
110    /// channel and passing it to all [`Context`]s so all events are received on the same
111    /// channel.
112    ///
113    /// Note that the [account manager](crate::accounts::Accounts) is designed to handle the
114    /// common case for using multiple [`Context`] instances.
115    pub fn with_events(mut self, events: Events) -> Self {
116        self.events = events;
117        self
118    }
119
120    /// Sets the [`StockStrings`] map to use for this [`Context`].
121    ///
122    /// This is useful in order to share the same translation strings in all [`Context`]s.
123    /// The mapping may be empty when set, it will be populated by
124    /// [`Context::set_stock_translation`] or [`Accounts::set_stock_translation`] calls.
125    ///
126    /// Note that the [account manager](crate::accounts::Accounts) is designed to handle the
127    /// common case for using multiple [`Context`] instances.
128    ///
129    /// [`Accounts::set_stock_translation`]: crate::accounts::Accounts::set_stock_translation
130    pub fn with_stock_strings(mut self, stock_strings: StockStrings) -> Self {
131        self.stock_strings = stock_strings;
132        self
133    }
134
135    /// Sets the password to unlock the database.
136    /// Deprecated 2025-11:
137    /// - Db encryption does nothing with blobs, so fs/disk encryption is recommended.
138    /// - Isolation from other apps is needed anyway.
139    ///
140    /// If an encrypted database is used it must be opened with a password.  Setting a
141    /// password on a new database will enable encryption.
142    #[deprecated(since = "TBD")]
143    pub fn with_password(mut self, password: String) -> Self {
144        self.password = Some(password);
145        self
146    }
147
148    /// Sets push subscriber.
149    pub(crate) fn with_push_subscriber(mut self, push_subscriber: PushSubscriber) -> Self {
150        self.push_subscriber = Some(push_subscriber);
151        self
152    }
153
154    /// Builds the [`Context`] without opening it.
155    pub async fn build(self) -> Result<Context> {
156        let push_subscriber = self.push_subscriber.unwrap_or_default();
157        let context = Context::new_closed(
158            &self.dbfile,
159            self.id,
160            self.events,
161            self.stock_strings,
162            push_subscriber,
163        )
164        .await?;
165        Ok(context)
166    }
167
168    /// Builds the [`Context`] and opens it.
169    ///
170    /// Returns error if context cannot be opened.
171    pub async fn open(self) -> Result<Context> {
172        let password = self.password.clone().unwrap_or_default();
173        let context = self.build().await?;
174        match context.open(password).await? {
175            true => Ok(context),
176            false => bail!("database could not be decrypted, incorrect or missing password"),
177        }
178    }
179}
180
181/// The context for a single DeltaChat account.
182///
183/// This contains all the state for a single DeltaChat account, including background tasks
184/// running in Tokio to operate the account.  The [`Context`] can be cheaply cloned.
185///
186/// Each context, and thus each account, must be associated with an directory where all the
187/// state is kept.  This state is also preserved between restarts.
188///
189/// To use multiple accounts it is best to look at the [accounts
190/// manager][crate::accounts::Accounts] which handles storing multiple accounts in a single
191/// directory structure and handles loading them all concurrently.
192#[derive(Clone, Debug)]
193pub struct Context {
194    pub(crate) inner: Arc<InnerContext>,
195}
196
197impl Deref for Context {
198    type Target = InnerContext;
199
200    fn deref(&self) -> &Self::Target {
201        &self.inner
202    }
203}
204
205/// A weak reference to a [`Context`]
206///
207/// Can be used to obtain a [`Context`]. An existing weak reference does not prevent the corresponding [`Context`] from being dropped.
208#[derive(Clone, Debug)]
209pub(crate) struct WeakContext {
210    inner: Weak<InnerContext>,
211}
212
213impl WeakContext {
214    /// Returns the [`Context`] if it is still available.
215    pub(crate) fn upgrade(&self) -> Result<Context> {
216        let inner = self
217            .inner
218            .upgrade()
219            .ok_or_else(|| anyhow::anyhow!("Inner struct has been dropped"))?;
220        Ok(Context { inner })
221    }
222}
223
224/// Actual context, expensive to clone.
225#[derive(Debug)]
226pub struct InnerContext {
227    /// Blob directory path
228    pub(crate) blobdir: PathBuf,
229    pub(crate) sql: Sql,
230    pub(crate) smeared_timestamp: SmearedTimestamp,
231    /// The global "ongoing" process state.
232    ///
233    /// This is a global mutex-like state for operations which should be modal in the
234    /// clients.
235    running_state: RwLock<RunningState>,
236    /// Mutex to avoid generating the key for the user more than once.
237    pub(crate) generating_key_mutex: Mutex<()>,
238    /// Mutex to enforce only a single running oauth2 is running.
239    pub(crate) oauth2_mutex: Mutex<()>,
240    /// Mutex to prevent a race condition when a "your pw is wrong" warning is sent, resulting in multiple messages being sent.
241    pub(crate) wrong_pw_warning_mutex: Mutex<()>,
242    pub(crate) translated_stockstrings: StockStrings,
243    pub(crate) events: Events,
244
245    pub(crate) scheduler: SchedulerState,
246    pub(crate) ratelimit: RwLock<Ratelimit>,
247
248    /// Recently loaded quota information for each trasnport, if any.
249    /// If quota was never tried to load, then the transport doesn't have an entry in the BTreeMap.
250    pub(crate) quota: RwLock<BTreeMap<u32, QuotaInfo>>,
251
252    /// Notify about new messages.
253    ///
254    /// This causes [`Context::wait_next_msgs`] to wake up.
255    pub(crate) new_msgs_notify: Notify,
256
257    /// Server ID response if ID capability is supported
258    /// and the server returned non-NIL on the inbox connection.
259    /// <https://datatracker.ietf.org/doc/html/rfc2971>
260    pub(crate) server_id: RwLock<Option<HashMap<String, String>>>,
261
262    /// IMAP METADATA.
263    pub(crate) metadata: RwLock<Option<ServerMetadata>>,
264
265    /// ID for this `Context` in the current process.
266    ///
267    /// This allows for multiple `Context`s open in a single process where each context can
268    /// be identified by this ID.
269    pub(crate) id: u32,
270
271    creation_time: tools::Time,
272
273    /// The text of the last error logged and emitted as an event.
274    /// If the ui wants to display an error after a failure,
275    /// `last_error` should be used to avoid races with the event thread.
276    pub(crate) last_error: parking_lot::RwLock<String>,
277
278    /// It's not possible to emit migration errors as an event,
279    /// because at the time of the migration, there is no event emitter yet.
280    /// So, this holds the error that happened during migration, if any.
281    /// This is necessary for the possibly-failible PGP migration,
282    /// which happened 2025-05, and can be removed a few releases later.
283    pub(crate) migration_error: parking_lot::RwLock<Option<String>>,
284
285    /// If debug logging is enabled, this contains all necessary information
286    ///
287    /// Standard RwLock instead of [`tokio::sync::RwLock`] is used
288    /// because the lock is used from synchronous [`Context::emit_event`].
289    pub(crate) debug_logging: std::sync::RwLock<Option<DebugLogging>>,
290
291    /// Push subscriber to store device token
292    /// and register for heartbeat notifications.
293    pub(crate) push_subscriber: PushSubscriber,
294
295    /// True if account has subscribed to push notifications via IMAP.
296    pub(crate) push_subscribed: AtomicBool,
297
298    /// TLS session resumption cache.
299    pub(crate) tls_session_store: TlsSessionStore,
300
301    /// Iroh for realtime peer channels.
302    pub(crate) iroh: Arc<RwLock<Option<Iroh>>>,
303
304    /// The own fingerprint, if it was computed already.
305    /// tokio::sync::OnceCell would be possible to use, but overkill for our usecase;
306    /// the standard library's OnceLock is enough, and it's a lot smaller in memory.
307    pub(crate) self_fingerprint: OnceLock<String>,
308
309    /// `Connectivity` values for mailboxes, unordered. Used to compute the aggregate connectivity,
310    /// see [`Context::get_connectivity()`].
311    pub(crate) connectivities: parking_lot::Mutex<Vec<ConnectivityStore>>,
312
313    #[expect(clippy::type_complexity)]
314    /// Transforms the root of the cryptographic payload before encryption.
315    pub(crate) pre_encrypt_mime_hook: parking_lot::Mutex<
316        Option<
317            for<'a> fn(
318                &Context,
319                mail_builder::mime::MimePart<'a>,
320            ) -> mail_builder::mime::MimePart<'a>,
321        >,
322    >,
323}
324
325/// The state of ongoing process.
326#[derive(Debug, Default)]
327enum RunningState {
328    /// Ongoing process is allocated.
329    Running { cancel_sender: Sender<()> },
330
331    /// Cancel signal has been sent, waiting for ongoing process to be freed.
332    ShallStop { request: tools::Time },
333
334    /// There is no ongoing process, a new one can be allocated.
335    #[default]
336    Stopped,
337}
338
339/// Return some info about deltachat-core
340///
341/// This contains information mostly about the library itself, the
342/// actual keys and their values which will be present are not
343/// guaranteed.  Calling [Context::get_info] also includes information
344/// about the context on top of the information here.
345pub fn get_info() -> BTreeMap<&'static str, String> {
346    let mut res = BTreeMap::new();
347
348    #[cfg(debug_assertions)]
349    res.insert(
350        "debug_assertions",
351        "On - DO NOT RELEASE THIS BUILD".to_string(),
352    );
353    #[cfg(not(debug_assertions))]
354    res.insert("debug_assertions", "Off".to_string());
355
356    res.insert("deltachat_core_version", format!("v{DC_VERSION_STR}"));
357    res.insert("sqlite_version", rusqlite::version().to_string());
358    res.insert("arch", (std::mem::size_of::<usize>() * 8).to_string());
359    res.insert("num_cpus", num_cpus::get().to_string());
360    res.insert("level", "awesome".into());
361    res
362}
363
364impl Context {
365    /// Creates new context and opens the database.
366    pub async fn new(
367        dbfile: &Path,
368        id: u32,
369        events: Events,
370        stock_strings: StockStrings,
371    ) -> Result<Context> {
372        let context =
373            Self::new_closed(dbfile, id, events, stock_strings, Default::default()).await?;
374
375        // Open the database if is not encrypted.
376        if context.check_passphrase("".to_string()).await? {
377            context.sql.open(&context, "".to_string()).await?;
378        }
379        Ok(context)
380    }
381
382    /// Creates new context without opening the database.
383    pub async fn new_closed(
384        dbfile: &Path,
385        id: u32,
386        events: Events,
387        stockstrings: StockStrings,
388        push_subscriber: PushSubscriber,
389    ) -> Result<Context> {
390        let mut blob_fname = OsString::new();
391        blob_fname.push(dbfile.file_name().unwrap_or_default());
392        blob_fname.push("-blobs");
393        let blobdir = dbfile.with_file_name(blob_fname);
394        if !blobdir.exists() {
395            tokio::fs::create_dir_all(&blobdir).await?;
396        }
397        let context = Context::with_blobdir(
398            dbfile.into(),
399            blobdir,
400            id,
401            events,
402            stockstrings,
403            push_subscriber,
404        )?;
405        Ok(context)
406    }
407
408    /// Returns a weak reference to this [`Context`].
409    pub(crate) fn get_weak_context(&self) -> WeakContext {
410        WeakContext {
411            inner: Arc::downgrade(&self.inner),
412        }
413    }
414
415    /// Opens the database with the given passphrase.
416    /// NB: Db encryption is deprecated, so `passphrase` should be empty normally. See
417    /// [`ContextBuilder::with_password()`] for reasoning.
418    ///
419    /// Returns true if passphrase is correct, false is passphrase is not correct. Fails on other
420    /// errors.
421    #[deprecated(since = "TBD")]
422    pub async fn open(&self, passphrase: String) -> Result<bool> {
423        if self.sql.check_passphrase(passphrase.clone()).await? {
424            self.sql.open(self, passphrase).await?;
425            Ok(true)
426        } else {
427            Ok(false)
428        }
429    }
430
431    /// Changes encrypted database passphrase.
432    /// Deprecated 2025-11, see [`ContextBuilder::with_password()`] for reasoning.
433    pub async fn change_passphrase(&self, passphrase: String) -> Result<()> {
434        self.sql.change_passphrase(passphrase).await?;
435        Ok(())
436    }
437
438    /// Returns true if database is open.
439    pub async fn is_open(&self) -> bool {
440        self.sql.is_open().await
441    }
442
443    /// Tests the database passphrase.
444    ///
445    /// Returns true if passphrase is correct.
446    ///
447    /// Fails if database is already open.
448    pub(crate) async fn check_passphrase(&self, passphrase: String) -> Result<bool> {
449        self.sql.check_passphrase(passphrase).await
450    }
451
452    pub(crate) fn with_blobdir(
453        dbfile: PathBuf,
454        blobdir: PathBuf,
455        id: u32,
456        events: Events,
457        stockstrings: StockStrings,
458        push_subscriber: PushSubscriber,
459    ) -> Result<Context> {
460        ensure!(
461            blobdir.is_dir(),
462            "Blobdir does not exist: {}",
463            blobdir.display()
464        );
465
466        let new_msgs_notify = Notify::new();
467        // Notify once immediately to allow processing old messages
468        // without starting I/O.
469        new_msgs_notify.notify_one();
470
471        let inner = InnerContext {
472            id,
473            blobdir,
474            running_state: RwLock::new(Default::default()),
475            sql: Sql::new(dbfile),
476            smeared_timestamp: SmearedTimestamp::new(),
477            generating_key_mutex: Mutex::new(()),
478            oauth2_mutex: Mutex::new(()),
479            wrong_pw_warning_mutex: Mutex::new(()),
480            translated_stockstrings: stockstrings,
481            events,
482            scheduler: SchedulerState::new(),
483            ratelimit: RwLock::new(Ratelimit::new(Duration::new(3, 0), 3.0)), // Allow at least 1 message every second + a burst of 3.
484            quota: RwLock::new(BTreeMap::new()),
485            new_msgs_notify,
486            server_id: RwLock::new(None),
487            metadata: RwLock::new(None),
488            creation_time: tools::Time::now(),
489            last_error: parking_lot::RwLock::new("".to_string()),
490            migration_error: parking_lot::RwLock::new(None),
491            debug_logging: std::sync::RwLock::new(None),
492            push_subscriber,
493            push_subscribed: AtomicBool::new(false),
494            tls_session_store: TlsSessionStore::new(),
495            iroh: Arc::new(RwLock::new(None)),
496            self_fingerprint: OnceLock::new(),
497            connectivities: parking_lot::Mutex::new(Vec::new()),
498            pre_encrypt_mime_hook: None.into(),
499        };
500
501        let ctx = Context {
502            inner: Arc::new(inner),
503        };
504
505        Ok(ctx)
506    }
507
508    /// Starts the IO scheduler.
509    pub async fn start_io(&self) {
510        if !self.is_configured().await.unwrap_or_default() {
511            warn!(self, "can not start io on a context that is not configured");
512            return;
513        }
514
515        // The next line is mainly for iOS:
516        // iOS starts a separate process for receiving notifications and if the user concurrently
517        // starts the app, the UI process opens the database but waits with calling start_io()
518        // until the notifications process finishes.
519        // Now, some configs may have changed, so, we need to invalidate the cache.
520        self.sql.config_cache.write().await.clear();
521
522        self.scheduler.start(self).await;
523    }
524
525    /// Stops the IO scheduler.
526    pub async fn stop_io(&self) {
527        self.scheduler.stop(self).await;
528        if let Some(iroh) = self.iroh.write().await.take() {
529            // Close all QUIC connections.
530
531            // Spawn into a separate task,
532            // because Iroh calls `wait_idle()` internally
533            // and it may take time, especially if the network
534            // has become unavailable.
535            tokio::spawn(async move {
536                // We do not log the error because we do not want the task
537                // to hold the reference to Context.
538                let _ = tokio::time::timeout(Duration::from_secs(60), iroh.close()).await;
539            });
540        }
541    }
542
543    /// Restarts the IO scheduler if it was running before
544    /// when it is not running this is an no-op
545    pub async fn restart_io_if_running(&self) {
546        self.scheduler.restart(self).await;
547    }
548
549    /// Indicate that the network likely has come back.
550    pub async fn maybe_network(&self) {
551        if let Some(ref iroh) = *self.iroh.read().await {
552            iroh.network_change().await;
553        }
554        self.scheduler.maybe_network().await;
555    }
556
557    /// Returns true if an account is on a chatmail server.
558    pub async fn is_chatmail(&self) -> Result<bool> {
559        self.get_config_bool(Config::IsChatmail).await
560    }
561
562    /// Returns maximum number of recipients the provider allows to send a single email to.
563    pub(crate) async fn get_max_smtp_rcpt_to(&self) -> Result<usize> {
564        let is_chatmail = self.is_chatmail().await?;
565        let val = self
566            .get_configured_provider()
567            .await?
568            .and_then(|provider| provider.opt.max_smtp_rcpt_to)
569            .map_or_else(
570                || match is_chatmail {
571                    true => constants::DEFAULT_CHATMAIL_MAX_SMTP_RCPT_TO,
572                    false => constants::DEFAULT_MAX_SMTP_RCPT_TO,
573                },
574                usize::from,
575            );
576        Ok(val)
577    }
578
579    /// Does a single round of fetching from IMAP and returns.
580    ///
581    /// Can be used even if I/O is currently stopped.
582    /// If I/O is currently stopped, starts a new IMAP connection
583    /// and fetches from Inbox and DeltaChat folders.
584    pub async fn background_fetch(&self) -> Result<()> {
585        if !(self.is_configured().await?) {
586            return Ok(());
587        }
588
589        let address = self.get_primary_self_addr().await?;
590        let time_start = tools::Time::now();
591        info!(self, "background_fetch started fetching {address}.");
592
593        if self.scheduler.is_running().await {
594            self.scheduler.maybe_network().await;
595            self.wait_for_all_work_done().await;
596        } else {
597            // Pause the scheduler to ensure another connection does not start
598            // while we are fetching on a dedicated connection.
599            let _pause_guard = self.scheduler.pause(self).await?;
600
601            // Start a new dedicated connection.
602            let mut connection = Imap::new_configured(self, channel::bounded(1).1).await?;
603            let mut session = connection.prepare(self).await?;
604
605            // Fetch IMAP folders.
606            // Inbox is fetched before Mvbox because fetching from Inbox
607            // may result in moving some messages to Mvbox.
608            for folder_meaning in [FolderMeaning::Inbox, FolderMeaning::Mvbox] {
609                if let Some((_folder_config, watch_folder)) =
610                    convert_folder_meaning(self, folder_meaning).await?
611                {
612                    connection
613                        .fetch_move_delete(self, &mut session, &watch_folder, folder_meaning)
614                        .await?;
615                }
616            }
617
618            // Update quota (to send warning if full) - but only check it once in a while.
619            // note: For now this only checks quota of primary transport,
620            // because background check only checks primary transport at the moment
621            if self
622                .quota_needs_update(
623                    session.transport_id(),
624                    DC_BACKGROUND_FETCH_QUOTA_CHECK_RATELIMIT,
625                )
626                .await
627                && let Err(err) = self.update_recent_quota(&mut session).await
628            {
629                warn!(self, "Failed to update quota: {err:#}.");
630            }
631        }
632
633        info!(
634            self,
635            "background_fetch done for {address} took {:?}.",
636            time_elapsed(&time_start),
637        );
638
639        Ok(())
640    }
641
642    /// Returns a reference to the underlying SQL instance.
643    ///
644    /// Warning: this is only here for testing, not part of the public API.
645    #[cfg(feature = "internals")]
646    pub fn sql(&self) -> &Sql {
647        &self.inner.sql
648    }
649
650    /// Returns database file path.
651    pub fn get_dbfile(&self) -> &Path {
652        self.sql.dbfile.as_path()
653    }
654
655    /// Returns blob directory path.
656    pub fn get_blobdir(&self) -> &Path {
657        self.blobdir.as_path()
658    }
659
660    /// Emits a single event.
661    pub fn emit_event(&self, event: EventType) {
662        {
663            let lock = self.debug_logging.read().expect("RwLock is poisoned");
664            if let Some(debug_logging) = &*lock {
665                debug_logging.log_event(event.clone());
666            }
667        }
668        self.events.emit(Event {
669            id: self.id,
670            typ: event,
671        });
672    }
673
674    /// Emits a generic MsgsChanged event (without chat or message id)
675    pub fn emit_msgs_changed_without_ids(&self) {
676        self.emit_event(EventType::MsgsChanged {
677            chat_id: ChatId::new(0),
678            msg_id: MsgId::new(0),
679        });
680    }
681
682    /// Emits a MsgsChanged event with specified chat and message ids
683    ///
684    /// If IDs are unset, [`Self::emit_msgs_changed_without_ids`]
685    /// or [`Self::emit_msgs_changed_without_msg_id`] should be used
686    /// instead of this function.
687    pub fn emit_msgs_changed(&self, chat_id: ChatId, msg_id: MsgId) {
688        logged_debug_assert!(
689            self,
690            !chat_id.is_unset(),
691            "emit_msgs_changed: chat_id is unset."
692        );
693        logged_debug_assert!(
694            self,
695            !msg_id.is_unset(),
696            "emit_msgs_changed: msg_id is unset."
697        );
698
699        self.emit_event(EventType::MsgsChanged { chat_id, msg_id });
700        chatlist_events::emit_chatlist_changed(self);
701        chatlist_events::emit_chatlist_item_changed(self, chat_id);
702    }
703
704    /// Emits a MsgsChanged event with specified chat and without message id.
705    pub fn emit_msgs_changed_without_msg_id(&self, chat_id: ChatId) {
706        logged_debug_assert!(
707            self,
708            !chat_id.is_unset(),
709            "emit_msgs_changed_without_msg_id: chat_id is unset."
710        );
711
712        self.emit_event(EventType::MsgsChanged {
713            chat_id,
714            msg_id: MsgId::new(0),
715        });
716        chatlist_events::emit_chatlist_changed(self);
717        chatlist_events::emit_chatlist_item_changed(self, chat_id);
718    }
719
720    /// Emits an IncomingMsg event with specified chat and message ids
721    pub fn emit_incoming_msg(&self, chat_id: ChatId, msg_id: MsgId) {
722        debug_assert!(!chat_id.is_unset());
723        debug_assert!(!msg_id.is_unset());
724
725        self.emit_event(EventType::IncomingMsg { chat_id, msg_id });
726        chatlist_events::emit_chatlist_changed(self);
727        chatlist_events::emit_chatlist_item_changed(self, chat_id);
728    }
729
730    /// Emits an LocationChanged event and a WebxdcStatusUpdate in case there is a maps integration
731    pub async fn emit_location_changed(&self, contact_id: Option<ContactId>) -> Result<()> {
732        self.emit_event(EventType::LocationChanged(contact_id));
733
734        if let Some(msg_id) = self
735            .get_config_parsed::<u32>(Config::WebxdcIntegration)
736            .await?
737        {
738            self.emit_event(EventType::WebxdcStatusUpdate {
739                msg_id: MsgId::new(msg_id),
740                status_update_serial: Default::default(),
741            })
742        }
743
744        Ok(())
745    }
746
747    /// Returns a receiver for emitted events.
748    ///
749    /// Multiple emitters can be created, but note that in this case each emitted event will
750    /// only be received by one of the emitters, not by all of them.
751    pub fn get_event_emitter(&self) -> EventEmitter {
752        self.events.get_emitter()
753    }
754
755    /// Get the ID of this context.
756    pub fn get_id(&self) -> u32 {
757        self.id
758    }
759
760    // Ongoing process allocation/free/check
761
762    /// Tries to acquire the global UI "ongoing" mutex.
763    ///
764    /// This is for modal operations during which no other user actions are allowed.  Only
765    /// one such operation is allowed at any given time.
766    ///
767    /// The return value is a cancel token, which will release the ongoing mutex when
768    /// dropped.
769    pub(crate) async fn alloc_ongoing(&self) -> Result<Receiver<()>> {
770        let mut s = self.running_state.write().await;
771        ensure!(
772            matches!(*s, RunningState::Stopped),
773            "There is already another ongoing process running."
774        );
775
776        let (sender, receiver) = channel::bounded(1);
777        *s = RunningState::Running {
778            cancel_sender: sender,
779        };
780
781        Ok(receiver)
782    }
783
784    pub(crate) async fn free_ongoing(&self) {
785        let mut s = self.running_state.write().await;
786        if let RunningState::ShallStop { request } = *s {
787            info!(self, "Ongoing stopped in {:?}", time_elapsed(&request));
788        }
789        *s = RunningState::Stopped;
790    }
791
792    /// Signal an ongoing process to stop.
793    pub async fn stop_ongoing(&self) {
794        let mut s = self.running_state.write().await;
795        match &*s {
796            RunningState::Running { cancel_sender } => {
797                if let Err(err) = cancel_sender.send(()).await {
798                    warn!(self, "could not cancel ongoing: {:#}", err);
799                }
800                info!(self, "Signaling the ongoing process to stop ASAP.",);
801                *s = RunningState::ShallStop {
802                    request: tools::Time::now(),
803                };
804            }
805            RunningState::ShallStop { .. } | RunningState::Stopped => {
806                info!(self, "No ongoing process to stop.",);
807            }
808        }
809    }
810
811    #[allow(unused)]
812    pub(crate) async fn shall_stop_ongoing(&self) -> bool {
813        match &*self.running_state.read().await {
814            RunningState::Running { .. } => false,
815            RunningState::ShallStop { .. } | RunningState::Stopped => true,
816        }
817    }
818
819    /*******************************************************************************
820     * UI chat/message related API
821     ******************************************************************************/
822
823    /// Returns information about the context as key-value pairs.
824    pub async fn get_info(&self) -> Result<BTreeMap<&'static str, String>> {
825        let secondary_addrs = self.get_secondary_self_addrs().await?.join(", ");
826        let all_transports: Vec<String> = ConfiguredLoginParam::load_all(self)
827            .await?
828            .into_iter()
829            .map(|(transport_id, param)| format!("{transport_id}: {param}"))
830            .collect();
831        let all_transports = if all_transports.is_empty() {
832            "Not configured".to_string()
833        } else {
834            all_transports.join(",")
835        };
836        let chats = get_chat_cnt(self).await?;
837        let unblocked_msgs = message::get_unblocked_msg_cnt(self).await;
838        let request_msgs = message::get_request_msg_cnt(self).await;
839        let contacts = Contact::get_real_cnt(self).await?;
840        let proxy_enabled = self.get_config_int(Config::ProxyEnabled).await?;
841        let dbversion = self
842            .sql
843            .get_raw_config_int("dbversion")
844            .await?
845            .unwrap_or_default();
846        let journal_mode = self
847            .sql
848            .query_get_value("PRAGMA journal_mode;", ())
849            .await?
850            .unwrap_or_else(|| "unknown".to_string());
851        let mdns_enabled = self.get_config_int(Config::MdnsEnabled).await?;
852        let bcc_self = self.get_config_int(Config::BccSelf).await?;
853        let sync_msgs = self.get_config_int(Config::SyncMsgs).await?;
854        let disable_idle = self.get_config_bool(Config::DisableIdle).await?;
855
856        let prv_key_cnt = self.sql.count("SELECT COUNT(*) FROM keypairs;", ()).await?;
857
858        let pub_key_cnt = self
859            .sql
860            .count("SELECT COUNT(*) FROM public_keys;", ())
861            .await?;
862        let fingerprint_str = match self_fingerprint(self).await {
863            Ok(fp) => fp.to_string(),
864            Err(err) => format!("<key failure: {err}>"),
865        };
866
867        let mvbox_move = self.get_config_int(Config::MvboxMove).await?;
868        let only_fetch_mvbox = self.get_config_int(Config::OnlyFetchMvbox).await?;
869        let folders_configured = self
870            .sql
871            .get_raw_config_int(constants::DC_FOLDERS_CONFIGURED_KEY)
872            .await?
873            .unwrap_or_default();
874
875        let configured_inbox_folder = self
876            .get_config(Config::ConfiguredInboxFolder)
877            .await?
878            .unwrap_or_else(|| "<unset>".to_string());
879        let configured_mvbox_folder = self
880            .get_config(Config::ConfiguredMvboxFolder)
881            .await?
882            .unwrap_or_else(|| "<unset>".to_string());
883        let configured_trash_folder = self
884            .get_config(Config::ConfiguredTrashFolder)
885            .await?
886            .unwrap_or_else(|| "<unset>".to_string());
887
888        let mut res = get_info();
889
890        // insert values
891        res.insert("bot", self.get_config_int(Config::Bot).await?.to_string());
892        res.insert("number_of_chats", chats.to_string());
893        res.insert("number_of_chat_messages", unblocked_msgs.to_string());
894        res.insert("messages_in_contact_requests", request_msgs.to_string());
895        res.insert("number_of_contacts", contacts.to_string());
896        res.insert("database_dir", self.get_dbfile().display().to_string());
897        res.insert("database_version", dbversion.to_string());
898        res.insert(
899            "database_encrypted",
900            self.sql
901                .is_encrypted()
902                .await
903                .map_or_else(|| "closed".to_string(), |b| b.to_string()),
904        );
905        res.insert("journal_mode", journal_mode);
906        res.insert("blobdir", self.get_blobdir().display().to_string());
907        res.insert(
908            "selfavatar",
909            self.get_config(Config::Selfavatar)
910                .await?
911                .unwrap_or_else(|| "<unset>".to_string()),
912        );
913        res.insert("proxy_enabled", proxy_enabled.to_string());
914        res.insert("used_transport_settings", all_transports);
915
916        if let Some(server_id) = &*self.server_id.read().await {
917            res.insert("imap_server_id", format!("{server_id:?}"));
918        }
919
920        res.insert("is_chatmail", self.is_chatmail().await?.to_string());
921        res.insert(
922            "fix_is_chatmail",
923            self.get_config_bool(Config::FixIsChatmail)
924                .await?
925                .to_string(),
926        );
927        res.insert(
928            "is_muted",
929            self.get_config_bool(Config::IsMuted).await?.to_string(),
930        );
931        res.insert(
932            "private_tag",
933            self.get_config(Config::PrivateTag)
934                .await?
935                .unwrap_or_else(|| "<unset>".to_string()),
936        );
937
938        if let Some(metadata) = &*self.metadata.read().await {
939            if let Some(comment) = &metadata.comment {
940                res.insert("imap_server_comment", format!("{comment:?}"));
941            }
942
943            if let Some(admin) = &metadata.admin {
944                res.insert("imap_server_admin", format!("{admin:?}"));
945            }
946        }
947
948        res.insert("secondary_addrs", secondary_addrs);
949        res.insert(
950            "fetched_existing_msgs",
951            self.get_config_bool(Config::FetchedExistingMsgs)
952                .await?
953                .to_string(),
954        );
955        res.insert(
956            "show_emails",
957            self.get_config_int(Config::ShowEmails).await?.to_string(),
958        );
959        res.insert(
960            "who_can_call_me",
961            self.get_config_int(Config::WhoCanCallMe).await?.to_string(),
962        );
963        res.insert(
964            "download_limit",
965            self.get_config_int(Config::DownloadLimit)
966                .await?
967                .to_string(),
968        );
969        res.insert("mvbox_move", mvbox_move.to_string());
970        res.insert("only_fetch_mvbox", only_fetch_mvbox.to_string());
971        res.insert(
972            constants::DC_FOLDERS_CONFIGURED_KEY,
973            folders_configured.to_string(),
974        );
975        res.insert("configured_inbox_folder", configured_inbox_folder);
976        res.insert("configured_mvbox_folder", configured_mvbox_folder);
977        res.insert("configured_trash_folder", configured_trash_folder);
978        res.insert("mdns_enabled", mdns_enabled.to_string());
979        res.insert("bcc_self", bcc_self.to_string());
980        res.insert("sync_msgs", sync_msgs.to_string());
981        res.insert("disable_idle", disable_idle.to_string());
982        res.insert("private_key_count", prv_key_cnt.to_string());
983        res.insert("public_key_count", pub_key_cnt.to_string());
984        res.insert("fingerprint", fingerprint_str);
985        res.insert(
986            "media_quality",
987            self.get_config_int(Config::MediaQuality).await?.to_string(),
988        );
989        res.insert(
990            "delete_device_after",
991            self.get_config_int(Config::DeleteDeviceAfter)
992                .await?
993                .to_string(),
994        );
995        res.insert(
996            "delete_server_after",
997            self.get_config_int(Config::DeleteServerAfter)
998                .await?
999                .to_string(),
1000        );
1001        res.insert(
1002            "delete_to_trash",
1003            self.get_config(Config::DeleteToTrash)
1004                .await?
1005                .unwrap_or_else(|| "<unset>".to_string()),
1006        );
1007        res.insert(
1008            "last_housekeeping",
1009            self.get_config_int(Config::LastHousekeeping)
1010                .await?
1011                .to_string(),
1012        );
1013        res.insert(
1014            "last_cant_decrypt_outgoing_msgs",
1015            self.get_config_int(Config::LastCantDecryptOutgoingMsgs)
1016                .await?
1017                .to_string(),
1018        );
1019        res.insert(
1020            "scan_all_folders_debounce_secs",
1021            self.get_config_int(Config::ScanAllFoldersDebounceSecs)
1022                .await?
1023                .to_string(),
1024        );
1025        res.insert(
1026            "quota_exceeding",
1027            self.get_config_int(Config::QuotaExceeding)
1028                .await?
1029                .to_string(),
1030        );
1031        res.insert(
1032            "authserv_id_candidates",
1033            self.get_config(Config::AuthservIdCandidates)
1034                .await?
1035                .unwrap_or_default(),
1036        );
1037        res.insert(
1038            "sign_unencrypted",
1039            self.get_config_int(Config::SignUnencrypted)
1040                .await?
1041                .to_string(),
1042        );
1043        res.insert(
1044            "debug_logging",
1045            self.get_config_int(Config::DebugLogging).await?.to_string(),
1046        );
1047        res.insert(
1048            "last_msg_id",
1049            self.get_config_int(Config::LastMsgId).await?.to_string(),
1050        );
1051        res.insert(
1052            "gossip_period",
1053            self.get_config_int(Config::GossipPeriod).await?.to_string(),
1054        );
1055        res.insert(
1056            "webxdc_realtime_enabled",
1057            self.get_config_bool(Config::WebxdcRealtimeEnabled)
1058                .await?
1059                .to_string(),
1060        );
1061        res.insert(
1062            "donation_request_next_check",
1063            self.get_config_i64(Config::DonationRequestNextCheck)
1064                .await?
1065                .to_string(),
1066        );
1067        res.insert(
1068            "first_key_contacts_msg_id",
1069            self.sql
1070                .get_raw_config("first_key_contacts_msg_id")
1071                .await?
1072                .unwrap_or_default(),
1073        );
1074        res.insert(
1075            "stats_id",
1076            self.get_config(Config::StatsId)
1077                .await?
1078                .unwrap_or_else(|| "<unset>".to_string()),
1079        );
1080        res.insert(
1081            "stats_sending",
1082            stats::should_send_stats(self).await?.to_string(),
1083        );
1084        res.insert(
1085            "stats_last_sent",
1086            self.get_config_i64(Config::StatsLastSent)
1087                .await?
1088                .to_string(),
1089        );
1090        res.insert(
1091            "test_hooks",
1092            self.sql
1093                .get_raw_config("test_hooks")
1094                .await?
1095                .unwrap_or_default(),
1096        );
1097        res.insert(
1098            "std_header_protection_composing",
1099            self.sql
1100                .get_raw_config("std_header_protection_composing")
1101                .await?
1102                .unwrap_or_default(),
1103        );
1104        res.insert(
1105            "team_profile",
1106            self.get_config_bool(Config::TeamProfile).await?.to_string(),
1107        );
1108
1109        let elapsed = time_elapsed(&self.creation_time);
1110        res.insert("uptime", duration_to_str(elapsed));
1111
1112        Ok(res)
1113    }
1114
1115    /// Get a list of fresh, unmuted messages in unblocked chats.
1116    ///
1117    /// The list starts with the most recent message
1118    /// and is typically used to show notifications.
1119    /// Moreover, the number of returned messages
1120    /// can be used for a badge counter on the app icon.
1121    pub async fn get_fresh_msgs(&self) -> Result<Vec<MsgId>> {
1122        let list = self
1123            .sql
1124            .query_map_vec(
1125                "SELECT m.id
1126FROM msgs m
1127LEFT JOIN contacts ct
1128    ON m.from_id=ct.id
1129LEFT JOIN chats c
1130    ON m.chat_id=c.id
1131WHERE m.state=?
1132AND m.hidden=0
1133AND m.chat_id>9
1134AND ct.blocked=0
1135AND c.blocked=0
1136AND NOT(c.muted_until=-1 OR c.muted_until>?)
1137ORDER BY m.timestamp DESC,m.id DESC",
1138                (MessageState::InFresh, time()),
1139                |row| {
1140                    let msg_id: MsgId = row.get(0)?;
1141                    Ok(msg_id)
1142                },
1143            )
1144            .await?;
1145        Ok(list)
1146    }
1147
1148    /// Returns a list of messages with database ID higher than requested.
1149    ///
1150    /// Blocked contacts and chats are excluded,
1151    /// but self-sent messages and contact requests are included in the results.
1152    pub async fn get_next_msgs(&self) -> Result<Vec<MsgId>> {
1153        let last_msg_id = match self.get_config(Config::LastMsgId).await? {
1154            Some(s) => MsgId::new(s.parse()?),
1155            None => {
1156                // If `last_msg_id` is not set yet,
1157                // subtract 1 from the last id,
1158                // so a single message is returned and can
1159                // be marked as seen.
1160                self.sql
1161                    .query_row(
1162                        "SELECT IFNULL((SELECT MAX(id) - 1 FROM msgs), 0)",
1163                        (),
1164                        |row| {
1165                            let msg_id: MsgId = row.get(0)?;
1166                            Ok(msg_id)
1167                        },
1168                    )
1169                    .await?
1170            }
1171        };
1172
1173        let list = self
1174            .sql
1175            .query_map_vec(
1176                "SELECT m.id
1177                     FROM msgs m
1178                     LEFT JOIN contacts ct
1179                            ON m.from_id=ct.id
1180                     LEFT JOIN chats c
1181                            ON m.chat_id=c.id
1182                     WHERE m.id>?
1183                       AND m.hidden=0
1184                       AND m.chat_id>9
1185                       AND ct.blocked=0
1186                       AND c.blocked!=1
1187                     ORDER BY m.id ASC",
1188                (
1189                    last_msg_id.to_u32(), // Explicitly convert to u32 because 0 is allowed.
1190                ),
1191                |row| {
1192                    let msg_id: MsgId = row.get(0)?;
1193                    Ok(msg_id)
1194                },
1195            )
1196            .await?;
1197        Ok(list)
1198    }
1199
1200    /// Returns a list of messages with database ID higher than last marked as seen.
1201    ///
1202    /// This function is supposed to be used by bot to request messages
1203    /// that are not processed yet.
1204    ///
1205    /// Waits for notification and returns a result.
1206    /// Note that the result may be empty if the message is deleted
1207    /// shortly after notification or notification is manually triggered
1208    /// to interrupt waiting.
1209    /// Notification may be manually triggered by calling [`Self::stop_io`].
1210    pub async fn wait_next_msgs(&self) -> Result<Vec<MsgId>> {
1211        self.new_msgs_notify.notified().await;
1212        let list = self.get_next_msgs().await?;
1213        Ok(list)
1214    }
1215
1216    /// Searches for messages containing the query string case-insensitively.
1217    ///
1218    /// If `chat_id` is provided this searches only for messages in this chat, if `chat_id`
1219    /// is `None` this searches messages from all chats.
1220    ///
1221    /// NB: Wrt the search in long messages which are shown truncated with the "Show Full Message…"
1222    /// button, we only look at the first several kilobytes. Let's not fix this -- one can send a
1223    /// dictionary in the message that matches any reasonable search request, but the user won't see
1224    /// the match because they should tap on "Show Full Message…" for that. Probably such messages
1225    /// would only clutter search results.
1226    pub async fn search_msgs(&self, chat_id: Option<ChatId>, query: &str) -> Result<Vec<MsgId>> {
1227        let real_query = query.trim().to_lowercase();
1228        if real_query.is_empty() {
1229            return Ok(Vec::new());
1230        }
1231        let str_like_in_text = format!("%{real_query}%");
1232
1233        let list = if let Some(chat_id) = chat_id {
1234            self.sql
1235                .query_map_vec(
1236                    "SELECT m.id AS id
1237                 FROM msgs m
1238                 LEFT JOIN contacts ct
1239                        ON m.from_id=ct.id
1240                 WHERE m.chat_id=?
1241                   AND m.hidden=0
1242                   AND ct.blocked=0
1243                   AND IFNULL(txt_normalized, txt) LIKE ?
1244                 ORDER BY m.timestamp,m.id;",
1245                    (chat_id, str_like_in_text),
1246                    |row| {
1247                        let msg_id: MsgId = row.get("id")?;
1248                        Ok(msg_id)
1249                    },
1250                )
1251                .await?
1252        } else {
1253            // For performance reasons results are sorted only by `id`, that is in the order of
1254            // message reception.
1255            //
1256            // Unlike chat view, sorting by `timestamp` is not necessary but slows down the query by
1257            // ~25% according to benchmarks.
1258            //
1259            // To speed up incremental search, where queries for few characters usually return lots
1260            // of unwanted results that are discarded moments later, we added `LIMIT 1000`.
1261            // According to some tests, this limit speeds up eg. 2 character searches by factor 10.
1262            // The limit is documented and UI may add a hint when getting 1000 results.
1263            self.sql
1264                .query_map_vec(
1265                    "SELECT m.id AS id
1266                 FROM msgs m
1267                 LEFT JOIN contacts ct
1268                        ON m.from_id=ct.id
1269                 LEFT JOIN chats c
1270                        ON m.chat_id=c.id
1271                 WHERE m.chat_id>9
1272                   AND m.hidden=0
1273                   AND c.blocked!=1
1274                   AND ct.blocked=0
1275                   AND IFNULL(txt_normalized, txt) LIKE ?
1276                 ORDER BY m.id DESC LIMIT 1000",
1277                    (str_like_in_text,),
1278                    |row| {
1279                        let msg_id: MsgId = row.get("id")?;
1280                        Ok(msg_id)
1281                    },
1282                )
1283                .await?
1284        };
1285
1286        Ok(list)
1287    }
1288
1289    /// Returns true if given folder name is the name of the inbox.
1290    pub async fn is_inbox(&self, folder_name: &str) -> Result<bool> {
1291        let inbox = self.get_config(Config::ConfiguredInboxFolder).await?;
1292        Ok(inbox.as_deref() == Some(folder_name))
1293    }
1294
1295    /// Returns true if given folder name is the name of the "DeltaChat" folder.
1296    pub async fn is_mvbox(&self, folder_name: &str) -> Result<bool> {
1297        let mvbox = self.get_config(Config::ConfiguredMvboxFolder).await?;
1298        Ok(mvbox.as_deref() == Some(folder_name))
1299    }
1300
1301    /// Returns true if given folder name is the name of the trash folder.
1302    pub async fn is_trash(&self, folder_name: &str) -> Result<bool> {
1303        let trash = self.get_config(Config::ConfiguredTrashFolder).await?;
1304        Ok(trash.as_deref() == Some(folder_name))
1305    }
1306
1307    pub(crate) async fn should_delete_to_trash(&self) -> Result<bool> {
1308        if let Some(v) = self.get_config_bool_opt(Config::DeleteToTrash).await? {
1309            return Ok(v);
1310        }
1311        if let Some(provider) = self.get_configured_provider().await? {
1312            return Ok(provider.opt.delete_to_trash);
1313        }
1314        Ok(false)
1315    }
1316
1317    /// Returns `target` for deleted messages as per `imap` table. Empty string means "delete w/o
1318    /// moving to trash".
1319    pub(crate) async fn get_delete_msgs_target(&self) -> Result<String> {
1320        if !self.should_delete_to_trash().await? {
1321            return Ok("".into());
1322        }
1323        self.get_config(Config::ConfiguredTrashFolder)
1324            .await?
1325            .context("No configured trash folder")
1326    }
1327
1328    pub(crate) fn derive_blobdir(dbfile: &Path) -> PathBuf {
1329        let mut blob_fname = OsString::new();
1330        blob_fname.push(dbfile.file_name().unwrap_or_default());
1331        blob_fname.push("-blobs");
1332        dbfile.with_file_name(blob_fname)
1333    }
1334
1335    pub(crate) fn derive_walfile(dbfile: &Path) -> PathBuf {
1336        let mut wal_fname = OsString::new();
1337        wal_fname.push(dbfile.file_name().unwrap_or_default());
1338        wal_fname.push("-wal");
1339        dbfile.with_file_name(wal_fname)
1340    }
1341}
1342
1343#[cfg(test)]
1344mod context_tests;