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