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, Weak};
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(Clone, Debug)]
208pub(crate) struct WeakContext {
209 inner: Weak<InnerContext>,
210}
211
212impl WeakContext {
213 pub(crate) fn upgrade(&self) -> Result<Context> {
215 let inner = self
216 .inner
217 .upgrade()
218 .ok_or_else(|| anyhow::anyhow!("Inner struct has been dropped"))?;
219 Ok(Context { inner })
220 }
221}
222
223#[derive(Debug)]
225pub struct InnerContext {
226 pub(crate) blobdir: PathBuf,
228 pub(crate) sql: Sql,
229 pub(crate) smeared_timestamp: SmearedTimestamp,
230 running_state: RwLock<RunningState>,
235 pub(crate) generating_key_mutex: Mutex<()>,
237 pub(crate) oauth2_mutex: Mutex<()>,
239 pub(crate) wrong_pw_warning_mutex: Mutex<()>,
241 pub(crate) translated_stockstrings: StockStrings,
242 pub(crate) events: Events,
243
244 pub(crate) scheduler: SchedulerState,
245 pub(crate) ratelimit: RwLock<Ratelimit>,
246
247 pub(crate) quota: RwLock<Option<QuotaInfo>>,
250
251 pub(crate) new_msgs_notify: Notify,
255
256 pub(crate) server_id: RwLock<Option<HashMap<String, String>>>,
260
261 pub(crate) metadata: RwLock<Option<ServerMetadata>>,
263
264 pub(crate) id: u32,
269
270 creation_time: tools::Time,
271
272 pub(crate) last_error: parking_lot::RwLock<String>,
276
277 pub(crate) migration_error: parking_lot::RwLock<Option<String>>,
283
284 pub(crate) debug_logging: std::sync::RwLock<Option<DebugLogging>>,
289
290 pub(crate) push_subscriber: PushSubscriber,
293
294 pub(crate) push_subscribed: AtomicBool,
296
297 pub(crate) tls_session_store: TlsSessionStore,
299
300 pub(crate) iroh: Arc<RwLock<Option<Iroh>>>,
302
303 pub(crate) self_fingerprint: OnceLock<String>,
307
308 pub(crate) connectivities: parking_lot::Mutex<Vec<ConnectivityStore>>,
311
312 #[expect(clippy::type_complexity)]
313 pub(crate) pre_encrypt_mime_hook: parking_lot::Mutex<
315 Option<
316 for<'a> fn(
317 &Context,
318 mail_builder::mime::MimePart<'a>,
319 ) -> mail_builder::mime::MimePart<'a>,
320 >,
321 >,
322}
323
324#[derive(Debug, Default)]
326enum RunningState {
327 Running { cancel_sender: Sender<()> },
329
330 ShallStop { request: tools::Time },
332
333 #[default]
335 Stopped,
336}
337
338pub fn get_info() -> BTreeMap<&'static str, String> {
345 let mut res = BTreeMap::new();
346
347 #[cfg(debug_assertions)]
348 res.insert(
349 "debug_assertions",
350 "On - DO NOT RELEASE THIS BUILD".to_string(),
351 );
352 #[cfg(not(debug_assertions))]
353 res.insert("debug_assertions", "Off".to_string());
354
355 res.insert("deltachat_core_version", format!("v{}", &*DC_VERSION_STR));
356 res.insert("sqlite_version", rusqlite::version().to_string());
357 res.insert("arch", (std::mem::size_of::<usize>() * 8).to_string());
358 res.insert("num_cpus", num_cpus::get().to_string());
359 res.insert("level", "awesome".into());
360 res
361}
362
363impl Context {
364 pub async fn new(
366 dbfile: &Path,
367 id: u32,
368 events: Events,
369 stock_strings: StockStrings,
370 ) -> Result<Context> {
371 let context =
372 Self::new_closed(dbfile, id, events, stock_strings, Default::default()).await?;
373
374 if context.check_passphrase("".to_string()).await? {
376 context.sql.open(&context, "".to_string()).await?;
377 }
378 Ok(context)
379 }
380
381 pub async fn new_closed(
383 dbfile: &Path,
384 id: u32,
385 events: Events,
386 stockstrings: StockStrings,
387 push_subscriber: PushSubscriber,
388 ) -> Result<Context> {
389 let mut blob_fname = OsString::new();
390 blob_fname.push(dbfile.file_name().unwrap_or_default());
391 blob_fname.push("-blobs");
392 let blobdir = dbfile.with_file_name(blob_fname);
393 if !blobdir.exists() {
394 tokio::fs::create_dir_all(&blobdir).await?;
395 }
396 let context = Context::with_blobdir(
397 dbfile.into(),
398 blobdir,
399 id,
400 events,
401 stockstrings,
402 push_subscriber,
403 )?;
404 Ok(context)
405 }
406
407 pub(crate) fn get_weak_context(&self) -> WeakContext {
409 WeakContext {
410 inner: Arc::downgrade(&self.inner),
411 }
412 }
413
414 #[deprecated(since = "TBD")]
421 pub async fn open(&self, passphrase: String) -> Result<bool> {
422 if self.sql.check_passphrase(passphrase.clone()).await? {
423 self.sql.open(self, passphrase).await?;
424 Ok(true)
425 } else {
426 Ok(false)
427 }
428 }
429
430 pub async fn change_passphrase(&self, passphrase: String) -> Result<()> {
433 self.sql.change_passphrase(passphrase).await?;
434 Ok(())
435 }
436
437 pub async fn is_open(&self) -> bool {
439 self.sql.is_open().await
440 }
441
442 pub(crate) async fn check_passphrase(&self, passphrase: String) -> Result<bool> {
448 self.sql.check_passphrase(passphrase).await
449 }
450
451 pub(crate) fn with_blobdir(
452 dbfile: PathBuf,
453 blobdir: PathBuf,
454 id: u32,
455 events: Events,
456 stockstrings: StockStrings,
457 push_subscriber: PushSubscriber,
458 ) -> Result<Context> {
459 ensure!(
460 blobdir.is_dir(),
461 "Blobdir does not exist: {}",
462 blobdir.display()
463 );
464
465 let new_msgs_notify = Notify::new();
466 new_msgs_notify.notify_one();
469
470 let inner = InnerContext {
471 id,
472 blobdir,
473 running_state: RwLock::new(Default::default()),
474 sql: Sql::new(dbfile),
475 smeared_timestamp: SmearedTimestamp::new(),
476 generating_key_mutex: Mutex::new(()),
477 oauth2_mutex: Mutex::new(()),
478 wrong_pw_warning_mutex: Mutex::new(()),
479 translated_stockstrings: stockstrings,
480 events,
481 scheduler: SchedulerState::new(),
482 ratelimit: RwLock::new(Ratelimit::new(Duration::new(3, 0), 3.0)), quota: RwLock::new(None),
484 new_msgs_notify,
485 server_id: RwLock::new(None),
486 metadata: RwLock::new(None),
487 creation_time: tools::Time::now(),
488 last_error: parking_lot::RwLock::new("".to_string()),
489 migration_error: parking_lot::RwLock::new(None),
490 debug_logging: std::sync::RwLock::new(None),
491 push_subscriber,
492 push_subscribed: AtomicBool::new(false),
493 tls_session_store: TlsSessionStore::new(),
494 iroh: Arc::new(RwLock::new(None)),
495 self_fingerprint: OnceLock::new(),
496 connectivities: parking_lot::Mutex::new(Vec::new()),
497 pre_encrypt_mime_hook: None.into(),
498 };
499
500 let ctx = Context {
501 inner: Arc::new(inner),
502 };
503
504 Ok(ctx)
505 }
506
507 pub async fn start_io(&self) {
509 if !self.is_configured().await.unwrap_or_default() {
510 warn!(self, "can not start io on a context that is not configured");
511 return;
512 }
513
514 self.sql.config_cache.write().await.clear();
520
521 self.scheduler.start(self).await;
522 }
523
524 pub async fn stop_io(&self) {
526 self.scheduler.stop(self).await;
527 if let Some(iroh) = self.iroh.write().await.take() {
528 tokio::spawn(async move {
535 let _ = tokio::time::timeout(Duration::from_secs(60), iroh.close()).await;
538 });
539 }
540 }
541
542 pub async fn restart_io_if_running(&self) {
545 self.scheduler.restart(self).await;
546 }
547
548 pub async fn maybe_network(&self) {
550 if let Some(ref iroh) = *self.iroh.read().await {
551 iroh.network_change().await;
552 }
553 self.scheduler.maybe_network().await;
554 }
555
556 pub async fn is_chatmail(&self) -> Result<bool> {
558 self.get_config_bool(Config::IsChatmail).await
559 }
560
561 pub(crate) async fn get_max_smtp_rcpt_to(&self) -> Result<usize> {
563 let is_chatmail = self.is_chatmail().await?;
564 let val = self
565 .get_configured_provider()
566 .await?
567 .and_then(|provider| provider.opt.max_smtp_rcpt_to)
568 .map_or_else(
569 || match is_chatmail {
570 true => constants::DEFAULT_CHATMAIL_MAX_SMTP_RCPT_TO,
571 false => constants::DEFAULT_MAX_SMTP_RCPT_TO,
572 },
573 usize::from,
574 );
575 Ok(val)
576 }
577
578 pub async fn background_fetch(&self) -> Result<()> {
584 if !(self.is_configured().await?) {
585 return Ok(());
586 }
587
588 let address = self.get_primary_self_addr().await?;
589 let time_start = tools::Time::now();
590 info!(self, "background_fetch started fetching {address}.");
591
592 if self.scheduler.is_running().await {
593 self.scheduler.maybe_network().await;
594 self.wait_for_all_work_done().await;
595 } else {
596 let _pause_guard = self.scheduler.pause(self).await?;
599
600 let mut connection = Imap::new_configured(self, channel::bounded(1).1).await?;
602 let mut session = connection.prepare(self).await?;
603
604 for folder_meaning in [FolderMeaning::Inbox, FolderMeaning::Mvbox] {
608 if let Some((_folder_config, watch_folder)) =
609 convert_folder_meaning(self, folder_meaning).await?
610 {
611 connection
612 .fetch_move_delete(self, &mut session, &watch_folder, folder_meaning)
613 .await?;
614 }
615 }
616
617 if self
619 .quota_needs_update(DC_BACKGROUND_FETCH_QUOTA_CHECK_RATELIMIT)
620 .await
621 && let Err(err) = self.update_recent_quota(&mut session).await
622 {
623 warn!(self, "Failed to update quota: {err:#}.");
624 }
625 }
626
627 info!(
628 self,
629 "background_fetch done for {address} took {:?}.",
630 time_elapsed(&time_start),
631 );
632
633 Ok(())
634 }
635
636 #[cfg(feature = "internals")]
640 pub fn sql(&self) -> &Sql {
641 &self.inner.sql
642 }
643
644 pub fn get_dbfile(&self) -> &Path {
646 self.sql.dbfile.as_path()
647 }
648
649 pub fn get_blobdir(&self) -> &Path {
651 self.blobdir.as_path()
652 }
653
654 pub fn emit_event(&self, event: EventType) {
656 {
657 let lock = self.debug_logging.read().expect("RwLock is poisoned");
658 if let Some(debug_logging) = &*lock {
659 debug_logging.log_event(event.clone());
660 }
661 }
662 self.events.emit(Event {
663 id: self.id,
664 typ: event,
665 });
666 }
667
668 pub fn emit_msgs_changed_without_ids(&self) {
670 self.emit_event(EventType::MsgsChanged {
671 chat_id: ChatId::new(0),
672 msg_id: MsgId::new(0),
673 });
674 }
675
676 pub fn emit_msgs_changed(&self, chat_id: ChatId, msg_id: MsgId) {
682 logged_debug_assert!(
683 self,
684 !chat_id.is_unset(),
685 "emit_msgs_changed: chat_id is unset."
686 );
687 logged_debug_assert!(
688 self,
689 !msg_id.is_unset(),
690 "emit_msgs_changed: msg_id is unset."
691 );
692
693 self.emit_event(EventType::MsgsChanged { 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 fn emit_msgs_changed_without_msg_id(&self, chat_id: ChatId) {
700 logged_debug_assert!(
701 self,
702 !chat_id.is_unset(),
703 "emit_msgs_changed_without_msg_id: chat_id is unset."
704 );
705
706 self.emit_event(EventType::MsgsChanged {
707 chat_id,
708 msg_id: MsgId::new(0),
709 });
710 chatlist_events::emit_chatlist_changed(self);
711 chatlist_events::emit_chatlist_item_changed(self, chat_id);
712 }
713
714 pub fn emit_incoming_msg(&self, chat_id: ChatId, msg_id: MsgId) {
716 debug_assert!(!chat_id.is_unset());
717 debug_assert!(!msg_id.is_unset());
718
719 self.emit_event(EventType::IncomingMsg { chat_id, msg_id });
720 chatlist_events::emit_chatlist_changed(self);
721 chatlist_events::emit_chatlist_item_changed(self, chat_id);
722 }
723
724 pub async fn emit_location_changed(&self, contact_id: Option<ContactId>) -> Result<()> {
726 self.emit_event(EventType::LocationChanged(contact_id));
727
728 if let Some(msg_id) = self
729 .get_config_parsed::<u32>(Config::WebxdcIntegration)
730 .await?
731 {
732 self.emit_event(EventType::WebxdcStatusUpdate {
733 msg_id: MsgId::new(msg_id),
734 status_update_serial: Default::default(),
735 })
736 }
737
738 Ok(())
739 }
740
741 pub fn get_event_emitter(&self) -> EventEmitter {
746 self.events.get_emitter()
747 }
748
749 pub fn get_id(&self) -> u32 {
751 self.id
752 }
753
754 pub(crate) async fn alloc_ongoing(&self) -> Result<Receiver<()>> {
764 let mut s = self.running_state.write().await;
765 ensure!(
766 matches!(*s, RunningState::Stopped),
767 "There is already another ongoing process running."
768 );
769
770 let (sender, receiver) = channel::bounded(1);
771 *s = RunningState::Running {
772 cancel_sender: sender,
773 };
774
775 Ok(receiver)
776 }
777
778 pub(crate) async fn free_ongoing(&self) {
779 let mut s = self.running_state.write().await;
780 if let RunningState::ShallStop { request } = *s {
781 info!(self, "Ongoing stopped in {:?}", time_elapsed(&request));
782 }
783 *s = RunningState::Stopped;
784 }
785
786 pub async fn stop_ongoing(&self) {
788 let mut s = self.running_state.write().await;
789 match &*s {
790 RunningState::Running { cancel_sender } => {
791 if let Err(err) = cancel_sender.send(()).await {
792 warn!(self, "could not cancel ongoing: {:#}", err);
793 }
794 info!(self, "Signaling the ongoing process to stop ASAP.",);
795 *s = RunningState::ShallStop {
796 request: tools::Time::now(),
797 };
798 }
799 RunningState::ShallStop { .. } | RunningState::Stopped => {
800 info!(self, "No ongoing process to stop.",);
801 }
802 }
803 }
804
805 #[allow(unused)]
806 pub(crate) async fn shall_stop_ongoing(&self) -> bool {
807 match &*self.running_state.read().await {
808 RunningState::Running { .. } => false,
809 RunningState::ShallStop { .. } | RunningState::Stopped => true,
810 }
811 }
812
813 pub async fn get_info(&self) -> Result<BTreeMap<&'static str, String>> {
819 let l = EnteredLoginParam::load(self).await?;
820 let l2 = ConfiguredLoginParam::load(self).await?.map_or_else(
821 || "Not configured".to_string(),
822 |(_transport_id, param)| param.to_string(),
823 );
824 let secondary_addrs = self.get_secondary_self_addrs().await?.join(", ");
825 let all_transports: Vec<String> = ConfiguredLoginParam::load_all(self)
826 .await?
827 .into_iter()
828 .map(|(transport_id, param)| format!("{transport_id}: {param}"))
829 .collect();
830 let all_transports = if all_transports.is_empty() {
831 "Not configured".to_string()
832 } else {
833 all_transports.join(",")
834 };
835 let chats = get_chat_cnt(self).await?;
836 let unblocked_msgs = message::get_unblocked_msg_cnt(self).await;
837 let request_msgs = message::get_request_msg_cnt(self).await;
838 let contacts = Contact::get_real_cnt(self).await?;
839 let proxy_enabled = self.get_config_int(Config::ProxyEnabled).await?;
840 let dbversion = self
841 .sql
842 .get_raw_config_int("dbversion")
843 .await?
844 .unwrap_or_default();
845 let journal_mode = self
846 .sql
847 .query_get_value("PRAGMA journal_mode;", ())
848 .await?
849 .unwrap_or_else(|| "unknown".to_string());
850 let mdns_enabled = self.get_config_int(Config::MdnsEnabled).await?;
851 let bcc_self = self.get_config_int(Config::BccSelf).await?;
852 let sync_msgs = self.get_config_int(Config::SyncMsgs).await?;
853 let disable_idle = self.get_config_bool(Config::DisableIdle).await?;
854
855 let prv_key_cnt = self.sql.count("SELECT COUNT(*) FROM keypairs;", ()).await?;
856
857 let pub_key_cnt = self
858 .sql
859 .count("SELECT COUNT(*) FROM public_keys;", ())
860 .await?;
861 let fingerprint_str = match self_fingerprint(self).await {
862 Ok(fp) => fp.to_string(),
863 Err(err) => format!("<key failure: {err}>"),
864 };
865
866 let mvbox_move = self.get_config_int(Config::MvboxMove).await?;
867 let only_fetch_mvbox = self.get_config_int(Config::OnlyFetchMvbox).await?;
868 let folders_configured = self
869 .sql
870 .get_raw_config_int(constants::DC_FOLDERS_CONFIGURED_KEY)
871 .await?
872 .unwrap_or_default();
873
874 let configured_inbox_folder = self
875 .get_config(Config::ConfiguredInboxFolder)
876 .await?
877 .unwrap_or_else(|| "<unset>".to_string());
878 let configured_mvbox_folder = self
879 .get_config(Config::ConfiguredMvboxFolder)
880 .await?
881 .unwrap_or_else(|| "<unset>".to_string());
882 let configured_trash_folder = self
883 .get_config(Config::ConfiguredTrashFolder)
884 .await?
885 .unwrap_or_else(|| "<unset>".to_string());
886
887 let mut res = get_info();
888
889 res.insert("bot", self.get_config_int(Config::Bot).await?.to_string());
891 res.insert("number_of_chats", chats.to_string());
892 res.insert("number_of_chat_messages", unblocked_msgs.to_string());
893 res.insert("messages_in_contact_requests", request_msgs.to_string());
894 res.insert("number_of_contacts", contacts.to_string());
895 res.insert("database_dir", self.get_dbfile().display().to_string());
896 res.insert("database_version", dbversion.to_string());
897 res.insert(
898 "database_encrypted",
899 self.sql
900 .is_encrypted()
901 .await
902 .map_or_else(|| "closed".to_string(), |b| b.to_string()),
903 );
904 res.insert("journal_mode", journal_mode);
905 res.insert("blobdir", self.get_blobdir().display().to_string());
906 res.insert(
907 "selfavatar",
908 self.get_config(Config::Selfavatar)
909 .await?
910 .unwrap_or_else(|| "<unset>".to_string()),
911 );
912 res.insert("proxy_enabled", proxy_enabled.to_string());
913 res.insert("entered_account_settings", l.to_string());
914 res.insert("used_account_settings", l2);
915 res.insert("used_transport_settings", all_transports);
916
917 if let Some(server_id) = &*self.server_id.read().await {
918 res.insert("imap_server_id", format!("{server_id:?}"));
919 }
920
921 res.insert("is_chatmail", self.is_chatmail().await?.to_string());
922 res.insert(
923 "fix_is_chatmail",
924 self.get_config_bool(Config::FixIsChatmail)
925 .await?
926 .to_string(),
927 );
928 res.insert(
929 "is_muted",
930 self.get_config_bool(Config::IsMuted).await?.to_string(),
931 );
932 res.insert(
933 "private_tag",
934 self.get_config(Config::PrivateTag)
935 .await?
936 .unwrap_or_else(|| "<unset>".to_string()),
937 );
938
939 if let Some(metadata) = &*self.metadata.read().await {
940 if let Some(comment) = &metadata.comment {
941 res.insert("imap_server_comment", format!("{comment:?}"));
942 }
943
944 if let Some(admin) = &metadata.admin {
945 res.insert("imap_server_admin", format!("{admin:?}"));
946 }
947 }
948
949 res.insert("secondary_addrs", secondary_addrs);
950 res.insert(
951 "fetched_existing_msgs",
952 self.get_config_bool(Config::FetchedExistingMsgs)
953 .await?
954 .to_string(),
955 );
956 res.insert(
957 "show_emails",
958 self.get_config_int(Config::ShowEmails).await?.to_string(),
959 );
960 res.insert(
961 "download_limit",
962 self.get_config_int(Config::DownloadLimit)
963 .await?
964 .to_string(),
965 );
966 res.insert("mvbox_move", mvbox_move.to_string());
967 res.insert("only_fetch_mvbox", only_fetch_mvbox.to_string());
968 res.insert(
969 constants::DC_FOLDERS_CONFIGURED_KEY,
970 folders_configured.to_string(),
971 );
972 res.insert("configured_inbox_folder", configured_inbox_folder);
973 res.insert("configured_mvbox_folder", configured_mvbox_folder);
974 res.insert("configured_trash_folder", configured_trash_folder);
975 res.insert("mdns_enabled", mdns_enabled.to_string());
976 res.insert("bcc_self", bcc_self.to_string());
977 res.insert("sync_msgs", sync_msgs.to_string());
978 res.insert("disable_idle", disable_idle.to_string());
979 res.insert("private_key_count", prv_key_cnt.to_string());
980 res.insert("public_key_count", pub_key_cnt.to_string());
981 res.insert("fingerprint", fingerprint_str);
982 res.insert(
983 "media_quality",
984 self.get_config_int(Config::MediaQuality).await?.to_string(),
985 );
986 res.insert(
987 "delete_device_after",
988 self.get_config_int(Config::DeleteDeviceAfter)
989 .await?
990 .to_string(),
991 );
992 res.insert(
993 "delete_server_after",
994 self.get_config_int(Config::DeleteServerAfter)
995 .await?
996 .to_string(),
997 );
998 res.insert(
999 "delete_to_trash",
1000 self.get_config(Config::DeleteToTrash)
1001 .await?
1002 .unwrap_or_else(|| "<unset>".to_string()),
1003 );
1004 res.insert(
1005 "last_housekeeping",
1006 self.get_config_int(Config::LastHousekeeping)
1007 .await?
1008 .to_string(),
1009 );
1010 res.insert(
1011 "last_cant_decrypt_outgoing_msgs",
1012 self.get_config_int(Config::LastCantDecryptOutgoingMsgs)
1013 .await?
1014 .to_string(),
1015 );
1016 res.insert(
1017 "scan_all_folders_debounce_secs",
1018 self.get_config_int(Config::ScanAllFoldersDebounceSecs)
1019 .await?
1020 .to_string(),
1021 );
1022 res.insert(
1023 "quota_exceeding",
1024 self.get_config_int(Config::QuotaExceeding)
1025 .await?
1026 .to_string(),
1027 );
1028 res.insert(
1029 "authserv_id_candidates",
1030 self.get_config(Config::AuthservIdCandidates)
1031 .await?
1032 .unwrap_or_default(),
1033 );
1034 res.insert(
1035 "sign_unencrypted",
1036 self.get_config_int(Config::SignUnencrypted)
1037 .await?
1038 .to_string(),
1039 );
1040 res.insert(
1041 "debug_logging",
1042 self.get_config_int(Config::DebugLogging).await?.to_string(),
1043 );
1044 res.insert(
1045 "last_msg_id",
1046 self.get_config_int(Config::LastMsgId).await?.to_string(),
1047 );
1048 res.insert(
1049 "gossip_period",
1050 self.get_config_int(Config::GossipPeriod).await?.to_string(),
1051 );
1052 res.insert(
1053 "webxdc_realtime_enabled",
1054 self.get_config_bool(Config::WebxdcRealtimeEnabled)
1055 .await?
1056 .to_string(),
1057 );
1058 res.insert(
1059 "donation_request_next_check",
1060 self.get_config_i64(Config::DonationRequestNextCheck)
1061 .await?
1062 .to_string(),
1063 );
1064 res.insert(
1065 "first_key_contacts_msg_id",
1066 self.sql
1067 .get_raw_config("first_key_contacts_msg_id")
1068 .await?
1069 .unwrap_or_default(),
1070 );
1071 res.insert(
1072 "stats_id",
1073 self.get_config(Config::StatsId)
1074 .await?
1075 .unwrap_or_else(|| "<unset>".to_string()),
1076 );
1077 res.insert(
1078 "stats_sending",
1079 stats::should_send_stats(self).await?.to_string(),
1080 );
1081 res.insert(
1082 "stats_last_sent",
1083 self.get_config_i64(Config::StatsLastSent)
1084 .await?
1085 .to_string(),
1086 );
1087 res.insert(
1088 "test_hooks",
1089 self.sql
1090 .get_raw_config("test_hooks")
1091 .await?
1092 .unwrap_or_default(),
1093 );
1094 res.insert(
1095 "fail_on_receiving_full_msg",
1096 self.sql
1097 .get_raw_config("fail_on_receiving_full_msg")
1098 .await?
1099 .unwrap_or_default(),
1100 );
1101 res.insert(
1102 "std_header_protection_composing",
1103 self.sql
1104 .get_raw_config("std_header_protection_composing")
1105 .await?
1106 .unwrap_or_default(),
1107 );
1108
1109 let elapsed = time_elapsed(&self.creation_time);
1110 res.insert("uptime", duration_to_str(elapsed));
1111
1112 Ok(res)
1113 }
1114
1115 pub async fn get_fresh_msgs(&self) -> Result<Vec<MsgId>> {
1122 let list = self
1123 .sql
1124 .query_map_vec(
1125 concat!(
1126 "SELECT m.id",
1127 " FROM msgs m",
1128 " LEFT JOIN contacts ct",
1129 " ON m.from_id=ct.id",
1130 " LEFT JOIN chats c",
1131 " ON m.chat_id=c.id",
1132 " WHERE m.state=?",
1133 " AND m.hidden=0",
1134 " AND m.chat_id>9",
1135 " AND ct.blocked=0",
1136 " AND c.blocked=0",
1137 " AND NOT(c.muted_until=-1 OR c.muted_until>?)",
1138 " ORDER BY m.timestamp DESC,m.id DESC;"
1139 ),
1140 (MessageState::InFresh, time()),
1141 |row| {
1142 let msg_id: MsgId = row.get(0)?;
1143 Ok(msg_id)
1144 },
1145 )
1146 .await?;
1147 Ok(list)
1148 }
1149
1150 pub async fn get_next_msgs(&self) -> Result<Vec<MsgId>> {
1155 let last_msg_id = match self.get_config(Config::LastMsgId).await? {
1156 Some(s) => MsgId::new(s.parse()?),
1157 None => {
1158 self.sql
1163 .query_row(
1164 "SELECT IFNULL((SELECT MAX(id) - 1 FROM msgs), 0)",
1165 (),
1166 |row| {
1167 let msg_id: MsgId = row.get(0)?;
1168 Ok(msg_id)
1169 },
1170 )
1171 .await?
1172 }
1173 };
1174
1175 let list = self
1176 .sql
1177 .query_map_vec(
1178 "SELECT m.id
1179 FROM msgs m
1180 LEFT JOIN contacts ct
1181 ON m.from_id=ct.id
1182 LEFT JOIN chats c
1183 ON m.chat_id=c.id
1184 WHERE m.id>?
1185 AND m.hidden=0
1186 AND m.chat_id>9
1187 AND ct.blocked=0
1188 AND c.blocked!=1
1189 ORDER BY m.id ASC",
1190 (
1191 last_msg_id.to_u32(), ),
1193 |row| {
1194 let msg_id: MsgId = row.get(0)?;
1195 Ok(msg_id)
1196 },
1197 )
1198 .await?;
1199 Ok(list)
1200 }
1201
1202 pub async fn wait_next_msgs(&self) -> Result<Vec<MsgId>> {
1213 self.new_msgs_notify.notified().await;
1214 let list = self.get_next_msgs().await?;
1215 Ok(list)
1216 }
1217
1218 pub async fn search_msgs(&self, chat_id: Option<ChatId>, query: &str) -> Result<Vec<MsgId>> {
1229 let real_query = query.trim().to_lowercase();
1230 if real_query.is_empty() {
1231 return Ok(Vec::new());
1232 }
1233 let str_like_in_text = format!("%{real_query}%");
1234
1235 let list = if let Some(chat_id) = chat_id {
1236 self.sql
1237 .query_map_vec(
1238 "SELECT m.id AS id
1239 FROM msgs m
1240 LEFT JOIN contacts ct
1241 ON m.from_id=ct.id
1242 WHERE m.chat_id=?
1243 AND m.hidden=0
1244 AND ct.blocked=0
1245 AND IFNULL(txt_normalized, txt) LIKE ?
1246 ORDER BY m.timestamp,m.id;",
1247 (chat_id, str_like_in_text),
1248 |row| {
1249 let msg_id: MsgId = row.get("id")?;
1250 Ok(msg_id)
1251 },
1252 )
1253 .await?
1254 } else {
1255 self.sql
1266 .query_map_vec(
1267 "SELECT m.id AS id
1268 FROM msgs m
1269 LEFT JOIN contacts ct
1270 ON m.from_id=ct.id
1271 LEFT JOIN chats c
1272 ON m.chat_id=c.id
1273 WHERE m.chat_id>9
1274 AND m.hidden=0
1275 AND c.blocked!=1
1276 AND ct.blocked=0
1277 AND IFNULL(txt_normalized, txt) LIKE ?
1278 ORDER BY m.id DESC LIMIT 1000",
1279 (str_like_in_text,),
1280 |row| {
1281 let msg_id: MsgId = row.get("id")?;
1282 Ok(msg_id)
1283 },
1284 )
1285 .await?
1286 };
1287
1288 Ok(list)
1289 }
1290
1291 pub async fn is_inbox(&self, folder_name: &str) -> Result<bool> {
1293 let inbox = self.get_config(Config::ConfiguredInboxFolder).await?;
1294 Ok(inbox.as_deref() == Some(folder_name))
1295 }
1296
1297 pub async fn is_mvbox(&self, folder_name: &str) -> Result<bool> {
1299 let mvbox = self.get_config(Config::ConfiguredMvboxFolder).await?;
1300 Ok(mvbox.as_deref() == Some(folder_name))
1301 }
1302
1303 pub async fn is_trash(&self, folder_name: &str) -> Result<bool> {
1305 let trash = self.get_config(Config::ConfiguredTrashFolder).await?;
1306 Ok(trash.as_deref() == Some(folder_name))
1307 }
1308
1309 pub(crate) async fn should_delete_to_trash(&self) -> Result<bool> {
1310 if let Some(v) = self.get_config_bool_opt(Config::DeleteToTrash).await? {
1311 return Ok(v);
1312 }
1313 if let Some(provider) = self.get_configured_provider().await? {
1314 return Ok(provider.opt.delete_to_trash);
1315 }
1316 Ok(false)
1317 }
1318
1319 pub(crate) async fn get_delete_msgs_target(&self) -> Result<String> {
1322 if !self.should_delete_to_trash().await? {
1323 return Ok("".into());
1324 }
1325 self.get_config(Config::ConfiguredTrashFolder)
1326 .await?
1327 .context("No configured trash folder")
1328 }
1329
1330 pub(crate) fn derive_blobdir(dbfile: &Path) -> PathBuf {
1331 let mut blob_fname = OsString::new();
1332 blob_fname.push(dbfile.file_name().unwrap_or_default());
1333 blob_fname.push("-blobs");
1334 dbfile.with_file_name(blob_fname)
1335 }
1336
1337 pub(crate) fn derive_walfile(dbfile: &Path) -> PathBuf {
1338 let mut wal_fname = OsString::new();
1339 wal_fname.push(dbfile.file_name().unwrap_or_default());
1340 wal_fname.push("-wal");
1341 dbfile.with_file_name(wal_fname)
1342 }
1343}
1344
1345pub fn get_version_str() -> &'static str {
1347 &DC_VERSION_STR
1348}
1349
1350#[cfg(test)]
1351mod context_tests;