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::{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
309#[derive(Debug)]
311enum RunningState {
312 Running { cancel_sender: Sender<()> },
314
315 ShallStop { request: tools::Time },
317
318 Stopped,
320}
321
322impl Default for RunningState {
323 fn default() -> Self {
324 Self::Stopped
325 }
326}
327
328pub fn get_info() -> BTreeMap<&'static str, String> {
335 let mut res = BTreeMap::new();
336
337 #[cfg(debug_assertions)]
338 res.insert(
339 "debug_assertions",
340 "On - DO NOT RELEASE THIS BUILD".to_string(),
341 );
342 #[cfg(not(debug_assertions))]
343 res.insert("debug_assertions", "Off".to_string());
344
345 res.insert("deltachat_core_version", format!("v{}", &*DC_VERSION_STR));
346 res.insert("sqlite_version", rusqlite::version().to_string());
347 res.insert("arch", (std::mem::size_of::<usize>() * 8).to_string());
348 res.insert("num_cpus", num_cpus::get().to_string());
349 res.insert("level", "awesome".into());
350 res
351}
352
353impl Context {
354 pub async fn new(
356 dbfile: &Path,
357 id: u32,
358 events: Events,
359 stock_strings: StockStrings,
360 ) -> Result<Context> {
361 let context =
362 Self::new_closed(dbfile, id, events, stock_strings, Default::default()).await?;
363
364 if context.check_passphrase("".to_string()).await? {
366 context.sql.open(&context, "".to_string()).await?;
367 }
368 Ok(context)
369 }
370
371 pub async fn new_closed(
373 dbfile: &Path,
374 id: u32,
375 events: Events,
376 stockstrings: StockStrings,
377 push_subscriber: PushSubscriber,
378 ) -> Result<Context> {
379 let mut blob_fname = OsString::new();
380 blob_fname.push(dbfile.file_name().unwrap_or_default());
381 blob_fname.push("-blobs");
382 let blobdir = dbfile.with_file_name(blob_fname);
383 if !blobdir.exists() {
384 tokio::fs::create_dir_all(&blobdir).await?;
385 }
386 let context = Context::with_blobdir(
387 dbfile.into(),
388 blobdir,
389 id,
390 events,
391 stockstrings,
392 push_subscriber,
393 )?;
394 Ok(context)
395 }
396
397 pub async fn open(&self, passphrase: String) -> Result<bool> {
402 if self.sql.check_passphrase(passphrase.clone()).await? {
403 self.sql.open(self, passphrase).await?;
404 Ok(true)
405 } else {
406 Ok(false)
407 }
408 }
409
410 pub async fn change_passphrase(&self, passphrase: String) -> Result<()> {
412 self.sql.change_passphrase(passphrase).await?;
413 Ok(())
414 }
415
416 pub async fn is_open(&self) -> bool {
418 self.sql.is_open().await
419 }
420
421 pub(crate) async fn check_passphrase(&self, passphrase: String) -> Result<bool> {
427 self.sql.check_passphrase(passphrase).await
428 }
429
430 pub(crate) fn with_blobdir(
431 dbfile: PathBuf,
432 blobdir: PathBuf,
433 id: u32,
434 events: Events,
435 stockstrings: StockStrings,
436 push_subscriber: PushSubscriber,
437 ) -> Result<Context> {
438 ensure!(
439 blobdir.is_dir(),
440 "Blobdir does not exist: {}",
441 blobdir.display()
442 );
443
444 let new_msgs_notify = Notify::new();
445 new_msgs_notify.notify_one();
448
449 let inner = InnerContext {
450 id,
451 blobdir,
452 running_state: RwLock::new(Default::default()),
453 sql: Sql::new(dbfile),
454 smeared_timestamp: SmearedTimestamp::new(),
455 generating_key_mutex: Mutex::new(()),
456 oauth2_mutex: Mutex::new(()),
457 wrong_pw_warning_mutex: Mutex::new(()),
458 translated_stockstrings: stockstrings,
459 events,
460 scheduler: SchedulerState::new(),
461 ratelimit: RwLock::new(Ratelimit::new(Duration::new(60, 0), 6.0)), quota: RwLock::new(None),
463 resync_request: AtomicBool::new(false),
464 new_msgs_notify,
465 server_id: RwLock::new(None),
466 metadata: RwLock::new(None),
467 creation_time: tools::Time::now(),
468 last_full_folder_scan: Mutex::new(None),
469 last_error: parking_lot::RwLock::new("".to_string()),
470 migration_error: parking_lot::RwLock::new(None),
471 debug_logging: std::sync::RwLock::new(None),
472 push_subscriber,
473 push_subscribed: AtomicBool::new(false),
474 iroh: Arc::new(RwLock::new(None)),
475 self_fingerprint: OnceLock::new(),
476 };
477
478 let ctx = Context {
479 inner: Arc::new(inner),
480 };
481
482 Ok(ctx)
483 }
484
485 pub async fn start_io(&self) {
487 if !self.is_configured().await.unwrap_or_default() {
488 warn!(self, "can not start io on a context that is not configured");
489 return;
490 }
491
492 if self.is_chatmail().await.unwrap_or_default() {
493 let mut lock = self.ratelimit.write().await;
494 *lock = Ratelimit::new(Duration::new(3, 0), 3.0);
496 }
497
498 self.sql.config_cache.write().await.clear();
504
505 self.scheduler.start(self.clone()).await;
506 }
507
508 pub async fn stop_io(&self) {
510 self.scheduler.stop(self).await;
511 if let Some(iroh) = self.iroh.write().await.take() {
512 tokio::spawn(async move {
519 let _ = tokio::time::timeout(Duration::from_secs(60), iroh.close()).await;
522 });
523 }
524 }
525
526 pub async fn restart_io_if_running(&self) {
529 self.scheduler.restart(self).await;
530 }
531
532 pub async fn maybe_network(&self) {
534 if let Some(ref iroh) = *self.iroh.read().await {
535 iroh.network_change().await;
536 }
537 self.scheduler.maybe_network().await;
538 }
539
540 pub async fn is_chatmail(&self) -> Result<bool> {
542 self.get_config_bool(Config::IsChatmail).await
543 }
544
545 pub(crate) async fn get_max_smtp_rcpt_to(&self) -> Result<usize> {
547 let is_chatmail = self.is_chatmail().await?;
548 let val = self
549 .get_configured_provider()
550 .await?
551 .and_then(|provider| provider.opt.max_smtp_rcpt_to)
552 .map_or_else(
553 || match is_chatmail {
554 true => usize::MAX,
555 false => constants::DEFAULT_MAX_SMTP_RCPT_TO,
556 },
557 usize::from,
558 );
559 Ok(val)
560 }
561
562 pub async fn background_fetch(&self) -> Result<()> {
568 if !(self.is_configured().await?) {
569 return Ok(());
570 }
571
572 let address = self.get_primary_self_addr().await?;
573 let time_start = tools::Time::now();
574 info!(self, "background_fetch started fetching {address}.");
575
576 if self.scheduler.is_running().await {
577 self.scheduler.maybe_network().await;
578 self.wait_for_all_work_done().await;
579 } else {
580 let _pause_guard = self.scheduler.pause(self.clone()).await?;
583
584 let mut connection = Imap::new_configured(self, channel::bounded(1).1).await?;
586 let mut session = connection.prepare(self).await?;
587
588 for folder_meaning in [FolderMeaning::Inbox, FolderMeaning::Mvbox] {
592 if let Some((_folder_config, watch_folder)) =
593 convert_folder_meaning(self, folder_meaning).await?
594 {
595 connection
596 .fetch_move_delete(self, &mut session, &watch_folder, folder_meaning)
597 .await?;
598 }
599 }
600
601 if self
603 .quota_needs_update(DC_BACKGROUND_FETCH_QUOTA_CHECK_RATELIMIT)
604 .await
605 {
606 if let Err(err) = self.update_recent_quota(&mut session).await {
607 warn!(self, "Failed to update quota: {err:#}.");
608 }
609 }
610 }
611
612 info!(
613 self,
614 "background_fetch done for {address} took {:?}.",
615 time_elapsed(&time_start),
616 );
617
618 Ok(())
619 }
620
621 pub(crate) async fn schedule_resync(&self) -> Result<()> {
622 self.resync_request.store(true, Ordering::Relaxed);
623 self.scheduler.interrupt_inbox().await;
624 Ok(())
625 }
626
627 #[cfg(feature = "internals")]
631 pub fn sql(&self) -> &Sql {
632 &self.inner.sql
633 }
634
635 pub fn get_dbfile(&self) -> &Path {
637 self.sql.dbfile.as_path()
638 }
639
640 pub fn get_blobdir(&self) -> &Path {
642 self.blobdir.as_path()
643 }
644
645 pub fn emit_event(&self, event: EventType) {
647 {
648 let lock = self.debug_logging.read().expect("RwLock is poisoned");
649 if let Some(debug_logging) = &*lock {
650 debug_logging.log_event(event.clone());
651 }
652 }
653 self.events.emit(Event {
654 id: self.id,
655 typ: event,
656 });
657 }
658
659 pub fn emit_msgs_changed_without_ids(&self) {
661 self.emit_event(EventType::MsgsChanged {
662 chat_id: ChatId::new(0),
663 msg_id: MsgId::new(0),
664 });
665 }
666
667 pub fn emit_msgs_changed(&self, chat_id: ChatId, msg_id: MsgId) {
673 logged_debug_assert!(
674 self,
675 !chat_id.is_unset(),
676 "emit_msgs_changed: chat_id is unset."
677 );
678 logged_debug_assert!(
679 self,
680 !msg_id.is_unset(),
681 "emit_msgs_changed: msg_id is unset."
682 );
683
684 self.emit_event(EventType::MsgsChanged { chat_id, msg_id });
685 chatlist_events::emit_chatlist_changed(self);
686 chatlist_events::emit_chatlist_item_changed(self, chat_id);
687 }
688
689 pub fn emit_msgs_changed_without_msg_id(&self, chat_id: ChatId) {
691 logged_debug_assert!(
692 self,
693 !chat_id.is_unset(),
694 "emit_msgs_changed_without_msg_id: chat_id is unset."
695 );
696
697 self.emit_event(EventType::MsgsChanged {
698 chat_id,
699 msg_id: MsgId::new(0),
700 });
701 chatlist_events::emit_chatlist_changed(self);
702 chatlist_events::emit_chatlist_item_changed(self, chat_id);
703 }
704
705 pub fn emit_incoming_msg(&self, chat_id: ChatId, msg_id: MsgId) {
707 debug_assert!(!chat_id.is_unset());
708 debug_assert!(!msg_id.is_unset());
709
710 self.emit_event(EventType::IncomingMsg { chat_id, msg_id });
711 chatlist_events::emit_chatlist_changed(self);
712 chatlist_events::emit_chatlist_item_changed(self, chat_id);
713 }
714
715 pub async fn emit_location_changed(&self, contact_id: Option<ContactId>) -> Result<()> {
717 self.emit_event(EventType::LocationChanged(contact_id));
718
719 if let Some(msg_id) = self
720 .get_config_parsed::<u32>(Config::WebxdcIntegration)
721 .await?
722 {
723 self.emit_event(EventType::WebxdcStatusUpdate {
724 msg_id: MsgId::new(msg_id),
725 status_update_serial: Default::default(),
726 })
727 }
728
729 Ok(())
730 }
731
732 pub fn get_event_emitter(&self) -> EventEmitter {
737 self.events.get_emitter()
738 }
739
740 pub fn get_id(&self) -> u32 {
742 self.id
743 }
744
745 pub(crate) async fn alloc_ongoing(&self) -> Result<Receiver<()>> {
755 let mut s = self.running_state.write().await;
756 ensure!(
757 matches!(*s, RunningState::Stopped),
758 "There is already another ongoing process running."
759 );
760
761 let (sender, receiver) = channel::bounded(1);
762 *s = RunningState::Running {
763 cancel_sender: sender,
764 };
765
766 Ok(receiver)
767 }
768
769 pub(crate) async fn free_ongoing(&self) {
770 let mut s = self.running_state.write().await;
771 if let RunningState::ShallStop { request } = *s {
772 info!(self, "Ongoing stopped in {:?}", time_elapsed(&request));
773 }
774 *s = RunningState::Stopped;
775 }
776
777 pub async fn stop_ongoing(&self) {
779 let mut s = self.running_state.write().await;
780 match &*s {
781 RunningState::Running { cancel_sender } => {
782 if let Err(err) = cancel_sender.send(()).await {
783 warn!(self, "could not cancel ongoing: {:#}", err);
784 }
785 info!(self, "Signaling the ongoing process to stop ASAP.",);
786 *s = RunningState::ShallStop {
787 request: tools::Time::now(),
788 };
789 }
790 RunningState::ShallStop { .. } | RunningState::Stopped => {
791 info!(self, "No ongoing process to stop.",);
792 }
793 }
794 }
795
796 #[allow(unused)]
797 pub(crate) async fn shall_stop_ongoing(&self) -> bool {
798 match &*self.running_state.read().await {
799 RunningState::Running { .. } => false,
800 RunningState::ShallStop { .. } | RunningState::Stopped => true,
801 }
802 }
803
804 pub async fn get_info(&self) -> Result<BTreeMap<&'static str, String>> {
810 let l = EnteredLoginParam::load(self).await?;
811 let l2 = ConfiguredLoginParam::load(self)
812 .await?
813 .map_or_else(|| "Not configured".to_string(), |param| param.to_string());
814 let secondary_addrs = self.get_secondary_self_addrs().await?.join(", ");
815 let chats = get_chat_cnt(self).await?;
816 let unblocked_msgs = message::get_unblocked_msg_cnt(self).await;
817 let request_msgs = message::get_request_msg_cnt(self).await;
818 let contacts = Contact::get_real_cnt(self).await?;
819 let is_configured = self.get_config_int(Config::Configured).await?;
820 let proxy_enabled = self.get_config_int(Config::ProxyEnabled).await?;
821 let dbversion = self
822 .sql
823 .get_raw_config_int("dbversion")
824 .await?
825 .unwrap_or_default();
826 let journal_mode = self
827 .sql
828 .query_get_value("PRAGMA journal_mode;", ())
829 .await?
830 .unwrap_or_else(|| "unknown".to_string());
831 let e2ee_enabled = self.get_config_int(Config::E2eeEnabled).await?;
832 let mdns_enabled = self.get_config_int(Config::MdnsEnabled).await?;
833 let bcc_self = self.get_config_int(Config::BccSelf).await?;
834 let sync_msgs = self.get_config_int(Config::SyncMsgs).await?;
835 let disable_idle = self.get_config_bool(Config::DisableIdle).await?;
836
837 let prv_key_cnt = self.sql.count("SELECT COUNT(*) FROM keypairs;", ()).await?;
838
839 let pub_key_cnt = self
840 .sql
841 .count("SELECT COUNT(*) FROM public_keys;", ())
842 .await?;
843 let fingerprint_str = match self_fingerprint(self).await {
844 Ok(fp) => fp.to_string(),
845 Err(err) => format!("<key failure: {err}>"),
846 };
847
848 let sentbox_watch = self.get_config_int(Config::SentboxWatch).await?;
849 let mvbox_move = self.get_config_int(Config::MvboxMove).await?;
850 let only_fetch_mvbox = self.get_config_int(Config::OnlyFetchMvbox).await?;
851 let folders_configured = self
852 .sql
853 .get_raw_config_int(constants::DC_FOLDERS_CONFIGURED_KEY)
854 .await?
855 .unwrap_or_default();
856
857 let configured_inbox_folder = self
858 .get_config(Config::ConfiguredInboxFolder)
859 .await?
860 .unwrap_or_else(|| "<unset>".to_string());
861 let configured_sentbox_folder = self
862 .get_config(Config::ConfiguredSentboxFolder)
863 .await?
864 .unwrap_or_else(|| "<unset>".to_string());
865 let configured_mvbox_folder = self
866 .get_config(Config::ConfiguredMvboxFolder)
867 .await?
868 .unwrap_or_else(|| "<unset>".to_string());
869 let configured_trash_folder = self
870 .get_config(Config::ConfiguredTrashFolder)
871 .await?
872 .unwrap_or_else(|| "<unset>".to_string());
873
874 let mut res = get_info();
875
876 res.insert("bot", self.get_config_int(Config::Bot).await?.to_string());
878 res.insert("number_of_chats", chats.to_string());
879 res.insert("number_of_chat_messages", unblocked_msgs.to_string());
880 res.insert("messages_in_contact_requests", request_msgs.to_string());
881 res.insert("number_of_contacts", contacts.to_string());
882 res.insert("database_dir", self.get_dbfile().display().to_string());
883 res.insert("database_version", dbversion.to_string());
884 res.insert(
885 "database_encrypted",
886 self.sql
887 .is_encrypted()
888 .await
889 .map_or_else(|| "closed".to_string(), |b| b.to_string()),
890 );
891 res.insert("journal_mode", journal_mode);
892 res.insert("blobdir", self.get_blobdir().display().to_string());
893 res.insert(
894 "selfavatar",
895 self.get_config(Config::Selfavatar)
896 .await?
897 .unwrap_or_else(|| "<unset>".to_string()),
898 );
899 res.insert("is_configured", is_configured.to_string());
900 res.insert("proxy_enabled", proxy_enabled.to_string());
901 res.insert("entered_account_settings", l.to_string());
902 res.insert("used_account_settings", l2);
903
904 if let Some(server_id) = &*self.server_id.read().await {
905 res.insert("imap_server_id", format!("{server_id:?}"));
906 }
907
908 res.insert("is_chatmail", self.is_chatmail().await?.to_string());
909 res.insert(
910 "fix_is_chatmail",
911 self.get_config_bool(Config::FixIsChatmail)
912 .await?
913 .to_string(),
914 );
915 res.insert(
916 "is_muted",
917 self.get_config_bool(Config::IsMuted).await?.to_string(),
918 );
919 res.insert(
920 "private_tag",
921 self.get_config(Config::PrivateTag)
922 .await?
923 .unwrap_or_else(|| "<unset>".to_string()),
924 );
925
926 if let Some(metadata) = &*self.metadata.read().await {
927 if let Some(comment) = &metadata.comment {
928 res.insert("imap_server_comment", format!("{comment:?}"));
929 }
930
931 if let Some(admin) = &metadata.admin {
932 res.insert("imap_server_admin", format!("{admin:?}"));
933 }
934 }
935
936 res.insert("secondary_addrs", secondary_addrs);
937 res.insert(
938 "fetched_existing_msgs",
939 self.get_config_bool(Config::FetchedExistingMsgs)
940 .await?
941 .to_string(),
942 );
943 res.insert(
944 "show_emails",
945 self.get_config_int(Config::ShowEmails).await?.to_string(),
946 );
947 res.insert(
948 "download_limit",
949 self.get_config_int(Config::DownloadLimit)
950 .await?
951 .to_string(),
952 );
953 res.insert("sentbox_watch", sentbox_watch.to_string());
954 res.insert("mvbox_move", mvbox_move.to_string());
955 res.insert("only_fetch_mvbox", only_fetch_mvbox.to_string());
956 res.insert(
957 constants::DC_FOLDERS_CONFIGURED_KEY,
958 folders_configured.to_string(),
959 );
960 res.insert("configured_inbox_folder", configured_inbox_folder);
961 res.insert("configured_sentbox_folder", configured_sentbox_folder);
962 res.insert("configured_mvbox_folder", configured_mvbox_folder);
963 res.insert("configured_trash_folder", configured_trash_folder);
964 res.insert("mdns_enabled", mdns_enabled.to_string());
965 res.insert("e2ee_enabled", e2ee_enabled.to_string());
966 res.insert("bcc_self", bcc_self.to_string());
967 res.insert("sync_msgs", sync_msgs.to_string());
968 res.insert("disable_idle", disable_idle.to_string());
969 res.insert("private_key_count", prv_key_cnt.to_string());
970 res.insert("public_key_count", pub_key_cnt.to_string());
971 res.insert("fingerprint", fingerprint_str);
972 res.insert(
973 "webrtc_instance",
974 self.get_config(Config::WebrtcInstance)
975 .await?
976 .unwrap_or_else(|| "<unset>".to_string()),
977 );
978 res.insert(
979 "media_quality",
980 self.get_config_int(Config::MediaQuality).await?.to_string(),
981 );
982 res.insert(
983 "delete_device_after",
984 self.get_config_int(Config::DeleteDeviceAfter)
985 .await?
986 .to_string(),
987 );
988 res.insert(
989 "delete_server_after",
990 self.get_config_int(Config::DeleteServerAfter)
991 .await?
992 .to_string(),
993 );
994 res.insert(
995 "delete_to_trash",
996 self.get_config(Config::DeleteToTrash)
997 .await?
998 .unwrap_or_else(|| "<unset>".to_string()),
999 );
1000 res.insert(
1001 "last_housekeeping",
1002 self.get_config_int(Config::LastHousekeeping)
1003 .await?
1004 .to_string(),
1005 );
1006 res.insert(
1007 "last_cant_decrypt_outgoing_msgs",
1008 self.get_config_int(Config::LastCantDecryptOutgoingMsgs)
1009 .await?
1010 .to_string(),
1011 );
1012 res.insert(
1013 "scan_all_folders_debounce_secs",
1014 self.get_config_int(Config::ScanAllFoldersDebounceSecs)
1015 .await?
1016 .to_string(),
1017 );
1018 res.insert(
1019 "quota_exceeding",
1020 self.get_config_int(Config::QuotaExceeding)
1021 .await?
1022 .to_string(),
1023 );
1024 res.insert(
1025 "authserv_id_candidates",
1026 self.get_config(Config::AuthservIdCandidates)
1027 .await?
1028 .unwrap_or_default(),
1029 );
1030 res.insert(
1031 "sign_unencrypted",
1032 self.get_config_int(Config::SignUnencrypted)
1033 .await?
1034 .to_string(),
1035 );
1036 res.insert(
1037 "protect_autocrypt",
1038 self.get_config_int(Config::ProtectAutocrypt)
1039 .await?
1040 .to_string(),
1041 );
1042 res.insert(
1043 "debug_logging",
1044 self.get_config_int(Config::DebugLogging).await?.to_string(),
1045 );
1046 res.insert(
1047 "last_msg_id",
1048 self.get_config_int(Config::LastMsgId).await?.to_string(),
1049 );
1050 res.insert(
1051 "gossip_period",
1052 self.get_config_int(Config::GossipPeriod).await?.to_string(),
1053 );
1054 res.insert(
1055 "verified_one_on_one_chats", self.get_config_bool(Config::VerifiedOneOnOneChats)
1057 .await?
1058 .to_string(),
1059 );
1060 res.insert(
1061 "webxdc_realtime_enabled",
1062 self.get_config_bool(Config::WebxdcRealtimeEnabled)
1063 .await?
1064 .to_string(),
1065 );
1066 res.insert(
1067 "donation_request_next_check",
1068 self.get_config_i64(Config::DonationRequestNextCheck)
1069 .await?
1070 .to_string(),
1071 );
1072 res.insert(
1073 "first_key_contacts_msg_id",
1074 self.sql
1075 .get_raw_config("first_key_contacts_msg_id")
1076 .await?
1077 .unwrap_or_default(),
1078 );
1079
1080 let elapsed = time_elapsed(&self.creation_time);
1081 res.insert("uptime", duration_to_str(elapsed));
1082
1083 Ok(res)
1084 }
1085
1086 async fn get_self_report(&self) -> Result<String> {
1087 #[derive(Default)]
1088 struct ChatNumbers {
1089 protected: u32,
1090 opportunistic_dc: u32,
1091 opportunistic_mua: u32,
1092 unencrypted_dc: u32,
1093 unencrypted_mua: u32,
1094 }
1095
1096 let mut res = String::new();
1097 res += &format!("core_version {}\n", get_version_str());
1098
1099 let num_msgs: u32 = self
1100 .sql
1101 .query_get_value(
1102 "SELECT COUNT(*) FROM msgs WHERE hidden=0 AND chat_id!=?",
1103 (DC_CHAT_ID_TRASH,),
1104 )
1105 .await?
1106 .unwrap_or_default();
1107 res += &format!("num_msgs {num_msgs}\n");
1108
1109 let num_chats: u32 = self
1110 .sql
1111 .query_get_value("SELECT COUNT(*) FROM chats WHERE id>9 AND blocked!=1", ())
1112 .await?
1113 .unwrap_or_default();
1114 res += &format!("num_chats {num_chats}\n");
1115
1116 let db_size = tokio::fs::metadata(&self.sql.dbfile).await?.len();
1117 res += &format!("db_size_bytes {db_size}\n");
1118
1119 let secret_key = &load_self_secret_key(self).await?.primary_key;
1120 let key_created = secret_key.public_key().created_at().timestamp();
1121 res += &format!("key_created {key_created}\n");
1122
1123 let three_months_ago = time().saturating_sub(3600 * 24 * 30 * 3);
1130 let chats = self
1131 .sql
1132 .query_map(
1133 "SELECT c.protected, m.param, m.msgrmsg
1134 FROM chats c
1135 JOIN msgs m
1136 ON c.id=m.chat_id
1137 AND m.id=(
1138 SELECT id
1139 FROM msgs
1140 WHERE chat_id=c.id
1141 AND hidden=0
1142 AND download_state=?
1143 AND to_id!=?
1144 ORDER BY timestamp DESC, id DESC LIMIT 1)
1145 WHERE c.id>9
1146 AND (c.blocked=0 OR c.blocked=2)
1147 AND IFNULL(m.timestamp,c.created_timestamp) > ?
1148 GROUP BY c.id",
1149 (DownloadState::Done, ContactId::INFO, three_months_ago),
1150 |row| {
1151 let protected: ProtectionStatus = row.get(0)?;
1152 let message_param: Params =
1153 row.get::<_, String>(1)?.parse().unwrap_or_default();
1154 let is_dc_message: bool = row.get(2)?;
1155 Ok((protected, message_param, is_dc_message))
1156 },
1157 |rows| {
1158 let mut chats = ChatNumbers::default();
1159 for row in rows {
1160 let (protected, message_param, is_dc_message) = row?;
1161 let encrypted = message_param
1162 .get_bool(Param::GuaranteeE2ee)
1163 .unwrap_or(false);
1164
1165 if protected == ProtectionStatus::Protected {
1166 chats.protected += 1;
1167 } else if encrypted {
1168 if is_dc_message {
1169 chats.opportunistic_dc += 1;
1170 } else {
1171 chats.opportunistic_mua += 1;
1172 }
1173 } else if is_dc_message {
1174 chats.unencrypted_dc += 1;
1175 } else {
1176 chats.unencrypted_mua += 1;
1177 }
1178 }
1179 Ok(chats)
1180 },
1181 )
1182 .await?;
1183 res += &format!("chats_protected {}\n", chats.protected);
1184 res += &format!("chats_opportunistic_dc {}\n", chats.opportunistic_dc);
1185 res += &format!("chats_opportunistic_mua {}\n", chats.opportunistic_mua);
1186 res += &format!("chats_unencrypted_dc {}\n", chats.unencrypted_dc);
1187 res += &format!("chats_unencrypted_mua {}\n", chats.unencrypted_mua);
1188
1189 let self_reporting_id = match self.get_config(Config::SelfReportingId).await? {
1190 Some(id) => id,
1191 None => {
1192 let id = create_id();
1193 self.set_config(Config::SelfReportingId, Some(&id)).await?;
1194 id
1195 }
1196 };
1197 res += &format!("self_reporting_id {self_reporting_id}");
1198
1199 Ok(res)
1200 }
1201
1202 pub async fn draft_self_report(&self) -> Result<ChatId> {
1208 const SELF_REPORTING_BOT_VCARD: &str = include_str!("../assets/self-reporting-bot.vcf");
1209 let contact_id: ContactId = *import_vcard(self, SELF_REPORTING_BOT_VCARD)
1210 .await?
1211 .first()
1212 .context("Self reporting bot vCard does not contain a contact")?;
1213 mark_contact_id_as_verified(self, contact_id, ContactId::SELF).await?;
1214
1215 let chat_id = ChatId::create_for_contact(self, contact_id).await?;
1216 chat_id
1217 .set_protection(self, ProtectionStatus::Protected, time(), Some(contact_id))
1218 .await?;
1219
1220 let mut msg = Message::new_text(self.get_self_report().await?);
1221
1222 chat_id.set_draft(self, Some(&mut msg)).await?;
1223
1224 Ok(chat_id)
1225 }
1226
1227 pub async fn get_fresh_msgs(&self) -> Result<Vec<MsgId>> {
1234 let list = self
1235 .sql
1236 .query_map(
1237 concat!(
1238 "SELECT m.id",
1239 " FROM msgs m",
1240 " LEFT JOIN contacts ct",
1241 " ON m.from_id=ct.id",
1242 " LEFT JOIN chats c",
1243 " ON m.chat_id=c.id",
1244 " WHERE m.state=?",
1245 " AND m.hidden=0",
1246 " AND m.chat_id>9",
1247 " AND ct.blocked=0",
1248 " AND c.blocked=0",
1249 " AND NOT(c.muted_until=-1 OR c.muted_until>?)",
1250 " ORDER BY m.timestamp DESC,m.id DESC;"
1251 ),
1252 (MessageState::InFresh, time()),
1253 |row| row.get::<_, MsgId>(0),
1254 |rows| {
1255 let mut list = Vec::new();
1256 for row in rows {
1257 list.push(row?);
1258 }
1259 Ok(list)
1260 },
1261 )
1262 .await?;
1263 Ok(list)
1264 }
1265
1266 pub async fn get_next_msgs(&self) -> Result<Vec<MsgId>> {
1271 let last_msg_id = match self.get_config(Config::LastMsgId).await? {
1272 Some(s) => MsgId::new(s.parse()?),
1273 None => {
1274 self.sql
1279 .query_row(
1280 "SELECT IFNULL((SELECT MAX(id) - 1 FROM msgs), 0)",
1281 (),
1282 |row| {
1283 let msg_id: MsgId = row.get(0)?;
1284 Ok(msg_id)
1285 },
1286 )
1287 .await?
1288 }
1289 };
1290
1291 let list = self
1292 .sql
1293 .query_map(
1294 "SELECT m.id
1295 FROM msgs m
1296 LEFT JOIN contacts ct
1297 ON m.from_id=ct.id
1298 LEFT JOIN chats c
1299 ON m.chat_id=c.id
1300 WHERE m.id>?
1301 AND m.hidden=0
1302 AND m.chat_id>9
1303 AND ct.blocked=0
1304 AND c.blocked!=1
1305 ORDER BY m.id ASC",
1306 (
1307 last_msg_id.to_u32(), ),
1309 |row| {
1310 let msg_id: MsgId = row.get(0)?;
1311 Ok(msg_id)
1312 },
1313 |rows| {
1314 let mut list = Vec::new();
1315 for row in rows {
1316 list.push(row?);
1317 }
1318 Ok(list)
1319 },
1320 )
1321 .await?;
1322 Ok(list)
1323 }
1324
1325 pub async fn wait_next_msgs(&self) -> Result<Vec<MsgId>> {
1336 self.new_msgs_notify.notified().await;
1337 let list = self.get_next_msgs().await?;
1338 Ok(list)
1339 }
1340
1341 pub async fn search_msgs(&self, chat_id: Option<ChatId>, query: &str) -> Result<Vec<MsgId>> {
1352 let real_query = query.trim().to_lowercase();
1353 if real_query.is_empty() {
1354 return Ok(Vec::new());
1355 }
1356 let str_like_in_text = format!("%{real_query}%");
1357
1358 let list = if let Some(chat_id) = chat_id {
1359 self.sql
1360 .query_map(
1361 "SELECT m.id AS id
1362 FROM msgs m
1363 LEFT JOIN contacts ct
1364 ON m.from_id=ct.id
1365 WHERE m.chat_id=?
1366 AND m.hidden=0
1367 AND ct.blocked=0
1368 AND IFNULL(txt_normalized, txt) LIKE ?
1369 ORDER BY m.timestamp,m.id;",
1370 (chat_id, str_like_in_text),
1371 |row| row.get::<_, MsgId>("id"),
1372 |rows| {
1373 let mut ret = Vec::new();
1374 for id in rows {
1375 ret.push(id?);
1376 }
1377 Ok(ret)
1378 },
1379 )
1380 .await?
1381 } else {
1382 self.sql
1393 .query_map(
1394 "SELECT m.id AS id
1395 FROM msgs m
1396 LEFT JOIN contacts ct
1397 ON m.from_id=ct.id
1398 LEFT JOIN chats c
1399 ON m.chat_id=c.id
1400 WHERE m.chat_id>9
1401 AND m.hidden=0
1402 AND c.blocked!=1
1403 AND ct.blocked=0
1404 AND IFNULL(txt_normalized, txt) LIKE ?
1405 ORDER BY m.id DESC LIMIT 1000",
1406 (str_like_in_text,),
1407 |row| row.get::<_, MsgId>("id"),
1408 |rows| {
1409 let mut ret = Vec::new();
1410 for id in rows {
1411 ret.push(id?);
1412 }
1413 Ok(ret)
1414 },
1415 )
1416 .await?
1417 };
1418
1419 Ok(list)
1420 }
1421
1422 pub async fn is_inbox(&self, folder_name: &str) -> Result<bool> {
1424 let inbox = self.get_config(Config::ConfiguredInboxFolder).await?;
1425 Ok(inbox.as_deref() == Some(folder_name))
1426 }
1427
1428 pub async fn is_sentbox(&self, folder_name: &str) -> Result<bool> {
1430 let sentbox = self.get_config(Config::ConfiguredSentboxFolder).await?;
1431 Ok(sentbox.as_deref() == Some(folder_name))
1432 }
1433
1434 pub async fn is_mvbox(&self, folder_name: &str) -> Result<bool> {
1436 let mvbox = self.get_config(Config::ConfiguredMvboxFolder).await?;
1437 Ok(mvbox.as_deref() == Some(folder_name))
1438 }
1439
1440 pub async fn is_trash(&self, folder_name: &str) -> Result<bool> {
1442 let trash = self.get_config(Config::ConfiguredTrashFolder).await?;
1443 Ok(trash.as_deref() == Some(folder_name))
1444 }
1445
1446 pub(crate) async fn should_delete_to_trash(&self) -> Result<bool> {
1447 if let Some(v) = self.get_config_bool_opt(Config::DeleteToTrash).await? {
1448 return Ok(v);
1449 }
1450 if let Some(provider) = self.get_configured_provider().await? {
1451 return Ok(provider.opt.delete_to_trash);
1452 }
1453 Ok(false)
1454 }
1455
1456 pub(crate) async fn get_delete_msgs_target(&self) -> Result<String> {
1459 if !self.should_delete_to_trash().await? {
1460 return Ok("".into());
1461 }
1462 self.get_config(Config::ConfiguredTrashFolder)
1463 .await?
1464 .context("No configured trash folder")
1465 }
1466
1467 pub(crate) fn derive_blobdir(dbfile: &Path) -> PathBuf {
1468 let mut blob_fname = OsString::new();
1469 blob_fname.push(dbfile.file_name().unwrap_or_default());
1470 blob_fname.push("-blobs");
1471 dbfile.with_file_name(blob_fname)
1472 }
1473
1474 pub(crate) fn derive_walfile(dbfile: &Path) -> PathBuf {
1475 let mut wal_fname = OsString::new();
1476 wal_fname.push(dbfile.file_name().unwrap_or_default());
1477 wal_fname.push("-wal");
1478 dbfile.with_file_name(wal_fname)
1479 }
1480}
1481
1482pub fn get_version_str() -> &'static str {
1484 &DC_VERSION_STR
1485}
1486
1487#[cfg(test)]
1488mod context_tests;