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