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