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 mdns_enabled = self.get_config_int(Config::MdnsEnabled).await?;
837 let bcc_self = self.get_config_int(Config::BccSelf).await?;
838 let sync_msgs = self.get_config_int(Config::SyncMsgs).await?;
839 let disable_idle = self.get_config_bool(Config::DisableIdle).await?;
840
841 let prv_key_cnt = self.sql.count("SELECT COUNT(*) FROM keypairs;", ()).await?;
842
843 let pub_key_cnt = self
844 .sql
845 .count("SELECT COUNT(*) FROM public_keys;", ())
846 .await?;
847 let fingerprint_str = match self_fingerprint(self).await {
848 Ok(fp) => fp.to_string(),
849 Err(err) => format!("<key failure: {err}>"),
850 };
851
852 let sentbox_watch = self.get_config_int(Config::SentboxWatch).await?;
853 let mvbox_move = self.get_config_int(Config::MvboxMove).await?;
854 let only_fetch_mvbox = self.get_config_int(Config::OnlyFetchMvbox).await?;
855 let folders_configured = self
856 .sql
857 .get_raw_config_int(constants::DC_FOLDERS_CONFIGURED_KEY)
858 .await?
859 .unwrap_or_default();
860
861 let configured_inbox_folder = self
862 .get_config(Config::ConfiguredInboxFolder)
863 .await?
864 .unwrap_or_else(|| "<unset>".to_string());
865 let configured_sentbox_folder = self
866 .get_config(Config::ConfiguredSentboxFolder)
867 .await?
868 .unwrap_or_else(|| "<unset>".to_string());
869 let configured_mvbox_folder = self
870 .get_config(Config::ConfiguredMvboxFolder)
871 .await?
872 .unwrap_or_else(|| "<unset>".to_string());
873 let configured_trash_folder = self
874 .get_config(Config::ConfiguredTrashFolder)
875 .await?
876 .unwrap_or_else(|| "<unset>".to_string());
877
878 let mut res = get_info();
879
880 res.insert("bot", self.get_config_int(Config::Bot).await?.to_string());
882 res.insert("number_of_chats", chats.to_string());
883 res.insert("number_of_chat_messages", unblocked_msgs.to_string());
884 res.insert("messages_in_contact_requests", request_msgs.to_string());
885 res.insert("number_of_contacts", contacts.to_string());
886 res.insert("database_dir", self.get_dbfile().display().to_string());
887 res.insert("database_version", dbversion.to_string());
888 res.insert(
889 "database_encrypted",
890 self.sql
891 .is_encrypted()
892 .await
893 .map_or_else(|| "closed".to_string(), |b| b.to_string()),
894 );
895 res.insert("journal_mode", journal_mode);
896 res.insert("blobdir", self.get_blobdir().display().to_string());
897 res.insert(
898 "selfavatar",
899 self.get_config(Config::Selfavatar)
900 .await?
901 .unwrap_or_else(|| "<unset>".to_string()),
902 );
903 res.insert("is_configured", is_configured.to_string());
904 res.insert("proxy_enabled", proxy_enabled.to_string());
905 res.insert("entered_account_settings", l.to_string());
906 res.insert("used_account_settings", l2);
907
908 if let Some(server_id) = &*self.server_id.read().await {
909 res.insert("imap_server_id", format!("{server_id:?}"));
910 }
911
912 res.insert("is_chatmail", self.is_chatmail().await?.to_string());
913 res.insert(
914 "fix_is_chatmail",
915 self.get_config_bool(Config::FixIsChatmail)
916 .await?
917 .to_string(),
918 );
919 res.insert(
920 "is_muted",
921 self.get_config_bool(Config::IsMuted).await?.to_string(),
922 );
923 res.insert(
924 "private_tag",
925 self.get_config(Config::PrivateTag)
926 .await?
927 .unwrap_or_else(|| "<unset>".to_string()),
928 );
929
930 if let Some(metadata) = &*self.metadata.read().await {
931 if let Some(comment) = &metadata.comment {
932 res.insert("imap_server_comment", format!("{comment:?}"));
933 }
934
935 if let Some(admin) = &metadata.admin {
936 res.insert("imap_server_admin", format!("{admin:?}"));
937 }
938 }
939
940 res.insert("secondary_addrs", secondary_addrs);
941 res.insert(
942 "fetched_existing_msgs",
943 self.get_config_bool(Config::FetchedExistingMsgs)
944 .await?
945 .to_string(),
946 );
947 res.insert(
948 "show_emails",
949 self.get_config_int(Config::ShowEmails).await?.to_string(),
950 );
951 res.insert(
952 "download_limit",
953 self.get_config_int(Config::DownloadLimit)
954 .await?
955 .to_string(),
956 );
957 res.insert("sentbox_watch", sentbox_watch.to_string());
958 res.insert("mvbox_move", mvbox_move.to_string());
959 res.insert("only_fetch_mvbox", only_fetch_mvbox.to_string());
960 res.insert(
961 constants::DC_FOLDERS_CONFIGURED_KEY,
962 folders_configured.to_string(),
963 );
964 res.insert("configured_inbox_folder", configured_inbox_folder);
965 res.insert("configured_sentbox_folder", configured_sentbox_folder);
966 res.insert("configured_mvbox_folder", configured_mvbox_folder);
967 res.insert("configured_trash_folder", configured_trash_folder);
968 res.insert("mdns_enabled", mdns_enabled.to_string());
969 res.insert("bcc_self", bcc_self.to_string());
970 res.insert("sync_msgs", sync_msgs.to_string());
971 res.insert("disable_idle", disable_idle.to_string());
972 res.insert("private_key_count", prv_key_cnt.to_string());
973 res.insert("public_key_count", pub_key_cnt.to_string());
974 res.insert("fingerprint", fingerprint_str);
975 res.insert(
976 "webrtc_instance",
977 self.get_config(Config::WebrtcInstance)
978 .await?
979 .unwrap_or_else(|| "<unset>".to_string()),
980 );
981 res.insert(
982 "media_quality",
983 self.get_config_int(Config::MediaQuality).await?.to_string(),
984 );
985 res.insert(
986 "delete_device_after",
987 self.get_config_int(Config::DeleteDeviceAfter)
988 .await?
989 .to_string(),
990 );
991 res.insert(
992 "delete_server_after",
993 self.get_config_int(Config::DeleteServerAfter)
994 .await?
995 .to_string(),
996 );
997 res.insert(
998 "delete_to_trash",
999 self.get_config(Config::DeleteToTrash)
1000 .await?
1001 .unwrap_or_else(|| "<unset>".to_string()),
1002 );
1003 res.insert(
1004 "last_housekeeping",
1005 self.get_config_int(Config::LastHousekeeping)
1006 .await?
1007 .to_string(),
1008 );
1009 res.insert(
1010 "last_cant_decrypt_outgoing_msgs",
1011 self.get_config_int(Config::LastCantDecryptOutgoingMsgs)
1012 .await?
1013 .to_string(),
1014 );
1015 res.insert(
1016 "scan_all_folders_debounce_secs",
1017 self.get_config_int(Config::ScanAllFoldersDebounceSecs)
1018 .await?
1019 .to_string(),
1020 );
1021 res.insert(
1022 "quota_exceeding",
1023 self.get_config_int(Config::QuotaExceeding)
1024 .await?
1025 .to_string(),
1026 );
1027 res.insert(
1028 "authserv_id_candidates",
1029 self.get_config(Config::AuthservIdCandidates)
1030 .await?
1031 .unwrap_or_default(),
1032 );
1033 res.insert(
1034 "sign_unencrypted",
1035 self.get_config_int(Config::SignUnencrypted)
1036 .await?
1037 .to_string(),
1038 );
1039 res.insert(
1040 "protect_autocrypt",
1041 self.get_config_int(Config::ProtectAutocrypt)
1042 .await?
1043 .to_string(),
1044 );
1045 res.insert(
1046 "debug_logging",
1047 self.get_config_int(Config::DebugLogging).await?.to_string(),
1048 );
1049 res.insert(
1050 "last_msg_id",
1051 self.get_config_int(Config::LastMsgId).await?.to_string(),
1052 );
1053 res.insert(
1054 "gossip_period",
1055 self.get_config_int(Config::GossipPeriod).await?.to_string(),
1056 );
1057 res.insert(
1058 "verified_one_on_one_chats", self.get_config_bool(Config::VerifiedOneOnOneChats)
1060 .await?
1061 .to_string(),
1062 );
1063 res.insert(
1064 "webxdc_realtime_enabled",
1065 self.get_config_bool(Config::WebxdcRealtimeEnabled)
1066 .await?
1067 .to_string(),
1068 );
1069 res.insert(
1070 "donation_request_next_check",
1071 self.get_config_i64(Config::DonationRequestNextCheck)
1072 .await?
1073 .to_string(),
1074 );
1075 res.insert(
1076 "first_key_contacts_msg_id",
1077 self.sql
1078 .get_raw_config("first_key_contacts_msg_id")
1079 .await?
1080 .unwrap_or_default(),
1081 );
1082
1083 let elapsed = time_elapsed(&self.creation_time);
1084 res.insert("uptime", duration_to_str(elapsed));
1085
1086 Ok(res)
1087 }
1088
1089 async fn get_self_report(&self) -> Result<String> {
1090 #[derive(Default)]
1091 struct ChatNumbers {
1092 protected: u32,
1093 opportunistic_dc: u32,
1094 opportunistic_mua: u32,
1095 unencrypted_dc: u32,
1096 unencrypted_mua: u32,
1097 }
1098
1099 let mut res = String::new();
1100 res += &format!("core_version {}\n", get_version_str());
1101
1102 let num_msgs: u32 = self
1103 .sql
1104 .query_get_value(
1105 "SELECT COUNT(*) FROM msgs WHERE hidden=0 AND chat_id!=?",
1106 (DC_CHAT_ID_TRASH,),
1107 )
1108 .await?
1109 .unwrap_or_default();
1110 res += &format!("num_msgs {num_msgs}\n");
1111
1112 let num_chats: u32 = self
1113 .sql
1114 .query_get_value("SELECT COUNT(*) FROM chats WHERE id>9 AND blocked!=1", ())
1115 .await?
1116 .unwrap_or_default();
1117 res += &format!("num_chats {num_chats}\n");
1118
1119 let db_size = tokio::fs::metadata(&self.sql.dbfile).await?.len();
1120 res += &format!("db_size_bytes {db_size}\n");
1121
1122 let secret_key = &load_self_secret_key(self).await?.primary_key;
1123 let key_created = secret_key.public_key().created_at().timestamp();
1124 res += &format!("key_created {key_created}\n");
1125
1126 let three_months_ago = time().saturating_sub(3600 * 24 * 30 * 3);
1133 let chats = self
1134 .sql
1135 .query_map(
1136 "SELECT c.protected, m.param, m.msgrmsg
1137 FROM chats c
1138 JOIN msgs m
1139 ON c.id=m.chat_id
1140 AND m.id=(
1141 SELECT id
1142 FROM msgs
1143 WHERE chat_id=c.id
1144 AND hidden=0
1145 AND download_state=?
1146 AND to_id!=?
1147 ORDER BY timestamp DESC, id DESC LIMIT 1)
1148 WHERE c.id>9
1149 AND (c.blocked=0 OR c.blocked=2)
1150 AND IFNULL(m.timestamp,c.created_timestamp) > ?
1151 GROUP BY c.id",
1152 (DownloadState::Done, ContactId::INFO, three_months_ago),
1153 |row| {
1154 let protected: ProtectionStatus = row.get(0)?;
1155 let message_param: Params =
1156 row.get::<_, String>(1)?.parse().unwrap_or_default();
1157 let is_dc_message: bool = row.get(2)?;
1158 Ok((protected, message_param, is_dc_message))
1159 },
1160 |rows| {
1161 let mut chats = ChatNumbers::default();
1162 for row in rows {
1163 let (protected, message_param, is_dc_message) = row?;
1164 let encrypted = message_param
1165 .get_bool(Param::GuaranteeE2ee)
1166 .unwrap_or(false);
1167
1168 if protected == ProtectionStatus::Protected {
1169 chats.protected += 1;
1170 } else if encrypted {
1171 if is_dc_message {
1172 chats.opportunistic_dc += 1;
1173 } else {
1174 chats.opportunistic_mua += 1;
1175 }
1176 } else if is_dc_message {
1177 chats.unencrypted_dc += 1;
1178 } else {
1179 chats.unencrypted_mua += 1;
1180 }
1181 }
1182 Ok(chats)
1183 },
1184 )
1185 .await?;
1186 res += &format!("chats_protected {}\n", chats.protected);
1187 res += &format!("chats_opportunistic_dc {}\n", chats.opportunistic_dc);
1188 res += &format!("chats_opportunistic_mua {}\n", chats.opportunistic_mua);
1189 res += &format!("chats_unencrypted_dc {}\n", chats.unencrypted_dc);
1190 res += &format!("chats_unencrypted_mua {}\n", chats.unencrypted_mua);
1191
1192 let self_reporting_id = match self.get_config(Config::SelfReportingId).await? {
1193 Some(id) => id,
1194 None => {
1195 let id = create_id();
1196 self.set_config(Config::SelfReportingId, Some(&id)).await?;
1197 id
1198 }
1199 };
1200 res += &format!("self_reporting_id {self_reporting_id}");
1201
1202 Ok(res)
1203 }
1204
1205 pub async fn draft_self_report(&self) -> Result<ChatId> {
1211 const SELF_REPORTING_BOT_VCARD: &str = include_str!("../assets/self-reporting-bot.vcf");
1212 let contact_id: ContactId = *import_vcard(self, SELF_REPORTING_BOT_VCARD)
1213 .await?
1214 .first()
1215 .context("Self reporting bot vCard does not contain a contact")?;
1216 mark_contact_id_as_verified(self, contact_id, Some(ContactId::SELF)).await?;
1217
1218 let chat_id = ChatId::create_for_contact(self, contact_id).await?;
1219 chat_id
1220 .set_protection(self, ProtectionStatus::Protected, time(), Some(contact_id))
1221 .await?;
1222
1223 let mut msg = Message::new_text(self.get_self_report().await?);
1224
1225 chat_id.set_draft(self, Some(&mut msg)).await?;
1226
1227 Ok(chat_id)
1228 }
1229
1230 pub async fn get_fresh_msgs(&self) -> Result<Vec<MsgId>> {
1237 let list = self
1238 .sql
1239 .query_map(
1240 concat!(
1241 "SELECT m.id",
1242 " FROM msgs m",
1243 " LEFT JOIN contacts ct",
1244 " ON m.from_id=ct.id",
1245 " LEFT JOIN chats c",
1246 " ON m.chat_id=c.id",
1247 " WHERE m.state=?",
1248 " AND m.hidden=0",
1249 " AND m.chat_id>9",
1250 " AND ct.blocked=0",
1251 " AND c.blocked=0",
1252 " AND NOT(c.muted_until=-1 OR c.muted_until>?)",
1253 " ORDER BY m.timestamp DESC,m.id DESC;"
1254 ),
1255 (MessageState::InFresh, time()),
1256 |row| row.get::<_, MsgId>(0),
1257 |rows| {
1258 let mut list = Vec::new();
1259 for row in rows {
1260 list.push(row?);
1261 }
1262 Ok(list)
1263 },
1264 )
1265 .await?;
1266 Ok(list)
1267 }
1268
1269 pub async fn get_next_msgs(&self) -> Result<Vec<MsgId>> {
1274 let last_msg_id = match self.get_config(Config::LastMsgId).await? {
1275 Some(s) => MsgId::new(s.parse()?),
1276 None => {
1277 self.sql
1282 .query_row(
1283 "SELECT IFNULL((SELECT MAX(id) - 1 FROM msgs), 0)",
1284 (),
1285 |row| {
1286 let msg_id: MsgId = row.get(0)?;
1287 Ok(msg_id)
1288 },
1289 )
1290 .await?
1291 }
1292 };
1293
1294 let list = self
1295 .sql
1296 .query_map(
1297 "SELECT m.id
1298 FROM msgs m
1299 LEFT JOIN contacts ct
1300 ON m.from_id=ct.id
1301 LEFT JOIN chats c
1302 ON m.chat_id=c.id
1303 WHERE m.id>?
1304 AND m.hidden=0
1305 AND m.chat_id>9
1306 AND ct.blocked=0
1307 AND c.blocked!=1
1308 ORDER BY m.id ASC",
1309 (
1310 last_msg_id.to_u32(), ),
1312 |row| {
1313 let msg_id: MsgId = row.get(0)?;
1314 Ok(msg_id)
1315 },
1316 |rows| {
1317 let mut list = Vec::new();
1318 for row in rows {
1319 list.push(row?);
1320 }
1321 Ok(list)
1322 },
1323 )
1324 .await?;
1325 Ok(list)
1326 }
1327
1328 pub async fn wait_next_msgs(&self) -> Result<Vec<MsgId>> {
1339 self.new_msgs_notify.notified().await;
1340 let list = self.get_next_msgs().await?;
1341 Ok(list)
1342 }
1343
1344 pub async fn search_msgs(&self, chat_id: Option<ChatId>, query: &str) -> Result<Vec<MsgId>> {
1355 let real_query = query.trim().to_lowercase();
1356 if real_query.is_empty() {
1357 return Ok(Vec::new());
1358 }
1359 let str_like_in_text = format!("%{real_query}%");
1360
1361 let list = if let Some(chat_id) = chat_id {
1362 self.sql
1363 .query_map(
1364 "SELECT m.id AS id
1365 FROM msgs m
1366 LEFT JOIN contacts ct
1367 ON m.from_id=ct.id
1368 WHERE m.chat_id=?
1369 AND m.hidden=0
1370 AND ct.blocked=0
1371 AND IFNULL(txt_normalized, txt) LIKE ?
1372 ORDER BY m.timestamp,m.id;",
1373 (chat_id, str_like_in_text),
1374 |row| row.get::<_, MsgId>("id"),
1375 |rows| {
1376 let mut ret = Vec::new();
1377 for id in rows {
1378 ret.push(id?);
1379 }
1380 Ok(ret)
1381 },
1382 )
1383 .await?
1384 } else {
1385 self.sql
1396 .query_map(
1397 "SELECT m.id AS id
1398 FROM msgs m
1399 LEFT JOIN contacts ct
1400 ON m.from_id=ct.id
1401 LEFT JOIN chats c
1402 ON m.chat_id=c.id
1403 WHERE m.chat_id>9
1404 AND m.hidden=0
1405 AND c.blocked!=1
1406 AND ct.blocked=0
1407 AND IFNULL(txt_normalized, txt) LIKE ?
1408 ORDER BY m.id DESC LIMIT 1000",
1409 (str_like_in_text,),
1410 |row| row.get::<_, MsgId>("id"),
1411 |rows| {
1412 let mut ret = Vec::new();
1413 for id in rows {
1414 ret.push(id?);
1415 }
1416 Ok(ret)
1417 },
1418 )
1419 .await?
1420 };
1421
1422 Ok(list)
1423 }
1424
1425 pub async fn is_inbox(&self, folder_name: &str) -> Result<bool> {
1427 let inbox = self.get_config(Config::ConfiguredInboxFolder).await?;
1428 Ok(inbox.as_deref() == Some(folder_name))
1429 }
1430
1431 pub async fn is_sentbox(&self, folder_name: &str) -> Result<bool> {
1433 let sentbox = self.get_config(Config::ConfiguredSentboxFolder).await?;
1434 Ok(sentbox.as_deref() == Some(folder_name))
1435 }
1436
1437 pub async fn is_mvbox(&self, folder_name: &str) -> Result<bool> {
1439 let mvbox = self.get_config(Config::ConfiguredMvboxFolder).await?;
1440 Ok(mvbox.as_deref() == Some(folder_name))
1441 }
1442
1443 pub async fn is_trash(&self, folder_name: &str) -> Result<bool> {
1445 let trash = self.get_config(Config::ConfiguredTrashFolder).await?;
1446 Ok(trash.as_deref() == Some(folder_name))
1447 }
1448
1449 pub(crate) async fn should_delete_to_trash(&self) -> Result<bool> {
1450 if let Some(v) = self.get_config_bool_opt(Config::DeleteToTrash).await? {
1451 return Ok(v);
1452 }
1453 if let Some(provider) = self.get_configured_provider().await? {
1454 return Ok(provider.opt.delete_to_trash);
1455 }
1456 Ok(false)
1457 }
1458
1459 pub(crate) async fn get_delete_msgs_target(&self) -> Result<String> {
1462 if !self.should_delete_to_trash().await? {
1463 return Ok("".into());
1464 }
1465 self.get_config(Config::ConfiguredTrashFolder)
1466 .await?
1467 .context("No configured trash folder")
1468 }
1469
1470 pub(crate) fn derive_blobdir(dbfile: &Path) -> PathBuf {
1471 let mut blob_fname = OsString::new();
1472 blob_fname.push(dbfile.file_name().unwrap_or_default());
1473 blob_fname.push("-blobs");
1474 dbfile.with_file_name(blob_fname)
1475 }
1476
1477 pub(crate) fn derive_walfile(dbfile: &Path) -> PathBuf {
1478 let mut wal_fname = OsString::new();
1479 wal_fname.push(dbfile.file_name().unwrap_or_default());
1480 wal_fname.push("-wal");
1481 dbfile.with_file_name(wal_fname)
1482 }
1483}
1484
1485pub fn get_version_str() -> &'static str {
1487 &DC_VERSION_STR
1488}
1489
1490#[cfg(test)]
1491mod context_tests;