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