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::message::{self, MessageState, MsgId};
27use crate::net::tls::TlsSessionStore;
28use crate::peer_channels::Iroh;
29use crate::push::PushSubscriber;
30use crate::quota::QuotaInfo;
31use crate::scheduler::{ConnectivityStore, SchedulerState, convert_folder_meaning};
32use crate::sql::Sql;
33use crate::stock_str::StockStrings;
34use crate::timesmearing::SmearedTimestamp;
35use crate::tools::{self, duration_to_str, time, time_elapsed};
36use crate::transport::ConfiguredLoginParam;
37use crate::{chatlist_events, stats};
38
39#[derive(Clone, Debug)]
64pub struct ContextBuilder {
65 dbfile: PathBuf,
66 id: u32,
67 events: Events,
68 stock_strings: StockStrings,
69 password: Option<String>,
70
71 push_subscriber: Option<PushSubscriber>,
72}
73
74impl ContextBuilder {
75 pub fn new(dbfile: PathBuf) -> Self {
81 ContextBuilder {
82 dbfile,
83 id: rand::random(),
84 events: Events::new(),
85 stock_strings: StockStrings::new(),
86 password: None,
87 push_subscriber: None,
88 }
89 }
90
91 pub fn with_id(mut self, id: u32) -> Self {
101 self.id = id;
102 self
103 }
104
105 pub fn with_events(mut self, events: Events) -> Self {
114 self.events = events;
115 self
116 }
117
118 pub fn with_stock_strings(mut self, stock_strings: StockStrings) -> Self {
129 self.stock_strings = stock_strings;
130 self
131 }
132
133 #[deprecated(since = "TBD")]
141 pub fn with_password(mut self, password: String) -> Self {
142 self.password = Some(password);
143 self
144 }
145
146 pub(crate) fn with_push_subscriber(mut self, push_subscriber: PushSubscriber) -> Self {
148 self.push_subscriber = Some(push_subscriber);
149 self
150 }
151
152 pub async fn build(self) -> Result<Context> {
154 let push_subscriber = self.push_subscriber.unwrap_or_default();
155 let context = Context::new_closed(
156 &self.dbfile,
157 self.id,
158 self.events,
159 self.stock_strings,
160 push_subscriber,
161 )
162 .await?;
163 Ok(context)
164 }
165
166 pub async fn open(self) -> Result<Context> {
170 let password = self.password.clone().unwrap_or_default();
171 let context = self.build().await?;
172 match context.open(password).await? {
173 true => Ok(context),
174 false => bail!("database could not be decrypted, incorrect or missing password"),
175 }
176 }
177}
178
179#[derive(Clone, Debug)]
191pub struct Context {
192 pub(crate) inner: Arc<InnerContext>,
193}
194
195impl Deref for Context {
196 type Target = InnerContext;
197
198 fn deref(&self) -> &Self::Target {
199 &self.inner
200 }
201}
202
203#[derive(Clone, Debug)]
207pub(crate) struct WeakContext {
208 inner: Weak<InnerContext>,
209}
210
211impl WeakContext {
212 pub(crate) fn upgrade(&self) -> Result<Context> {
214 let inner = self
215 .inner
216 .upgrade()
217 .ok_or_else(|| anyhow::anyhow!("Inner struct has been dropped"))?;
218 Ok(Context { inner })
219 }
220}
221
222#[derive(Debug)]
224pub struct InnerContext {
225 pub(crate) blobdir: PathBuf,
227 pub(crate) sql: Sql,
228 pub(crate) smeared_timestamp: SmearedTimestamp,
229 running_state: RwLock<RunningState>,
234 pub(crate) generating_key_mutex: Mutex<()>,
236 pub(crate) oauth2_mutex: Mutex<()>,
238 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 pub(crate) quota: RwLock<Option<QuotaInfo>>,
249
250 pub(crate) new_msgs_notify: Notify,
254
255 pub(crate) server_id: RwLock<Option<HashMap<String, String>>>,
259
260 pub(crate) metadata: RwLock<Option<ServerMetadata>>,
262
263 pub(crate) id: u32,
268
269 creation_time: tools::Time,
270
271 pub(crate) last_error: parking_lot::RwLock<String>,
275
276 pub(crate) migration_error: parking_lot::RwLock<Option<String>>,
282
283 pub(crate) debug_logging: std::sync::RwLock<Option<DebugLogging>>,
288
289 pub(crate) push_subscriber: PushSubscriber,
292
293 pub(crate) push_subscribed: AtomicBool,
295
296 pub(crate) tls_session_store: TlsSessionStore,
298
299 pub(crate) iroh: Arc<RwLock<Option<Iroh>>>,
301
302 pub(crate) self_fingerprint: OnceLock<String>,
306
307 pub(crate) connectivities: parking_lot::Mutex<Vec<ConnectivityStore>>,
310
311 #[expect(clippy::type_complexity)]
312 pub(crate) pre_encrypt_mime_hook: parking_lot::Mutex<
314 Option<
315 for<'a> fn(
316 &Context,
317 mail_builder::mime::MimePart<'a>,
318 ) -> mail_builder::mime::MimePart<'a>,
319 >,
320 >,
321}
322
323#[derive(Debug, Default)]
325enum RunningState {
326 Running { cancel_sender: Sender<()> },
328
329 ShallStop { request: tools::Time },
331
332 #[default]
334 Stopped,
335}
336
337pub fn get_info() -> BTreeMap<&'static str, String> {
344 let mut res = BTreeMap::new();
345
346 #[cfg(debug_assertions)]
347 res.insert(
348 "debug_assertions",
349 "On - DO NOT RELEASE THIS BUILD".to_string(),
350 );
351 #[cfg(not(debug_assertions))]
352 res.insert("debug_assertions", "Off".to_string());
353
354 res.insert("deltachat_core_version", format!("v{}", &*DC_VERSION_STR));
355 res.insert("sqlite_version", rusqlite::version().to_string());
356 res.insert("arch", (std::mem::size_of::<usize>() * 8).to_string());
357 res.insert("num_cpus", num_cpus::get().to_string());
358 res.insert("level", "awesome".into());
359 res
360}
361
362impl Context {
363 pub async fn new(
365 dbfile: &Path,
366 id: u32,
367 events: Events,
368 stock_strings: StockStrings,
369 ) -> Result<Context> {
370 let context =
371 Self::new_closed(dbfile, id, events, stock_strings, Default::default()).await?;
372
373 if context.check_passphrase("".to_string()).await? {
375 context.sql.open(&context, "".to_string()).await?;
376 }
377 Ok(context)
378 }
379
380 pub async fn new_closed(
382 dbfile: &Path,
383 id: u32,
384 events: Events,
385 stockstrings: StockStrings,
386 push_subscriber: PushSubscriber,
387 ) -> Result<Context> {
388 let mut blob_fname = OsString::new();
389 blob_fname.push(dbfile.file_name().unwrap_or_default());
390 blob_fname.push("-blobs");
391 let blobdir = dbfile.with_file_name(blob_fname);
392 if !blobdir.exists() {
393 tokio::fs::create_dir_all(&blobdir).await?;
394 }
395 let context = Context::with_blobdir(
396 dbfile.into(),
397 blobdir,
398 id,
399 events,
400 stockstrings,
401 push_subscriber,
402 )?;
403 Ok(context)
404 }
405
406 pub(crate) fn get_weak_context(&self) -> WeakContext {
408 WeakContext {
409 inner: Arc::downgrade(&self.inner),
410 }
411 }
412
413 #[deprecated(since = "TBD")]
420 pub async fn open(&self, passphrase: String) -> Result<bool> {
421 if self.sql.check_passphrase(passphrase.clone()).await? {
422 self.sql.open(self, passphrase).await?;
423 Ok(true)
424 } else {
425 Ok(false)
426 }
427 }
428
429 pub async fn change_passphrase(&self, passphrase: String) -> Result<()> {
432 self.sql.change_passphrase(passphrase).await?;
433 Ok(())
434 }
435
436 pub async fn is_open(&self) -> bool {
438 self.sql.is_open().await
439 }
440
441 pub(crate) async fn check_passphrase(&self, passphrase: String) -> Result<bool> {
447 self.sql.check_passphrase(passphrase).await
448 }
449
450 pub(crate) fn with_blobdir(
451 dbfile: PathBuf,
452 blobdir: PathBuf,
453 id: u32,
454 events: Events,
455 stockstrings: StockStrings,
456 push_subscriber: PushSubscriber,
457 ) -> Result<Context> {
458 ensure!(
459 blobdir.is_dir(),
460 "Blobdir does not exist: {}",
461 blobdir.display()
462 );
463
464 let new_msgs_notify = Notify::new();
465 new_msgs_notify.notify_one();
468
469 let inner = InnerContext {
470 id,
471 blobdir,
472 running_state: RwLock::new(Default::default()),
473 sql: Sql::new(dbfile),
474 smeared_timestamp: SmearedTimestamp::new(),
475 generating_key_mutex: Mutex::new(()),
476 oauth2_mutex: Mutex::new(()),
477 wrong_pw_warning_mutex: Mutex::new(()),
478 translated_stockstrings: stockstrings,
479 events,
480 scheduler: SchedulerState::new(),
481 ratelimit: RwLock::new(Ratelimit::new(Duration::new(3, 0), 3.0)), quota: RwLock::new(None),
483 new_msgs_notify,
484 server_id: RwLock::new(None),
485 metadata: RwLock::new(None),
486 creation_time: tools::Time::now(),
487 last_error: parking_lot::RwLock::new("".to_string()),
488 migration_error: parking_lot::RwLock::new(None),
489 debug_logging: std::sync::RwLock::new(None),
490 push_subscriber,
491 push_subscribed: AtomicBool::new(false),
492 tls_session_store: TlsSessionStore::new(),
493 iroh: Arc::new(RwLock::new(None)),
494 self_fingerprint: OnceLock::new(),
495 connectivities: parking_lot::Mutex::new(Vec::new()),
496 pre_encrypt_mime_hook: None.into(),
497 };
498
499 let ctx = Context {
500 inner: Arc::new(inner),
501 };
502
503 Ok(ctx)
504 }
505
506 pub async fn start_io(&self) {
508 if !self.is_configured().await.unwrap_or_default() {
509 warn!(self, "can not start io on a context that is not configured");
510 return;
511 }
512
513 self.sql.config_cache.write().await.clear();
519
520 self.scheduler.start(self).await;
521 }
522
523 pub async fn stop_io(&self) {
525 self.scheduler.stop(self).await;
526 if let Some(iroh) = self.iroh.write().await.take() {
527 tokio::spawn(async move {
534 let _ = tokio::time::timeout(Duration::from_secs(60), iroh.close()).await;
537 });
538 }
539 }
540
541 pub async fn restart_io_if_running(&self) {
544 self.scheduler.restart(self).await;
545 }
546
547 pub async fn maybe_network(&self) {
549 if let Some(ref iroh) = *self.iroh.read().await {
550 iroh.network_change().await;
551 }
552 self.scheduler.maybe_network().await;
553 }
554
555 pub async fn is_chatmail(&self) -> Result<bool> {
557 self.get_config_bool(Config::IsChatmail).await
558 }
559
560 pub(crate) async fn get_max_smtp_rcpt_to(&self) -> Result<usize> {
562 let is_chatmail = self.is_chatmail().await?;
563 let val = self
564 .get_configured_provider()
565 .await?
566 .and_then(|provider| provider.opt.max_smtp_rcpt_to)
567 .map_or_else(
568 || match is_chatmail {
569 true => constants::DEFAULT_CHATMAIL_MAX_SMTP_RCPT_TO,
570 false => constants::DEFAULT_MAX_SMTP_RCPT_TO,
571 },
572 usize::from,
573 );
574 Ok(val)
575 }
576
577 pub async fn background_fetch(&self) -> Result<()> {
583 if !(self.is_configured().await?) {
584 return Ok(());
585 }
586
587 let address = self.get_primary_self_addr().await?;
588 let time_start = tools::Time::now();
589 info!(self, "background_fetch started fetching {address}.");
590
591 if self.scheduler.is_running().await {
592 self.scheduler.maybe_network().await;
593 self.wait_for_all_work_done().await;
594 } else {
595 let _pause_guard = self.scheduler.pause(self).await?;
598
599 let mut connection = Imap::new_configured(self, channel::bounded(1).1).await?;
601 let mut session = connection.prepare(self).await?;
602
603 for folder_meaning in [FolderMeaning::Inbox, FolderMeaning::Mvbox] {
607 if let Some((_folder_config, watch_folder)) =
608 convert_folder_meaning(self, folder_meaning).await?
609 {
610 connection
611 .fetch_move_delete(self, &mut session, &watch_folder, folder_meaning)
612 .await?;
613 }
614 }
615
616 if self
618 .quota_needs_update(DC_BACKGROUND_FETCH_QUOTA_CHECK_RATELIMIT)
619 .await
620 && let Err(err) = self.update_recent_quota(&mut session).await
621 {
622 warn!(self, "Failed to update quota: {err:#}.");
623 }
624 }
625
626 info!(
627 self,
628 "background_fetch done for {address} took {:?}.",
629 time_elapsed(&time_start),
630 );
631
632 Ok(())
633 }
634
635 #[cfg(feature = "internals")]
639 pub fn sql(&self) -> &Sql {
640 &self.inner.sql
641 }
642
643 pub fn get_dbfile(&self) -> &Path {
645 self.sql.dbfile.as_path()
646 }
647
648 pub fn get_blobdir(&self) -> &Path {
650 self.blobdir.as_path()
651 }
652
653 pub fn emit_event(&self, event: EventType) {
655 {
656 let lock = self.debug_logging.read().expect("RwLock is poisoned");
657 if let Some(debug_logging) = &*lock {
658 debug_logging.log_event(event.clone());
659 }
660 }
661 self.events.emit(Event {
662 id: self.id,
663 typ: event,
664 });
665 }
666
667 pub fn emit_msgs_changed_without_ids(&self) {
669 self.emit_event(EventType::MsgsChanged {
670 chat_id: ChatId::new(0),
671 msg_id: MsgId::new(0),
672 });
673 }
674
675 pub fn emit_msgs_changed(&self, chat_id: ChatId, msg_id: MsgId) {
681 logged_debug_assert!(
682 self,
683 !chat_id.is_unset(),
684 "emit_msgs_changed: chat_id is unset."
685 );
686 logged_debug_assert!(
687 self,
688 !msg_id.is_unset(),
689 "emit_msgs_changed: msg_id is unset."
690 );
691
692 self.emit_event(EventType::MsgsChanged { chat_id, msg_id });
693 chatlist_events::emit_chatlist_changed(self);
694 chatlist_events::emit_chatlist_item_changed(self, chat_id);
695 }
696
697 pub fn emit_msgs_changed_without_msg_id(&self, chat_id: ChatId) {
699 logged_debug_assert!(
700 self,
701 !chat_id.is_unset(),
702 "emit_msgs_changed_without_msg_id: chat_id is unset."
703 );
704
705 self.emit_event(EventType::MsgsChanged {
706 chat_id,
707 msg_id: MsgId::new(0),
708 });
709 chatlist_events::emit_chatlist_changed(self);
710 chatlist_events::emit_chatlist_item_changed(self, chat_id);
711 }
712
713 pub fn emit_incoming_msg(&self, chat_id: ChatId, msg_id: MsgId) {
715 debug_assert!(!chat_id.is_unset());
716 debug_assert!(!msg_id.is_unset());
717
718 self.emit_event(EventType::IncomingMsg { chat_id, msg_id });
719 chatlist_events::emit_chatlist_changed(self);
720 chatlist_events::emit_chatlist_item_changed(self, chat_id);
721 }
722
723 pub async fn emit_location_changed(&self, contact_id: Option<ContactId>) -> Result<()> {
725 self.emit_event(EventType::LocationChanged(contact_id));
726
727 if let Some(msg_id) = self
728 .get_config_parsed::<u32>(Config::WebxdcIntegration)
729 .await?
730 {
731 self.emit_event(EventType::WebxdcStatusUpdate {
732 msg_id: MsgId::new(msg_id),
733 status_update_serial: Default::default(),
734 })
735 }
736
737 Ok(())
738 }
739
740 pub fn get_event_emitter(&self) -> EventEmitter {
745 self.events.get_emitter()
746 }
747
748 pub fn get_id(&self) -> u32 {
750 self.id
751 }
752
753 pub(crate) async fn alloc_ongoing(&self) -> Result<Receiver<()>> {
763 let mut s = self.running_state.write().await;
764 ensure!(
765 matches!(*s, RunningState::Stopped),
766 "There is already another ongoing process running."
767 );
768
769 let (sender, receiver) = channel::bounded(1);
770 *s = RunningState::Running {
771 cancel_sender: sender,
772 };
773
774 Ok(receiver)
775 }
776
777 pub(crate) async fn free_ongoing(&self) {
778 let mut s = self.running_state.write().await;
779 if let RunningState::ShallStop { request } = *s {
780 info!(self, "Ongoing stopped in {:?}", time_elapsed(&request));
781 }
782 *s = RunningState::Stopped;
783 }
784
785 pub async fn stop_ongoing(&self) {
787 let mut s = self.running_state.write().await;
788 match &*s {
789 RunningState::Running { cancel_sender } => {
790 if let Err(err) = cancel_sender.send(()).await {
791 warn!(self, "could not cancel ongoing: {:#}", err);
792 }
793 info!(self, "Signaling the ongoing process to stop ASAP.",);
794 *s = RunningState::ShallStop {
795 request: tools::Time::now(),
796 };
797 }
798 RunningState::ShallStop { .. } | RunningState::Stopped => {
799 info!(self, "No ongoing process to stop.",);
800 }
801 }
802 }
803
804 #[allow(unused)]
805 pub(crate) async fn shall_stop_ongoing(&self) -> bool {
806 match &*self.running_state.read().await {
807 RunningState::Running { .. } => false,
808 RunningState::ShallStop { .. } | RunningState::Stopped => true,
809 }
810 }
811
812 pub async fn get_info(&self) -> Result<BTreeMap<&'static str, String>> {
818 let secondary_addrs = self.get_secondary_self_addrs().await?.join(", ");
819 let all_transports: Vec<String> = ConfiguredLoginParam::load_all(self)
820 .await?
821 .into_iter()
822 .map(|(transport_id, param)| format!("{transport_id}: {param}"))
823 .collect();
824 let all_transports = if all_transports.is_empty() {
825 "Not configured".to_string()
826 } else {
827 all_transports.join(",")
828 };
829 let chats = get_chat_cnt(self).await?;
830 let unblocked_msgs = message::get_unblocked_msg_cnt(self).await;
831 let request_msgs = message::get_request_msg_cnt(self).await;
832 let contacts = Contact::get_real_cnt(self).await?;
833 let proxy_enabled = self.get_config_int(Config::ProxyEnabled).await?;
834 let dbversion = self
835 .sql
836 .get_raw_config_int("dbversion")
837 .await?
838 .unwrap_or_default();
839 let journal_mode = self
840 .sql
841 .query_get_value("PRAGMA journal_mode;", ())
842 .await?
843 .unwrap_or_else(|| "unknown".to_string());
844 let mdns_enabled = self.get_config_int(Config::MdnsEnabled).await?;
845 let bcc_self = self.get_config_int(Config::BccSelf).await?;
846 let sync_msgs = self.get_config_int(Config::SyncMsgs).await?;
847 let disable_idle = self.get_config_bool(Config::DisableIdle).await?;
848
849 let prv_key_cnt = self.sql.count("SELECT COUNT(*) FROM keypairs;", ()).await?;
850
851 let pub_key_cnt = self
852 .sql
853 .count("SELECT COUNT(*) FROM public_keys;", ())
854 .await?;
855 let fingerprint_str = match self_fingerprint(self).await {
856 Ok(fp) => fp.to_string(),
857 Err(err) => format!("<key failure: {err}>"),
858 };
859
860 let mvbox_move = self.get_config_int(Config::MvboxMove).await?;
861 let only_fetch_mvbox = self.get_config_int(Config::OnlyFetchMvbox).await?;
862 let folders_configured = self
863 .sql
864 .get_raw_config_int(constants::DC_FOLDERS_CONFIGURED_KEY)
865 .await?
866 .unwrap_or_default();
867
868 let configured_inbox_folder = self
869 .get_config(Config::ConfiguredInboxFolder)
870 .await?
871 .unwrap_or_else(|| "<unset>".to_string());
872 let configured_mvbox_folder = self
873 .get_config(Config::ConfiguredMvboxFolder)
874 .await?
875 .unwrap_or_else(|| "<unset>".to_string());
876 let configured_trash_folder = self
877 .get_config(Config::ConfiguredTrashFolder)
878 .await?
879 .unwrap_or_else(|| "<unset>".to_string());
880
881 let mut res = get_info();
882
883 res.insert("bot", self.get_config_int(Config::Bot).await?.to_string());
885 res.insert("number_of_chats", chats.to_string());
886 res.insert("number_of_chat_messages", unblocked_msgs.to_string());
887 res.insert("messages_in_contact_requests", request_msgs.to_string());
888 res.insert("number_of_contacts", contacts.to_string());
889 res.insert("database_dir", self.get_dbfile().display().to_string());
890 res.insert("database_version", dbversion.to_string());
891 res.insert(
892 "database_encrypted",
893 self.sql
894 .is_encrypted()
895 .await
896 .map_or_else(|| "closed".to_string(), |b| b.to_string()),
897 );
898 res.insert("journal_mode", journal_mode);
899 res.insert("blobdir", self.get_blobdir().display().to_string());
900 res.insert(
901 "selfavatar",
902 self.get_config(Config::Selfavatar)
903 .await?
904 .unwrap_or_else(|| "<unset>".to_string()),
905 );
906 res.insert("proxy_enabled", proxy_enabled.to_string());
907 res.insert("used_transport_settings", all_transports);
908
909 if let Some(server_id) = &*self.server_id.read().await {
910 res.insert("imap_server_id", format!("{server_id:?}"));
911 }
912
913 res.insert("is_chatmail", self.is_chatmail().await?.to_string());
914 res.insert(
915 "fix_is_chatmail",
916 self.get_config_bool(Config::FixIsChatmail)
917 .await?
918 .to_string(),
919 );
920 res.insert(
921 "is_muted",
922 self.get_config_bool(Config::IsMuted).await?.to_string(),
923 );
924 res.insert(
925 "private_tag",
926 self.get_config(Config::PrivateTag)
927 .await?
928 .unwrap_or_else(|| "<unset>".to_string()),
929 );
930
931 if let Some(metadata) = &*self.metadata.read().await {
932 if let Some(comment) = &metadata.comment {
933 res.insert("imap_server_comment", format!("{comment:?}"));
934 }
935
936 if let Some(admin) = &metadata.admin {
937 res.insert("imap_server_admin", format!("{admin:?}"));
938 }
939 }
940
941 res.insert("secondary_addrs", secondary_addrs);
942 res.insert(
943 "fetched_existing_msgs",
944 self.get_config_bool(Config::FetchedExistingMsgs)
945 .await?
946 .to_string(),
947 );
948 res.insert(
949 "show_emails",
950 self.get_config_int(Config::ShowEmails).await?.to_string(),
951 );
952 res.insert(
953 "download_limit",
954 self.get_config_int(Config::DownloadLimit)
955 .await?
956 .to_string(),
957 );
958 res.insert("mvbox_move", mvbox_move.to_string());
959 res.insert("only_fetch_mvbox", only_fetch_mvbox.to_string());
960 res.insert(
961 constants::DC_FOLDERS_CONFIGURED_KEY,
962 folders_configured.to_string(),
963 );
964 res.insert("configured_inbox_folder", configured_inbox_folder);
965 res.insert("configured_mvbox_folder", configured_mvbox_folder);
966 res.insert("configured_trash_folder", configured_trash_folder);
967 res.insert("mdns_enabled", mdns_enabled.to_string());
968 res.insert("bcc_self", bcc_self.to_string());
969 res.insert("sync_msgs", sync_msgs.to_string());
970 res.insert("disable_idle", disable_idle.to_string());
971 res.insert("private_key_count", prv_key_cnt.to_string());
972 res.insert("public_key_count", pub_key_cnt.to_string());
973 res.insert("fingerprint", fingerprint_str);
974 res.insert(
975 "media_quality",
976 self.get_config_int(Config::MediaQuality).await?.to_string(),
977 );
978 res.insert(
979 "delete_device_after",
980 self.get_config_int(Config::DeleteDeviceAfter)
981 .await?
982 .to_string(),
983 );
984 res.insert(
985 "delete_server_after",
986 self.get_config_int(Config::DeleteServerAfter)
987 .await?
988 .to_string(),
989 );
990 res.insert(
991 "delete_to_trash",
992 self.get_config(Config::DeleteToTrash)
993 .await?
994 .unwrap_or_else(|| "<unset>".to_string()),
995 );
996 res.insert(
997 "last_housekeeping",
998 self.get_config_int(Config::LastHousekeeping)
999 .await?
1000 .to_string(),
1001 );
1002 res.insert(
1003 "last_cant_decrypt_outgoing_msgs",
1004 self.get_config_int(Config::LastCantDecryptOutgoingMsgs)
1005 .await?
1006 .to_string(),
1007 );
1008 res.insert(
1009 "scan_all_folders_debounce_secs",
1010 self.get_config_int(Config::ScanAllFoldersDebounceSecs)
1011 .await?
1012 .to_string(),
1013 );
1014 res.insert(
1015 "quota_exceeding",
1016 self.get_config_int(Config::QuotaExceeding)
1017 .await?
1018 .to_string(),
1019 );
1020 res.insert(
1021 "authserv_id_candidates",
1022 self.get_config(Config::AuthservIdCandidates)
1023 .await?
1024 .unwrap_or_default(),
1025 );
1026 res.insert(
1027 "sign_unencrypted",
1028 self.get_config_int(Config::SignUnencrypted)
1029 .await?
1030 .to_string(),
1031 );
1032 res.insert(
1033 "debug_logging",
1034 self.get_config_int(Config::DebugLogging).await?.to_string(),
1035 );
1036 res.insert(
1037 "last_msg_id",
1038 self.get_config_int(Config::LastMsgId).await?.to_string(),
1039 );
1040 res.insert(
1041 "gossip_period",
1042 self.get_config_int(Config::GossipPeriod).await?.to_string(),
1043 );
1044 res.insert(
1045 "webxdc_realtime_enabled",
1046 self.get_config_bool(Config::WebxdcRealtimeEnabled)
1047 .await?
1048 .to_string(),
1049 );
1050 res.insert(
1051 "donation_request_next_check",
1052 self.get_config_i64(Config::DonationRequestNextCheck)
1053 .await?
1054 .to_string(),
1055 );
1056 res.insert(
1057 "first_key_contacts_msg_id",
1058 self.sql
1059 .get_raw_config("first_key_contacts_msg_id")
1060 .await?
1061 .unwrap_or_default(),
1062 );
1063 res.insert(
1064 "stats_id",
1065 self.get_config(Config::StatsId)
1066 .await?
1067 .unwrap_or_else(|| "<unset>".to_string()),
1068 );
1069 res.insert(
1070 "stats_sending",
1071 stats::should_send_stats(self).await?.to_string(),
1072 );
1073 res.insert(
1074 "stats_last_sent",
1075 self.get_config_i64(Config::StatsLastSent)
1076 .await?
1077 .to_string(),
1078 );
1079 res.insert(
1080 "test_hooks",
1081 self.sql
1082 .get_raw_config("test_hooks")
1083 .await?
1084 .unwrap_or_default(),
1085 );
1086 res.insert(
1087 "fail_on_receiving_full_msg",
1088 self.sql
1089 .get_raw_config("fail_on_receiving_full_msg")
1090 .await?
1091 .unwrap_or_default(),
1092 );
1093 res.insert(
1094 "std_header_protection_composing",
1095 self.sql
1096 .get_raw_config("std_header_protection_composing")
1097 .await?
1098 .unwrap_or_default(),
1099 );
1100
1101 let elapsed = time_elapsed(&self.creation_time);
1102 res.insert("uptime", duration_to_str(elapsed));
1103
1104 Ok(res)
1105 }
1106
1107 pub async fn get_fresh_msgs(&self) -> Result<Vec<MsgId>> {
1114 let list = self
1115 .sql
1116 .query_map_vec(
1117 concat!(
1118 "SELECT m.id",
1119 " FROM msgs m",
1120 " LEFT JOIN contacts ct",
1121 " ON m.from_id=ct.id",
1122 " LEFT JOIN chats c",
1123 " ON m.chat_id=c.id",
1124 " WHERE m.state=?",
1125 " AND m.hidden=0",
1126 " AND m.chat_id>9",
1127 " AND ct.blocked=0",
1128 " AND c.blocked=0",
1129 " AND NOT(c.muted_until=-1 OR c.muted_until>?)",
1130 " ORDER BY m.timestamp DESC,m.id DESC;"
1131 ),
1132 (MessageState::InFresh, time()),
1133 |row| {
1134 let msg_id: MsgId = row.get(0)?;
1135 Ok(msg_id)
1136 },
1137 )
1138 .await?;
1139 Ok(list)
1140 }
1141
1142 pub async fn get_next_msgs(&self) -> Result<Vec<MsgId>> {
1147 let last_msg_id = match self.get_config(Config::LastMsgId).await? {
1148 Some(s) => MsgId::new(s.parse()?),
1149 None => {
1150 self.sql
1155 .query_row(
1156 "SELECT IFNULL((SELECT MAX(id) - 1 FROM msgs), 0)",
1157 (),
1158 |row| {
1159 let msg_id: MsgId = row.get(0)?;
1160 Ok(msg_id)
1161 },
1162 )
1163 .await?
1164 }
1165 };
1166
1167 let list = self
1168 .sql
1169 .query_map_vec(
1170 "SELECT m.id
1171 FROM msgs m
1172 LEFT JOIN contacts ct
1173 ON m.from_id=ct.id
1174 LEFT JOIN chats c
1175 ON m.chat_id=c.id
1176 WHERE m.id>?
1177 AND m.hidden=0
1178 AND m.chat_id>9
1179 AND ct.blocked=0
1180 AND c.blocked!=1
1181 ORDER BY m.id ASC",
1182 (
1183 last_msg_id.to_u32(), ),
1185 |row| {
1186 let msg_id: MsgId = row.get(0)?;
1187 Ok(msg_id)
1188 },
1189 )
1190 .await?;
1191 Ok(list)
1192 }
1193
1194 pub async fn wait_next_msgs(&self) -> Result<Vec<MsgId>> {
1205 self.new_msgs_notify.notified().await;
1206 let list = self.get_next_msgs().await?;
1207 Ok(list)
1208 }
1209
1210 pub async fn search_msgs(&self, chat_id: Option<ChatId>, query: &str) -> Result<Vec<MsgId>> {
1221 let real_query = query.trim().to_lowercase();
1222 if real_query.is_empty() {
1223 return Ok(Vec::new());
1224 }
1225 let str_like_in_text = format!("%{real_query}%");
1226
1227 let list = if let Some(chat_id) = chat_id {
1228 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 WHERE m.chat_id=?
1235 AND m.hidden=0
1236 AND ct.blocked=0
1237 AND IFNULL(txt_normalized, txt) LIKE ?
1238 ORDER BY m.timestamp,m.id;",
1239 (chat_id, str_like_in_text),
1240 |row| {
1241 let msg_id: MsgId = row.get("id")?;
1242 Ok(msg_id)
1243 },
1244 )
1245 .await?
1246 } else {
1247 self.sql
1258 .query_map_vec(
1259 "SELECT m.id AS id
1260 FROM msgs m
1261 LEFT JOIN contacts ct
1262 ON m.from_id=ct.id
1263 LEFT JOIN chats c
1264 ON m.chat_id=c.id
1265 WHERE m.chat_id>9
1266 AND m.hidden=0
1267 AND c.blocked!=1
1268 AND ct.blocked=0
1269 AND IFNULL(txt_normalized, txt) LIKE ?
1270 ORDER BY m.id DESC LIMIT 1000",
1271 (str_like_in_text,),
1272 |row| {
1273 let msg_id: MsgId = row.get("id")?;
1274 Ok(msg_id)
1275 },
1276 )
1277 .await?
1278 };
1279
1280 Ok(list)
1281 }
1282
1283 pub async fn is_inbox(&self, folder_name: &str) -> Result<bool> {
1285 let inbox = self.get_config(Config::ConfiguredInboxFolder).await?;
1286 Ok(inbox.as_deref() == Some(folder_name))
1287 }
1288
1289 pub async fn is_mvbox(&self, folder_name: &str) -> Result<bool> {
1291 let mvbox = self.get_config(Config::ConfiguredMvboxFolder).await?;
1292 Ok(mvbox.as_deref() == Some(folder_name))
1293 }
1294
1295 pub async fn is_trash(&self, folder_name: &str) -> Result<bool> {
1297 let trash = self.get_config(Config::ConfiguredTrashFolder).await?;
1298 Ok(trash.as_deref() == Some(folder_name))
1299 }
1300
1301 pub(crate) async fn should_delete_to_trash(&self) -> Result<bool> {
1302 if let Some(v) = self.get_config_bool_opt(Config::DeleteToTrash).await? {
1303 return Ok(v);
1304 }
1305 if let Some(provider) = self.get_configured_provider().await? {
1306 return Ok(provider.opt.delete_to_trash);
1307 }
1308 Ok(false)
1309 }
1310
1311 pub(crate) async fn get_delete_msgs_target(&self) -> Result<String> {
1314 if !self.should_delete_to_trash().await? {
1315 return Ok("".into());
1316 }
1317 self.get_config(Config::ConfiguredTrashFolder)
1318 .await?
1319 .context("No configured trash folder")
1320 }
1321
1322 pub(crate) fn derive_blobdir(dbfile: &Path) -> PathBuf {
1323 let mut blob_fname = OsString::new();
1324 blob_fname.push(dbfile.file_name().unwrap_or_default());
1325 blob_fname.push("-blobs");
1326 dbfile.with_file_name(blob_fname)
1327 }
1328
1329 pub(crate) fn derive_walfile(dbfile: &Path) -> PathBuf {
1330 let mut wal_fname = OsString::new();
1331 wal_fname.push(dbfile.file_name().unwrap_or_default());
1332 wal_fname.push("-wal");
1333 dbfile.with_file_name(wal_fname)
1334 }
1335}
1336
1337pub fn get_version_str() -> &'static str {
1339 &DC_VERSION_STR
1340}
1341
1342#[cfg(test)]
1343mod context_tests;