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::{Result, bail, ensure};
12use async_channel::{self as channel, Receiver, Sender};
13use pgp::composed::SignedPublicKey;
14use ratelimit::Ratelimit;
15use tokio::sync::{Mutex, Notify, RwLock};
16
17use crate::chat::{ChatId, get_chat_cnt};
18use crate::config::Config;
19use crate::constants::{self, DC_BACKGROUND_FETCH_QUOTA_CHECK_RATELIMIT, DC_VERSION_STR};
20use crate::contact::{Contact, ContactId};
21use crate::debug_logging::DebugLogging;
22use crate::events::{Event, EventEmitter, EventType, Events};
23use crate::imap::{FolderMeaning, Imap, ServerMetadata};
24use crate::key::self_fingerprint;
25use crate::log::warn;
26use crate::logged_debug_assert;
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
40pub use crate::scheduler::connectivity::Connectivity;
41
42#[derive(Clone, Debug)]
67pub struct ContextBuilder {
68 dbfile: PathBuf,
69 id: u32,
70 events: Events,
71 stock_strings: StockStrings,
72 password: Option<String>,
73
74 push_subscriber: Option<PushSubscriber>,
75}
76
77impl ContextBuilder {
78 pub fn new(dbfile: PathBuf) -> Self {
84 ContextBuilder {
85 dbfile,
86 id: rand::random(),
87 events: Events::new(),
88 stock_strings: StockStrings::new(),
89 password: None,
90 push_subscriber: None,
91 }
92 }
93
94 pub fn with_id(mut self, id: u32) -> Self {
104 self.id = id;
105 self
106 }
107
108 pub fn with_events(mut self, events: Events) -> Self {
117 self.events = events;
118 self
119 }
120
121 pub fn with_stock_strings(mut self, stock_strings: StockStrings) -> Self {
132 self.stock_strings = stock_strings;
133 self
134 }
135
136 #[deprecated(since = "TBD")]
144 pub fn with_password(mut self, password: String) -> Self {
145 self.password = Some(password);
146 self
147 }
148
149 pub(crate) fn with_push_subscriber(mut self, push_subscriber: PushSubscriber) -> Self {
151 self.push_subscriber = Some(push_subscriber);
152 self
153 }
154
155 pub async fn build(self) -> Result<Context> {
157 let push_subscriber = self.push_subscriber.unwrap_or_default();
158 let context = Context::new_closed(
159 &self.dbfile,
160 self.id,
161 self.events,
162 self.stock_strings,
163 push_subscriber,
164 )
165 .await?;
166 Ok(context)
167 }
168
169 pub async fn open(self) -> Result<Context> {
173 let password = self.password.clone().unwrap_or_default();
174 let context = self.build().await?;
175 match context.open(password).await? {
176 true => Ok(context),
177 false => bail!("database could not be decrypted, incorrect or missing password"),
178 }
179 }
180}
181
182#[derive(Clone, Debug)]
194pub struct Context {
195 pub(crate) inner: Arc<InnerContext>,
196}
197
198impl Deref for Context {
199 type Target = InnerContext;
200
201 fn deref(&self) -> &Self::Target {
202 &self.inner
203 }
204}
205
206#[derive(Clone, Debug)]
210pub(crate) struct WeakContext {
211 inner: Weak<InnerContext>,
212}
213
214impl WeakContext {
215 pub(crate) fn upgrade(&self) -> Result<Context> {
217 let inner = self
218 .inner
219 .upgrade()
220 .ok_or_else(|| anyhow::anyhow!("Inner struct has been dropped"))?;
221 Ok(Context { inner })
222 }
223}
224
225#[derive(Debug)]
227pub struct InnerContext {
228 pub(crate) blobdir: PathBuf,
230 pub(crate) sql: Sql,
231 pub(crate) smeared_timestamp: SmearedTimestamp,
232 running_state: RwLock<RunningState>,
237 pub(crate) oauth2_mutex: Mutex<()>,
239 pub(crate) wrong_pw_warning_mutex: Mutex<()>,
241 pub(crate) housekeeping_mutex: Mutex<()>,
243
244 pub(crate) fetch_msgs_mutex: Mutex<()>,
251
252 pub(crate) translated_stockstrings: StockStrings,
253 pub(crate) events: Events,
254
255 pub(crate) scheduler: SchedulerState,
256 pub(crate) ratelimit: RwLock<Ratelimit>,
257
258 pub(crate) quota: RwLock<BTreeMap<u32, QuotaInfo>>,
261
262 pub(crate) new_msgs_notify: Notify,
266
267 pub(crate) server_id: RwLock<Option<HashMap<String, String>>>,
271
272 pub(crate) metadata: RwLock<Option<ServerMetadata>>,
274
275 pub(crate) id: u32,
280
281 creation_time: tools::Time,
282
283 pub(crate) last_error: parking_lot::RwLock<String>,
287
288 pub(crate) migration_error: parking_lot::RwLock<Option<String>>,
294
295 pub(crate) debug_logging: std::sync::RwLock<Option<DebugLogging>>,
300
301 pub(crate) push_subscriber: PushSubscriber,
304
305 pub(crate) push_subscribed: AtomicBool,
307
308 pub(crate) tls_session_store: TlsSessionStore,
310
311 pub(crate) iroh: Arc<RwLock<Option<Iroh>>>,
313
314 pub(crate) self_fingerprint: OnceLock<String>,
318
319 pub(crate) self_public_key: Mutex<Option<SignedPublicKey>>,
325
326 pub(crate) connectivities: parking_lot::Mutex<Vec<ConnectivityStore>>,
329
330 #[expect(clippy::type_complexity)]
331 pub(crate) pre_encrypt_mime_hook: parking_lot::Mutex<
333 Option<
334 for<'a> fn(
335 &Context,
336 mail_builder::mime::MimePart<'a>,
337 ) -> mail_builder::mime::MimePart<'a>,
338 >,
339 >,
340}
341
342#[derive(Debug, Default)]
344enum RunningState {
345 Running { cancel_sender: Sender<()> },
347
348 ShallStop { request: tools::Time },
350
351 #[default]
353 Stopped,
354}
355
356#[expect(clippy::arithmetic_side_effects)]
363pub fn get_info() -> BTreeMap<&'static str, String> {
364 let mut res = BTreeMap::new();
365
366 #[cfg(debug_assertions)]
367 res.insert(
368 "debug_assertions",
369 "On - DO NOT RELEASE THIS BUILD".to_string(),
370 );
371 #[cfg(not(debug_assertions))]
372 res.insert("debug_assertions", "Off".to_string());
373
374 res.insert("deltachat_core_version", format!("v{DC_VERSION_STR}"));
375 res.insert("sqlite_version", rusqlite::version().to_string());
376 res.insert("arch", (std::mem::size_of::<usize>() * 8).to_string());
377 res.insert("num_cpus", num_cpus::get().to_string());
378 res.insert("level", "awesome".into());
379 res
380}
381
382impl Context {
383 pub async fn new(
385 dbfile: &Path,
386 id: u32,
387 events: Events,
388 stock_strings: StockStrings,
389 ) -> Result<Context> {
390 let context =
391 Self::new_closed(dbfile, id, events, stock_strings, Default::default()).await?;
392
393 if context.check_passphrase("".to_string()).await? {
395 context.sql.open(&context, "".to_string()).await?;
396 }
397 Ok(context)
398 }
399
400 pub async fn new_closed(
402 dbfile: &Path,
403 id: u32,
404 events: Events,
405 stockstrings: StockStrings,
406 push_subscriber: PushSubscriber,
407 ) -> Result<Context> {
408 let mut blob_fname = OsString::new();
409 blob_fname.push(dbfile.file_name().unwrap_or_default());
410 blob_fname.push("-blobs");
411 let blobdir = dbfile.with_file_name(blob_fname);
412 if !blobdir.exists() {
413 tokio::fs::create_dir_all(&blobdir).await?;
414 }
415 let context = Context::with_blobdir(
416 dbfile.into(),
417 blobdir,
418 id,
419 events,
420 stockstrings,
421 push_subscriber,
422 )?;
423 Ok(context)
424 }
425
426 pub(crate) fn get_weak_context(&self) -> WeakContext {
428 WeakContext {
429 inner: Arc::downgrade(&self.inner),
430 }
431 }
432
433 #[deprecated(since = "TBD")]
440 pub async fn open(&self, passphrase: String) -> Result<bool> {
441 if self.sql.check_passphrase(passphrase.clone()).await? {
442 self.sql.open(self, passphrase).await?;
443 Ok(true)
444 } else {
445 Ok(false)
446 }
447 }
448
449 pub async fn change_passphrase(&self, passphrase: String) -> Result<()> {
452 self.sql.change_passphrase(passphrase).await?;
453 Ok(())
454 }
455
456 pub async fn is_open(&self) -> bool {
458 self.sql.is_open().await
459 }
460
461 pub(crate) async fn check_passphrase(&self, passphrase: String) -> Result<bool> {
467 self.sql.check_passphrase(passphrase).await
468 }
469
470 pub(crate) fn with_blobdir(
471 dbfile: PathBuf,
472 blobdir: PathBuf,
473 id: u32,
474 events: Events,
475 stockstrings: StockStrings,
476 push_subscriber: PushSubscriber,
477 ) -> Result<Context> {
478 ensure!(
479 blobdir.is_dir(),
480 "Blobdir does not exist: {}",
481 blobdir.display()
482 );
483
484 let new_msgs_notify = Notify::new();
485 new_msgs_notify.notify_one();
488
489 let inner = InnerContext {
490 id,
491 blobdir,
492 running_state: RwLock::new(Default::default()),
493 sql: Sql::new(dbfile),
494 smeared_timestamp: SmearedTimestamp::new(),
495 oauth2_mutex: Mutex::new(()),
496 wrong_pw_warning_mutex: Mutex::new(()),
497 housekeeping_mutex: Mutex::new(()),
498 fetch_msgs_mutex: Mutex::new(()),
499 translated_stockstrings: stockstrings,
500 events,
501 scheduler: SchedulerState::new(),
502 ratelimit: RwLock::new(Ratelimit::new(Duration::new(3, 0), 3.0)), quota: RwLock::new(BTreeMap::new()),
504 new_msgs_notify,
505 server_id: RwLock::new(None),
506 metadata: RwLock::new(None),
507 creation_time: tools::Time::now(),
508 last_error: parking_lot::RwLock::new("".to_string()),
509 migration_error: parking_lot::RwLock::new(None),
510 debug_logging: std::sync::RwLock::new(None),
511 push_subscriber,
512 push_subscribed: AtomicBool::new(false),
513 tls_session_store: TlsSessionStore::new(),
514 iroh: Arc::new(RwLock::new(None)),
515 self_fingerprint: OnceLock::new(),
516 self_public_key: Mutex::new(None),
517 connectivities: parking_lot::Mutex::new(Vec::new()),
518 pre_encrypt_mime_hook: None.into(),
519 };
520
521 let ctx = Context {
522 inner: Arc::new(inner),
523 };
524
525 Ok(ctx)
526 }
527
528 pub async fn start_io(&self) {
530 if !self.is_configured().await.unwrap_or_default() {
531 warn!(self, "can not start io on a context that is not configured");
532 return;
533 }
534
535 self.sql.config_cache.write().await.clear();
541
542 self.scheduler.start(self).await;
543 }
544
545 pub async fn stop_io(&self) {
547 self.scheduler.stop(self).await;
548 if let Some(iroh) = self.iroh.write().await.take() {
549 tokio::spawn(async move {
556 let _ = tokio::time::timeout(Duration::from_secs(60), iroh.close()).await;
559 });
560 }
561 }
562
563 pub async fn restart_io_if_running(&self) {
566 self.scheduler.restart(self).await;
567 }
568
569 pub async fn maybe_network(&self) {
571 if let Some(ref iroh) = *self.iroh.read().await {
572 iroh.network_change().await;
573 }
574 self.scheduler.maybe_network().await;
575 }
576
577 pub async fn is_chatmail(&self) -> Result<bool> {
579 self.get_config_bool(Config::IsChatmail).await
580 }
581
582 pub(crate) async fn get_max_smtp_rcpt_to(&self) -> Result<usize> {
584 let is_chatmail = self.is_chatmail().await?;
585 let val = self
586 .get_configured_provider()
587 .await?
588 .and_then(|provider| provider.opt.max_smtp_rcpt_to)
589 .map_or_else(
590 || match is_chatmail {
591 true => constants::DEFAULT_CHATMAIL_MAX_SMTP_RCPT_TO,
592 false => constants::DEFAULT_MAX_SMTP_RCPT_TO,
593 },
594 usize::from,
595 );
596 Ok(val)
597 }
598
599 pub async fn background_fetch(&self) -> Result<()> {
605 if !(self.is_configured().await?) {
606 return Ok(());
607 }
608
609 let address = self.get_primary_self_addr().await?;
610 let time_start = tools::Time::now();
611 info!(self, "background_fetch started fetching {address}.");
612
613 if self.scheduler.is_running().await {
614 self.scheduler.maybe_network().await;
615 self.wait_for_all_work_done().await;
616 } else {
617 let _pause_guard = self.scheduler.pause(self).await?;
620
621 let mut connection = Imap::new_configured(self, channel::bounded(1).1).await?;
623 let mut session = connection.prepare(self).await?;
624
625 for folder_meaning in [FolderMeaning::Inbox, FolderMeaning::Mvbox] {
629 if let Some((_folder_config, watch_folder)) =
630 convert_folder_meaning(self, folder_meaning).await?
631 {
632 connection
633 .fetch_move_delete(self, &mut session, &watch_folder, folder_meaning)
634 .await?;
635 }
636 }
637
638 if self
642 .quota_needs_update(
643 session.transport_id(),
644 DC_BACKGROUND_FETCH_QUOTA_CHECK_RATELIMIT,
645 )
646 .await
647 && let Err(err) = self.update_recent_quota(&mut session).await
648 {
649 warn!(self, "Failed to update quota: {err:#}.");
650 }
651 }
652
653 info!(
654 self,
655 "background_fetch done for {address} took {:?}.",
656 time_elapsed(&time_start),
657 );
658
659 Ok(())
660 }
661
662 #[cfg(feature = "internals")]
666 pub fn sql(&self) -> &Sql {
667 &self.inner.sql
668 }
669
670 pub fn get_dbfile(&self) -> &Path {
672 self.sql.dbfile.as_path()
673 }
674
675 pub fn get_blobdir(&self) -> &Path {
677 self.blobdir.as_path()
678 }
679
680 pub fn emit_event(&self, event: EventType) {
682 {
683 let lock = self.debug_logging.read().expect("RwLock is poisoned");
684 if let Some(debug_logging) = &*lock {
685 debug_logging.log_event(event.clone());
686 }
687 }
688 self.events.emit(Event {
689 id: self.id,
690 typ: event,
691 });
692 }
693
694 pub fn emit_msgs_changed_without_ids(&self) {
696 self.emit_event(EventType::MsgsChanged {
697 chat_id: ChatId::new(0),
698 msg_id: MsgId::new(0),
699 });
700 }
701
702 pub fn emit_msgs_changed(&self, chat_id: ChatId, msg_id: MsgId) {
708 logged_debug_assert!(
709 self,
710 !chat_id.is_unset(),
711 "emit_msgs_changed: chat_id is unset."
712 );
713 logged_debug_assert!(
714 self,
715 !msg_id.is_unset(),
716 "emit_msgs_changed: msg_id is unset."
717 );
718
719 self.emit_event(EventType::MsgsChanged { 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 fn emit_msgs_changed_without_msg_id(&self, chat_id: ChatId) {
726 logged_debug_assert!(
727 self,
728 !chat_id.is_unset(),
729 "emit_msgs_changed_without_msg_id: chat_id is unset."
730 );
731
732 self.emit_event(EventType::MsgsChanged {
733 chat_id,
734 msg_id: MsgId::new(0),
735 });
736 chatlist_events::emit_chatlist_changed(self);
737 chatlist_events::emit_chatlist_item_changed(self, chat_id);
738 }
739
740 pub fn emit_incoming_msg(&self, chat_id: ChatId, msg_id: MsgId) {
742 debug_assert!(!chat_id.is_unset());
743 debug_assert!(!msg_id.is_unset());
744
745 self.emit_event(EventType::IncomingMsg { chat_id, msg_id });
746 chatlist_events::emit_chatlist_changed(self);
747 chatlist_events::emit_chatlist_item_changed(self, chat_id);
748 }
749
750 pub async fn emit_location_changed(&self, contact_id: Option<ContactId>) -> Result<()> {
752 self.emit_event(EventType::LocationChanged(contact_id));
753
754 if let Some(msg_id) = self
755 .get_config_parsed::<u32>(Config::WebxdcIntegration)
756 .await?
757 {
758 self.emit_event(EventType::WebxdcStatusUpdate {
759 msg_id: MsgId::new(msg_id),
760 status_update_serial: Default::default(),
761 })
762 }
763
764 Ok(())
765 }
766
767 pub fn get_event_emitter(&self) -> EventEmitter {
772 self.events.get_emitter()
773 }
774
775 pub fn get_id(&self) -> u32 {
777 self.id
778 }
779
780 pub(crate) async fn alloc_ongoing(&self) -> Result<Receiver<()>> {
790 let mut s = self.running_state.write().await;
791 ensure!(
792 matches!(*s, RunningState::Stopped),
793 "There is already another ongoing process running."
794 );
795
796 let (sender, receiver) = channel::bounded(1);
797 *s = RunningState::Running {
798 cancel_sender: sender,
799 };
800
801 Ok(receiver)
802 }
803
804 pub(crate) async fn free_ongoing(&self) {
805 let mut s = self.running_state.write().await;
806 if let RunningState::ShallStop { request } = *s {
807 info!(self, "Ongoing stopped in {:?}", time_elapsed(&request));
808 }
809 *s = RunningState::Stopped;
810 }
811
812 pub async fn stop_ongoing(&self) {
814 let mut s = self.running_state.write().await;
815 match &*s {
816 RunningState::Running { cancel_sender } => {
817 if let Err(err) = cancel_sender.send(()).await {
818 warn!(self, "could not cancel ongoing: {:#}", err);
819 }
820 info!(self, "Signaling the ongoing process to stop ASAP.",);
821 *s = RunningState::ShallStop {
822 request: tools::Time::now(),
823 };
824 }
825 RunningState::ShallStop { .. } | RunningState::Stopped => {
826 info!(self, "No ongoing process to stop.",);
827 }
828 }
829 }
830
831 #[allow(unused)]
832 pub(crate) async fn shall_stop_ongoing(&self) -> bool {
833 match &*self.running_state.read().await {
834 RunningState::Running { .. } => false,
835 RunningState::ShallStop { .. } | RunningState::Stopped => true,
836 }
837 }
838
839 pub async fn get_info(&self) -> Result<BTreeMap<&'static str, String>> {
845 let secondary_addrs = self.get_secondary_self_addrs().await?.join(", ");
846 let all_transports: Vec<String> = ConfiguredLoginParam::load_all(self)
847 .await?
848 .into_iter()
849 .map(|(transport_id, param)| format!("{transport_id}: {param}"))
850 .collect();
851 let all_transports = if all_transports.is_empty() {
852 "Not configured".to_string()
853 } else {
854 all_transports.join(",")
855 };
856 let chats = get_chat_cnt(self).await?;
857 let unblocked_msgs = message::get_unblocked_msg_cnt(self).await;
858 let request_msgs = message::get_request_msg_cnt(self).await;
859 let contacts = Contact::get_real_cnt(self).await?;
860 let proxy_enabled = self.get_config_int(Config::ProxyEnabled).await?;
861 let dbversion = self
862 .sql
863 .get_raw_config_int("dbversion")
864 .await?
865 .unwrap_or_default();
866 let journal_mode = self
867 .sql
868 .query_get_value("PRAGMA journal_mode;", ())
869 .await?
870 .unwrap_or_else(|| "unknown".to_string());
871 let mdns_enabled = self.get_config_int(Config::MdnsEnabled).await?;
872 let bcc_self = self.get_config_int(Config::BccSelf).await?;
873 let sync_msgs = self.get_config_int(Config::SyncMsgs).await?;
874 let disable_idle = self.get_config_bool(Config::DisableIdle).await?;
875
876 let prv_key_cnt = self.sql.count("SELECT COUNT(*) FROM keypairs;", ()).await?;
877
878 let pub_key_cnt = self
879 .sql
880 .count("SELECT COUNT(*) FROM public_keys;", ())
881 .await?;
882 let fingerprint_str = match self_fingerprint(self).await {
883 Ok(fp) => fp.to_string(),
884 Err(err) => format!("<key failure: {err}>"),
885 };
886
887 let mvbox_move = self.get_config_int(Config::MvboxMove).await?;
888 let only_fetch_mvbox = self.get_config_int(Config::OnlyFetchMvbox).await?;
889 let folders_configured = self
890 .sql
891 .get_raw_config_int(constants::DC_FOLDERS_CONFIGURED_KEY)
892 .await?
893 .unwrap_or_default();
894
895 let configured_inbox_folder = self
896 .get_config(Config::ConfiguredInboxFolder)
897 .await?
898 .unwrap_or_else(|| "<unset>".to_string());
899 let configured_mvbox_folder = self
900 .get_config(Config::ConfiguredMvboxFolder)
901 .await?
902 .unwrap_or_else(|| "<unset>".to_string());
903
904 let mut res = get_info();
905
906 res.insert("bot", self.get_config_int(Config::Bot).await?.to_string());
908 res.insert("number_of_chats", chats.to_string());
909 res.insert("number_of_chat_messages", unblocked_msgs.to_string());
910 res.insert("messages_in_contact_requests", request_msgs.to_string());
911 res.insert("number_of_contacts", contacts.to_string());
912 res.insert("database_dir", self.get_dbfile().display().to_string());
913 res.insert("database_version", dbversion.to_string());
914 res.insert(
915 "database_encrypted",
916 self.sql
917 .is_encrypted()
918 .await
919 .map_or_else(|| "closed".to_string(), |b| b.to_string()),
920 );
921 res.insert("journal_mode", journal_mode);
922 res.insert("blobdir", self.get_blobdir().display().to_string());
923 res.insert(
924 "selfavatar",
925 self.get_config(Config::Selfavatar)
926 .await?
927 .unwrap_or_else(|| "<unset>".to_string()),
928 );
929 res.insert("proxy_enabled", proxy_enabled.to_string());
930 res.insert("used_transport_settings", all_transports);
931
932 if let Some(server_id) = &*self.server_id.read().await {
933 res.insert("imap_server_id", format!("{server_id:?}"));
934 }
935
936 res.insert("is_chatmail", self.is_chatmail().await?.to_string());
937 res.insert(
938 "fix_is_chatmail",
939 self.get_config_bool(Config::FixIsChatmail)
940 .await?
941 .to_string(),
942 );
943 res.insert(
944 "is_muted",
945 self.get_config_bool(Config::IsMuted).await?.to_string(),
946 );
947 res.insert(
948 "private_tag",
949 self.get_config(Config::PrivateTag)
950 .await?
951 .unwrap_or_else(|| "<unset>".to_string()),
952 );
953
954 if let Some(metadata) = &*self.metadata.read().await {
955 if let Some(comment) = &metadata.comment {
956 res.insert("imap_server_comment", format!("{comment:?}"));
957 }
958
959 if let Some(admin) = &metadata.admin {
960 res.insert("imap_server_admin", format!("{admin:?}"));
961 }
962 }
963
964 res.insert("secondary_addrs", secondary_addrs);
965 res.insert(
966 "show_emails",
967 self.get_config_int(Config::ShowEmails).await?.to_string(),
968 );
969 res.insert(
970 "who_can_call_me",
971 self.get_config_int(Config::WhoCanCallMe).await?.to_string(),
972 );
973 res.insert(
974 "download_limit",
975 self.get_config_int(Config::DownloadLimit)
976 .await?
977 .to_string(),
978 );
979 res.insert("mvbox_move", mvbox_move.to_string());
980 res.insert("only_fetch_mvbox", only_fetch_mvbox.to_string());
981 res.insert(
982 constants::DC_FOLDERS_CONFIGURED_KEY,
983 folders_configured.to_string(),
984 );
985 res.insert("configured_inbox_folder", configured_inbox_folder);
986 res.insert("configured_mvbox_folder", configured_mvbox_folder);
987 res.insert("mdns_enabled", mdns_enabled.to_string());
988 res.insert("bcc_self", bcc_self.to_string());
989 res.insert("sync_msgs", sync_msgs.to_string());
990 res.insert("disable_idle", disable_idle.to_string());
991 res.insert("private_key_count", prv_key_cnt.to_string());
992 res.insert("public_key_count", pub_key_cnt.to_string());
993 res.insert("fingerprint", fingerprint_str);
994 res.insert(
995 "media_quality",
996 self.get_config_int(Config::MediaQuality).await?.to_string(),
997 );
998 res.insert(
999 "delete_device_after",
1000 self.get_config_int(Config::DeleteDeviceAfter)
1001 .await?
1002 .to_string(),
1003 );
1004 res.insert(
1005 "delete_server_after",
1006 self.get_config_int(Config::DeleteServerAfter)
1007 .await?
1008 .to_string(),
1009 );
1010 res.insert(
1011 "last_housekeeping",
1012 self.get_config_int(Config::LastHousekeeping)
1013 .await?
1014 .to_string(),
1015 );
1016 res.insert(
1017 "last_cant_decrypt_outgoing_msgs",
1018 self.get_config_int(Config::LastCantDecryptOutgoingMsgs)
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 "std_header_protection_composing",
1096 self.sql
1097 .get_raw_config("std_header_protection_composing")
1098 .await?
1099 .unwrap_or_default(),
1100 );
1101 res.insert(
1102 "team_profile",
1103 self.get_config_bool(Config::TeamProfile).await?.to_string(),
1104 );
1105
1106 let elapsed = time_elapsed(&self.creation_time);
1107 res.insert("uptime", duration_to_str(elapsed));
1108
1109 Ok(res)
1110 }
1111
1112 pub async fn get_fresh_msgs(&self) -> Result<Vec<MsgId>> {
1119 let list = self
1120 .sql
1121 .query_map_vec(
1122 "SELECT m.id
1123FROM msgs m
1124LEFT JOIN contacts ct
1125 ON m.from_id=ct.id
1126LEFT JOIN chats c
1127 ON m.chat_id=c.id
1128WHERE m.state=?
1129AND m.hidden=0
1130AND m.chat_id>9
1131AND ct.blocked=0
1132AND c.blocked=0
1133AND NOT(c.muted_until=-1 OR c.muted_until>?)
1134ORDER BY m.timestamp DESC,m.id DESC",
1135 (MessageState::InFresh, time()),
1136 |row| {
1137 let msg_id: MsgId = row.get(0)?;
1138 Ok(msg_id)
1139 },
1140 )
1141 .await?;
1142 Ok(list)
1143 }
1144
1145 pub async fn get_next_msgs(&self) -> Result<Vec<MsgId>> {
1150 let last_msg_id = match self.get_config(Config::LastMsgId).await? {
1151 Some(s) => MsgId::new(s.parse()?),
1152 None => {
1153 self.sql
1158 .query_row(
1159 "SELECT IFNULL((SELECT MAX(id) - 1 FROM msgs), 0)",
1160 (),
1161 |row| {
1162 let msg_id: MsgId = row.get(0)?;
1163 Ok(msg_id)
1164 },
1165 )
1166 .await?
1167 }
1168 };
1169
1170 let list = self
1171 .sql
1172 .query_map_vec(
1173 "SELECT m.id
1174 FROM msgs m
1175 LEFT JOIN contacts ct
1176 ON m.from_id=ct.id
1177 LEFT JOIN chats c
1178 ON m.chat_id=c.id
1179 WHERE m.id>?
1180 AND m.hidden=0
1181 AND m.chat_id>9
1182 AND ct.blocked=0
1183 AND c.blocked!=1
1184 ORDER BY m.id ASC",
1185 (
1186 last_msg_id.to_u32(), ),
1188 |row| {
1189 let msg_id: MsgId = row.get(0)?;
1190 Ok(msg_id)
1191 },
1192 )
1193 .await?;
1194 Ok(list)
1195 }
1196
1197 pub async fn wait_next_msgs(&self) -> Result<Vec<MsgId>> {
1208 self.new_msgs_notify.notified().await;
1209 let list = self.get_next_msgs().await?;
1210 Ok(list)
1211 }
1212
1213 pub async fn search_msgs(&self, chat_id: Option<ChatId>, query: &str) -> Result<Vec<MsgId>> {
1224 let real_query = query.trim().to_lowercase();
1225 if real_query.is_empty() {
1226 return Ok(Vec::new());
1227 }
1228 let str_like_in_text = format!("%{real_query}%");
1229
1230 let list = if let Some(chat_id) = chat_id {
1231 self.sql
1232 .query_map_vec(
1233 "SELECT m.id AS id
1234 FROM msgs m
1235 LEFT JOIN contacts ct
1236 ON m.from_id=ct.id
1237 WHERE m.chat_id=?
1238 AND m.hidden=0
1239 AND ct.blocked=0
1240 AND IFNULL(txt_normalized, txt) LIKE ?
1241 ORDER BY m.timestamp,m.id;",
1242 (chat_id, str_like_in_text),
1243 |row| {
1244 let msg_id: MsgId = row.get("id")?;
1245 Ok(msg_id)
1246 },
1247 )
1248 .await?
1249 } else {
1250 self.sql
1261 .query_map_vec(
1262 "SELECT m.id AS id
1263 FROM msgs m
1264 LEFT JOIN contacts ct
1265 ON m.from_id=ct.id
1266 LEFT JOIN chats c
1267 ON m.chat_id=c.id
1268 WHERE m.chat_id>9
1269 AND m.hidden=0
1270 AND c.blocked!=1
1271 AND ct.blocked=0
1272 AND IFNULL(txt_normalized, txt) LIKE ?
1273 ORDER BY m.id DESC LIMIT 1000",
1274 (str_like_in_text,),
1275 |row| {
1276 let msg_id: MsgId = row.get("id")?;
1277 Ok(msg_id)
1278 },
1279 )
1280 .await?
1281 };
1282
1283 Ok(list)
1284 }
1285
1286 pub async fn is_mvbox(&self, folder_name: &str) -> Result<bool> {
1288 let mvbox = self.get_config(Config::ConfiguredMvboxFolder).await?;
1289 Ok(mvbox.as_deref() == Some(folder_name))
1290 }
1291
1292 pub(crate) fn derive_blobdir(dbfile: &Path) -> PathBuf {
1293 let mut blob_fname = OsString::new();
1294 blob_fname.push(dbfile.file_name().unwrap_or_default());
1295 blob_fname.push("-blobs");
1296 dbfile.with_file_name(blob_fname)
1297 }
1298
1299 pub(crate) fn derive_walfile(dbfile: &Path) -> PathBuf {
1300 let mut wal_fname = OsString::new();
1301 wal_fname.push(dbfile.file_name().unwrap_or_default());
1302 wal_fname.push("-wal");
1303 dbfile.with_file_name(wal_fname)
1304 }
1305}
1306
1307#[cfg(test)]
1308mod context_tests;