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