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