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