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