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, ProtectionStatus, 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::param::{Param, Params};
34use crate::peer_channels::Iroh;
35use crate::push::PushSubscriber;
36use crate::quota::QuotaInfo;
37use crate::scheduler::{ConnectivityStore, SchedulerState, convert_folder_meaning};
38use crate::sql::Sql;
39use crate::stock_str::StockStrings;
40use crate::timesmearing::SmearedTimestamp;
41use crate::tools::{self, create_id, duration_to_str, time, time_elapsed};
42
43#[derive(Clone, Debug)]
86pub struct ContextBuilder {
87 dbfile: PathBuf,
88 id: u32,
89 events: Events,
90 stock_strings: StockStrings,
91 password: Option<String>,
92
93 push_subscriber: Option<PushSubscriber>,
94}
95
96impl ContextBuilder {
97 pub fn new(dbfile: PathBuf) -> Self {
103 ContextBuilder {
104 dbfile,
105 id: rand::random(),
106 events: Events::new(),
107 stock_strings: StockStrings::new(),
108 password: None,
109 push_subscriber: None,
110 }
111 }
112
113 pub fn with_id(mut self, id: u32) -> Self {
123 self.id = id;
124 self
125 }
126
127 pub fn with_events(mut self, events: Events) -> Self {
136 self.events = events;
137 self
138 }
139
140 pub fn with_stock_strings(mut self, stock_strings: StockStrings) -> Self {
151 self.stock_strings = stock_strings;
152 self
153 }
154
155 pub fn with_password(mut self, password: String) -> Self {
160 self.password = Some(password);
161 self
162 }
163
164 pub(crate) fn with_push_subscriber(mut self, push_subscriber: PushSubscriber) -> Self {
166 self.push_subscriber = Some(push_subscriber);
167 self
168 }
169
170 pub async fn build(self) -> Result<Context> {
172 let push_subscriber = self.push_subscriber.unwrap_or_default();
173 let context = Context::new_closed(
174 &self.dbfile,
175 self.id,
176 self.events,
177 self.stock_strings,
178 push_subscriber,
179 )
180 .await?;
181 Ok(context)
182 }
183
184 pub async fn open(self) -> Result<Context> {
188 let password = self.password.clone().unwrap_or_default();
189 let context = self.build().await?;
190 match context.open(password).await? {
191 true => Ok(context),
192 false => bail!("database could not be decrypted, incorrect or missing password"),
193 }
194 }
195}
196
197#[derive(Clone, Debug)]
209pub struct Context {
210 pub(crate) inner: Arc<InnerContext>,
211}
212
213impl Deref for Context {
214 type Target = InnerContext;
215
216 fn deref(&self) -> &Self::Target {
217 &self.inner
218 }
219}
220
221#[derive(Debug)]
223pub struct InnerContext {
224 pub(crate) blobdir: PathBuf,
226 pub(crate) sql: Sql,
227 pub(crate) smeared_timestamp: SmearedTimestamp,
228 running_state: RwLock<RunningState>,
233 pub(crate) generating_key_mutex: Mutex<()>,
235 pub(crate) oauth2_mutex: Mutex<()>,
237 pub(crate) wrong_pw_warning_mutex: Mutex<()>,
239 pub(crate) translated_stockstrings: StockStrings,
240 pub(crate) events: Events,
241
242 pub(crate) scheduler: SchedulerState,
243 pub(crate) ratelimit: RwLock<Ratelimit>,
244
245 pub(crate) quota: RwLock<Option<QuotaInfo>>,
248
249 pub(crate) resync_request: AtomicBool,
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) last_full_folder_scan: Mutex<Option<tools::Time>>,
266
267 pub(crate) id: u32,
272
273 creation_time: tools::Time,
274
275 pub(crate) last_error: parking_lot::RwLock<String>,
279
280 pub(crate) migration_error: parking_lot::RwLock<Option<String>>,
286
287 pub(crate) debug_logging: std::sync::RwLock<Option<DebugLogging>>,
292
293 pub(crate) push_subscriber: PushSubscriber,
296
297 pub(crate) push_subscribed: AtomicBool,
299
300 pub(crate) iroh: Arc<RwLock<Option<Iroh>>>,
302
303 pub(crate) self_fingerprint: OnceLock<String>,
307
308 pub(crate) connectivities: parking_lot::Mutex<Vec<ConnectivityStore>>,
311}
312
313#[derive(Debug)]
315enum RunningState {
316 Running { cancel_sender: Sender<()> },
318
319 ShallStop { request: tools::Time },
321
322 Stopped,
324}
325
326impl Default for RunningState {
327 fn default() -> Self {
328 Self::Stopped
329 }
330}
331
332pub fn get_info() -> BTreeMap<&'static str, String> {
339 let mut res = BTreeMap::new();
340
341 #[cfg(debug_assertions)]
342 res.insert(
343 "debug_assertions",
344 "On - DO NOT RELEASE THIS BUILD".to_string(),
345 );
346 #[cfg(not(debug_assertions))]
347 res.insert("debug_assertions", "Off".to_string());
348
349 res.insert("deltachat_core_version", format!("v{}", &*DC_VERSION_STR));
350 res.insert("sqlite_version", rusqlite::version().to_string());
351 res.insert("arch", (std::mem::size_of::<usize>() * 8).to_string());
352 res.insert("num_cpus", num_cpus::get().to_string());
353 res.insert("level", "awesome".into());
354 res
355}
356
357impl Context {
358 pub async fn new(
360 dbfile: &Path,
361 id: u32,
362 events: Events,
363 stock_strings: StockStrings,
364 ) -> Result<Context> {
365 let context =
366 Self::new_closed(dbfile, id, events, stock_strings, Default::default()).await?;
367
368 if context.check_passphrase("".to_string()).await? {
370 context.sql.open(&context, "".to_string()).await?;
371 }
372 Ok(context)
373 }
374
375 pub async fn new_closed(
377 dbfile: &Path,
378 id: u32,
379 events: Events,
380 stockstrings: StockStrings,
381 push_subscriber: PushSubscriber,
382 ) -> Result<Context> {
383 let mut blob_fname = OsString::new();
384 blob_fname.push(dbfile.file_name().unwrap_or_default());
385 blob_fname.push("-blobs");
386 let blobdir = dbfile.with_file_name(blob_fname);
387 if !blobdir.exists() {
388 tokio::fs::create_dir_all(&blobdir).await?;
389 }
390 let context = Context::with_blobdir(
391 dbfile.into(),
392 blobdir,
393 id,
394 events,
395 stockstrings,
396 push_subscriber,
397 )?;
398 Ok(context)
399 }
400
401 pub async fn open(&self, passphrase: String) -> Result<bool> {
406 if self.sql.check_passphrase(passphrase.clone()).await? {
407 self.sql.open(self, passphrase).await?;
408 Ok(true)
409 } else {
410 Ok(false)
411 }
412 }
413
414 pub async fn change_passphrase(&self, passphrase: String) -> Result<()> {
416 self.sql.change_passphrase(passphrase).await?;
417 Ok(())
418 }
419
420 pub async fn is_open(&self) -> bool {
422 self.sql.is_open().await
423 }
424
425 pub(crate) async fn check_passphrase(&self, passphrase: String) -> Result<bool> {
431 self.sql.check_passphrase(passphrase).await
432 }
433
434 pub(crate) fn with_blobdir(
435 dbfile: PathBuf,
436 blobdir: PathBuf,
437 id: u32,
438 events: Events,
439 stockstrings: StockStrings,
440 push_subscriber: PushSubscriber,
441 ) -> Result<Context> {
442 ensure!(
443 blobdir.is_dir(),
444 "Blobdir does not exist: {}",
445 blobdir.display()
446 );
447
448 let new_msgs_notify = Notify::new();
449 new_msgs_notify.notify_one();
452
453 let inner = InnerContext {
454 id,
455 blobdir,
456 running_state: RwLock::new(Default::default()),
457 sql: Sql::new(dbfile),
458 smeared_timestamp: SmearedTimestamp::new(),
459 generating_key_mutex: Mutex::new(()),
460 oauth2_mutex: Mutex::new(()),
461 wrong_pw_warning_mutex: Mutex::new(()),
462 translated_stockstrings: stockstrings,
463 events,
464 scheduler: SchedulerState::new(),
465 ratelimit: RwLock::new(Ratelimit::new(Duration::new(60, 0), 6.0)), quota: RwLock::new(None),
467 resync_request: AtomicBool::new(false),
468 new_msgs_notify,
469 server_id: RwLock::new(None),
470 metadata: RwLock::new(None),
471 creation_time: tools::Time::now(),
472 last_full_folder_scan: Mutex::new(None),
473 last_error: parking_lot::RwLock::new("".to_string()),
474 migration_error: parking_lot::RwLock::new(None),
475 debug_logging: std::sync::RwLock::new(None),
476 push_subscriber,
477 push_subscribed: AtomicBool::new(false),
478 iroh: Arc::new(RwLock::new(None)),
479 self_fingerprint: OnceLock::new(),
480 connectivities: parking_lot::Mutex::new(Vec::new()),
481 };
482
483 let ctx = Context {
484 inner: Arc::new(inner),
485 };
486
487 Ok(ctx)
488 }
489
490 pub async fn start_io(&self) {
492 if !self.is_configured().await.unwrap_or_default() {
493 warn!(self, "can not start io on a context that is not configured");
494 return;
495 }
496
497 if self.is_chatmail().await.unwrap_or_default() {
498 let mut lock = self.ratelimit.write().await;
499 *lock = Ratelimit::new(Duration::new(3, 0), 3.0);
501 }
502
503 self.sql.config_cache.write().await.clear();
509
510 self.scheduler.start(self).await;
511 }
512
513 pub async fn stop_io(&self) {
515 self.scheduler.stop(self).await;
516 if let Some(iroh) = self.iroh.write().await.take() {
517 tokio::spawn(async move {
524 let _ = tokio::time::timeout(Duration::from_secs(60), iroh.close()).await;
527 });
528 }
529 }
530
531 pub async fn restart_io_if_running(&self) {
534 self.scheduler.restart(self).await;
535 }
536
537 pub async fn maybe_network(&self) {
539 if let Some(ref iroh) = *self.iroh.read().await {
540 iroh.network_change().await;
541 }
542 self.scheduler.maybe_network().await;
543 }
544
545 pub async fn is_chatmail(&self) -> Result<bool> {
547 self.get_config_bool(Config::IsChatmail).await
548 }
549
550 pub(crate) async fn get_max_smtp_rcpt_to(&self) -> Result<usize> {
552 let is_chatmail = self.is_chatmail().await?;
553 let val = self
554 .get_configured_provider()
555 .await?
556 .and_then(|provider| provider.opt.max_smtp_rcpt_to)
557 .map_or_else(
558 || match is_chatmail {
559 true => usize::MAX,
560 false => constants::DEFAULT_MAX_SMTP_RCPT_TO,
561 },
562 usize::from,
563 );
564 Ok(val)
565 }
566
567 pub async fn background_fetch(&self) -> Result<()> {
573 if !(self.is_configured().await?) {
574 return Ok(());
575 }
576
577 let address = self.get_primary_self_addr().await?;
578 let time_start = tools::Time::now();
579 info!(self, "background_fetch started fetching {address}.");
580
581 if self.scheduler.is_running().await {
582 self.scheduler.maybe_network().await;
583 self.wait_for_all_work_done().await;
584 } else {
585 let _pause_guard = self.scheduler.pause(self).await?;
588
589 let mut connection = Imap::new_configured(self, channel::bounded(1).1).await?;
591 let mut session = connection.prepare(self).await?;
592
593 for folder_meaning in [FolderMeaning::Inbox, FolderMeaning::Mvbox] {
597 if let Some((_folder_config, watch_folder)) =
598 convert_folder_meaning(self, folder_meaning).await?
599 {
600 connection
601 .fetch_move_delete(self, &mut session, &watch_folder, folder_meaning)
602 .await?;
603 }
604 }
605
606 if self
608 .quota_needs_update(DC_BACKGROUND_FETCH_QUOTA_CHECK_RATELIMIT)
609 .await
610 {
611 if let Err(err) = self.update_recent_quota(&mut session).await {
612 warn!(self, "Failed to update quota: {err:#}.");
613 }
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 pub(crate) async fn schedule_resync(&self) -> Result<()> {
627 self.resync_request.store(true, Ordering::Relaxed);
628 self.scheduler.interrupt_inbox().await;
629 Ok(())
630 }
631
632 #[cfg(feature = "internals")]
636 pub fn sql(&self) -> &Sql {
637 &self.inner.sql
638 }
639
640 pub fn get_dbfile(&self) -> &Path {
642 self.sql.dbfile.as_path()
643 }
644
645 pub fn get_blobdir(&self) -> &Path {
647 self.blobdir.as_path()
648 }
649
650 pub fn emit_event(&self, event: EventType) {
652 {
653 let lock = self.debug_logging.read().expect("RwLock is poisoned");
654 if let Some(debug_logging) = &*lock {
655 debug_logging.log_event(event.clone());
656 }
657 }
658 self.events.emit(Event {
659 id: self.id,
660 typ: event,
661 });
662 }
663
664 pub fn emit_msgs_changed_without_ids(&self) {
666 self.emit_event(EventType::MsgsChanged {
667 chat_id: ChatId::new(0),
668 msg_id: MsgId::new(0),
669 });
670 }
671
672 pub fn emit_msgs_changed(&self, chat_id: ChatId, msg_id: MsgId) {
678 logged_debug_assert!(
679 self,
680 !chat_id.is_unset(),
681 "emit_msgs_changed: chat_id is unset."
682 );
683 logged_debug_assert!(
684 self,
685 !msg_id.is_unset(),
686 "emit_msgs_changed: msg_id is unset."
687 );
688
689 self.emit_event(EventType::MsgsChanged { chat_id, msg_id });
690 chatlist_events::emit_chatlist_changed(self);
691 chatlist_events::emit_chatlist_item_changed(self, chat_id);
692 }
693
694 pub fn emit_msgs_changed_without_msg_id(&self, chat_id: ChatId) {
696 logged_debug_assert!(
697 self,
698 !chat_id.is_unset(),
699 "emit_msgs_changed_without_msg_id: chat_id is unset."
700 );
701
702 self.emit_event(EventType::MsgsChanged {
703 chat_id,
704 msg_id: MsgId::new(0),
705 });
706 chatlist_events::emit_chatlist_changed(self);
707 chatlist_events::emit_chatlist_item_changed(self, chat_id);
708 }
709
710 pub fn emit_incoming_msg(&self, chat_id: ChatId, msg_id: MsgId) {
712 debug_assert!(!chat_id.is_unset());
713 debug_assert!(!msg_id.is_unset());
714
715 self.emit_event(EventType::IncomingMsg { chat_id, msg_id });
716 chatlist_events::emit_chatlist_changed(self);
717 chatlist_events::emit_chatlist_item_changed(self, chat_id);
718 }
719
720 pub async fn emit_location_changed(&self, contact_id: Option<ContactId>) -> Result<()> {
722 self.emit_event(EventType::LocationChanged(contact_id));
723
724 if let Some(msg_id) = self
725 .get_config_parsed::<u32>(Config::WebxdcIntegration)
726 .await?
727 {
728 self.emit_event(EventType::WebxdcStatusUpdate {
729 msg_id: MsgId::new(msg_id),
730 status_update_serial: Default::default(),
731 })
732 }
733
734 Ok(())
735 }
736
737 pub fn get_event_emitter(&self) -> EventEmitter {
742 self.events.get_emitter()
743 }
744
745 pub fn get_id(&self) -> u32 {
747 self.id
748 }
749
750 pub(crate) async fn alloc_ongoing(&self) -> Result<Receiver<()>> {
760 let mut s = self.running_state.write().await;
761 ensure!(
762 matches!(*s, RunningState::Stopped),
763 "There is already another ongoing process running."
764 );
765
766 let (sender, receiver) = channel::bounded(1);
767 *s = RunningState::Running {
768 cancel_sender: sender,
769 };
770
771 Ok(receiver)
772 }
773
774 pub(crate) async fn free_ongoing(&self) {
775 let mut s = self.running_state.write().await;
776 if let RunningState::ShallStop { request } = *s {
777 info!(self, "Ongoing stopped in {:?}", time_elapsed(&request));
778 }
779 *s = RunningState::Stopped;
780 }
781
782 pub async fn stop_ongoing(&self) {
784 let mut s = self.running_state.write().await;
785 match &*s {
786 RunningState::Running { cancel_sender } => {
787 if let Err(err) = cancel_sender.send(()).await {
788 warn!(self, "could not cancel ongoing: {:#}", err);
789 }
790 info!(self, "Signaling the ongoing process to stop ASAP.",);
791 *s = RunningState::ShallStop {
792 request: tools::Time::now(),
793 };
794 }
795 RunningState::ShallStop { .. } | RunningState::Stopped => {
796 info!(self, "No ongoing process to stop.",);
797 }
798 }
799 }
800
801 #[allow(unused)]
802 pub(crate) async fn shall_stop_ongoing(&self) -> bool {
803 match &*self.running_state.read().await {
804 RunningState::Running { .. } => false,
805 RunningState::ShallStop { .. } | RunningState::Stopped => true,
806 }
807 }
808
809 pub async fn get_info(&self) -> Result<BTreeMap<&'static str, String>> {
815 let l = EnteredLoginParam::load(self).await?;
816 let l2 = ConfiguredLoginParam::load(self)
817 .await?
818 .map_or_else(|| "Not configured".to_string(), |param| param.to_string());
819 let secondary_addrs = self.get_secondary_self_addrs().await?.join(", ");
820 let chats = get_chat_cnt(self).await?;
821 let unblocked_msgs = message::get_unblocked_msg_cnt(self).await;
822 let request_msgs = message::get_request_msg_cnt(self).await;
823 let contacts = Contact::get_real_cnt(self).await?;
824 let is_configured = self.get_config_int(Config::Configured).await?;
825 let proxy_enabled = self.get_config_int(Config::ProxyEnabled).await?;
826 let dbversion = self
827 .sql
828 .get_raw_config_int("dbversion")
829 .await?
830 .unwrap_or_default();
831 let journal_mode = self
832 .sql
833 .query_get_value("PRAGMA journal_mode;", ())
834 .await?
835 .unwrap_or_else(|| "unknown".to_string());
836 let e2ee_enabled = self.get_config_int(Config::E2eeEnabled).await?;
837 let mdns_enabled = self.get_config_int(Config::MdnsEnabled).await?;
838 let bcc_self = self.get_config_int(Config::BccSelf).await?;
839 let sync_msgs = self.get_config_int(Config::SyncMsgs).await?;
840 let disable_idle = self.get_config_bool(Config::DisableIdle).await?;
841
842 let prv_key_cnt = self.sql.count("SELECT COUNT(*) FROM keypairs;", ()).await?;
843
844 let pub_key_cnt = self
845 .sql
846 .count("SELECT COUNT(*) FROM public_keys;", ())
847 .await?;
848 let fingerprint_str = match self_fingerprint(self).await {
849 Ok(fp) => fp.to_string(),
850 Err(err) => format!("<key failure: {err}>"),
851 };
852
853 let sentbox_watch = self.get_config_int(Config::SentboxWatch).await?;
854 let mvbox_move = self.get_config_int(Config::MvboxMove).await?;
855 let only_fetch_mvbox = self.get_config_int(Config::OnlyFetchMvbox).await?;
856 let folders_configured = self
857 .sql
858 .get_raw_config_int(constants::DC_FOLDERS_CONFIGURED_KEY)
859 .await?
860 .unwrap_or_default();
861
862 let configured_inbox_folder = self
863 .get_config(Config::ConfiguredInboxFolder)
864 .await?
865 .unwrap_or_else(|| "<unset>".to_string());
866 let configured_sentbox_folder = self
867 .get_config(Config::ConfiguredSentboxFolder)
868 .await?
869 .unwrap_or_else(|| "<unset>".to_string());
870 let configured_mvbox_folder = self
871 .get_config(Config::ConfiguredMvboxFolder)
872 .await?
873 .unwrap_or_else(|| "<unset>".to_string());
874 let configured_trash_folder = self
875 .get_config(Config::ConfiguredTrashFolder)
876 .await?
877 .unwrap_or_else(|| "<unset>".to_string());
878
879 let mut res = get_info();
880
881 res.insert("bot", self.get_config_int(Config::Bot).await?.to_string());
883 res.insert("number_of_chats", chats.to_string());
884 res.insert("number_of_chat_messages", unblocked_msgs.to_string());
885 res.insert("messages_in_contact_requests", request_msgs.to_string());
886 res.insert("number_of_contacts", contacts.to_string());
887 res.insert("database_dir", self.get_dbfile().display().to_string());
888 res.insert("database_version", dbversion.to_string());
889 res.insert(
890 "database_encrypted",
891 self.sql
892 .is_encrypted()
893 .await
894 .map_or_else(|| "closed".to_string(), |b| b.to_string()),
895 );
896 res.insert("journal_mode", journal_mode);
897 res.insert("blobdir", self.get_blobdir().display().to_string());
898 res.insert(
899 "selfavatar",
900 self.get_config(Config::Selfavatar)
901 .await?
902 .unwrap_or_else(|| "<unset>".to_string()),
903 );
904 res.insert("is_configured", is_configured.to_string());
905 res.insert("proxy_enabled", proxy_enabled.to_string());
906 res.insert("entered_account_settings", l.to_string());
907 res.insert("used_account_settings", l2);
908
909 if let Some(server_id) = &*self.server_id.read().await {
910 res.insert("imap_server_id", format!("{server_id:?}"));
911 }
912
913 res.insert("is_chatmail", self.is_chatmail().await?.to_string());
914 res.insert(
915 "fix_is_chatmail",
916 self.get_config_bool(Config::FixIsChatmail)
917 .await?
918 .to_string(),
919 );
920 res.insert(
921 "is_muted",
922 self.get_config_bool(Config::IsMuted).await?.to_string(),
923 );
924 res.insert(
925 "private_tag",
926 self.get_config(Config::PrivateTag)
927 .await?
928 .unwrap_or_else(|| "<unset>".to_string()),
929 );
930
931 if let Some(metadata) = &*self.metadata.read().await {
932 if let Some(comment) = &metadata.comment {
933 res.insert("imap_server_comment", format!("{comment:?}"));
934 }
935
936 if let Some(admin) = &metadata.admin {
937 res.insert("imap_server_admin", format!("{admin:?}"));
938 }
939 }
940
941 res.insert("secondary_addrs", secondary_addrs);
942 res.insert(
943 "fetched_existing_msgs",
944 self.get_config_bool(Config::FetchedExistingMsgs)
945 .await?
946 .to_string(),
947 );
948 res.insert(
949 "show_emails",
950 self.get_config_int(Config::ShowEmails).await?.to_string(),
951 );
952 res.insert(
953 "download_limit",
954 self.get_config_int(Config::DownloadLimit)
955 .await?
956 .to_string(),
957 );
958 res.insert("sentbox_watch", sentbox_watch.to_string());
959 res.insert("mvbox_move", mvbox_move.to_string());
960 res.insert("only_fetch_mvbox", only_fetch_mvbox.to_string());
961 res.insert(
962 constants::DC_FOLDERS_CONFIGURED_KEY,
963 folders_configured.to_string(),
964 );
965 res.insert("configured_inbox_folder", configured_inbox_folder);
966 res.insert("configured_sentbox_folder", configured_sentbox_folder);
967 res.insert("configured_mvbox_folder", configured_mvbox_folder);
968 res.insert("configured_trash_folder", configured_trash_folder);
969 res.insert("mdns_enabled", mdns_enabled.to_string());
970 res.insert("e2ee_enabled", e2ee_enabled.to_string());
971 res.insert("bcc_self", bcc_self.to_string());
972 res.insert("sync_msgs", sync_msgs.to_string());
973 res.insert("disable_idle", disable_idle.to_string());
974 res.insert("private_key_count", prv_key_cnt.to_string());
975 res.insert("public_key_count", pub_key_cnt.to_string());
976 res.insert("fingerprint", fingerprint_str);
977 res.insert(
978 "webrtc_instance",
979 self.get_config(Config::WebrtcInstance)
980 .await?
981 .unwrap_or_else(|| "<unset>".to_string()),
982 );
983 res.insert(
984 "media_quality",
985 self.get_config_int(Config::MediaQuality).await?.to_string(),
986 );
987 res.insert(
988 "delete_device_after",
989 self.get_config_int(Config::DeleteDeviceAfter)
990 .await?
991 .to_string(),
992 );
993 res.insert(
994 "delete_server_after",
995 self.get_config_int(Config::DeleteServerAfter)
996 .await?
997 .to_string(),
998 );
999 res.insert(
1000 "delete_to_trash",
1001 self.get_config(Config::DeleteToTrash)
1002 .await?
1003 .unwrap_or_else(|| "<unset>".to_string()),
1004 );
1005 res.insert(
1006 "last_housekeeping",
1007 self.get_config_int(Config::LastHousekeeping)
1008 .await?
1009 .to_string(),
1010 );
1011 res.insert(
1012 "last_cant_decrypt_outgoing_msgs",
1013 self.get_config_int(Config::LastCantDecryptOutgoingMsgs)
1014 .await?
1015 .to_string(),
1016 );
1017 res.insert(
1018 "scan_all_folders_debounce_secs",
1019 self.get_config_int(Config::ScanAllFoldersDebounceSecs)
1020 .await?
1021 .to_string(),
1022 );
1023 res.insert(
1024 "quota_exceeding",
1025 self.get_config_int(Config::QuotaExceeding)
1026 .await?
1027 .to_string(),
1028 );
1029 res.insert(
1030 "authserv_id_candidates",
1031 self.get_config(Config::AuthservIdCandidates)
1032 .await?
1033 .unwrap_or_default(),
1034 );
1035 res.insert(
1036 "sign_unencrypted",
1037 self.get_config_int(Config::SignUnencrypted)
1038 .await?
1039 .to_string(),
1040 );
1041 res.insert(
1042 "protect_autocrypt",
1043 self.get_config_int(Config::ProtectAutocrypt)
1044 .await?
1045 .to_string(),
1046 );
1047 res.insert(
1048 "debug_logging",
1049 self.get_config_int(Config::DebugLogging).await?.to_string(),
1050 );
1051 res.insert(
1052 "last_msg_id",
1053 self.get_config_int(Config::LastMsgId).await?.to_string(),
1054 );
1055 res.insert(
1056 "gossip_period",
1057 self.get_config_int(Config::GossipPeriod).await?.to_string(),
1058 );
1059 res.insert(
1060 "verified_one_on_one_chats", self.get_config_bool(Config::VerifiedOneOnOneChats)
1062 .await?
1063 .to_string(),
1064 );
1065 res.insert(
1066 "webxdc_realtime_enabled",
1067 self.get_config_bool(Config::WebxdcRealtimeEnabled)
1068 .await?
1069 .to_string(),
1070 );
1071 res.insert(
1072 "donation_request_next_check",
1073 self.get_config_i64(Config::DonationRequestNextCheck)
1074 .await?
1075 .to_string(),
1076 );
1077 res.insert(
1078 "first_key_contacts_msg_id",
1079 self.sql
1080 .get_raw_config("first_key_contacts_msg_id")
1081 .await?
1082 .unwrap_or_default(),
1083 );
1084
1085 let elapsed = time_elapsed(&self.creation_time);
1086 res.insert("uptime", duration_to_str(elapsed));
1087
1088 Ok(res)
1089 }
1090
1091 async fn get_self_report(&self) -> Result<String> {
1092 #[derive(Default)]
1093 struct ChatNumbers {
1094 protected: u32,
1095 opportunistic_dc: u32,
1096 opportunistic_mua: u32,
1097 unencrypted_dc: u32,
1098 unencrypted_mua: u32,
1099 }
1100
1101 let mut res = String::new();
1102 res += &format!("core_version {}\n", get_version_str());
1103
1104 let num_msgs: u32 = self
1105 .sql
1106 .query_get_value(
1107 "SELECT COUNT(*) FROM msgs WHERE hidden=0 AND chat_id!=?",
1108 (DC_CHAT_ID_TRASH,),
1109 )
1110 .await?
1111 .unwrap_or_default();
1112 res += &format!("num_msgs {num_msgs}\n");
1113
1114 let num_chats: u32 = self
1115 .sql
1116 .query_get_value("SELECT COUNT(*) FROM chats WHERE id>9 AND blocked!=1", ())
1117 .await?
1118 .unwrap_or_default();
1119 res += &format!("num_chats {num_chats}\n");
1120
1121 let db_size = tokio::fs::metadata(&self.sql.dbfile).await?.len();
1122 res += &format!("db_size_bytes {db_size}\n");
1123
1124 let secret_key = &load_self_secret_key(self).await?.primary_key;
1125 let key_created = secret_key.public_key().created_at().timestamp();
1126 res += &format!("key_created {key_created}\n");
1127
1128 let three_months_ago = time().saturating_sub(3600 * 24 * 30 * 3);
1135 let chats = self
1136 .sql
1137 .query_map(
1138 "SELECT c.protected, m.param, m.msgrmsg
1139 FROM chats c
1140 JOIN msgs m
1141 ON c.id=m.chat_id
1142 AND m.id=(
1143 SELECT id
1144 FROM msgs
1145 WHERE chat_id=c.id
1146 AND hidden=0
1147 AND download_state=?
1148 AND to_id!=?
1149 ORDER BY timestamp DESC, id DESC LIMIT 1)
1150 WHERE c.id>9
1151 AND (c.blocked=0 OR c.blocked=2)
1152 AND IFNULL(m.timestamp,c.created_timestamp) > ?
1153 GROUP BY c.id",
1154 (DownloadState::Done, ContactId::INFO, three_months_ago),
1155 |row| {
1156 let protected: ProtectionStatus = row.get(0)?;
1157 let message_param: Params =
1158 row.get::<_, String>(1)?.parse().unwrap_or_default();
1159 let is_dc_message: bool = row.get(2)?;
1160 Ok((protected, message_param, is_dc_message))
1161 },
1162 |rows| {
1163 let mut chats = ChatNumbers::default();
1164 for row in rows {
1165 let (protected, message_param, is_dc_message) = row?;
1166 let encrypted = message_param
1167 .get_bool(Param::GuaranteeE2ee)
1168 .unwrap_or(false);
1169
1170 if protected == ProtectionStatus::Protected {
1171 chats.protected += 1;
1172 } else if encrypted {
1173 if is_dc_message {
1174 chats.opportunistic_dc += 1;
1175 } else {
1176 chats.opportunistic_mua += 1;
1177 }
1178 } else if is_dc_message {
1179 chats.unencrypted_dc += 1;
1180 } else {
1181 chats.unencrypted_mua += 1;
1182 }
1183 }
1184 Ok(chats)
1185 },
1186 )
1187 .await?;
1188 res += &format!("chats_protected {}\n", chats.protected);
1189 res += &format!("chats_opportunistic_dc {}\n", chats.opportunistic_dc);
1190 res += &format!("chats_opportunistic_mua {}\n", chats.opportunistic_mua);
1191 res += &format!("chats_unencrypted_dc {}\n", chats.unencrypted_dc);
1192 res += &format!("chats_unencrypted_mua {}\n", chats.unencrypted_mua);
1193
1194 let self_reporting_id = match self.get_config(Config::SelfReportingId).await? {
1195 Some(id) => id,
1196 None => {
1197 let id = create_id();
1198 self.set_config(Config::SelfReportingId, Some(&id)).await?;
1199 id
1200 }
1201 };
1202 res += &format!("self_reporting_id {self_reporting_id}");
1203
1204 Ok(res)
1205 }
1206
1207 pub async fn draft_self_report(&self) -> Result<ChatId> {
1213 const SELF_REPORTING_BOT_VCARD: &str = include_str!("../assets/self-reporting-bot.vcf");
1214 let contact_id: ContactId = *import_vcard(self, SELF_REPORTING_BOT_VCARD)
1215 .await?
1216 .first()
1217 .context("Self reporting bot vCard does not contain a contact")?;
1218 mark_contact_id_as_verified(self, contact_id, ContactId::SELF).await?;
1219
1220 let chat_id = ChatId::create_for_contact(self, contact_id).await?;
1221 chat_id
1222 .set_protection(self, ProtectionStatus::Protected, time(), Some(contact_id))
1223 .await?;
1224
1225 let mut msg = Message::new_text(self.get_self_report().await?);
1226
1227 chat_id.set_draft(self, Some(&mut msg)).await?;
1228
1229 Ok(chat_id)
1230 }
1231
1232 pub async fn get_fresh_msgs(&self) -> Result<Vec<MsgId>> {
1239 let list = self
1240 .sql
1241 .query_map(
1242 concat!(
1243 "SELECT m.id",
1244 " FROM msgs m",
1245 " LEFT JOIN contacts ct",
1246 " ON m.from_id=ct.id",
1247 " LEFT JOIN chats c",
1248 " ON m.chat_id=c.id",
1249 " WHERE m.state=?",
1250 " AND m.hidden=0",
1251 " AND m.chat_id>9",
1252 " AND ct.blocked=0",
1253 " AND c.blocked=0",
1254 " AND NOT(c.muted_until=-1 OR c.muted_until>?)",
1255 " ORDER BY m.timestamp DESC,m.id DESC;"
1256 ),
1257 (MessageState::InFresh, time()),
1258 |row| row.get::<_, MsgId>(0),
1259 |rows| {
1260 let mut list = Vec::new();
1261 for row in rows {
1262 list.push(row?);
1263 }
1264 Ok(list)
1265 },
1266 )
1267 .await?;
1268 Ok(list)
1269 }
1270
1271 pub async fn get_next_msgs(&self) -> Result<Vec<MsgId>> {
1276 let last_msg_id = match self.get_config(Config::LastMsgId).await? {
1277 Some(s) => MsgId::new(s.parse()?),
1278 None => {
1279 self.sql
1284 .query_row(
1285 "SELECT IFNULL((SELECT MAX(id) - 1 FROM msgs), 0)",
1286 (),
1287 |row| {
1288 let msg_id: MsgId = row.get(0)?;
1289 Ok(msg_id)
1290 },
1291 )
1292 .await?
1293 }
1294 };
1295
1296 let list = self
1297 .sql
1298 .query_map(
1299 "SELECT m.id
1300 FROM msgs m
1301 LEFT JOIN contacts ct
1302 ON m.from_id=ct.id
1303 LEFT JOIN chats c
1304 ON m.chat_id=c.id
1305 WHERE m.id>?
1306 AND m.hidden=0
1307 AND m.chat_id>9
1308 AND ct.blocked=0
1309 AND c.blocked!=1
1310 ORDER BY m.id ASC",
1311 (
1312 last_msg_id.to_u32(), ),
1314 |row| {
1315 let msg_id: MsgId = row.get(0)?;
1316 Ok(msg_id)
1317 },
1318 |rows| {
1319 let mut list = Vec::new();
1320 for row in rows {
1321 list.push(row?);
1322 }
1323 Ok(list)
1324 },
1325 )
1326 .await?;
1327 Ok(list)
1328 }
1329
1330 pub async fn wait_next_msgs(&self) -> Result<Vec<MsgId>> {
1341 self.new_msgs_notify.notified().await;
1342 let list = self.get_next_msgs().await?;
1343 Ok(list)
1344 }
1345
1346 pub async fn search_msgs(&self, chat_id: Option<ChatId>, query: &str) -> Result<Vec<MsgId>> {
1357 let real_query = query.trim().to_lowercase();
1358 if real_query.is_empty() {
1359 return Ok(Vec::new());
1360 }
1361 let str_like_in_text = format!("%{real_query}%");
1362
1363 let list = if let Some(chat_id) = chat_id {
1364 self.sql
1365 .query_map(
1366 "SELECT m.id AS id
1367 FROM msgs m
1368 LEFT JOIN contacts ct
1369 ON m.from_id=ct.id
1370 WHERE m.chat_id=?
1371 AND m.hidden=0
1372 AND ct.blocked=0
1373 AND IFNULL(txt_normalized, txt) LIKE ?
1374 ORDER BY m.timestamp,m.id;",
1375 (chat_id, str_like_in_text),
1376 |row| row.get::<_, MsgId>("id"),
1377 |rows| {
1378 let mut ret = Vec::new();
1379 for id in rows {
1380 ret.push(id?);
1381 }
1382 Ok(ret)
1383 },
1384 )
1385 .await?
1386 } else {
1387 self.sql
1398 .query_map(
1399 "SELECT m.id AS id
1400 FROM msgs m
1401 LEFT JOIN contacts ct
1402 ON m.from_id=ct.id
1403 LEFT JOIN chats c
1404 ON m.chat_id=c.id
1405 WHERE m.chat_id>9
1406 AND m.hidden=0
1407 AND c.blocked!=1
1408 AND ct.blocked=0
1409 AND IFNULL(txt_normalized, txt) LIKE ?
1410 ORDER BY m.id DESC LIMIT 1000",
1411 (str_like_in_text,),
1412 |row| row.get::<_, MsgId>("id"),
1413 |rows| {
1414 let mut ret = Vec::new();
1415 for id in rows {
1416 ret.push(id?);
1417 }
1418 Ok(ret)
1419 },
1420 )
1421 .await?
1422 };
1423
1424 Ok(list)
1425 }
1426
1427 pub async fn is_inbox(&self, folder_name: &str) -> Result<bool> {
1429 let inbox = self.get_config(Config::ConfiguredInboxFolder).await?;
1430 Ok(inbox.as_deref() == Some(folder_name))
1431 }
1432
1433 pub async fn is_sentbox(&self, folder_name: &str) -> Result<bool> {
1435 let sentbox = self.get_config(Config::ConfiguredSentboxFolder).await?;
1436 Ok(sentbox.as_deref() == Some(folder_name))
1437 }
1438
1439 pub async fn is_mvbox(&self, folder_name: &str) -> Result<bool> {
1441 let mvbox = self.get_config(Config::ConfiguredMvboxFolder).await?;
1442 Ok(mvbox.as_deref() == Some(folder_name))
1443 }
1444
1445 pub async fn is_trash(&self, folder_name: &str) -> Result<bool> {
1447 let trash = self.get_config(Config::ConfiguredTrashFolder).await?;
1448 Ok(trash.as_deref() == Some(folder_name))
1449 }
1450
1451 pub(crate) async fn should_delete_to_trash(&self) -> Result<bool> {
1452 if let Some(v) = self.get_config_bool_opt(Config::DeleteToTrash).await? {
1453 return Ok(v);
1454 }
1455 if let Some(provider) = self.get_configured_provider().await? {
1456 return Ok(provider.opt.delete_to_trash);
1457 }
1458 Ok(false)
1459 }
1460
1461 pub(crate) async fn get_delete_msgs_target(&self) -> Result<String> {
1464 if !self.should_delete_to_trash().await? {
1465 return Ok("".into());
1466 }
1467 self.get_config(Config::ConfiguredTrashFolder)
1468 .await?
1469 .context("No configured trash folder")
1470 }
1471
1472 pub(crate) fn derive_blobdir(dbfile: &Path) -> PathBuf {
1473 let mut blob_fname = OsString::new();
1474 blob_fname.push(dbfile.file_name().unwrap_or_default());
1475 blob_fname.push("-blobs");
1476 dbfile.with_file_name(blob_fname)
1477 }
1478
1479 pub(crate) fn derive_walfile(dbfile: &Path) -> PathBuf {
1480 let mut wal_fname = OsString::new();
1481 wal_fname.push(dbfile.file_name().unwrap_or_default());
1482 wal_fname.push("-wal");
1483 dbfile.with_file_name(wal_fname)
1484 }
1485}
1486
1487pub fn get_version_str() -> &'static str {
1489 &DC_VERSION_STR
1490}
1491
1492#[cfg(test)]
1493mod context_tests;