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