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::{bail, ensure, Context as _, Result};
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::{get_chat_cnt, ChatId, ProtectionStatus};
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::{import_vcard, mark_contact_id_as_verified, Contact, ContactId};
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::{convert_folder_meaning, SchedulerState};
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
1045 let elapsed = time_elapsed(&self.creation_time);
1046 res.insert("uptime", duration_to_str(elapsed));
1047
1048 Ok(res)
1049 }
1050
1051 async fn get_self_report(&self) -> Result<String> {
1052 #[derive(Default)]
1053 struct ChatNumbers {
1054 protected: u32,
1055 protection_broken: u32,
1056 opportunistic_dc: u32,
1057 opportunistic_mua: u32,
1058 unencrypted_dc: u32,
1059 unencrypted_mua: u32,
1060 }
1061
1062 let mut res = String::new();
1063 res += &format!("core_version {}\n", get_version_str());
1064
1065 let num_msgs: u32 = self
1066 .sql
1067 .query_get_value(
1068 "SELECT COUNT(*) FROM msgs WHERE hidden=0 AND chat_id!=?",
1069 (DC_CHAT_ID_TRASH,),
1070 )
1071 .await?
1072 .unwrap_or_default();
1073 res += &format!("num_msgs {num_msgs}\n");
1074
1075 let num_chats: u32 = self
1076 .sql
1077 .query_get_value("SELECT COUNT(*) FROM chats WHERE id>9 AND blocked!=1", ())
1078 .await?
1079 .unwrap_or_default();
1080 res += &format!("num_chats {num_chats}\n");
1081
1082 let db_size = tokio::fs::metadata(&self.sql.dbfile).await?.len();
1083 res += &format!("db_size_bytes {db_size}\n");
1084
1085 let secret_key = &load_self_secret_key(self).await?.primary_key;
1086 let key_created = secret_key.public_key().created_at().timestamp();
1087 res += &format!("key_created {key_created}\n");
1088
1089 let three_months_ago = time().saturating_sub(3600 * 24 * 30 * 3);
1097 let chats = self
1098 .sql
1099 .query_map(
1100 "SELECT c.protected, m.param, m.msgrmsg
1101 FROM chats c
1102 JOIN msgs m
1103 ON c.id=m.chat_id
1104 AND m.id=(
1105 SELECT id
1106 FROM msgs
1107 WHERE chat_id=c.id
1108 AND hidden=0
1109 AND download_state=?
1110 AND to_id!=?
1111 ORDER BY timestamp DESC, id DESC LIMIT 1)
1112 WHERE c.id>9
1113 AND (c.blocked=0 OR c.blocked=2)
1114 AND IFNULL(m.timestamp,c.created_timestamp) > ?
1115 GROUP BY c.id",
1116 (DownloadState::Done, ContactId::INFO, three_months_ago),
1117 |row| {
1118 let protected: ProtectionStatus = row.get(0)?;
1119 let message_param: Params =
1120 row.get::<_, String>(1)?.parse().unwrap_or_default();
1121 let is_dc_message: bool = row.get(2)?;
1122 Ok((protected, message_param, is_dc_message))
1123 },
1124 |rows| {
1125 let mut chats = ChatNumbers::default();
1126 for row in rows {
1127 let (protected, message_param, is_dc_message) = row?;
1128 let encrypted = message_param
1129 .get_bool(Param::GuaranteeE2ee)
1130 .unwrap_or(false);
1131
1132 if protected == ProtectionStatus::Protected {
1133 chats.protected += 1;
1134 } else if protected == ProtectionStatus::ProtectionBroken {
1135 chats.protection_broken += 1;
1136 } else if encrypted {
1137 if is_dc_message {
1138 chats.opportunistic_dc += 1;
1139 } else {
1140 chats.opportunistic_mua += 1;
1141 }
1142 } else if is_dc_message {
1143 chats.unencrypted_dc += 1;
1144 } else {
1145 chats.unencrypted_mua += 1;
1146 }
1147 }
1148 Ok(chats)
1149 },
1150 )
1151 .await?;
1152 res += &format!("chats_protected {}\n", chats.protected);
1153 res += &format!("chats_protection_broken {}\n", chats.protection_broken);
1154 res += &format!("chats_opportunistic_dc {}\n", chats.opportunistic_dc);
1155 res += &format!("chats_opportunistic_mua {}\n", chats.opportunistic_mua);
1156 res += &format!("chats_unencrypted_dc {}\n", chats.unencrypted_dc);
1157 res += &format!("chats_unencrypted_mua {}\n", chats.unencrypted_mua);
1158
1159 let self_reporting_id = match self.get_config(Config::SelfReportingId).await? {
1160 Some(id) => id,
1161 None => {
1162 let id = create_id();
1163 self.set_config(Config::SelfReportingId, Some(&id)).await?;
1164 id
1165 }
1166 };
1167 res += &format!("self_reporting_id {self_reporting_id}");
1168
1169 Ok(res)
1170 }
1171
1172 pub async fn draft_self_report(&self) -> Result<ChatId> {
1178 const SELF_REPORTING_BOT_VCARD: &str = include_str!("../assets/self-reporting-bot.vcf");
1179 let contact_id: ContactId = *import_vcard(self, SELF_REPORTING_BOT_VCARD)
1180 .await?
1181 .first()
1182 .context("Self reporting bot vCard does not contain a contact")?;
1183 mark_contact_id_as_verified(self, contact_id, ContactId::SELF).await?;
1184
1185 let chat_id = ChatId::create_for_contact(self, contact_id).await?;
1186 chat_id
1187 .set_protection(self, ProtectionStatus::Protected, time(), Some(contact_id))
1188 .await?;
1189
1190 let mut msg = Message::new_text(self.get_self_report().await?);
1191
1192 chat_id.set_draft(self, Some(&mut msg)).await?;
1193
1194 Ok(chat_id)
1195 }
1196
1197 pub async fn get_fresh_msgs(&self) -> Result<Vec<MsgId>> {
1204 let list = self
1205 .sql
1206 .query_map(
1207 concat!(
1208 "SELECT m.id",
1209 " FROM msgs m",
1210 " LEFT JOIN contacts ct",
1211 " ON m.from_id=ct.id",
1212 " LEFT JOIN chats c",
1213 " ON m.chat_id=c.id",
1214 " WHERE m.state=?",
1215 " AND m.hidden=0",
1216 " AND m.chat_id>9",
1217 " AND ct.blocked=0",
1218 " AND c.blocked=0",
1219 " AND NOT(c.muted_until=-1 OR c.muted_until>?)",
1220 " ORDER BY m.timestamp DESC,m.id DESC;"
1221 ),
1222 (MessageState::InFresh, time()),
1223 |row| row.get::<_, MsgId>(0),
1224 |rows| {
1225 let mut list = Vec::new();
1226 for row in rows {
1227 list.push(row?);
1228 }
1229 Ok(list)
1230 },
1231 )
1232 .await?;
1233 Ok(list)
1234 }
1235
1236 pub async fn get_next_msgs(&self) -> Result<Vec<MsgId>> {
1241 let last_msg_id = match self.get_config(Config::LastMsgId).await? {
1242 Some(s) => MsgId::new(s.parse()?),
1243 None => {
1244 self.sql
1249 .query_row(
1250 "SELECT IFNULL((SELECT MAX(id) - 1 FROM msgs), 0)",
1251 (),
1252 |row| {
1253 let msg_id: MsgId = row.get(0)?;
1254 Ok(msg_id)
1255 },
1256 )
1257 .await?
1258 }
1259 };
1260
1261 let list = self
1262 .sql
1263 .query_map(
1264 "SELECT m.id
1265 FROM msgs m
1266 LEFT JOIN contacts ct
1267 ON m.from_id=ct.id
1268 LEFT JOIN chats c
1269 ON m.chat_id=c.id
1270 WHERE m.id>?
1271 AND m.hidden=0
1272 AND m.chat_id>9
1273 AND ct.blocked=0
1274 AND c.blocked!=1
1275 ORDER BY m.id ASC",
1276 (
1277 last_msg_id.to_u32(), ),
1279 |row| {
1280 let msg_id: MsgId = row.get(0)?;
1281 Ok(msg_id)
1282 },
1283 |rows| {
1284 let mut list = Vec::new();
1285 for row in rows {
1286 list.push(row?);
1287 }
1288 Ok(list)
1289 },
1290 )
1291 .await?;
1292 Ok(list)
1293 }
1294
1295 pub async fn wait_next_msgs(&self) -> Result<Vec<MsgId>> {
1306 self.new_msgs_notify.notified().await;
1307 let list = self.get_next_msgs().await?;
1308 Ok(list)
1309 }
1310
1311 pub async fn search_msgs(&self, chat_id: Option<ChatId>, query: &str) -> Result<Vec<MsgId>> {
1322 let real_query = query.trim().to_lowercase();
1323 if real_query.is_empty() {
1324 return Ok(Vec::new());
1325 }
1326 let str_like_in_text = format!("%{real_query}%");
1327
1328 let list = if let Some(chat_id) = chat_id {
1329 self.sql
1330 .query_map(
1331 "SELECT m.id AS id
1332 FROM msgs m
1333 LEFT JOIN contacts ct
1334 ON m.from_id=ct.id
1335 WHERE m.chat_id=?
1336 AND m.hidden=0
1337 AND ct.blocked=0
1338 AND IFNULL(txt_normalized, txt) LIKE ?
1339 ORDER BY m.timestamp,m.id;",
1340 (chat_id, str_like_in_text),
1341 |row| row.get::<_, MsgId>("id"),
1342 |rows| {
1343 let mut ret = Vec::new();
1344 for id in rows {
1345 ret.push(id?);
1346 }
1347 Ok(ret)
1348 },
1349 )
1350 .await?
1351 } else {
1352 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 LEFT JOIN chats c
1369 ON m.chat_id=c.id
1370 WHERE m.chat_id>9
1371 AND m.hidden=0
1372 AND c.blocked!=1
1373 AND ct.blocked=0
1374 AND IFNULL(txt_normalized, txt) LIKE ?
1375 ORDER BY m.id DESC LIMIT 1000",
1376 (str_like_in_text,),
1377 |row| row.get::<_, MsgId>("id"),
1378 |rows| {
1379 let mut ret = Vec::new();
1380 for id in rows {
1381 ret.push(id?);
1382 }
1383 Ok(ret)
1384 },
1385 )
1386 .await?
1387 };
1388
1389 Ok(list)
1390 }
1391
1392 pub async fn is_inbox(&self, folder_name: &str) -> Result<bool> {
1394 let inbox = self.get_config(Config::ConfiguredInboxFolder).await?;
1395 Ok(inbox.as_deref() == Some(folder_name))
1396 }
1397
1398 pub async fn is_sentbox(&self, folder_name: &str) -> Result<bool> {
1400 let sentbox = self.get_config(Config::ConfiguredSentboxFolder).await?;
1401 Ok(sentbox.as_deref() == Some(folder_name))
1402 }
1403
1404 pub async fn is_mvbox(&self, folder_name: &str) -> Result<bool> {
1406 let mvbox = self.get_config(Config::ConfiguredMvboxFolder).await?;
1407 Ok(mvbox.as_deref() == Some(folder_name))
1408 }
1409
1410 pub async fn is_trash(&self, folder_name: &str) -> Result<bool> {
1412 let trash = self.get_config(Config::ConfiguredTrashFolder).await?;
1413 Ok(trash.as_deref() == Some(folder_name))
1414 }
1415
1416 pub(crate) async fn should_delete_to_trash(&self) -> Result<bool> {
1417 if let Some(v) = self.get_config_bool_opt(Config::DeleteToTrash).await? {
1418 return Ok(v);
1419 }
1420 if let Some(provider) = self.get_configured_provider().await? {
1421 return Ok(provider.opt.delete_to_trash);
1422 }
1423 Ok(false)
1424 }
1425
1426 pub(crate) async fn get_delete_msgs_target(&self) -> Result<String> {
1429 if !self.should_delete_to_trash().await? {
1430 return Ok("".into());
1431 }
1432 self.get_config(Config::ConfiguredTrashFolder)
1433 .await?
1434 .context("No configured trash folder")
1435 }
1436
1437 pub(crate) fn derive_blobdir(dbfile: &Path) -> PathBuf {
1438 let mut blob_fname = OsString::new();
1439 blob_fname.push(dbfile.file_name().unwrap_or_default());
1440 blob_fname.push("-blobs");
1441 dbfile.with_file_name(blob_fname)
1442 }
1443
1444 pub(crate) fn derive_walfile(dbfile: &Path) -> PathBuf {
1445 let mut wal_fname = OsString::new();
1446 wal_fname.push(dbfile.file_name().unwrap_or_default());
1447 wal_fname.push("-wal");
1448 dbfile.with_file_name(wal_fname)
1449 }
1450}
1451
1452pub fn get_version_str() -> &'static str {
1454 &DC_VERSION_STR
1455}
1456
1457#[cfg(test)]
1458mod context_tests;