1use std::collections::{BTreeMap, HashMap};
4use std::ffi::OsString;
5use std::ops::Deref;
6use std::path::{Path, PathBuf};
7use std::sync::atomic::AtomicBool;
8use std::sync::{Arc, OnceLock};
9use std::time::Duration;
10
11use anyhow::{Context as _, Result, bail, ensure};
12use async_channel::{self as channel, Receiver, Sender};
13use ratelimit::Ratelimit;
14use tokio::sync::{Mutex, Notify, RwLock};
15
16use crate::chat::{ChatId, get_chat_cnt};
17use crate::config::Config;
18use crate::constants::{self, DC_BACKGROUND_FETCH_QUOTA_CHECK_RATELIMIT, DC_VERSION_STR};
19use crate::contact::{Contact, ContactId};
20use crate::debug_logging::DebugLogging;
21use crate::events::{Event, EventEmitter, EventType, Events};
22use crate::imap::{FolderMeaning, Imap, ServerMetadata};
23use crate::key::self_fingerprint;
24use crate::log::warn;
25use crate::logged_debug_assert;
26use crate::login_param::EnteredLoginParam;
27use crate::message::{self, MessageState, MsgId};
28use crate::net::tls::TlsSessionStore;
29use crate::peer_channels::Iroh;
30use crate::push::PushSubscriber;
31use crate::quota::QuotaInfo;
32use crate::scheduler::{ConnectivityStore, SchedulerState, convert_folder_meaning};
33use crate::sql::Sql;
34use crate::stock_str::StockStrings;
35use crate::timesmearing::SmearedTimestamp;
36use crate::tools::{self, duration_to_str, time, time_elapsed};
37use crate::transport::ConfiguredLoginParam;
38use crate::{chatlist_events, stats};
39
40#[derive(Clone, Debug)]
65pub struct ContextBuilder {
66 dbfile: PathBuf,
67 id: u32,
68 events: Events,
69 stock_strings: StockStrings,
70 password: Option<String>,
71
72 push_subscriber: Option<PushSubscriber>,
73}
74
75impl ContextBuilder {
76 pub fn new(dbfile: PathBuf) -> Self {
82 ContextBuilder {
83 dbfile,
84 id: rand::random(),
85 events: Events::new(),
86 stock_strings: StockStrings::new(),
87 password: None,
88 push_subscriber: None,
89 }
90 }
91
92 pub fn with_id(mut self, id: u32) -> Self {
102 self.id = id;
103 self
104 }
105
106 pub fn with_events(mut self, events: Events) -> Self {
115 self.events = events;
116 self
117 }
118
119 pub fn with_stock_strings(mut self, stock_strings: StockStrings) -> Self {
130 self.stock_strings = stock_strings;
131 self
132 }
133
134 #[deprecated(since = "TBD")]
142 pub fn with_password(mut self, password: String) -> Self {
143 self.password = Some(password);
144 self
145 }
146
147 pub(crate) fn with_push_subscriber(mut self, push_subscriber: PushSubscriber) -> Self {
149 self.push_subscriber = Some(push_subscriber);
150 self
151 }
152
153 pub async fn build(self) -> Result<Context> {
155 let push_subscriber = self.push_subscriber.unwrap_or_default();
156 let context = Context::new_closed(
157 &self.dbfile,
158 self.id,
159 self.events,
160 self.stock_strings,
161 push_subscriber,
162 )
163 .await?;
164 Ok(context)
165 }
166
167 pub async fn open(self) -> Result<Context> {
171 let password = self.password.clone().unwrap_or_default();
172 let context = self.build().await?;
173 match context.open(password).await? {
174 true => Ok(context),
175 false => bail!("database could not be decrypted, incorrect or missing password"),
176 }
177 }
178}
179
180#[derive(Clone, Debug)]
192pub struct Context {
193 pub(crate) inner: Arc<InnerContext>,
194}
195
196impl Deref for Context {
197 type Target = InnerContext;
198
199 fn deref(&self) -> &Self::Target {
200 &self.inner
201 }
202}
203
204#[derive(Debug)]
206pub struct InnerContext {
207 pub(crate) blobdir: PathBuf,
209 pub(crate) sql: Sql,
210 pub(crate) smeared_timestamp: SmearedTimestamp,
211 running_state: RwLock<RunningState>,
216 pub(crate) generating_key_mutex: Mutex<()>,
218 pub(crate) oauth2_mutex: Mutex<()>,
220 pub(crate) wrong_pw_warning_mutex: Mutex<()>,
222 pub(crate) translated_stockstrings: StockStrings,
223 pub(crate) events: Events,
224
225 pub(crate) scheduler: SchedulerState,
226 pub(crate) ratelimit: RwLock<Ratelimit>,
227
228 pub(crate) quota: RwLock<Option<QuotaInfo>>,
231
232 pub(crate) new_msgs_notify: Notify,
236
237 pub(crate) server_id: RwLock<Option<HashMap<String, String>>>,
241
242 pub(crate) metadata: RwLock<Option<ServerMetadata>>,
244
245 pub(crate) id: u32,
250
251 creation_time: tools::Time,
252
253 pub(crate) last_error: parking_lot::RwLock<String>,
257
258 pub(crate) migration_error: parking_lot::RwLock<Option<String>>,
264
265 pub(crate) debug_logging: std::sync::RwLock<Option<DebugLogging>>,
270
271 pub(crate) push_subscriber: PushSubscriber,
274
275 pub(crate) push_subscribed: AtomicBool,
277
278 pub(crate) tls_session_store: TlsSessionStore,
280
281 pub(crate) iroh: Arc<RwLock<Option<Iroh>>>,
283
284 pub(crate) self_fingerprint: OnceLock<String>,
288
289 pub(crate) connectivities: parking_lot::Mutex<Vec<ConnectivityStore>>,
292
293 #[expect(clippy::type_complexity)]
294 pub(crate) pre_encrypt_mime_hook: parking_lot::Mutex<
296 Option<
297 for<'a> fn(
298 &Context,
299 mail_builder::mime::MimePart<'a>,
300 ) -> mail_builder::mime::MimePart<'a>,
301 >,
302 >,
303}
304
305#[derive(Debug, Default)]
307enum RunningState {
308 Running { cancel_sender: Sender<()> },
310
311 ShallStop { request: tools::Time },
313
314 #[default]
316 Stopped,
317}
318
319pub fn get_info() -> BTreeMap<&'static str, String> {
326 let mut res = BTreeMap::new();
327
328 #[cfg(debug_assertions)]
329 res.insert(
330 "debug_assertions",
331 "On - DO NOT RELEASE THIS BUILD".to_string(),
332 );
333 #[cfg(not(debug_assertions))]
334 res.insert("debug_assertions", "Off".to_string());
335
336 res.insert("deltachat_core_version", format!("v{}", &*DC_VERSION_STR));
337 res.insert("sqlite_version", rusqlite::version().to_string());
338 res.insert("arch", (std::mem::size_of::<usize>() * 8).to_string());
339 res.insert("num_cpus", num_cpus::get().to_string());
340 res.insert("level", "awesome".into());
341 res
342}
343
344impl Context {
345 pub async fn new(
347 dbfile: &Path,
348 id: u32,
349 events: Events,
350 stock_strings: StockStrings,
351 ) -> Result<Context> {
352 let context =
353 Self::new_closed(dbfile, id, events, stock_strings, Default::default()).await?;
354
355 if context.check_passphrase("".to_string()).await? {
357 context.sql.open(&context, "".to_string()).await?;
358 }
359 Ok(context)
360 }
361
362 pub async fn new_closed(
364 dbfile: &Path,
365 id: u32,
366 events: Events,
367 stockstrings: StockStrings,
368 push_subscriber: PushSubscriber,
369 ) -> Result<Context> {
370 let mut blob_fname = OsString::new();
371 blob_fname.push(dbfile.file_name().unwrap_or_default());
372 blob_fname.push("-blobs");
373 let blobdir = dbfile.with_file_name(blob_fname);
374 if !blobdir.exists() {
375 tokio::fs::create_dir_all(&blobdir).await?;
376 }
377 let context = Context::with_blobdir(
378 dbfile.into(),
379 blobdir,
380 id,
381 events,
382 stockstrings,
383 push_subscriber,
384 )?;
385 Ok(context)
386 }
387
388 #[deprecated(since = "TBD")]
395 pub async fn open(&self, passphrase: String) -> Result<bool> {
396 if self.sql.check_passphrase(passphrase.clone()).await? {
397 self.sql.open(self, passphrase).await?;
398 Ok(true)
399 } else {
400 Ok(false)
401 }
402 }
403
404 pub async fn change_passphrase(&self, passphrase: String) -> Result<()> {
407 self.sql.change_passphrase(passphrase).await?;
408 Ok(())
409 }
410
411 pub async fn is_open(&self) -> bool {
413 self.sql.is_open().await
414 }
415
416 pub(crate) async fn check_passphrase(&self, passphrase: String) -> Result<bool> {
422 self.sql.check_passphrase(passphrase).await
423 }
424
425 pub(crate) fn with_blobdir(
426 dbfile: PathBuf,
427 blobdir: PathBuf,
428 id: u32,
429 events: Events,
430 stockstrings: StockStrings,
431 push_subscriber: PushSubscriber,
432 ) -> Result<Context> {
433 ensure!(
434 blobdir.is_dir(),
435 "Blobdir does not exist: {}",
436 blobdir.display()
437 );
438
439 let new_msgs_notify = Notify::new();
440 new_msgs_notify.notify_one();
443
444 let inner = InnerContext {
445 id,
446 blobdir,
447 running_state: RwLock::new(Default::default()),
448 sql: Sql::new(dbfile),
449 smeared_timestamp: SmearedTimestamp::new(),
450 generating_key_mutex: Mutex::new(()),
451 oauth2_mutex: Mutex::new(()),
452 wrong_pw_warning_mutex: Mutex::new(()),
453 translated_stockstrings: stockstrings,
454 events,
455 scheduler: SchedulerState::new(),
456 ratelimit: RwLock::new(Ratelimit::new(Duration::new(3, 0), 3.0)), quota: RwLock::new(None),
458 new_msgs_notify,
459 server_id: RwLock::new(None),
460 metadata: RwLock::new(None),
461 creation_time: tools::Time::now(),
462 last_error: parking_lot::RwLock::new("".to_string()),
463 migration_error: parking_lot::RwLock::new(None),
464 debug_logging: std::sync::RwLock::new(None),
465 push_subscriber,
466 push_subscribed: AtomicBool::new(false),
467 tls_session_store: TlsSessionStore::new(),
468 iroh: Arc::new(RwLock::new(None)),
469 self_fingerprint: OnceLock::new(),
470 connectivities: parking_lot::Mutex::new(Vec::new()),
471 pre_encrypt_mime_hook: None.into(),
472 };
473
474 let ctx = Context {
475 inner: Arc::new(inner),
476 };
477
478 Ok(ctx)
479 }
480
481 pub async fn start_io(&self) {
483 if !self.is_configured().await.unwrap_or_default() {
484 warn!(self, "can not start io on a context that is not configured");
485 return;
486 }
487
488 self.sql.config_cache.write().await.clear();
494
495 self.scheduler.start(self).await;
496 }
497
498 pub async fn stop_io(&self) {
500 self.scheduler.stop(self).await;
501 if let Some(iroh) = self.iroh.write().await.take() {
502 tokio::spawn(async move {
509 let _ = tokio::time::timeout(Duration::from_secs(60), iroh.close()).await;
512 });
513 }
514 }
515
516 pub async fn restart_io_if_running(&self) {
519 self.scheduler.restart(self).await;
520 }
521
522 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 pub async fn is_chatmail(&self) -> Result<bool> {
532 self.get_config_bool(Config::IsChatmail).await
533 }
534
535 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 => constants::DEFAULT_CHATMAIL_MAX_SMTP_RCPT_TO,
545 false => constants::DEFAULT_MAX_SMTP_RCPT_TO,
546 },
547 usize::from,
548 );
549 Ok(val)
550 }
551
552 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 let _pause_guard = self.scheduler.pause(self).await?;
573
574 let mut connection = Imap::new_configured(self, channel::bounded(1).1).await?;
576 let mut session = connection.prepare(self).await?;
577
578 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 if self
593 .quota_needs_update(DC_BACKGROUND_FETCH_QUOTA_CHECK_RATELIMIT)
594 .await
595 && let Err(err) = self.update_recent_quota(&mut session).await
596 {
597 warn!(self, "Failed to update quota: {err:#}.");
598 }
599 }
600
601 info!(
602 self,
603 "background_fetch done for {address} took {:?}.",
604 time_elapsed(&time_start),
605 );
606
607 Ok(())
608 }
609
610 #[cfg(feature = "internals")]
614 pub fn sql(&self) -> &Sql {
615 &self.inner.sql
616 }
617
618 pub fn get_dbfile(&self) -> &Path {
620 self.sql.dbfile.as_path()
621 }
622
623 pub fn get_blobdir(&self) -> &Path {
625 self.blobdir.as_path()
626 }
627
628 pub fn emit_event(&self, event: EventType) {
630 {
631 let lock = self.debug_logging.read().expect("RwLock is poisoned");
632 if let Some(debug_logging) = &*lock {
633 debug_logging.log_event(event.clone());
634 }
635 }
636 self.events.emit(Event {
637 id: self.id,
638 typ: event,
639 });
640 }
641
642 pub fn emit_msgs_changed_without_ids(&self) {
644 self.emit_event(EventType::MsgsChanged {
645 chat_id: ChatId::new(0),
646 msg_id: MsgId::new(0),
647 });
648 }
649
650 pub fn emit_msgs_changed(&self, chat_id: ChatId, msg_id: MsgId) {
656 logged_debug_assert!(
657 self,
658 !chat_id.is_unset(),
659 "emit_msgs_changed: chat_id is unset."
660 );
661 logged_debug_assert!(
662 self,
663 !msg_id.is_unset(),
664 "emit_msgs_changed: msg_id is unset."
665 );
666
667 self.emit_event(EventType::MsgsChanged { chat_id, msg_id });
668 chatlist_events::emit_chatlist_changed(self);
669 chatlist_events::emit_chatlist_item_changed(self, chat_id);
670 }
671
672 pub fn emit_msgs_changed_without_msg_id(&self, chat_id: ChatId) {
674 logged_debug_assert!(
675 self,
676 !chat_id.is_unset(),
677 "emit_msgs_changed_without_msg_id: chat_id is unset."
678 );
679
680 self.emit_event(EventType::MsgsChanged {
681 chat_id,
682 msg_id: MsgId::new(0),
683 });
684 chatlist_events::emit_chatlist_changed(self);
685 chatlist_events::emit_chatlist_item_changed(self, chat_id);
686 }
687
688 pub fn emit_incoming_msg(&self, chat_id: ChatId, msg_id: MsgId) {
690 debug_assert!(!chat_id.is_unset());
691 debug_assert!(!msg_id.is_unset());
692
693 self.emit_event(EventType::IncomingMsg { chat_id, msg_id });
694 chatlist_events::emit_chatlist_changed(self);
695 chatlist_events::emit_chatlist_item_changed(self, chat_id);
696 }
697
698 pub async fn emit_location_changed(&self, contact_id: Option<ContactId>) -> Result<()> {
700 self.emit_event(EventType::LocationChanged(contact_id));
701
702 if let Some(msg_id) = self
703 .get_config_parsed::<u32>(Config::WebxdcIntegration)
704 .await?
705 {
706 self.emit_event(EventType::WebxdcStatusUpdate {
707 msg_id: MsgId::new(msg_id),
708 status_update_serial: Default::default(),
709 })
710 }
711
712 Ok(())
713 }
714
715 pub fn get_event_emitter(&self) -> EventEmitter {
720 self.events.get_emitter()
721 }
722
723 pub fn get_id(&self) -> u32 {
725 self.id
726 }
727
728 pub(crate) async fn alloc_ongoing(&self) -> Result<Receiver<()>> {
738 let mut s = self.running_state.write().await;
739 ensure!(
740 matches!(*s, RunningState::Stopped),
741 "There is already another ongoing process running."
742 );
743
744 let (sender, receiver) = channel::bounded(1);
745 *s = RunningState::Running {
746 cancel_sender: sender,
747 };
748
749 Ok(receiver)
750 }
751
752 pub(crate) async fn free_ongoing(&self) {
753 let mut s = self.running_state.write().await;
754 if let RunningState::ShallStop { request } = *s {
755 info!(self, "Ongoing stopped in {:?}", time_elapsed(&request));
756 }
757 *s = RunningState::Stopped;
758 }
759
760 pub async fn stop_ongoing(&self) {
762 let mut s = self.running_state.write().await;
763 match &*s {
764 RunningState::Running { cancel_sender } => {
765 if let Err(err) = cancel_sender.send(()).await {
766 warn!(self, "could not cancel ongoing: {:#}", err);
767 }
768 info!(self, "Signaling the ongoing process to stop ASAP.",);
769 *s = RunningState::ShallStop {
770 request: tools::Time::now(),
771 };
772 }
773 RunningState::ShallStop { .. } | RunningState::Stopped => {
774 info!(self, "No ongoing process to stop.",);
775 }
776 }
777 }
778
779 #[allow(unused)]
780 pub(crate) async fn shall_stop_ongoing(&self) -> bool {
781 match &*self.running_state.read().await {
782 RunningState::Running { .. } => false,
783 RunningState::ShallStop { .. } | RunningState::Stopped => true,
784 }
785 }
786
787 pub async fn get_info(&self) -> Result<BTreeMap<&'static str, String>> {
793 let l = EnteredLoginParam::load(self).await?;
794 let l2 = ConfiguredLoginParam::load(self).await?.map_or_else(
795 || "Not configured".to_string(),
796 |(_transport_id, param)| param.to_string(),
797 );
798 let secondary_addrs = self.get_secondary_self_addrs().await?.join(", ");
799 let chats = get_chat_cnt(self).await?;
800 let unblocked_msgs = message::get_unblocked_msg_cnt(self).await;
801 let request_msgs = message::get_request_msg_cnt(self).await;
802 let contacts = Contact::get_real_cnt(self).await?;
803 let proxy_enabled = self.get_config_int(Config::ProxyEnabled).await?;
804 let dbversion = self
805 .sql
806 .get_raw_config_int("dbversion")
807 .await?
808 .unwrap_or_default();
809 let journal_mode = self
810 .sql
811 .query_get_value("PRAGMA journal_mode;", ())
812 .await?
813 .unwrap_or_else(|| "unknown".to_string());
814 let mdns_enabled = self.get_config_int(Config::MdnsEnabled).await?;
815 let bcc_self = self.get_config_int(Config::BccSelf).await?;
816 let sync_msgs = self.get_config_int(Config::SyncMsgs).await?;
817 let disable_idle = self.get_config_bool(Config::DisableIdle).await?;
818
819 let prv_key_cnt = self.sql.count("SELECT COUNT(*) FROM keypairs;", ()).await?;
820
821 let pub_key_cnt = self
822 .sql
823 .count("SELECT COUNT(*) FROM public_keys;", ())
824 .await?;
825 let fingerprint_str = match self_fingerprint(self).await {
826 Ok(fp) => fp.to_string(),
827 Err(err) => format!("<key failure: {err}>"),
828 };
829
830 let mvbox_move = self.get_config_int(Config::MvboxMove).await?;
831 let only_fetch_mvbox = self.get_config_int(Config::OnlyFetchMvbox).await?;
832 let folders_configured = self
833 .sql
834 .get_raw_config_int(constants::DC_FOLDERS_CONFIGURED_KEY)
835 .await?
836 .unwrap_or_default();
837
838 let configured_inbox_folder = self
839 .get_config(Config::ConfiguredInboxFolder)
840 .await?
841 .unwrap_or_else(|| "<unset>".to_string());
842 let configured_mvbox_folder = self
843 .get_config(Config::ConfiguredMvboxFolder)
844 .await?
845 .unwrap_or_else(|| "<unset>".to_string());
846 let configured_trash_folder = self
847 .get_config(Config::ConfiguredTrashFolder)
848 .await?
849 .unwrap_or_else(|| "<unset>".to_string());
850
851 let mut res = get_info();
852
853 res.insert("bot", self.get_config_int(Config::Bot).await?.to_string());
855 res.insert("number_of_chats", chats.to_string());
856 res.insert("number_of_chat_messages", unblocked_msgs.to_string());
857 res.insert("messages_in_contact_requests", request_msgs.to_string());
858 res.insert("number_of_contacts", contacts.to_string());
859 res.insert("database_dir", self.get_dbfile().display().to_string());
860 res.insert("database_version", dbversion.to_string());
861 res.insert(
862 "database_encrypted",
863 self.sql
864 .is_encrypted()
865 .await
866 .map_or_else(|| "closed".to_string(), |b| b.to_string()),
867 );
868 res.insert("journal_mode", journal_mode);
869 res.insert("blobdir", self.get_blobdir().display().to_string());
870 res.insert(
871 "selfavatar",
872 self.get_config(Config::Selfavatar)
873 .await?
874 .unwrap_or_else(|| "<unset>".to_string()),
875 );
876 res.insert("proxy_enabled", proxy_enabled.to_string());
877 res.insert("entered_account_settings", l.to_string());
878 res.insert("used_account_settings", l2);
879
880 if let Some(server_id) = &*self.server_id.read().await {
881 res.insert("imap_server_id", format!("{server_id:?}"));
882 }
883
884 res.insert("is_chatmail", self.is_chatmail().await?.to_string());
885 res.insert(
886 "fix_is_chatmail",
887 self.get_config_bool(Config::FixIsChatmail)
888 .await?
889 .to_string(),
890 );
891 res.insert(
892 "is_muted",
893 self.get_config_bool(Config::IsMuted).await?.to_string(),
894 );
895 res.insert(
896 "private_tag",
897 self.get_config(Config::PrivateTag)
898 .await?
899 .unwrap_or_else(|| "<unset>".to_string()),
900 );
901
902 if let Some(metadata) = &*self.metadata.read().await {
903 if let Some(comment) = &metadata.comment {
904 res.insert("imap_server_comment", format!("{comment:?}"));
905 }
906
907 if let Some(admin) = &metadata.admin {
908 res.insert("imap_server_admin", format!("{admin:?}"));
909 }
910 }
911
912 res.insert("secondary_addrs", secondary_addrs);
913 res.insert(
914 "fetched_existing_msgs",
915 self.get_config_bool(Config::FetchedExistingMsgs)
916 .await?
917 .to_string(),
918 );
919 res.insert(
920 "show_emails",
921 self.get_config_int(Config::ShowEmails).await?.to_string(),
922 );
923 res.insert(
924 "download_limit",
925 self.get_config_int(Config::DownloadLimit)
926 .await?
927 .to_string(),
928 );
929 res.insert("mvbox_move", mvbox_move.to_string());
930 res.insert("only_fetch_mvbox", only_fetch_mvbox.to_string());
931 res.insert(
932 constants::DC_FOLDERS_CONFIGURED_KEY,
933 folders_configured.to_string(),
934 );
935 res.insert("configured_inbox_folder", configured_inbox_folder);
936 res.insert("configured_mvbox_folder", configured_mvbox_folder);
937 res.insert("configured_trash_folder", configured_trash_folder);
938 res.insert("mdns_enabled", mdns_enabled.to_string());
939 res.insert("bcc_self", bcc_self.to_string());
940 res.insert("sync_msgs", sync_msgs.to_string());
941 res.insert("disable_idle", disable_idle.to_string());
942 res.insert("private_key_count", prv_key_cnt.to_string());
943 res.insert("public_key_count", pub_key_cnt.to_string());
944 res.insert("fingerprint", fingerprint_str);
945 res.insert(
946 "media_quality",
947 self.get_config_int(Config::MediaQuality).await?.to_string(),
948 );
949 res.insert(
950 "delete_device_after",
951 self.get_config_int(Config::DeleteDeviceAfter)
952 .await?
953 .to_string(),
954 );
955 res.insert(
956 "delete_server_after",
957 self.get_config_int(Config::DeleteServerAfter)
958 .await?
959 .to_string(),
960 );
961 res.insert(
962 "delete_to_trash",
963 self.get_config(Config::DeleteToTrash)
964 .await?
965 .unwrap_or_else(|| "<unset>".to_string()),
966 );
967 res.insert(
968 "last_housekeeping",
969 self.get_config_int(Config::LastHousekeeping)
970 .await?
971 .to_string(),
972 );
973 res.insert(
974 "last_cant_decrypt_outgoing_msgs",
975 self.get_config_int(Config::LastCantDecryptOutgoingMsgs)
976 .await?
977 .to_string(),
978 );
979 res.insert(
980 "scan_all_folders_debounce_secs",
981 self.get_config_int(Config::ScanAllFoldersDebounceSecs)
982 .await?
983 .to_string(),
984 );
985 res.insert(
986 "quota_exceeding",
987 self.get_config_int(Config::QuotaExceeding)
988 .await?
989 .to_string(),
990 );
991 res.insert(
992 "authserv_id_candidates",
993 self.get_config(Config::AuthservIdCandidates)
994 .await?
995 .unwrap_or_default(),
996 );
997 res.insert(
998 "sign_unencrypted",
999 self.get_config_int(Config::SignUnencrypted)
1000 .await?
1001 .to_string(),
1002 );
1003 res.insert(
1004 "debug_logging",
1005 self.get_config_int(Config::DebugLogging).await?.to_string(),
1006 );
1007 res.insert(
1008 "last_msg_id",
1009 self.get_config_int(Config::LastMsgId).await?.to_string(),
1010 );
1011 res.insert(
1012 "gossip_period",
1013 self.get_config_int(Config::GossipPeriod).await?.to_string(),
1014 );
1015 res.insert(
1016 "webxdc_realtime_enabled",
1017 self.get_config_bool(Config::WebxdcRealtimeEnabled)
1018 .await?
1019 .to_string(),
1020 );
1021 res.insert(
1022 "donation_request_next_check",
1023 self.get_config_i64(Config::DonationRequestNextCheck)
1024 .await?
1025 .to_string(),
1026 );
1027 res.insert(
1028 "first_key_contacts_msg_id",
1029 self.sql
1030 .get_raw_config("first_key_contacts_msg_id")
1031 .await?
1032 .unwrap_or_default(),
1033 );
1034 res.insert(
1035 "stats_id",
1036 self.get_config(Config::StatsId)
1037 .await?
1038 .unwrap_or_else(|| "<unset>".to_string()),
1039 );
1040 res.insert(
1041 "stats_sending",
1042 stats::should_send_stats(self).await?.to_string(),
1043 );
1044 res.insert(
1045 "stats_last_sent",
1046 self.get_config_i64(Config::StatsLastSent)
1047 .await?
1048 .to_string(),
1049 );
1050 res.insert(
1051 "test_hooks",
1052 self.sql
1053 .get_raw_config("test_hooks")
1054 .await?
1055 .unwrap_or_default(),
1056 );
1057 res.insert(
1058 "fail_on_receiving_full_msg",
1059 self.sql
1060 .get_raw_config("fail_on_receiving_full_msg")
1061 .await?
1062 .unwrap_or_default(),
1063 );
1064 res.insert(
1065 "std_header_protection_composing",
1066 self.sql
1067 .get_raw_config("std_header_protection_composing")
1068 .await?
1069 .unwrap_or_default(),
1070 );
1071
1072 let elapsed = time_elapsed(&self.creation_time);
1073 res.insert("uptime", duration_to_str(elapsed));
1074
1075 Ok(res)
1076 }
1077
1078 pub async fn get_fresh_msgs(&self) -> Result<Vec<MsgId>> {
1085 let list = self
1086 .sql
1087 .query_map_vec(
1088 concat!(
1089 "SELECT m.id",
1090 " FROM msgs m",
1091 " LEFT JOIN contacts ct",
1092 " ON m.from_id=ct.id",
1093 " LEFT JOIN chats c",
1094 " ON m.chat_id=c.id",
1095 " WHERE m.state=?",
1096 " AND m.hidden=0",
1097 " AND m.chat_id>9",
1098 " AND ct.blocked=0",
1099 " AND c.blocked=0",
1100 " AND NOT(c.muted_until=-1 OR c.muted_until>?)",
1101 " ORDER BY m.timestamp DESC,m.id DESC;"
1102 ),
1103 (MessageState::InFresh, time()),
1104 |row| {
1105 let msg_id: MsgId = row.get(0)?;
1106 Ok(msg_id)
1107 },
1108 )
1109 .await?;
1110 Ok(list)
1111 }
1112
1113 pub async fn get_next_msgs(&self) -> Result<Vec<MsgId>> {
1118 let last_msg_id = match self.get_config(Config::LastMsgId).await? {
1119 Some(s) => MsgId::new(s.parse()?),
1120 None => {
1121 self.sql
1126 .query_row(
1127 "SELECT IFNULL((SELECT MAX(id) - 1 FROM msgs), 0)",
1128 (),
1129 |row| {
1130 let msg_id: MsgId = row.get(0)?;
1131 Ok(msg_id)
1132 },
1133 )
1134 .await?
1135 }
1136 };
1137
1138 let list = self
1139 .sql
1140 .query_map_vec(
1141 "SELECT m.id
1142 FROM msgs m
1143 LEFT JOIN contacts ct
1144 ON m.from_id=ct.id
1145 LEFT JOIN chats c
1146 ON m.chat_id=c.id
1147 WHERE m.id>?
1148 AND m.hidden=0
1149 AND m.chat_id>9
1150 AND ct.blocked=0
1151 AND c.blocked!=1
1152 ORDER BY m.id ASC",
1153 (
1154 last_msg_id.to_u32(), ),
1156 |row| {
1157 let msg_id: MsgId = row.get(0)?;
1158 Ok(msg_id)
1159 },
1160 )
1161 .await?;
1162 Ok(list)
1163 }
1164
1165 pub async fn wait_next_msgs(&self) -> Result<Vec<MsgId>> {
1176 self.new_msgs_notify.notified().await;
1177 let list = self.get_next_msgs().await?;
1178 Ok(list)
1179 }
1180
1181 pub async fn search_msgs(&self, chat_id: Option<ChatId>, query: &str) -> Result<Vec<MsgId>> {
1192 let real_query = query.trim().to_lowercase();
1193 if real_query.is_empty() {
1194 return Ok(Vec::new());
1195 }
1196 let str_like_in_text = format!("%{real_query}%");
1197
1198 let list = if let Some(chat_id) = chat_id {
1199 self.sql
1200 .query_map_vec(
1201 "SELECT m.id AS id
1202 FROM msgs m
1203 LEFT JOIN contacts ct
1204 ON m.from_id=ct.id
1205 WHERE m.chat_id=?
1206 AND m.hidden=0
1207 AND ct.blocked=0
1208 AND IFNULL(txt_normalized, txt) LIKE ?
1209 ORDER BY m.timestamp,m.id;",
1210 (chat_id, str_like_in_text),
1211 |row| {
1212 let msg_id: MsgId = row.get("id")?;
1213 Ok(msg_id)
1214 },
1215 )
1216 .await?
1217 } else {
1218 self.sql
1229 .query_map_vec(
1230 "SELECT m.id AS id
1231 FROM msgs m
1232 LEFT JOIN contacts ct
1233 ON m.from_id=ct.id
1234 LEFT JOIN chats c
1235 ON m.chat_id=c.id
1236 WHERE m.chat_id>9
1237 AND m.hidden=0
1238 AND c.blocked!=1
1239 AND ct.blocked=0
1240 AND IFNULL(txt_normalized, txt) LIKE ?
1241 ORDER BY m.id DESC LIMIT 1000",
1242 (str_like_in_text,),
1243 |row| {
1244 let msg_id: MsgId = row.get("id")?;
1245 Ok(msg_id)
1246 },
1247 )
1248 .await?
1249 };
1250
1251 Ok(list)
1252 }
1253
1254 pub async fn is_inbox(&self, folder_name: &str) -> Result<bool> {
1256 let inbox = self.get_config(Config::ConfiguredInboxFolder).await?;
1257 Ok(inbox.as_deref() == Some(folder_name))
1258 }
1259
1260 pub async fn is_mvbox(&self, folder_name: &str) -> Result<bool> {
1262 let mvbox = self.get_config(Config::ConfiguredMvboxFolder).await?;
1263 Ok(mvbox.as_deref() == Some(folder_name))
1264 }
1265
1266 pub async fn is_trash(&self, folder_name: &str) -> Result<bool> {
1268 let trash = self.get_config(Config::ConfiguredTrashFolder).await?;
1269 Ok(trash.as_deref() == Some(folder_name))
1270 }
1271
1272 pub(crate) async fn should_delete_to_trash(&self) -> Result<bool> {
1273 if let Some(v) = self.get_config_bool_opt(Config::DeleteToTrash).await? {
1274 return Ok(v);
1275 }
1276 if let Some(provider) = self.get_configured_provider().await? {
1277 return Ok(provider.opt.delete_to_trash);
1278 }
1279 Ok(false)
1280 }
1281
1282 pub(crate) async fn get_delete_msgs_target(&self) -> Result<String> {
1285 if !self.should_delete_to_trash().await? {
1286 return Ok("".into());
1287 }
1288 self.get_config(Config::ConfiguredTrashFolder)
1289 .await?
1290 .context("No configured trash folder")
1291 }
1292
1293 pub(crate) fn derive_blobdir(dbfile: &Path) -> PathBuf {
1294 let mut blob_fname = OsString::new();
1295 blob_fname.push(dbfile.file_name().unwrap_or_default());
1296 blob_fname.push("-blobs");
1297 dbfile.with_file_name(blob_fname)
1298 }
1299
1300 pub(crate) fn derive_walfile(dbfile: &Path) -> PathBuf {
1301 let mut wal_fname = OsString::new();
1302 wal_fname.push(dbfile.file_name().unwrap_or_default());
1303 wal_fname.push("-wal");
1304 dbfile.with_file_name(wal_fname)
1305 }
1306}
1307
1308pub fn get_version_str() -> &'static str {
1310 &DC_VERSION_STR
1311}
1312
1313#[cfg(test)]
1314mod context_tests;