1use std::collections::{BTreeMap, HashMap};
4use std::ffi::OsString;
5use std::ops::Deref;
6use std::path::{Path, PathBuf};
7use std::sync::atomic::{AtomicBool, Ordering};
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 pgp::types::PublicKeyTrait;
14use ratelimit::Ratelimit;
15use tokio::sync::{Mutex, Notify, RwLock};
16
17use crate::chat::{ChatId, get_chat_cnt};
18use crate::chatlist_events;
19use crate::config::Config;
20use crate::constants::{
21 self, DC_BACKGROUND_FETCH_QUOTA_CHECK_RATELIMIT, DC_CHAT_ID_TRASH, DC_VERSION_STR,
22};
23use crate::contact::{Contact, ContactId, import_vcard, mark_contact_id_as_verified};
24use crate::debug_logging::DebugLogging;
25use crate::download::DownloadState;
26use crate::events::{Event, EventEmitter, EventType, Events};
27use crate::imap::{FolderMeaning, Imap, ServerMetadata};
28use crate::key::{load_self_secret_key, self_fingerprint};
29use crate::log::{info, warn};
30use crate::logged_debug_assert;
31use crate::login_param::{ConfiguredLoginParam, EnteredLoginParam};
32use crate::message::{self, Message, MessageState, MsgId};
33use crate::net::tls::TlsSessionStore;
34use crate::param::{Param, Params};
35use crate::peer_channels::Iroh;
36use crate::push::PushSubscriber;
37use crate::quota::QuotaInfo;
38use crate::scheduler::{ConnectivityStore, SchedulerState, convert_folder_meaning};
39use crate::sql::Sql;
40use crate::stock_str::StockStrings;
41use crate::timesmearing::SmearedTimestamp;
42use crate::tools::{self, create_id, duration_to_str, time, time_elapsed};
43
44#[derive(Clone, Debug)]
87pub struct ContextBuilder {
88 dbfile: PathBuf,
89 id: u32,
90 events: Events,
91 stock_strings: StockStrings,
92 password: Option<String>,
93
94 push_subscriber: Option<PushSubscriber>,
95}
96
97impl ContextBuilder {
98 pub fn new(dbfile: PathBuf) -> Self {
104 ContextBuilder {
105 dbfile,
106 id: rand::random(),
107 events: Events::new(),
108 stock_strings: StockStrings::new(),
109 password: None,
110 push_subscriber: None,
111 }
112 }
113
114 pub fn with_id(mut self, id: u32) -> Self {
124 self.id = id;
125 self
126 }
127
128 pub fn with_events(mut self, events: Events) -> Self {
137 self.events = events;
138 self
139 }
140
141 pub fn with_stock_strings(mut self, stock_strings: StockStrings) -> Self {
152 self.stock_strings = stock_strings;
153 self
154 }
155
156 pub fn with_password(mut self, password: String) -> Self {
161 self.password = Some(password);
162 self
163 }
164
165 pub(crate) fn with_push_subscriber(mut self, push_subscriber: PushSubscriber) -> Self {
167 self.push_subscriber = Some(push_subscriber);
168 self
169 }
170
171 pub async fn build(self) -> Result<Context> {
173 let push_subscriber = self.push_subscriber.unwrap_or_default();
174 let context = Context::new_closed(
175 &self.dbfile,
176 self.id,
177 self.events,
178 self.stock_strings,
179 push_subscriber,
180 )
181 .await?;
182 Ok(context)
183 }
184
185 pub async fn open(self) -> Result<Context> {
189 let password = self.password.clone().unwrap_or_default();
190 let context = self.build().await?;
191 match context.open(password).await? {
192 true => Ok(context),
193 false => bail!("database could not be decrypted, incorrect or missing password"),
194 }
195 }
196}
197
198#[derive(Clone, Debug)]
210pub struct Context {
211 pub(crate) inner: Arc<InnerContext>,
212}
213
214impl Deref for Context {
215 type Target = InnerContext;
216
217 fn deref(&self) -> &Self::Target {
218 &self.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) resync_request: AtomicBool,
252
253 pub(crate) new_msgs_notify: Notify,
257
258 pub(crate) server_id: RwLock<Option<HashMap<String, String>>>,
262
263 pub(crate) metadata: RwLock<Option<ServerMetadata>>,
265
266 pub(crate) last_full_folder_scan: Mutex<Option<tools::Time>>,
267
268 pub(crate) id: u32,
273
274 creation_time: tools::Time,
275
276 pub(crate) last_error: parking_lot::RwLock<String>,
280
281 pub(crate) migration_error: parking_lot::RwLock<Option<String>>,
287
288 pub(crate) debug_logging: std::sync::RwLock<Option<DebugLogging>>,
293
294 pub(crate) push_subscriber: PushSubscriber,
297
298 pub(crate) push_subscribed: AtomicBool,
300
301 pub(crate) tls_session_store: TlsSessionStore,
303
304 pub(crate) iroh: Arc<RwLock<Option<Iroh>>>,
306
307 pub(crate) self_fingerprint: OnceLock<String>,
311
312 pub(crate) connectivities: parking_lot::Mutex<Vec<ConnectivityStore>>,
315}
316
317#[derive(Debug)]
319enum RunningState {
320 Running { cancel_sender: Sender<()> },
322
323 ShallStop { request: tools::Time },
325
326 Stopped,
328}
329
330impl Default for RunningState {
331 fn default() -> Self {
332 Self::Stopped
333 }
334}
335
336pub fn get_info() -> BTreeMap<&'static str, String> {
343 let mut res = BTreeMap::new();
344
345 #[cfg(debug_assertions)]
346 res.insert(
347 "debug_assertions",
348 "On - DO NOT RELEASE THIS BUILD".to_string(),
349 );
350 #[cfg(not(debug_assertions))]
351 res.insert("debug_assertions", "Off".to_string());
352
353 res.insert("deltachat_core_version", format!("v{}", &*DC_VERSION_STR));
354 res.insert("sqlite_version", rusqlite::version().to_string());
355 res.insert("arch", (std::mem::size_of::<usize>() * 8).to_string());
356 res.insert("num_cpus", num_cpus::get().to_string());
357 res.insert("level", "awesome".into());
358 res
359}
360
361impl Context {
362 pub async fn new(
364 dbfile: &Path,
365 id: u32,
366 events: Events,
367 stock_strings: StockStrings,
368 ) -> Result<Context> {
369 let context =
370 Self::new_closed(dbfile, id, events, stock_strings, Default::default()).await?;
371
372 if context.check_passphrase("".to_string()).await? {
374 context.sql.open(&context, "".to_string()).await?;
375 }
376 Ok(context)
377 }
378
379 pub async fn new_closed(
381 dbfile: &Path,
382 id: u32,
383 events: Events,
384 stockstrings: StockStrings,
385 push_subscriber: PushSubscriber,
386 ) -> Result<Context> {
387 let mut blob_fname = OsString::new();
388 blob_fname.push(dbfile.file_name().unwrap_or_default());
389 blob_fname.push("-blobs");
390 let blobdir = dbfile.with_file_name(blob_fname);
391 if !blobdir.exists() {
392 tokio::fs::create_dir_all(&blobdir).await?;
393 }
394 let context = Context::with_blobdir(
395 dbfile.into(),
396 blobdir,
397 id,
398 events,
399 stockstrings,
400 push_subscriber,
401 )?;
402 Ok(context)
403 }
404
405 pub async fn open(&self, passphrase: String) -> Result<bool> {
410 if self.sql.check_passphrase(passphrase.clone()).await? {
411 self.sql.open(self, passphrase).await?;
412 Ok(true)
413 } else {
414 Ok(false)
415 }
416 }
417
418 pub async fn change_passphrase(&self, passphrase: String) -> Result<()> {
420 self.sql.change_passphrase(passphrase).await?;
421 Ok(())
422 }
423
424 pub async fn is_open(&self) -> bool {
426 self.sql.is_open().await
427 }
428
429 pub(crate) async fn check_passphrase(&self, passphrase: String) -> Result<bool> {
435 self.sql.check_passphrase(passphrase).await
436 }
437
438 pub(crate) fn with_blobdir(
439 dbfile: PathBuf,
440 blobdir: PathBuf,
441 id: u32,
442 events: Events,
443 stockstrings: StockStrings,
444 push_subscriber: PushSubscriber,
445 ) -> Result<Context> {
446 ensure!(
447 blobdir.is_dir(),
448 "Blobdir does not exist: {}",
449 blobdir.display()
450 );
451
452 let new_msgs_notify = Notify::new();
453 new_msgs_notify.notify_one();
456
457 let inner = InnerContext {
458 id,
459 blobdir,
460 running_state: RwLock::new(Default::default()),
461 sql: Sql::new(dbfile),
462 smeared_timestamp: SmearedTimestamp::new(),
463 generating_key_mutex: Mutex::new(()),
464 oauth2_mutex: Mutex::new(()),
465 wrong_pw_warning_mutex: Mutex::new(()),
466 translated_stockstrings: stockstrings,
467 events,
468 scheduler: SchedulerState::new(),
469 ratelimit: RwLock::new(Ratelimit::new(Duration::new(60, 0), 6.0)), quota: RwLock::new(None),
471 resync_request: AtomicBool::new(false),
472 new_msgs_notify,
473 server_id: RwLock::new(None),
474 metadata: RwLock::new(None),
475 creation_time: tools::Time::now(),
476 last_full_folder_scan: Mutex::new(None),
477 last_error: parking_lot::RwLock::new("".to_string()),
478 migration_error: parking_lot::RwLock::new(None),
479 debug_logging: std::sync::RwLock::new(None),
480 push_subscriber,
481 push_subscribed: AtomicBool::new(false),
482 tls_session_store: TlsSessionStore::new(),
483 iroh: Arc::new(RwLock::new(None)),
484 self_fingerprint: OnceLock::new(),
485 connectivities: parking_lot::Mutex::new(Vec::new()),
486 };
487
488 let ctx = Context {
489 inner: Arc::new(inner),
490 };
491
492 Ok(ctx)
493 }
494
495 pub async fn start_io(&self) {
497 if !self.is_configured().await.unwrap_or_default() {
498 warn!(self, "can not start io on a context that is not configured");
499 return;
500 }
501
502 if self.is_chatmail().await.unwrap_or_default() {
503 let mut lock = self.ratelimit.write().await;
504 *lock = Ratelimit::new(Duration::new(3, 0), 3.0);
506 }
507
508 self.sql.config_cache.write().await.clear();
514
515 self.scheduler.start(self).await;
516 }
517
518 pub async fn stop_io(&self) {
520 self.scheduler.stop(self).await;
521 if let Some(iroh) = self.iroh.write().await.take() {
522 tokio::spawn(async move {
529 let _ = tokio::time::timeout(Duration::from_secs(60), iroh.close()).await;
532 });
533 }
534 }
535
536 pub async fn restart_io_if_running(&self) {
539 self.scheduler.restart(self).await;
540 }
541
542 pub async fn maybe_network(&self) {
544 if let Some(ref iroh) = *self.iroh.read().await {
545 iroh.network_change().await;
546 }
547 self.scheduler.maybe_network().await;
548 }
549
550 pub async fn is_chatmail(&self) -> Result<bool> {
552 self.get_config_bool(Config::IsChatmail).await
553 }
554
555 pub(crate) async fn get_max_smtp_rcpt_to(&self) -> Result<usize> {
557 let is_chatmail = self.is_chatmail().await?;
558 let val = self
559 .get_configured_provider()
560 .await?
561 .and_then(|provider| provider.opt.max_smtp_rcpt_to)
562 .map_or_else(
563 || match is_chatmail {
564 true => usize::MAX,
565 false => constants::DEFAULT_MAX_SMTP_RCPT_TO,
566 },
567 usize::from,
568 );
569 Ok(val)
570 }
571
572 pub async fn background_fetch(&self) -> Result<()> {
578 if !(self.is_configured().await?) {
579 return Ok(());
580 }
581
582 let address = self.get_primary_self_addr().await?;
583 let time_start = tools::Time::now();
584 info!(self, "background_fetch started fetching {address}.");
585
586 if self.scheduler.is_running().await {
587 self.scheduler.maybe_network().await;
588 self.wait_for_all_work_done().await;
589 } else {
590 let _pause_guard = self.scheduler.pause(self).await?;
593
594 let mut connection = Imap::new_configured(self, channel::bounded(1).1).await?;
596 let mut session = connection.prepare(self).await?;
597
598 for folder_meaning in [FolderMeaning::Inbox, FolderMeaning::Mvbox] {
602 if let Some((_folder_config, watch_folder)) =
603 convert_folder_meaning(self, folder_meaning).await?
604 {
605 connection
606 .fetch_move_delete(self, &mut session, &watch_folder, folder_meaning)
607 .await?;
608 }
609 }
610
611 if self
613 .quota_needs_update(DC_BACKGROUND_FETCH_QUOTA_CHECK_RATELIMIT)
614 .await
615 {
616 if let Err(err) = self.update_recent_quota(&mut session).await {
617 warn!(self, "Failed to update quota: {err:#}.");
618 }
619 }
620 }
621
622 info!(
623 self,
624 "background_fetch done for {address} took {:?}.",
625 time_elapsed(&time_start),
626 );
627
628 Ok(())
629 }
630
631 pub(crate) async fn schedule_resync(&self) -> Result<()> {
632 self.resync_request.store(true, Ordering::Relaxed);
633 self.scheduler.interrupt_inbox().await;
634 Ok(())
635 }
636
637 #[cfg(feature = "internals")]
641 pub fn sql(&self) -> &Sql {
642 &self.inner.sql
643 }
644
645 pub fn get_dbfile(&self) -> &Path {
647 self.sql.dbfile.as_path()
648 }
649
650 pub fn get_blobdir(&self) -> &Path {
652 self.blobdir.as_path()
653 }
654
655 pub fn emit_event(&self, event: EventType) {
657 {
658 let lock = self.debug_logging.read().expect("RwLock is poisoned");
659 if let Some(debug_logging) = &*lock {
660 debug_logging.log_event(event.clone());
661 }
662 }
663 self.events.emit(Event {
664 id: self.id,
665 typ: event,
666 });
667 }
668
669 pub fn emit_msgs_changed_without_ids(&self) {
671 self.emit_event(EventType::MsgsChanged {
672 chat_id: ChatId::new(0),
673 msg_id: MsgId::new(0),
674 });
675 }
676
677 pub fn emit_msgs_changed(&self, chat_id: ChatId, msg_id: MsgId) {
683 logged_debug_assert!(
684 self,
685 !chat_id.is_unset(),
686 "emit_msgs_changed: chat_id is unset."
687 );
688 logged_debug_assert!(
689 self,
690 !msg_id.is_unset(),
691 "emit_msgs_changed: msg_id is unset."
692 );
693
694 self.emit_event(EventType::MsgsChanged { chat_id, msg_id });
695 chatlist_events::emit_chatlist_changed(self);
696 chatlist_events::emit_chatlist_item_changed(self, chat_id);
697 }
698
699 pub fn emit_msgs_changed_without_msg_id(&self, chat_id: ChatId) {
701 logged_debug_assert!(
702 self,
703 !chat_id.is_unset(),
704 "emit_msgs_changed_without_msg_id: chat_id is unset."
705 );
706
707 self.emit_event(EventType::MsgsChanged {
708 chat_id,
709 msg_id: MsgId::new(0),
710 });
711 chatlist_events::emit_chatlist_changed(self);
712 chatlist_events::emit_chatlist_item_changed(self, chat_id);
713 }
714
715 pub fn emit_incoming_msg(&self, chat_id: ChatId, msg_id: MsgId) {
717 debug_assert!(!chat_id.is_unset());
718 debug_assert!(!msg_id.is_unset());
719
720 self.emit_event(EventType::IncomingMsg { chat_id, msg_id });
721 chatlist_events::emit_chatlist_changed(self);
722 chatlist_events::emit_chatlist_item_changed(self, chat_id);
723 }
724
725 pub async fn emit_location_changed(&self, contact_id: Option<ContactId>) -> Result<()> {
727 self.emit_event(EventType::LocationChanged(contact_id));
728
729 if let Some(msg_id) = self
730 .get_config_parsed::<u32>(Config::WebxdcIntegration)
731 .await?
732 {
733 self.emit_event(EventType::WebxdcStatusUpdate {
734 msg_id: MsgId::new(msg_id),
735 status_update_serial: Default::default(),
736 })
737 }
738
739 Ok(())
740 }
741
742 pub fn get_event_emitter(&self) -> EventEmitter {
747 self.events.get_emitter()
748 }
749
750 pub fn get_id(&self) -> u32 {
752 self.id
753 }
754
755 pub(crate) async fn alloc_ongoing(&self) -> Result<Receiver<()>> {
765 let mut s = self.running_state.write().await;
766 ensure!(
767 matches!(*s, RunningState::Stopped),
768 "There is already another ongoing process running."
769 );
770
771 let (sender, receiver) = channel::bounded(1);
772 *s = RunningState::Running {
773 cancel_sender: sender,
774 };
775
776 Ok(receiver)
777 }
778
779 pub(crate) async fn free_ongoing(&self) {
780 let mut s = self.running_state.write().await;
781 if let RunningState::ShallStop { request } = *s {
782 info!(self, "Ongoing stopped in {:?}", time_elapsed(&request));
783 }
784 *s = RunningState::Stopped;
785 }
786
787 pub async fn stop_ongoing(&self) {
789 let mut s = self.running_state.write().await;
790 match &*s {
791 RunningState::Running { cancel_sender } => {
792 if let Err(err) = cancel_sender.send(()).await {
793 warn!(self, "could not cancel ongoing: {:#}", err);
794 }
795 info!(self, "Signaling the ongoing process to stop ASAP.",);
796 *s = RunningState::ShallStop {
797 request: tools::Time::now(),
798 };
799 }
800 RunningState::ShallStop { .. } | RunningState::Stopped => {
801 info!(self, "No ongoing process to stop.",);
802 }
803 }
804 }
805
806 #[allow(unused)]
807 pub(crate) async fn shall_stop_ongoing(&self) -> bool {
808 match &*self.running_state.read().await {
809 RunningState::Running { .. } => false,
810 RunningState::ShallStop { .. } | RunningState::Stopped => true,
811 }
812 }
813
814 pub async fn get_info(&self) -> Result<BTreeMap<&'static str, String>> {
820 let l = EnteredLoginParam::load(self).await?;
821 let l2 = ConfiguredLoginParam::load(self)
822 .await?
823 .map_or_else(|| "Not configured".to_string(), |param| param.to_string());
824 let secondary_addrs = self.get_secondary_self_addrs().await?.join(", ");
825 let chats = get_chat_cnt(self).await?;
826 let unblocked_msgs = message::get_unblocked_msg_cnt(self).await;
827 let request_msgs = message::get_request_msg_cnt(self).await;
828 let contacts = Contact::get_real_cnt(self).await?;
829 let is_configured = self.get_config_int(Config::Configured).await?;
830 let proxy_enabled = self.get_config_int(Config::ProxyEnabled).await?;
831 let dbversion = self
832 .sql
833 .get_raw_config_int("dbversion")
834 .await?
835 .unwrap_or_default();
836 let journal_mode = self
837 .sql
838 .query_get_value("PRAGMA journal_mode;", ())
839 .await?
840 .unwrap_or_else(|| "unknown".to_string());
841 let mdns_enabled = self.get_config_int(Config::MdnsEnabled).await?;
842 let bcc_self = self.get_config_int(Config::BccSelf).await?;
843 let sync_msgs = self.get_config_int(Config::SyncMsgs).await?;
844 let disable_idle = self.get_config_bool(Config::DisableIdle).await?;
845
846 let prv_key_cnt = self.sql.count("SELECT COUNT(*) FROM keypairs;", ()).await?;
847
848 let pub_key_cnt = self
849 .sql
850 .count("SELECT COUNT(*) FROM public_keys;", ())
851 .await?;
852 let fingerprint_str = match self_fingerprint(self).await {
853 Ok(fp) => fp.to_string(),
854 Err(err) => format!("<key failure: {err}>"),
855 };
856
857 let sentbox_watch = self.get_config_int(Config::SentboxWatch).await?;
858 let mvbox_move = self.get_config_int(Config::MvboxMove).await?;
859 let only_fetch_mvbox = self.get_config_int(Config::OnlyFetchMvbox).await?;
860 let folders_configured = self
861 .sql
862 .get_raw_config_int(constants::DC_FOLDERS_CONFIGURED_KEY)
863 .await?
864 .unwrap_or_default();
865
866 let configured_inbox_folder = self
867 .get_config(Config::ConfiguredInboxFolder)
868 .await?
869 .unwrap_or_else(|| "<unset>".to_string());
870 let configured_sentbox_folder = self
871 .get_config(Config::ConfiguredSentboxFolder)
872 .await?
873 .unwrap_or_else(|| "<unset>".to_string());
874 let configured_mvbox_folder = self
875 .get_config(Config::ConfiguredMvboxFolder)
876 .await?
877 .unwrap_or_else(|| "<unset>".to_string());
878 let configured_trash_folder = self
879 .get_config(Config::ConfiguredTrashFolder)
880 .await?
881 .unwrap_or_else(|| "<unset>".to_string());
882
883 let mut res = get_info();
884
885 res.insert("bot", self.get_config_int(Config::Bot).await?.to_string());
887 res.insert("number_of_chats", chats.to_string());
888 res.insert("number_of_chat_messages", unblocked_msgs.to_string());
889 res.insert("messages_in_contact_requests", request_msgs.to_string());
890 res.insert("number_of_contacts", contacts.to_string());
891 res.insert("database_dir", self.get_dbfile().display().to_string());
892 res.insert("database_version", dbversion.to_string());
893 res.insert(
894 "database_encrypted",
895 self.sql
896 .is_encrypted()
897 .await
898 .map_or_else(|| "closed".to_string(), |b| b.to_string()),
899 );
900 res.insert("journal_mode", journal_mode);
901 res.insert("blobdir", self.get_blobdir().display().to_string());
902 res.insert(
903 "selfavatar",
904 self.get_config(Config::Selfavatar)
905 .await?
906 .unwrap_or_else(|| "<unset>".to_string()),
907 );
908 res.insert("is_configured", is_configured.to_string());
909 res.insert("proxy_enabled", proxy_enabled.to_string());
910 res.insert("entered_account_settings", l.to_string());
911 res.insert("used_account_settings", l2);
912
913 if let Some(server_id) = &*self.server_id.read().await {
914 res.insert("imap_server_id", format!("{server_id:?}"));
915 }
916
917 res.insert("is_chatmail", self.is_chatmail().await?.to_string());
918 res.insert(
919 "fix_is_chatmail",
920 self.get_config_bool(Config::FixIsChatmail)
921 .await?
922 .to_string(),
923 );
924 res.insert(
925 "is_muted",
926 self.get_config_bool(Config::IsMuted).await?.to_string(),
927 );
928 res.insert(
929 "private_tag",
930 self.get_config(Config::PrivateTag)
931 .await?
932 .unwrap_or_else(|| "<unset>".to_string()),
933 );
934
935 if let Some(metadata) = &*self.metadata.read().await {
936 if let Some(comment) = &metadata.comment {
937 res.insert("imap_server_comment", format!("{comment:?}"));
938 }
939
940 if let Some(admin) = &metadata.admin {
941 res.insert("imap_server_admin", format!("{admin:?}"));
942 }
943 }
944
945 res.insert("secondary_addrs", secondary_addrs);
946 res.insert(
947 "fetched_existing_msgs",
948 self.get_config_bool(Config::FetchedExistingMsgs)
949 .await?
950 .to_string(),
951 );
952 res.insert(
953 "show_emails",
954 self.get_config_int(Config::ShowEmails).await?.to_string(),
955 );
956 res.insert(
957 "download_limit",
958 self.get_config_int(Config::DownloadLimit)
959 .await?
960 .to_string(),
961 );
962 res.insert("sentbox_watch", sentbox_watch.to_string());
963 res.insert("mvbox_move", mvbox_move.to_string());
964 res.insert("only_fetch_mvbox", only_fetch_mvbox.to_string());
965 res.insert(
966 constants::DC_FOLDERS_CONFIGURED_KEY,
967 folders_configured.to_string(),
968 );
969 res.insert("configured_inbox_folder", configured_inbox_folder);
970 res.insert("configured_sentbox_folder", configured_sentbox_folder);
971 res.insert("configured_mvbox_folder", configured_mvbox_folder);
972 res.insert("configured_trash_folder", configured_trash_folder);
973 res.insert("mdns_enabled", mdns_enabled.to_string());
974 res.insert("bcc_self", bcc_self.to_string());
975 res.insert("sync_msgs", sync_msgs.to_string());
976 res.insert("disable_idle", disable_idle.to_string());
977 res.insert("private_key_count", prv_key_cnt.to_string());
978 res.insert("public_key_count", pub_key_cnt.to_string());
979 res.insert("fingerprint", fingerprint_str);
980 res.insert(
981 "media_quality",
982 self.get_config_int(Config::MediaQuality).await?.to_string(),
983 );
984 res.insert(
985 "delete_device_after",
986 self.get_config_int(Config::DeleteDeviceAfter)
987 .await?
988 .to_string(),
989 );
990 res.insert(
991 "delete_server_after",
992 self.get_config_int(Config::DeleteServerAfter)
993 .await?
994 .to_string(),
995 );
996 res.insert(
997 "delete_to_trash",
998 self.get_config(Config::DeleteToTrash)
999 .await?
1000 .unwrap_or_else(|| "<unset>".to_string()),
1001 );
1002 res.insert(
1003 "last_housekeeping",
1004 self.get_config_int(Config::LastHousekeeping)
1005 .await?
1006 .to_string(),
1007 );
1008 res.insert(
1009 "last_cant_decrypt_outgoing_msgs",
1010 self.get_config_int(Config::LastCantDecryptOutgoingMsgs)
1011 .await?
1012 .to_string(),
1013 );
1014 res.insert(
1015 "scan_all_folders_debounce_secs",
1016 self.get_config_int(Config::ScanAllFoldersDebounceSecs)
1017 .await?
1018 .to_string(),
1019 );
1020 res.insert(
1021 "quota_exceeding",
1022 self.get_config_int(Config::QuotaExceeding)
1023 .await?
1024 .to_string(),
1025 );
1026 res.insert(
1027 "authserv_id_candidates",
1028 self.get_config(Config::AuthservIdCandidates)
1029 .await?
1030 .unwrap_or_default(),
1031 );
1032 res.insert(
1033 "sign_unencrypted",
1034 self.get_config_int(Config::SignUnencrypted)
1035 .await?
1036 .to_string(),
1037 );
1038 res.insert(
1039 "debug_logging",
1040 self.get_config_int(Config::DebugLogging).await?.to_string(),
1041 );
1042 res.insert(
1043 "last_msg_id",
1044 self.get_config_int(Config::LastMsgId).await?.to_string(),
1045 );
1046 res.insert(
1047 "gossip_period",
1048 self.get_config_int(Config::GossipPeriod).await?.to_string(),
1049 );
1050 res.insert(
1051 "webxdc_realtime_enabled",
1052 self.get_config_bool(Config::WebxdcRealtimeEnabled)
1053 .await?
1054 .to_string(),
1055 );
1056 res.insert(
1057 "donation_request_next_check",
1058 self.get_config_i64(Config::DonationRequestNextCheck)
1059 .await?
1060 .to_string(),
1061 );
1062 res.insert(
1063 "first_key_contacts_msg_id",
1064 self.sql
1065 .get_raw_config("first_key_contacts_msg_id")
1066 .await?
1067 .unwrap_or_default(),
1068 );
1069 res.insert(
1070 "fail_on_receiving_full_msg",
1071 self.sql
1072 .get_raw_config("fail_on_receiving_full_msg")
1073 .await?
1074 .unwrap_or_default(),
1075 );
1076
1077 let elapsed = time_elapsed(&self.creation_time);
1078 res.insert("uptime", duration_to_str(elapsed));
1079
1080 Ok(res)
1081 }
1082
1083 async fn get_self_report(&self) -> Result<String> {
1084 #[derive(Default)]
1085 struct ChatNumbers {
1086 opportunistic_dc: u32,
1087 opportunistic_mua: u32,
1088 unencrypted_dc: u32,
1089 unencrypted_mua: u32,
1090 }
1091
1092 let mut res = String::new();
1093 res += &format!("core_version {}\n", get_version_str());
1094
1095 let num_msgs: u32 = self
1096 .sql
1097 .query_get_value(
1098 "SELECT COUNT(*) FROM msgs WHERE hidden=0 AND chat_id!=?",
1099 (DC_CHAT_ID_TRASH,),
1100 )
1101 .await?
1102 .unwrap_or_default();
1103 res += &format!("num_msgs {num_msgs}\n");
1104
1105 let num_chats: u32 = self
1106 .sql
1107 .query_get_value("SELECT COUNT(*) FROM chats WHERE id>9 AND blocked!=1", ())
1108 .await?
1109 .unwrap_or_default();
1110 res += &format!("num_chats {num_chats}\n");
1111
1112 let db_size = tokio::fs::metadata(&self.sql.dbfile).await?.len();
1113 res += &format!("db_size_bytes {db_size}\n");
1114
1115 let secret_key = &load_self_secret_key(self).await?.primary_key;
1116 let key_created = secret_key.public_key().created_at().timestamp();
1117 res += &format!("key_created {key_created}\n");
1118
1119 let three_months_ago = time().saturating_sub(3600 * 24 * 30 * 3);
1125 let chats = self
1126 .sql
1127 .query_map(
1128 "SELECT m.param, m.msgrmsg
1129 FROM chats c
1130 JOIN msgs m
1131 ON c.id=m.chat_id
1132 AND m.id=(
1133 SELECT id
1134 FROM msgs
1135 WHERE chat_id=c.id
1136 AND hidden=0
1137 AND download_state=?
1138 AND to_id!=?
1139 ORDER BY timestamp DESC, id DESC LIMIT 1)
1140 WHERE c.id>9
1141 AND (c.blocked=0 OR c.blocked=2)
1142 AND IFNULL(m.timestamp,c.created_timestamp) > ?
1143 GROUP BY c.id",
1144 (DownloadState::Done, ContactId::INFO, three_months_ago),
1145 |row| {
1146 let message_param: Params =
1147 row.get::<_, String>(1)?.parse().unwrap_or_default();
1148 let is_dc_message: bool = row.get(2)?;
1149 Ok((message_param, is_dc_message))
1150 },
1151 |rows| {
1152 let mut chats = ChatNumbers::default();
1153 for row in rows {
1154 let (message_param, is_dc_message) = row?;
1155 let encrypted = message_param
1156 .get_bool(Param::GuaranteeE2ee)
1157 .unwrap_or(false);
1158
1159 if encrypted {
1160 if is_dc_message {
1161 chats.opportunistic_dc += 1;
1162 } else {
1163 chats.opportunistic_mua += 1;
1164 }
1165 } else if is_dc_message {
1166 chats.unencrypted_dc += 1;
1167 } else {
1168 chats.unencrypted_mua += 1;
1169 }
1170 }
1171 Ok(chats)
1172 },
1173 )
1174 .await?;
1175 res += &format!("chats_opportunistic_dc {}\n", chats.opportunistic_dc);
1176 res += &format!("chats_opportunistic_mua {}\n", chats.opportunistic_mua);
1177 res += &format!("chats_unencrypted_dc {}\n", chats.unencrypted_dc);
1178 res += &format!("chats_unencrypted_mua {}\n", chats.unencrypted_mua);
1179
1180 let self_reporting_id = match self.get_config(Config::SelfReportingId).await? {
1181 Some(id) => id,
1182 None => {
1183 let id = create_id();
1184 self.set_config(Config::SelfReportingId, Some(&id)).await?;
1185 id
1186 }
1187 };
1188 res += &format!("self_reporting_id {self_reporting_id}");
1189
1190 Ok(res)
1191 }
1192
1193 pub async fn draft_self_report(&self) -> Result<ChatId> {
1199 const SELF_REPORTING_BOT_VCARD: &str = include_str!("../assets/self-reporting-bot.vcf");
1200 let contact_id: ContactId = *import_vcard(self, SELF_REPORTING_BOT_VCARD)
1201 .await?
1202 .first()
1203 .context("Self reporting bot vCard does not contain a contact")?;
1204 mark_contact_id_as_verified(self, contact_id, Some(ContactId::SELF)).await?;
1205
1206 let chat_id = ChatId::create_for_contact(self, contact_id).await?;
1207
1208 let mut msg = Message::new_text(self.get_self_report().await?);
1209
1210 chat_id.set_draft(self, Some(&mut msg)).await?;
1211
1212 Ok(chat_id)
1213 }
1214
1215 pub async fn get_fresh_msgs(&self) -> Result<Vec<MsgId>> {
1222 let list = self
1223 .sql
1224 .query_map(
1225 concat!(
1226 "SELECT m.id",
1227 " FROM msgs m",
1228 " LEFT JOIN contacts ct",
1229 " ON m.from_id=ct.id",
1230 " LEFT JOIN chats c",
1231 " ON m.chat_id=c.id",
1232 " WHERE m.state=?",
1233 " AND m.hidden=0",
1234 " AND m.chat_id>9",
1235 " AND ct.blocked=0",
1236 " AND c.blocked=0",
1237 " AND NOT(c.muted_until=-1 OR c.muted_until>?)",
1238 " ORDER BY m.timestamp DESC,m.id DESC;"
1239 ),
1240 (MessageState::InFresh, time()),
1241 |row| row.get::<_, MsgId>(0),
1242 |rows| {
1243 let mut list = Vec::new();
1244 for row in rows {
1245 list.push(row?);
1246 }
1247 Ok(list)
1248 },
1249 )
1250 .await?;
1251 Ok(list)
1252 }
1253
1254 pub async fn get_next_msgs(&self) -> Result<Vec<MsgId>> {
1259 let last_msg_id = match self.get_config(Config::LastMsgId).await? {
1260 Some(s) => MsgId::new(s.parse()?),
1261 None => {
1262 self.sql
1267 .query_row(
1268 "SELECT IFNULL((SELECT MAX(id) - 1 FROM msgs), 0)",
1269 (),
1270 |row| {
1271 let msg_id: MsgId = row.get(0)?;
1272 Ok(msg_id)
1273 },
1274 )
1275 .await?
1276 }
1277 };
1278
1279 let list = self
1280 .sql
1281 .query_map(
1282 "SELECT m.id
1283 FROM msgs m
1284 LEFT JOIN contacts ct
1285 ON m.from_id=ct.id
1286 LEFT JOIN chats c
1287 ON m.chat_id=c.id
1288 WHERE m.id>?
1289 AND m.hidden=0
1290 AND m.chat_id>9
1291 AND ct.blocked=0
1292 AND c.blocked!=1
1293 ORDER BY m.id ASC",
1294 (
1295 last_msg_id.to_u32(), ),
1297 |row| {
1298 let msg_id: MsgId = row.get(0)?;
1299 Ok(msg_id)
1300 },
1301 |rows| {
1302 let mut list = Vec::new();
1303 for row in rows {
1304 list.push(row?);
1305 }
1306 Ok(list)
1307 },
1308 )
1309 .await?;
1310 Ok(list)
1311 }
1312
1313 pub async fn wait_next_msgs(&self) -> Result<Vec<MsgId>> {
1324 self.new_msgs_notify.notified().await;
1325 let list = self.get_next_msgs().await?;
1326 Ok(list)
1327 }
1328
1329 pub async fn search_msgs(&self, chat_id: Option<ChatId>, query: &str) -> Result<Vec<MsgId>> {
1340 let real_query = query.trim().to_lowercase();
1341 if real_query.is_empty() {
1342 return Ok(Vec::new());
1343 }
1344 let str_like_in_text = format!("%{real_query}%");
1345
1346 let list = if let Some(chat_id) = chat_id {
1347 self.sql
1348 .query_map(
1349 "SELECT m.id AS id
1350 FROM msgs m
1351 LEFT JOIN contacts ct
1352 ON m.from_id=ct.id
1353 WHERE m.chat_id=?
1354 AND m.hidden=0
1355 AND ct.blocked=0
1356 AND IFNULL(txt_normalized, txt) LIKE ?
1357 ORDER BY m.timestamp,m.id;",
1358 (chat_id, str_like_in_text),
1359 |row| row.get::<_, MsgId>("id"),
1360 |rows| {
1361 let mut ret = Vec::new();
1362 for id in rows {
1363 ret.push(id?);
1364 }
1365 Ok(ret)
1366 },
1367 )
1368 .await?
1369 } else {
1370 self.sql
1381 .query_map(
1382 "SELECT m.id AS id
1383 FROM msgs m
1384 LEFT JOIN contacts ct
1385 ON m.from_id=ct.id
1386 LEFT JOIN chats c
1387 ON m.chat_id=c.id
1388 WHERE m.chat_id>9
1389 AND m.hidden=0
1390 AND c.blocked!=1
1391 AND ct.blocked=0
1392 AND IFNULL(txt_normalized, txt) LIKE ?
1393 ORDER BY m.id DESC LIMIT 1000",
1394 (str_like_in_text,),
1395 |row| row.get::<_, MsgId>("id"),
1396 |rows| {
1397 let mut ret = Vec::new();
1398 for id in rows {
1399 ret.push(id?);
1400 }
1401 Ok(ret)
1402 },
1403 )
1404 .await?
1405 };
1406
1407 Ok(list)
1408 }
1409
1410 pub async fn is_inbox(&self, folder_name: &str) -> Result<bool> {
1412 let inbox = self.get_config(Config::ConfiguredInboxFolder).await?;
1413 Ok(inbox.as_deref() == Some(folder_name))
1414 }
1415
1416 pub async fn is_sentbox(&self, folder_name: &str) -> Result<bool> {
1418 let sentbox = self.get_config(Config::ConfiguredSentboxFolder).await?;
1419 Ok(sentbox.as_deref() == Some(folder_name))
1420 }
1421
1422 pub async fn is_mvbox(&self, folder_name: &str) -> Result<bool> {
1424 let mvbox = self.get_config(Config::ConfiguredMvboxFolder).await?;
1425 Ok(mvbox.as_deref() == Some(folder_name))
1426 }
1427
1428 pub async fn is_trash(&self, folder_name: &str) -> Result<bool> {
1430 let trash = self.get_config(Config::ConfiguredTrashFolder).await?;
1431 Ok(trash.as_deref() == Some(folder_name))
1432 }
1433
1434 pub(crate) async fn should_delete_to_trash(&self) -> Result<bool> {
1435 if let Some(v) = self.get_config_bool_opt(Config::DeleteToTrash).await? {
1436 return Ok(v);
1437 }
1438 if let Some(provider) = self.get_configured_provider().await? {
1439 return Ok(provider.opt.delete_to_trash);
1440 }
1441 Ok(false)
1442 }
1443
1444 pub(crate) async fn get_delete_msgs_target(&self) -> Result<String> {
1447 if !self.should_delete_to_trash().await? {
1448 return Ok("".into());
1449 }
1450 self.get_config(Config::ConfiguredTrashFolder)
1451 .await?
1452 .context("No configured trash folder")
1453 }
1454
1455 pub(crate) fn derive_blobdir(dbfile: &Path) -> PathBuf {
1456 let mut blob_fname = OsString::new();
1457 blob_fname.push(dbfile.file_name().unwrap_or_default());
1458 blob_fname.push("-blobs");
1459 dbfile.with_file_name(blob_fname)
1460 }
1461
1462 pub(crate) fn derive_walfile(dbfile: &Path) -> PathBuf {
1463 let mut wal_fname = OsString::new();
1464 wal_fname.push(dbfile.file_name().unwrap_or_default());
1465 wal_fname.push("-wal");
1466 dbfile.with_file_name(wal_fname)
1467 }
1468}
1469
1470pub fn get_version_str() -> &'static str {
1472 &DC_VERSION_STR
1473}
1474
1475#[cfg(test)]
1476mod context_tests;