1use std::collections::{BTreeMap, HashMap};
4use std::ffi::OsString;
5use std::ops::Deref;
6use std::path::{Path, PathBuf};
7use std::sync::atomic::AtomicBool;
8use std::sync::{Arc, OnceLock};
9use std::time::Duration;
10
11use anyhow::{Context as _, Result, bail, ensure};
12use async_channel::{self as channel, Receiver, Sender};
13use ratelimit::Ratelimit;
14use tokio::sync::{Mutex, Notify, RwLock};
15
16use crate::chat::{ChatId, get_chat_cnt};
17use crate::config::Config;
18use crate::constants::{self, DC_BACKGROUND_FETCH_QUOTA_CHECK_RATELIMIT, DC_VERSION_STR};
19use crate::contact::{Contact, ContactId};
20use crate::debug_logging::DebugLogging;
21use crate::events::{Event, EventEmitter, EventType, Events};
22use crate::imap::{FolderMeaning, Imap, ServerMetadata};
23use crate::key::self_fingerprint;
24use crate::log::warn;
25use crate::logged_debug_assert;
26use crate::login_param::EnteredLoginParam;
27use crate::message::{self, MessageState, MsgId};
28use crate::net::tls::TlsSessionStore;
29use crate::peer_channels::Iroh;
30use crate::push::PushSubscriber;
31use crate::quota::QuotaInfo;
32use crate::scheduler::{ConnectivityStore, SchedulerState, convert_folder_meaning};
33use crate::sql::Sql;
34use crate::stock_str::StockStrings;
35use crate::timesmearing::SmearedTimestamp;
36use crate::tools::{self, duration_to_str, time, time_elapsed};
37use crate::transport::ConfiguredLoginParam;
38use crate::{chatlist_events, stats};
39
40#[derive(Clone, Debug)]
83pub struct ContextBuilder {
84 dbfile: PathBuf,
85 id: u32,
86 events: Events,
87 stock_strings: StockStrings,
88 password: Option<String>,
89
90 push_subscriber: Option<PushSubscriber>,
91}
92
93impl ContextBuilder {
94 pub fn new(dbfile: PathBuf) -> Self {
100 ContextBuilder {
101 dbfile,
102 id: rand::random(),
103 events: Events::new(),
104 stock_strings: StockStrings::new(),
105 password: None,
106 push_subscriber: None,
107 }
108 }
109
110 pub fn with_id(mut self, id: u32) -> Self {
120 self.id = id;
121 self
122 }
123
124 pub fn with_events(mut self, events: Events) -> Self {
133 self.events = events;
134 self
135 }
136
137 pub fn with_stock_strings(mut self, stock_strings: StockStrings) -> Self {
148 self.stock_strings = stock_strings;
149 self
150 }
151
152 pub fn with_password(mut self, password: String) -> Self {
157 self.password = Some(password);
158 self
159 }
160
161 pub(crate) fn with_push_subscriber(mut self, push_subscriber: PushSubscriber) -> Self {
163 self.push_subscriber = Some(push_subscriber);
164 self
165 }
166
167 pub async fn build(self) -> Result<Context> {
169 let push_subscriber = self.push_subscriber.unwrap_or_default();
170 let context = Context::new_closed(
171 &self.dbfile,
172 self.id,
173 self.events,
174 self.stock_strings,
175 push_subscriber,
176 )
177 .await?;
178 Ok(context)
179 }
180
181 pub async fn open(self) -> Result<Context> {
185 let password = self.password.clone().unwrap_or_default();
186 let context = self.build().await?;
187 match context.open(password).await? {
188 true => Ok(context),
189 false => bail!("database could not be decrypted, incorrect or missing password"),
190 }
191 }
192}
193
194#[derive(Clone, Debug)]
206pub struct Context {
207 pub(crate) inner: Arc<InnerContext>,
208}
209
210impl Deref for Context {
211 type Target = InnerContext;
212
213 fn deref(&self) -> &Self::Target {
214 &self.inner
215 }
216}
217
218#[derive(Debug)]
220pub struct InnerContext {
221 pub(crate) blobdir: PathBuf,
223 pub(crate) sql: Sql,
224 pub(crate) smeared_timestamp: SmearedTimestamp,
225 running_state: RwLock<RunningState>,
230 pub(crate) generating_key_mutex: Mutex<()>,
232 pub(crate) oauth2_mutex: Mutex<()>,
234 pub(crate) wrong_pw_warning_mutex: Mutex<()>,
236 pub(crate) translated_stockstrings: StockStrings,
237 pub(crate) events: Events,
238
239 pub(crate) scheduler: SchedulerState,
240 pub(crate) ratelimit: RwLock<Ratelimit>,
241
242 pub(crate) quota: RwLock<Option<QuotaInfo>>,
245
246 pub(crate) new_msgs_notify: Notify,
250
251 pub(crate) server_id: RwLock<Option<HashMap<String, String>>>,
255
256 pub(crate) metadata: RwLock<Option<ServerMetadata>>,
258
259 pub(crate) id: u32,
264
265 creation_time: tools::Time,
266
267 pub(crate) last_error: parking_lot::RwLock<String>,
271
272 pub(crate) migration_error: parking_lot::RwLock<Option<String>>,
278
279 pub(crate) debug_logging: std::sync::RwLock<Option<DebugLogging>>,
284
285 pub(crate) push_subscriber: PushSubscriber,
288
289 pub(crate) push_subscribed: AtomicBool,
291
292 pub(crate) tls_session_store: TlsSessionStore,
294
295 pub(crate) iroh: Arc<RwLock<Option<Iroh>>>,
297
298 pub(crate) self_fingerprint: OnceLock<String>,
302
303 pub(crate) connectivities: parking_lot::Mutex<Vec<ConnectivityStore>>,
306
307 #[expect(clippy::type_complexity)]
308 pub(crate) pre_encrypt_mime_hook: parking_lot::Mutex<
310 Option<
311 for<'a> fn(
312 &Context,
313 mail_builder::mime::MimePart<'a>,
314 ) -> mail_builder::mime::MimePart<'a>,
315 >,
316 >,
317}
318
319#[derive(Debug, Default)]
321enum RunningState {
322 Running { cancel_sender: Sender<()> },
324
325 ShallStop { request: tools::Time },
327
328 #[default]
330 Stopped,
331}
332
333pub fn get_info() -> BTreeMap<&'static str, String> {
340 let mut res = BTreeMap::new();
341
342 #[cfg(debug_assertions)]
343 res.insert(
344 "debug_assertions",
345 "On - DO NOT RELEASE THIS BUILD".to_string(),
346 );
347 #[cfg(not(debug_assertions))]
348 res.insert("debug_assertions", "Off".to_string());
349
350 res.insert("deltachat_core_version", format!("v{}", &*DC_VERSION_STR));
351 res.insert("sqlite_version", rusqlite::version().to_string());
352 res.insert("arch", (std::mem::size_of::<usize>() * 8).to_string());
353 res.insert("num_cpus", num_cpus::get().to_string());
354 res.insert("level", "awesome".into());
355 res
356}
357
358impl Context {
359 pub async fn new(
361 dbfile: &Path,
362 id: u32,
363 events: Events,
364 stock_strings: StockStrings,
365 ) -> Result<Context> {
366 let context =
367 Self::new_closed(dbfile, id, events, stock_strings, Default::default()).await?;
368
369 if context.check_passphrase("".to_string()).await? {
371 context.sql.open(&context, "".to_string()).await?;
372 }
373 Ok(context)
374 }
375
376 pub async fn new_closed(
378 dbfile: &Path,
379 id: u32,
380 events: Events,
381 stockstrings: StockStrings,
382 push_subscriber: PushSubscriber,
383 ) -> Result<Context> {
384 let mut blob_fname = OsString::new();
385 blob_fname.push(dbfile.file_name().unwrap_or_default());
386 blob_fname.push("-blobs");
387 let blobdir = dbfile.with_file_name(blob_fname);
388 if !blobdir.exists() {
389 tokio::fs::create_dir_all(&blobdir).await?;
390 }
391 let context = Context::with_blobdir(
392 dbfile.into(),
393 blobdir,
394 id,
395 events,
396 stockstrings,
397 push_subscriber,
398 )?;
399 Ok(context)
400 }
401
402 pub async fn open(&self, passphrase: String) -> Result<bool> {
407 if self.sql.check_passphrase(passphrase.clone()).await? {
408 self.sql.open(self, passphrase).await?;
409 Ok(true)
410 } else {
411 Ok(false)
412 }
413 }
414
415 pub async fn change_passphrase(&self, passphrase: String) -> Result<()> {
417 self.sql.change_passphrase(passphrase).await?;
418 Ok(())
419 }
420
421 pub async fn is_open(&self) -> bool {
423 self.sql.is_open().await
424 }
425
426 pub(crate) async fn check_passphrase(&self, passphrase: String) -> Result<bool> {
432 self.sql.check_passphrase(passphrase).await
433 }
434
435 pub(crate) fn with_blobdir(
436 dbfile: PathBuf,
437 blobdir: PathBuf,
438 id: u32,
439 events: Events,
440 stockstrings: StockStrings,
441 push_subscriber: PushSubscriber,
442 ) -> Result<Context> {
443 ensure!(
444 blobdir.is_dir(),
445 "Blobdir does not exist: {}",
446 blobdir.display()
447 );
448
449 let new_msgs_notify = Notify::new();
450 new_msgs_notify.notify_one();
453
454 let inner = InnerContext {
455 id,
456 blobdir,
457 running_state: RwLock::new(Default::default()),
458 sql: Sql::new(dbfile),
459 smeared_timestamp: SmearedTimestamp::new(),
460 generating_key_mutex: Mutex::new(()),
461 oauth2_mutex: Mutex::new(()),
462 wrong_pw_warning_mutex: Mutex::new(()),
463 translated_stockstrings: stockstrings,
464 events,
465 scheduler: SchedulerState::new(),
466 ratelimit: RwLock::new(Ratelimit::new(Duration::new(60, 0), 6.0)), quota: RwLock::new(None),
468 new_msgs_notify,
469 server_id: RwLock::new(None),
470 metadata: RwLock::new(None),
471 creation_time: tools::Time::now(),
472 last_error: parking_lot::RwLock::new("".to_string()),
473 migration_error: parking_lot::RwLock::new(None),
474 debug_logging: std::sync::RwLock::new(None),
475 push_subscriber,
476 push_subscribed: AtomicBool::new(false),
477 tls_session_store: TlsSessionStore::new(),
478 iroh: Arc::new(RwLock::new(None)),
479 self_fingerprint: OnceLock::new(),
480 connectivities: parking_lot::Mutex::new(Vec::new()),
481 pre_encrypt_mime_hook: None.into(),
482 };
483
484 let ctx = Context {
485 inner: Arc::new(inner),
486 };
487
488 Ok(ctx)
489 }
490
491 pub async fn start_io(&self) {
493 if !self.is_configured().await.unwrap_or_default() {
494 warn!(self, "can not start io on a context that is not configured");
495 return;
496 }
497
498 if self.is_chatmail().await.unwrap_or_default() {
499 let mut lock = self.ratelimit.write().await;
500 *lock = Ratelimit::new(Duration::new(3, 0), 3.0);
502 }
503
504 self.sql.config_cache.write().await.clear();
510
511 self.scheduler.start(self).await;
512 }
513
514 pub async fn stop_io(&self) {
516 self.scheduler.stop(self).await;
517 if let Some(iroh) = self.iroh.write().await.take() {
518 tokio::spawn(async move {
525 let _ = tokio::time::timeout(Duration::from_secs(60), iroh.close()).await;
528 });
529 }
530 }
531
532 pub async fn restart_io_if_running(&self) {
535 self.scheduler.restart(self).await;
536 }
537
538 pub async fn maybe_network(&self) {
540 if let Some(ref iroh) = *self.iroh.read().await {
541 iroh.network_change().await;
542 }
543 self.scheduler.maybe_network().await;
544 }
545
546 pub async fn is_chatmail(&self) -> Result<bool> {
548 self.get_config_bool(Config::IsChatmail).await
549 }
550
551 pub(crate) async fn get_max_smtp_rcpt_to(&self) -> Result<usize> {
553 let is_chatmail = self.is_chatmail().await?;
554 let val = self
555 .get_configured_provider()
556 .await?
557 .and_then(|provider| provider.opt.max_smtp_rcpt_to)
558 .map_or_else(
559 || match is_chatmail {
560 true => constants::DEFAULT_CHATMAIL_MAX_SMTP_RCPT_TO,
561 false => constants::DEFAULT_MAX_SMTP_RCPT_TO,
562 },
563 usize::from,
564 );
565 Ok(val)
566 }
567
568 pub async fn background_fetch(&self) -> Result<()> {
574 if !(self.is_configured().await?) {
575 return Ok(());
576 }
577
578 let address = self.get_primary_self_addr().await?;
579 let time_start = tools::Time::now();
580 info!(self, "background_fetch started fetching {address}.");
581
582 if self.scheduler.is_running().await {
583 self.scheduler.maybe_network().await;
584 self.wait_for_all_work_done().await;
585 } else {
586 let _pause_guard = self.scheduler.pause(self).await?;
589
590 let mut connection = Imap::new_configured(self, channel::bounded(1).1).await?;
592 let mut session = connection.prepare(self).await?;
593
594 for folder_meaning in [FolderMeaning::Inbox, FolderMeaning::Mvbox] {
598 if let Some((_folder_config, watch_folder)) =
599 convert_folder_meaning(self, folder_meaning).await?
600 {
601 connection
602 .fetch_move_delete(self, &mut session, &watch_folder, folder_meaning)
603 .await?;
604 }
605 }
606
607 if self
609 .quota_needs_update(DC_BACKGROUND_FETCH_QUOTA_CHECK_RATELIMIT)
610 .await
611 && let Err(err) = self.update_recent_quota(&mut session).await
612 {
613 warn!(self, "Failed to update quota: {err:#}.");
614 }
615 }
616
617 info!(
618 self,
619 "background_fetch done for {address} took {:?}.",
620 time_elapsed(&time_start),
621 );
622
623 Ok(())
624 }
625
626 #[cfg(feature = "internals")]
630 pub fn sql(&self) -> &Sql {
631 &self.inner.sql
632 }
633
634 pub fn get_dbfile(&self) -> &Path {
636 self.sql.dbfile.as_path()
637 }
638
639 pub fn get_blobdir(&self) -> &Path {
641 self.blobdir.as_path()
642 }
643
644 pub fn emit_event(&self, event: EventType) {
646 {
647 let lock = self.debug_logging.read().expect("RwLock is poisoned");
648 if let Some(debug_logging) = &*lock {
649 debug_logging.log_event(event.clone());
650 }
651 }
652 self.events.emit(Event {
653 id: self.id,
654 typ: event,
655 });
656 }
657
658 pub fn emit_msgs_changed_without_ids(&self) {
660 self.emit_event(EventType::MsgsChanged {
661 chat_id: ChatId::new(0),
662 msg_id: MsgId::new(0),
663 });
664 }
665
666 pub fn emit_msgs_changed(&self, chat_id: ChatId, msg_id: MsgId) {
672 logged_debug_assert!(
673 self,
674 !chat_id.is_unset(),
675 "emit_msgs_changed: chat_id is unset."
676 );
677 logged_debug_assert!(
678 self,
679 !msg_id.is_unset(),
680 "emit_msgs_changed: msg_id is unset."
681 );
682
683 self.emit_event(EventType::MsgsChanged { chat_id, msg_id });
684 chatlist_events::emit_chatlist_changed(self);
685 chatlist_events::emit_chatlist_item_changed(self, chat_id);
686 }
687
688 pub fn emit_msgs_changed_without_msg_id(&self, chat_id: ChatId) {
690 logged_debug_assert!(
691 self,
692 !chat_id.is_unset(),
693 "emit_msgs_changed_without_msg_id: chat_id is unset."
694 );
695
696 self.emit_event(EventType::MsgsChanged {
697 chat_id,
698 msg_id: MsgId::new(0),
699 });
700 chatlist_events::emit_chatlist_changed(self);
701 chatlist_events::emit_chatlist_item_changed(self, chat_id);
702 }
703
704 pub fn emit_incoming_msg(&self, chat_id: ChatId, msg_id: MsgId) {
706 debug_assert!(!chat_id.is_unset());
707 debug_assert!(!msg_id.is_unset());
708
709 self.emit_event(EventType::IncomingMsg { chat_id, msg_id });
710 chatlist_events::emit_chatlist_changed(self);
711 chatlist_events::emit_chatlist_item_changed(self, chat_id);
712 }
713
714 pub async fn emit_location_changed(&self, contact_id: Option<ContactId>) -> Result<()> {
716 self.emit_event(EventType::LocationChanged(contact_id));
717
718 if let Some(msg_id) = self
719 .get_config_parsed::<u32>(Config::WebxdcIntegration)
720 .await?
721 {
722 self.emit_event(EventType::WebxdcStatusUpdate {
723 msg_id: MsgId::new(msg_id),
724 status_update_serial: Default::default(),
725 })
726 }
727
728 Ok(())
729 }
730
731 pub fn get_event_emitter(&self) -> EventEmitter {
736 self.events.get_emitter()
737 }
738
739 pub fn get_id(&self) -> u32 {
741 self.id
742 }
743
744 pub(crate) async fn alloc_ongoing(&self) -> Result<Receiver<()>> {
754 let mut s = self.running_state.write().await;
755 ensure!(
756 matches!(*s, RunningState::Stopped),
757 "There is already another ongoing process running."
758 );
759
760 let (sender, receiver) = channel::bounded(1);
761 *s = RunningState::Running {
762 cancel_sender: sender,
763 };
764
765 Ok(receiver)
766 }
767
768 pub(crate) async fn free_ongoing(&self) {
769 let mut s = self.running_state.write().await;
770 if let RunningState::ShallStop { request } = *s {
771 info!(self, "Ongoing stopped in {:?}", time_elapsed(&request));
772 }
773 *s = RunningState::Stopped;
774 }
775
776 pub async fn stop_ongoing(&self) {
778 let mut s = self.running_state.write().await;
779 match &*s {
780 RunningState::Running { cancel_sender } => {
781 if let Err(err) = cancel_sender.send(()).await {
782 warn!(self, "could not cancel ongoing: {:#}", err);
783 }
784 info!(self, "Signaling the ongoing process to stop ASAP.",);
785 *s = RunningState::ShallStop {
786 request: tools::Time::now(),
787 };
788 }
789 RunningState::ShallStop { .. } | RunningState::Stopped => {
790 info!(self, "No ongoing process to stop.",);
791 }
792 }
793 }
794
795 #[allow(unused)]
796 pub(crate) async fn shall_stop_ongoing(&self) -> bool {
797 match &*self.running_state.read().await {
798 RunningState::Running { .. } => false,
799 RunningState::ShallStop { .. } | RunningState::Stopped => true,
800 }
801 }
802
803 pub async fn get_info(&self) -> Result<BTreeMap<&'static str, String>> {
809 let l = EnteredLoginParam::load(self).await?;
810 let l2 = ConfiguredLoginParam::load(self)
811 .await?
812 .map_or_else(|| "Not configured".to_string(), |param| param.to_string());
813 let secondary_addrs = self.get_secondary_self_addrs().await?.join(", ");
814 let chats = get_chat_cnt(self).await?;
815 let unblocked_msgs = message::get_unblocked_msg_cnt(self).await;
816 let request_msgs = message::get_request_msg_cnt(self).await;
817 let contacts = Contact::get_real_cnt(self).await?;
818 let proxy_enabled = self.get_config_int(Config::ProxyEnabled).await?;
819 let dbversion = self
820 .sql
821 .get_raw_config_int("dbversion")
822 .await?
823 .unwrap_or_default();
824 let journal_mode = self
825 .sql
826 .query_get_value("PRAGMA journal_mode;", ())
827 .await?
828 .unwrap_or_else(|| "unknown".to_string());
829 let mdns_enabled = self.get_config_int(Config::MdnsEnabled).await?;
830 let bcc_self = self.get_config_int(Config::BccSelf).await?;
831 let sync_msgs = self.get_config_int(Config::SyncMsgs).await?;
832 let disable_idle = self.get_config_bool(Config::DisableIdle).await?;
833
834 let prv_key_cnt = self.sql.count("SELECT COUNT(*) FROM keypairs;", ()).await?;
835
836 let pub_key_cnt = self
837 .sql
838 .count("SELECT COUNT(*) FROM public_keys;", ())
839 .await?;
840 let fingerprint_str = match self_fingerprint(self).await {
841 Ok(fp) => fp.to_string(),
842 Err(err) => format!("<key failure: {err}>"),
843 };
844
845 let mvbox_move = self.get_config_int(Config::MvboxMove).await?;
846 let only_fetch_mvbox = self.get_config_int(Config::OnlyFetchMvbox).await?;
847 let folders_configured = self
848 .sql
849 .get_raw_config_int(constants::DC_FOLDERS_CONFIGURED_KEY)
850 .await?
851 .unwrap_or_default();
852
853 let configured_inbox_folder = self
854 .get_config(Config::ConfiguredInboxFolder)
855 .await?
856 .unwrap_or_else(|| "<unset>".to_string());
857 let configured_mvbox_folder = self
858 .get_config(Config::ConfiguredMvboxFolder)
859 .await?
860 .unwrap_or_else(|| "<unset>".to_string());
861 let configured_trash_folder = self
862 .get_config(Config::ConfiguredTrashFolder)
863 .await?
864 .unwrap_or_else(|| "<unset>".to_string());
865
866 let mut res = get_info();
867
868 res.insert("bot", self.get_config_int(Config::Bot).await?.to_string());
870 res.insert("number_of_chats", chats.to_string());
871 res.insert("number_of_chat_messages", unblocked_msgs.to_string());
872 res.insert("messages_in_contact_requests", request_msgs.to_string());
873 res.insert("number_of_contacts", contacts.to_string());
874 res.insert("database_dir", self.get_dbfile().display().to_string());
875 res.insert("database_version", dbversion.to_string());
876 res.insert(
877 "database_encrypted",
878 self.sql
879 .is_encrypted()
880 .await
881 .map_or_else(|| "closed".to_string(), |b| b.to_string()),
882 );
883 res.insert("journal_mode", journal_mode);
884 res.insert("blobdir", self.get_blobdir().display().to_string());
885 res.insert(
886 "selfavatar",
887 self.get_config(Config::Selfavatar)
888 .await?
889 .unwrap_or_else(|| "<unset>".to_string()),
890 );
891 res.insert("proxy_enabled", proxy_enabled.to_string());
892 res.insert("entered_account_settings", l.to_string());
893 res.insert("used_account_settings", l2);
894
895 if let Some(server_id) = &*self.server_id.read().await {
896 res.insert("imap_server_id", format!("{server_id:?}"));
897 }
898
899 res.insert("is_chatmail", self.is_chatmail().await?.to_string());
900 res.insert(
901 "fix_is_chatmail",
902 self.get_config_bool(Config::FixIsChatmail)
903 .await?
904 .to_string(),
905 );
906 res.insert(
907 "is_muted",
908 self.get_config_bool(Config::IsMuted).await?.to_string(),
909 );
910 res.insert(
911 "private_tag",
912 self.get_config(Config::PrivateTag)
913 .await?
914 .unwrap_or_else(|| "<unset>".to_string()),
915 );
916
917 if let Some(metadata) = &*self.metadata.read().await {
918 if let Some(comment) = &metadata.comment {
919 res.insert("imap_server_comment", format!("{comment:?}"));
920 }
921
922 if let Some(admin) = &metadata.admin {
923 res.insert("imap_server_admin", format!("{admin:?}"));
924 }
925 }
926
927 res.insert("secondary_addrs", secondary_addrs);
928 res.insert(
929 "fetched_existing_msgs",
930 self.get_config_bool(Config::FetchedExistingMsgs)
931 .await?
932 .to_string(),
933 );
934 res.insert(
935 "show_emails",
936 self.get_config_int(Config::ShowEmails).await?.to_string(),
937 );
938 res.insert(
939 "download_limit",
940 self.get_config_int(Config::DownloadLimit)
941 .await?
942 .to_string(),
943 );
944 res.insert("mvbox_move", mvbox_move.to_string());
945 res.insert("only_fetch_mvbox", only_fetch_mvbox.to_string());
946 res.insert(
947 constants::DC_FOLDERS_CONFIGURED_KEY,
948 folders_configured.to_string(),
949 );
950 res.insert("configured_inbox_folder", configured_inbox_folder);
951 res.insert("configured_mvbox_folder", configured_mvbox_folder);
952 res.insert("configured_trash_folder", configured_trash_folder);
953 res.insert("mdns_enabled", mdns_enabled.to_string());
954 res.insert("bcc_self", bcc_self.to_string());
955 res.insert("sync_msgs", sync_msgs.to_string());
956 res.insert("disable_idle", disable_idle.to_string());
957 res.insert("private_key_count", prv_key_cnt.to_string());
958 res.insert("public_key_count", pub_key_cnt.to_string());
959 res.insert("fingerprint", fingerprint_str);
960 res.insert(
961 "media_quality",
962 self.get_config_int(Config::MediaQuality).await?.to_string(),
963 );
964 res.insert(
965 "delete_device_after",
966 self.get_config_int(Config::DeleteDeviceAfter)
967 .await?
968 .to_string(),
969 );
970 res.insert(
971 "delete_server_after",
972 self.get_config_int(Config::DeleteServerAfter)
973 .await?
974 .to_string(),
975 );
976 res.insert(
977 "delete_to_trash",
978 self.get_config(Config::DeleteToTrash)
979 .await?
980 .unwrap_or_else(|| "<unset>".to_string()),
981 );
982 res.insert(
983 "last_housekeeping",
984 self.get_config_int(Config::LastHousekeeping)
985 .await?
986 .to_string(),
987 );
988 res.insert(
989 "last_cant_decrypt_outgoing_msgs",
990 self.get_config_int(Config::LastCantDecryptOutgoingMsgs)
991 .await?
992 .to_string(),
993 );
994 res.insert(
995 "scan_all_folders_debounce_secs",
996 self.get_config_int(Config::ScanAllFoldersDebounceSecs)
997 .await?
998 .to_string(),
999 );
1000 res.insert(
1001 "quota_exceeding",
1002 self.get_config_int(Config::QuotaExceeding)
1003 .await?
1004 .to_string(),
1005 );
1006 res.insert(
1007 "authserv_id_candidates",
1008 self.get_config(Config::AuthservIdCandidates)
1009 .await?
1010 .unwrap_or_default(),
1011 );
1012 res.insert(
1013 "sign_unencrypted",
1014 self.get_config_int(Config::SignUnencrypted)
1015 .await?
1016 .to_string(),
1017 );
1018 res.insert(
1019 "debug_logging",
1020 self.get_config_int(Config::DebugLogging).await?.to_string(),
1021 );
1022 res.insert(
1023 "last_msg_id",
1024 self.get_config_int(Config::LastMsgId).await?.to_string(),
1025 );
1026 res.insert(
1027 "gossip_period",
1028 self.get_config_int(Config::GossipPeriod).await?.to_string(),
1029 );
1030 res.insert(
1031 "webxdc_realtime_enabled",
1032 self.get_config_bool(Config::WebxdcRealtimeEnabled)
1033 .await?
1034 .to_string(),
1035 );
1036 res.insert(
1037 "donation_request_next_check",
1038 self.get_config_i64(Config::DonationRequestNextCheck)
1039 .await?
1040 .to_string(),
1041 );
1042 res.insert(
1043 "first_key_contacts_msg_id",
1044 self.sql
1045 .get_raw_config("first_key_contacts_msg_id")
1046 .await?
1047 .unwrap_or_default(),
1048 );
1049 res.insert(
1050 "stats_id",
1051 self.get_config(Config::StatsId)
1052 .await?
1053 .unwrap_or_else(|| "<unset>".to_string()),
1054 );
1055 res.insert(
1056 "stats_sending",
1057 stats::should_send_stats(self).await?.to_string(),
1058 );
1059 res.insert(
1060 "stats_last_sent",
1061 self.get_config_i64(Config::StatsLastSent)
1062 .await?
1063 .to_string(),
1064 );
1065 res.insert(
1066 "test_hooks",
1067 self.sql
1068 .get_raw_config("test_hooks")
1069 .await?
1070 .unwrap_or_default(),
1071 );
1072 res.insert(
1073 "fail_on_receiving_full_msg",
1074 self.sql
1075 .get_raw_config("fail_on_receiving_full_msg")
1076 .await?
1077 .unwrap_or_default(),
1078 );
1079 res.insert(
1080 "std_header_protection_composing",
1081 self.sql
1082 .get_raw_config("std_header_protection_composing")
1083 .await?
1084 .unwrap_or_default(),
1085 );
1086
1087 let elapsed = time_elapsed(&self.creation_time);
1088 res.insert("uptime", duration_to_str(elapsed));
1089
1090 Ok(res)
1091 }
1092
1093 pub async fn get_fresh_msgs(&self) -> Result<Vec<MsgId>> {
1100 let list = self
1101 .sql
1102 .query_map_vec(
1103 concat!(
1104 "SELECT m.id",
1105 " FROM msgs m",
1106 " LEFT JOIN contacts ct",
1107 " ON m.from_id=ct.id",
1108 " LEFT JOIN chats c",
1109 " ON m.chat_id=c.id",
1110 " WHERE m.state=?",
1111 " AND m.hidden=0",
1112 " AND m.chat_id>9",
1113 " AND ct.blocked=0",
1114 " AND c.blocked=0",
1115 " AND NOT(c.muted_until=-1 OR c.muted_until>?)",
1116 " ORDER BY m.timestamp DESC,m.id DESC;"
1117 ),
1118 (MessageState::InFresh, time()),
1119 |row| {
1120 let msg_id: MsgId = row.get(0)?;
1121 Ok(msg_id)
1122 },
1123 )
1124 .await?;
1125 Ok(list)
1126 }
1127
1128 pub async fn get_next_msgs(&self) -> Result<Vec<MsgId>> {
1133 let last_msg_id = match self.get_config(Config::LastMsgId).await? {
1134 Some(s) => MsgId::new(s.parse()?),
1135 None => {
1136 self.sql
1141 .query_row(
1142 "SELECT IFNULL((SELECT MAX(id) - 1 FROM msgs), 0)",
1143 (),
1144 |row| {
1145 let msg_id: MsgId = row.get(0)?;
1146 Ok(msg_id)
1147 },
1148 )
1149 .await?
1150 }
1151 };
1152
1153 let list = self
1154 .sql
1155 .query_map_vec(
1156 "SELECT m.id
1157 FROM msgs m
1158 LEFT JOIN contacts ct
1159 ON m.from_id=ct.id
1160 LEFT JOIN chats c
1161 ON m.chat_id=c.id
1162 WHERE m.id>?
1163 AND m.hidden=0
1164 AND m.chat_id>9
1165 AND ct.blocked=0
1166 AND c.blocked!=1
1167 ORDER BY m.id ASC",
1168 (
1169 last_msg_id.to_u32(), ),
1171 |row| {
1172 let msg_id: MsgId = row.get(0)?;
1173 Ok(msg_id)
1174 },
1175 )
1176 .await?;
1177 Ok(list)
1178 }
1179
1180 pub async fn wait_next_msgs(&self) -> Result<Vec<MsgId>> {
1191 self.new_msgs_notify.notified().await;
1192 let list = self.get_next_msgs().await?;
1193 Ok(list)
1194 }
1195
1196 pub async fn search_msgs(&self, chat_id: Option<ChatId>, query: &str) -> Result<Vec<MsgId>> {
1207 let real_query = query.trim().to_lowercase();
1208 if real_query.is_empty() {
1209 return Ok(Vec::new());
1210 }
1211 let str_like_in_text = format!("%{real_query}%");
1212
1213 let list = if let Some(chat_id) = chat_id {
1214 self.sql
1215 .query_map_vec(
1216 "SELECT m.id AS id
1217 FROM msgs m
1218 LEFT JOIN contacts ct
1219 ON m.from_id=ct.id
1220 WHERE m.chat_id=?
1221 AND m.hidden=0
1222 AND ct.blocked=0
1223 AND IFNULL(txt_normalized, txt) LIKE ?
1224 ORDER BY m.timestamp,m.id;",
1225 (chat_id, str_like_in_text),
1226 |row| {
1227 let msg_id: MsgId = row.get("id")?;
1228 Ok(msg_id)
1229 },
1230 )
1231 .await?
1232 } else {
1233 self.sql
1244 .query_map_vec(
1245 "SELECT m.id AS id
1246 FROM msgs m
1247 LEFT JOIN contacts ct
1248 ON m.from_id=ct.id
1249 LEFT JOIN chats c
1250 ON m.chat_id=c.id
1251 WHERE m.chat_id>9
1252 AND m.hidden=0
1253 AND c.blocked!=1
1254 AND ct.blocked=0
1255 AND IFNULL(txt_normalized, txt) LIKE ?
1256 ORDER BY m.id DESC LIMIT 1000",
1257 (str_like_in_text,),
1258 |row| {
1259 let msg_id: MsgId = row.get("id")?;
1260 Ok(msg_id)
1261 },
1262 )
1263 .await?
1264 };
1265
1266 Ok(list)
1267 }
1268
1269 pub async fn is_inbox(&self, folder_name: &str) -> Result<bool> {
1271 let inbox = self.get_config(Config::ConfiguredInboxFolder).await?;
1272 Ok(inbox.as_deref() == Some(folder_name))
1273 }
1274
1275 pub async fn is_mvbox(&self, folder_name: &str) -> Result<bool> {
1277 let mvbox = self.get_config(Config::ConfiguredMvboxFolder).await?;
1278 Ok(mvbox.as_deref() == Some(folder_name))
1279 }
1280
1281 pub async fn is_trash(&self, folder_name: &str) -> Result<bool> {
1283 let trash = self.get_config(Config::ConfiguredTrashFolder).await?;
1284 Ok(trash.as_deref() == Some(folder_name))
1285 }
1286
1287 pub(crate) async fn should_delete_to_trash(&self) -> Result<bool> {
1288 if let Some(v) = self.get_config_bool_opt(Config::DeleteToTrash).await? {
1289 return Ok(v);
1290 }
1291 if let Some(provider) = self.get_configured_provider().await? {
1292 return Ok(provider.opt.delete_to_trash);
1293 }
1294 Ok(false)
1295 }
1296
1297 pub(crate) async fn get_delete_msgs_target(&self) -> Result<String> {
1300 if !self.should_delete_to_trash().await? {
1301 return Ok("".into());
1302 }
1303 self.get_config(Config::ConfiguredTrashFolder)
1304 .await?
1305 .context("No configured trash folder")
1306 }
1307
1308 pub(crate) fn derive_blobdir(dbfile: &Path) -> PathBuf {
1309 let mut blob_fname = OsString::new();
1310 blob_fname.push(dbfile.file_name().unwrap_or_default());
1311 blob_fname.push("-blobs");
1312 dbfile.with_file_name(blob_fname)
1313 }
1314
1315 pub(crate) fn derive_walfile(dbfile: &Path) -> PathBuf {
1316 let mut wal_fname = OsString::new();
1317 wal_fname.push(dbfile.file_name().unwrap_or_default());
1318 wal_fname.push("-wal");
1319 dbfile.with_file_name(wal_fname)
1320 }
1321}
1322
1323pub fn get_version_str() -> &'static str {
1325 &DC_VERSION_STR
1326}
1327
1328#[cfg(test)]
1329mod context_tests;