1use std::collections::{BTreeMap, HashMap};
4use std::ffi::OsString;
5use std::ops::Deref;
6use std::path::{Path, PathBuf};
7use std::sync::atomic::AtomicBool;
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 ratelimit::Ratelimit;
14use tokio::sync::{Mutex, Notify, RwLock};
15
16use crate::chat::{ChatId, get_chat_cnt};
17use crate::config::Config;
18use crate::constants::{self, DC_BACKGROUND_FETCH_QUOTA_CHECK_RATELIMIT, DC_VERSION_STR};
19use crate::contact::{Contact, ContactId};
20use crate::debug_logging::DebugLogging;
21use crate::events::{Event, EventEmitter, EventType, Events};
22use crate::imap::{FolderMeaning, Imap, ServerMetadata};
23use crate::key::self_fingerprint;
24use crate::log::warn;
25use crate::logged_debug_assert;
26use crate::login_param::EnteredLoginParam;
27use crate::message::{self, MessageState, MsgId};
28use crate::net::tls::TlsSessionStore;
29use crate::peer_channels::Iroh;
30use crate::push::PushSubscriber;
31use crate::quota::QuotaInfo;
32use crate::scheduler::{ConnectivityStore, SchedulerState, convert_folder_meaning};
33use crate::sql::Sql;
34use crate::stock_str::StockStrings;
35use crate::timesmearing::SmearedTimestamp;
36use crate::tools::{self, duration_to_str, time, time_elapsed};
37use crate::transport::ConfiguredLoginParam;
38use crate::{chatlist_events, stats};
39
40#[derive(Clone, Debug)]
83pub struct ContextBuilder {
84 dbfile: PathBuf,
85 id: u32,
86 events: Events,
87 stock_strings: StockStrings,
88 password: Option<String>,
89
90 push_subscriber: Option<PushSubscriber>,
91}
92
93impl ContextBuilder {
94 pub fn new(dbfile: PathBuf) -> Self {
100 ContextBuilder {
101 dbfile,
102 id: rand::random(),
103 events: Events::new(),
104 stock_strings: StockStrings::new(),
105 password: None,
106 push_subscriber: None,
107 }
108 }
109
110 pub fn with_id(mut self, id: u32) -> Self {
120 self.id = id;
121 self
122 }
123
124 pub fn with_events(mut self, events: Events) -> Self {
133 self.events = events;
134 self
135 }
136
137 pub fn with_stock_strings(mut self, stock_strings: StockStrings) -> Self {
148 self.stock_strings = stock_strings;
149 self
150 }
151
152 pub fn with_password(mut self, password: String) -> Self {
157 self.password = Some(password);
158 self
159 }
160
161 pub(crate) fn with_push_subscriber(mut self, push_subscriber: PushSubscriber) -> Self {
163 self.push_subscriber = Some(push_subscriber);
164 self
165 }
166
167 pub async fn build(self) -> Result<Context> {
169 let push_subscriber = self.push_subscriber.unwrap_or_default();
170 let context = Context::new_closed(
171 &self.dbfile,
172 self.id,
173 self.events,
174 self.stock_strings,
175 push_subscriber,
176 )
177 .await?;
178 Ok(context)
179 }
180
181 pub async fn open(self) -> Result<Context> {
185 let password = self.password.clone().unwrap_or_default();
186 let context = self.build().await?;
187 match context.open(password).await? {
188 true => Ok(context),
189 false => bail!("database could not be decrypted, incorrect or missing password"),
190 }
191 }
192}
193
194#[derive(Clone, Debug)]
206pub struct Context {
207 pub(crate) inner: Arc<InnerContext>,
208}
209
210impl Deref for Context {
211 type Target = InnerContext;
212
213 fn deref(&self) -> &Self::Target {
214 &self.inner
215 }
216}
217
218#[derive(Debug)]
220pub struct InnerContext {
221 pub(crate) blobdir: PathBuf,
223 pub(crate) sql: Sql,
224 pub(crate) smeared_timestamp: SmearedTimestamp,
225 running_state: RwLock<RunningState>,
230 pub(crate) generating_key_mutex: Mutex<()>,
232 pub(crate) oauth2_mutex: Mutex<()>,
234 pub(crate) wrong_pw_warning_mutex: Mutex<()>,
236 pub(crate) translated_stockstrings: StockStrings,
237 pub(crate) events: Events,
238
239 pub(crate) scheduler: SchedulerState,
240 pub(crate) ratelimit: RwLock<Ratelimit>,
241
242 pub(crate) quota: RwLock<Option<QuotaInfo>>,
245
246 pub(crate) new_msgs_notify: Notify,
250
251 pub(crate) server_id: RwLock<Option<HashMap<String, String>>>,
255
256 pub(crate) metadata: RwLock<Option<ServerMetadata>>,
258
259 pub(crate) id: u32,
264
265 creation_time: tools::Time,
266
267 pub(crate) last_error: parking_lot::RwLock<String>,
271
272 pub(crate) migration_error: parking_lot::RwLock<Option<String>>,
278
279 pub(crate) debug_logging: std::sync::RwLock<Option<DebugLogging>>,
284
285 pub(crate) push_subscriber: PushSubscriber,
288
289 pub(crate) push_subscribed: AtomicBool,
291
292 pub(crate) tls_session_store: TlsSessionStore,
294
295 pub(crate) iroh: Arc<RwLock<Option<Iroh>>>,
297
298 pub(crate) self_fingerprint: OnceLock<String>,
302
303 pub(crate) connectivities: parking_lot::Mutex<Vec<ConnectivityStore>>,
306}
307
308#[derive(Debug, Default)]
310enum RunningState {
311 Running { cancel_sender: Sender<()> },
313
314 ShallStop { request: tools::Time },
316
317 #[default]
319 Stopped,
320}
321
322pub fn get_info() -> BTreeMap<&'static str, String> {
329 let mut res = BTreeMap::new();
330
331 #[cfg(debug_assertions)]
332 res.insert(
333 "debug_assertions",
334 "On - DO NOT RELEASE THIS BUILD".to_string(),
335 );
336 #[cfg(not(debug_assertions))]
337 res.insert("debug_assertions", "Off".to_string());
338
339 res.insert("deltachat_core_version", format!("v{}", &*DC_VERSION_STR));
340 res.insert("sqlite_version", rusqlite::version().to_string());
341 res.insert("arch", (std::mem::size_of::<usize>() * 8).to_string());
342 res.insert("num_cpus", num_cpus::get().to_string());
343 res.insert("level", "awesome".into());
344 res
345}
346
347impl Context {
348 pub async fn new(
350 dbfile: &Path,
351 id: u32,
352 events: Events,
353 stock_strings: StockStrings,
354 ) -> Result<Context> {
355 let context =
356 Self::new_closed(dbfile, id, events, stock_strings, Default::default()).await?;
357
358 if context.check_passphrase("".to_string()).await? {
360 context.sql.open(&context, "".to_string()).await?;
361 }
362 Ok(context)
363 }
364
365 pub async fn new_closed(
367 dbfile: &Path,
368 id: u32,
369 events: Events,
370 stockstrings: StockStrings,
371 push_subscriber: PushSubscriber,
372 ) -> Result<Context> {
373 let mut blob_fname = OsString::new();
374 blob_fname.push(dbfile.file_name().unwrap_or_default());
375 blob_fname.push("-blobs");
376 let blobdir = dbfile.with_file_name(blob_fname);
377 if !blobdir.exists() {
378 tokio::fs::create_dir_all(&blobdir).await?;
379 }
380 let context = Context::with_blobdir(
381 dbfile.into(),
382 blobdir,
383 id,
384 events,
385 stockstrings,
386 push_subscriber,
387 )?;
388 Ok(context)
389 }
390
391 pub async fn open(&self, passphrase: String) -> Result<bool> {
396 if self.sql.check_passphrase(passphrase.clone()).await? {
397 self.sql.open(self, passphrase).await?;
398 Ok(true)
399 } else {
400 Ok(false)
401 }
402 }
403
404 pub async fn change_passphrase(&self, passphrase: String) -> Result<()> {
406 self.sql.change_passphrase(passphrase).await?;
407 Ok(())
408 }
409
410 pub async fn is_open(&self) -> bool {
412 self.sql.is_open().await
413 }
414
415 pub(crate) async fn check_passphrase(&self, passphrase: String) -> Result<bool> {
421 self.sql.check_passphrase(passphrase).await
422 }
423
424 pub(crate) fn with_blobdir(
425 dbfile: PathBuf,
426 blobdir: PathBuf,
427 id: u32,
428 events: Events,
429 stockstrings: StockStrings,
430 push_subscriber: PushSubscriber,
431 ) -> Result<Context> {
432 ensure!(
433 blobdir.is_dir(),
434 "Blobdir does not exist: {}",
435 blobdir.display()
436 );
437
438 let new_msgs_notify = Notify::new();
439 new_msgs_notify.notify_one();
442
443 let inner = InnerContext {
444 id,
445 blobdir,
446 running_state: RwLock::new(Default::default()),
447 sql: Sql::new(dbfile),
448 smeared_timestamp: SmearedTimestamp::new(),
449 generating_key_mutex: Mutex::new(()),
450 oauth2_mutex: Mutex::new(()),
451 wrong_pw_warning_mutex: Mutex::new(()),
452 translated_stockstrings: stockstrings,
453 events,
454 scheduler: SchedulerState::new(),
455 ratelimit: RwLock::new(Ratelimit::new(Duration::new(60, 0), 6.0)), quota: RwLock::new(None),
457 new_msgs_notify,
458 server_id: RwLock::new(None),
459 metadata: RwLock::new(None),
460 creation_time: tools::Time::now(),
461 last_error: parking_lot::RwLock::new("".to_string()),
462 migration_error: parking_lot::RwLock::new(None),
463 debug_logging: std::sync::RwLock::new(None),
464 push_subscriber,
465 push_subscribed: AtomicBool::new(false),
466 tls_session_store: TlsSessionStore::new(),
467 iroh: Arc::new(RwLock::new(None)),
468 self_fingerprint: OnceLock::new(),
469 connectivities: parking_lot::Mutex::new(Vec::new()),
470 };
471
472 let ctx = Context {
473 inner: Arc::new(inner),
474 };
475
476 Ok(ctx)
477 }
478
479 pub async fn start_io(&self) {
481 if !self.is_configured().await.unwrap_or_default() {
482 warn!(self, "can not start io on a context that is not configured");
483 return;
484 }
485
486 if self.is_chatmail().await.unwrap_or_default() {
487 let mut lock = self.ratelimit.write().await;
488 *lock = Ratelimit::new(Duration::new(3, 0), 3.0);
490 }
491
492 self.sql.config_cache.write().await.clear();
498
499 self.scheduler.start(self).await;
500 }
501
502 pub async fn stop_io(&self) {
504 self.scheduler.stop(self).await;
505 if let Some(iroh) = self.iroh.write().await.take() {
506 tokio::spawn(async move {
513 let _ = tokio::time::timeout(Duration::from_secs(60), iroh.close()).await;
516 });
517 }
518 }
519
520 pub async fn restart_io_if_running(&self) {
523 self.scheduler.restart(self).await;
524 }
525
526 pub async fn maybe_network(&self) {
528 if let Some(ref iroh) = *self.iroh.read().await {
529 iroh.network_change().await;
530 }
531 self.scheduler.maybe_network().await;
532 }
533
534 pub async fn is_chatmail(&self) -> Result<bool> {
536 self.get_config_bool(Config::IsChatmail).await
537 }
538
539 pub(crate) async fn get_max_smtp_rcpt_to(&self) -> Result<usize> {
541 let is_chatmail = self.is_chatmail().await?;
542 let val = self
543 .get_configured_provider()
544 .await?
545 .and_then(|provider| provider.opt.max_smtp_rcpt_to)
546 .map_or_else(
547 || match is_chatmail {
548 true => constants::DEFAULT_CHATMAIL_MAX_SMTP_RCPT_TO,
549 false => constants::DEFAULT_MAX_SMTP_RCPT_TO,
550 },
551 usize::from,
552 );
553 Ok(val)
554 }
555
556 pub async fn background_fetch(&self) -> Result<()> {
562 if !(self.is_configured().await?) {
563 return Ok(());
564 }
565
566 let address = self.get_primary_self_addr().await?;
567 let time_start = tools::Time::now();
568 info!(self, "background_fetch started fetching {address}.");
569
570 if self.scheduler.is_running().await {
571 self.scheduler.maybe_network().await;
572 self.wait_for_all_work_done().await;
573 } else {
574 let _pause_guard = self.scheduler.pause(self).await?;
577
578 let mut connection = Imap::new_configured(self, channel::bounded(1).1).await?;
580 let mut session = connection.prepare(self).await?;
581
582 for folder_meaning in [FolderMeaning::Inbox, FolderMeaning::Mvbox] {
586 if let Some((_folder_config, watch_folder)) =
587 convert_folder_meaning(self, folder_meaning).await?
588 {
589 connection
590 .fetch_move_delete(self, &mut session, &watch_folder, folder_meaning)
591 .await?;
592 }
593 }
594
595 if self
597 .quota_needs_update(DC_BACKGROUND_FETCH_QUOTA_CHECK_RATELIMIT)
598 .await
599 {
600 if let Err(err) = self.update_recent_quota(&mut session).await {
601 warn!(self, "Failed to update quota: {err:#}.");
602 }
603 }
604 }
605
606 info!(
607 self,
608 "background_fetch done for {address} took {:?}.",
609 time_elapsed(&time_start),
610 );
611
612 Ok(())
613 }
614
615 #[cfg(feature = "internals")]
619 pub fn sql(&self) -> &Sql {
620 &self.inner.sql
621 }
622
623 pub fn get_dbfile(&self) -> &Path {
625 self.sql.dbfile.as_path()
626 }
627
628 pub fn get_blobdir(&self) -> &Path {
630 self.blobdir.as_path()
631 }
632
633 pub fn emit_event(&self, event: EventType) {
635 {
636 let lock = self.debug_logging.read().expect("RwLock is poisoned");
637 if let Some(debug_logging) = &*lock {
638 debug_logging.log_event(event.clone());
639 }
640 }
641 self.events.emit(Event {
642 id: self.id,
643 typ: event,
644 });
645 }
646
647 pub fn emit_msgs_changed_without_ids(&self) {
649 self.emit_event(EventType::MsgsChanged {
650 chat_id: ChatId::new(0),
651 msg_id: MsgId::new(0),
652 });
653 }
654
655 pub fn emit_msgs_changed(&self, chat_id: ChatId, msg_id: MsgId) {
661 logged_debug_assert!(
662 self,
663 !chat_id.is_unset(),
664 "emit_msgs_changed: chat_id is unset."
665 );
666 logged_debug_assert!(
667 self,
668 !msg_id.is_unset(),
669 "emit_msgs_changed: msg_id is unset."
670 );
671
672 self.emit_event(EventType::MsgsChanged { chat_id, msg_id });
673 chatlist_events::emit_chatlist_changed(self);
674 chatlist_events::emit_chatlist_item_changed(self, chat_id);
675 }
676
677 pub fn emit_msgs_changed_without_msg_id(&self, chat_id: ChatId) {
679 logged_debug_assert!(
680 self,
681 !chat_id.is_unset(),
682 "emit_msgs_changed_without_msg_id: chat_id is unset."
683 );
684
685 self.emit_event(EventType::MsgsChanged {
686 chat_id,
687 msg_id: MsgId::new(0),
688 });
689 chatlist_events::emit_chatlist_changed(self);
690 chatlist_events::emit_chatlist_item_changed(self, chat_id);
691 }
692
693 pub fn emit_incoming_msg(&self, chat_id: ChatId, msg_id: MsgId) {
695 debug_assert!(!chat_id.is_unset());
696 debug_assert!(!msg_id.is_unset());
697
698 self.emit_event(EventType::IncomingMsg { chat_id, msg_id });
699 chatlist_events::emit_chatlist_changed(self);
700 chatlist_events::emit_chatlist_item_changed(self, chat_id);
701 }
702
703 pub async fn emit_location_changed(&self, contact_id: Option<ContactId>) -> Result<()> {
705 self.emit_event(EventType::LocationChanged(contact_id));
706
707 if let Some(msg_id) = self
708 .get_config_parsed::<u32>(Config::WebxdcIntegration)
709 .await?
710 {
711 self.emit_event(EventType::WebxdcStatusUpdate {
712 msg_id: MsgId::new(msg_id),
713 status_update_serial: Default::default(),
714 })
715 }
716
717 Ok(())
718 }
719
720 pub fn get_event_emitter(&self) -> EventEmitter {
725 self.events.get_emitter()
726 }
727
728 pub fn get_id(&self) -> u32 {
730 self.id
731 }
732
733 pub(crate) async fn alloc_ongoing(&self) -> Result<Receiver<()>> {
743 let mut s = self.running_state.write().await;
744 ensure!(
745 matches!(*s, RunningState::Stopped),
746 "There is already another ongoing process running."
747 );
748
749 let (sender, receiver) = channel::bounded(1);
750 *s = RunningState::Running {
751 cancel_sender: sender,
752 };
753
754 Ok(receiver)
755 }
756
757 pub(crate) async fn free_ongoing(&self) {
758 let mut s = self.running_state.write().await;
759 if let RunningState::ShallStop { request } = *s {
760 info!(self, "Ongoing stopped in {:?}", time_elapsed(&request));
761 }
762 *s = RunningState::Stopped;
763 }
764
765 pub async fn stop_ongoing(&self) {
767 let mut s = self.running_state.write().await;
768 match &*s {
769 RunningState::Running { cancel_sender } => {
770 if let Err(err) = cancel_sender.send(()).await {
771 warn!(self, "could not cancel ongoing: {:#}", err);
772 }
773 info!(self, "Signaling the ongoing process to stop ASAP.",);
774 *s = RunningState::ShallStop {
775 request: tools::Time::now(),
776 };
777 }
778 RunningState::ShallStop { .. } | RunningState::Stopped => {
779 info!(self, "No ongoing process to stop.",);
780 }
781 }
782 }
783
784 #[allow(unused)]
785 pub(crate) async fn shall_stop_ongoing(&self) -> bool {
786 match &*self.running_state.read().await {
787 RunningState::Running { .. } => false,
788 RunningState::ShallStop { .. } | RunningState::Stopped => true,
789 }
790 }
791
792 pub async fn get_info(&self) -> Result<BTreeMap<&'static str, String>> {
798 let l = EnteredLoginParam::load(self).await?;
799 let l2 = ConfiguredLoginParam::load(self)
800 .await?
801 .map_or_else(|| "Not configured".to_string(), |param| param.to_string());
802 let secondary_addrs = self.get_secondary_self_addrs().await?.join(", ");
803 let chats = get_chat_cnt(self).await?;
804 let unblocked_msgs = message::get_unblocked_msg_cnt(self).await;
805 let request_msgs = message::get_request_msg_cnt(self).await;
806 let contacts = Contact::get_real_cnt(self).await?;
807 let proxy_enabled = self.get_config_int(Config::ProxyEnabled).await?;
808 let dbversion = self
809 .sql
810 .get_raw_config_int("dbversion")
811 .await?
812 .unwrap_or_default();
813 let journal_mode = self
814 .sql
815 .query_get_value("PRAGMA journal_mode;", ())
816 .await?
817 .unwrap_or_else(|| "unknown".to_string());
818 let mdns_enabled = self.get_config_int(Config::MdnsEnabled).await?;
819 let bcc_self = self.get_config_int(Config::BccSelf).await?;
820 let sync_msgs = self.get_config_int(Config::SyncMsgs).await?;
821 let disable_idle = self.get_config_bool(Config::DisableIdle).await?;
822
823 let prv_key_cnt = self.sql.count("SELECT COUNT(*) FROM keypairs;", ()).await?;
824
825 let pub_key_cnt = self
826 .sql
827 .count("SELECT COUNT(*) FROM public_keys;", ())
828 .await?;
829 let fingerprint_str = match self_fingerprint(self).await {
830 Ok(fp) => fp.to_string(),
831 Err(err) => format!("<key failure: {err}>"),
832 };
833
834 let mvbox_move = self.get_config_int(Config::MvboxMove).await?;
835 let only_fetch_mvbox = self.get_config_int(Config::OnlyFetchMvbox).await?;
836 let folders_configured = self
837 .sql
838 .get_raw_config_int(constants::DC_FOLDERS_CONFIGURED_KEY)
839 .await?
840 .unwrap_or_default();
841
842 let configured_inbox_folder = self
843 .get_config(Config::ConfiguredInboxFolder)
844 .await?
845 .unwrap_or_else(|| "<unset>".to_string());
846 let configured_mvbox_folder = self
847 .get_config(Config::ConfiguredMvboxFolder)
848 .await?
849 .unwrap_or_else(|| "<unset>".to_string());
850 let configured_trash_folder = self
851 .get_config(Config::ConfiguredTrashFolder)
852 .await?
853 .unwrap_or_else(|| "<unset>".to_string());
854
855 let mut res = get_info();
856
857 res.insert("bot", self.get_config_int(Config::Bot).await?.to_string());
859 res.insert("number_of_chats", chats.to_string());
860 res.insert("number_of_chat_messages", unblocked_msgs.to_string());
861 res.insert("messages_in_contact_requests", request_msgs.to_string());
862 res.insert("number_of_contacts", contacts.to_string());
863 res.insert("database_dir", self.get_dbfile().display().to_string());
864 res.insert("database_version", dbversion.to_string());
865 res.insert(
866 "database_encrypted",
867 self.sql
868 .is_encrypted()
869 .await
870 .map_or_else(|| "closed".to_string(), |b| b.to_string()),
871 );
872 res.insert("journal_mode", journal_mode);
873 res.insert("blobdir", self.get_blobdir().display().to_string());
874 res.insert(
875 "selfavatar",
876 self.get_config(Config::Selfavatar)
877 .await?
878 .unwrap_or_else(|| "<unset>".to_string()),
879 );
880 res.insert("proxy_enabled", proxy_enabled.to_string());
881 res.insert("entered_account_settings", l.to_string());
882 res.insert("used_account_settings", l2);
883
884 if let Some(server_id) = &*self.server_id.read().await {
885 res.insert("imap_server_id", format!("{server_id:?}"));
886 }
887
888 res.insert("is_chatmail", self.is_chatmail().await?.to_string());
889 res.insert(
890 "fix_is_chatmail",
891 self.get_config_bool(Config::FixIsChatmail)
892 .await?
893 .to_string(),
894 );
895 res.insert(
896 "is_muted",
897 self.get_config_bool(Config::IsMuted).await?.to_string(),
898 );
899 res.insert(
900 "private_tag",
901 self.get_config(Config::PrivateTag)
902 .await?
903 .unwrap_or_else(|| "<unset>".to_string()),
904 );
905
906 if let Some(metadata) = &*self.metadata.read().await {
907 if let Some(comment) = &metadata.comment {
908 res.insert("imap_server_comment", format!("{comment:?}"));
909 }
910
911 if let Some(admin) = &metadata.admin {
912 res.insert("imap_server_admin", format!("{admin:?}"));
913 }
914 }
915
916 res.insert("secondary_addrs", secondary_addrs);
917 res.insert(
918 "fetched_existing_msgs",
919 self.get_config_bool(Config::FetchedExistingMsgs)
920 .await?
921 .to_string(),
922 );
923 res.insert(
924 "show_emails",
925 self.get_config_int(Config::ShowEmails).await?.to_string(),
926 );
927 res.insert(
928 "download_limit",
929 self.get_config_int(Config::DownloadLimit)
930 .await?
931 .to_string(),
932 );
933 res.insert("mvbox_move", mvbox_move.to_string());
934 res.insert("only_fetch_mvbox", only_fetch_mvbox.to_string());
935 res.insert(
936 constants::DC_FOLDERS_CONFIGURED_KEY,
937 folders_configured.to_string(),
938 );
939 res.insert("configured_inbox_folder", configured_inbox_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("bcc_self", bcc_self.to_string());
944 res.insert("sync_msgs", sync_msgs.to_string());
945 res.insert("disable_idle", disable_idle.to_string());
946 res.insert("private_key_count", prv_key_cnt.to_string());
947 res.insert("public_key_count", pub_key_cnt.to_string());
948 res.insert("fingerprint", fingerprint_str);
949 res.insert(
950 "media_quality",
951 self.get_config_int(Config::MediaQuality).await?.to_string(),
952 );
953 res.insert(
954 "delete_device_after",
955 self.get_config_int(Config::DeleteDeviceAfter)
956 .await?
957 .to_string(),
958 );
959 res.insert(
960 "delete_server_after",
961 self.get_config_int(Config::DeleteServerAfter)
962 .await?
963 .to_string(),
964 );
965 res.insert(
966 "delete_to_trash",
967 self.get_config(Config::DeleteToTrash)
968 .await?
969 .unwrap_or_else(|| "<unset>".to_string()),
970 );
971 res.insert(
972 "last_housekeeping",
973 self.get_config_int(Config::LastHousekeeping)
974 .await?
975 .to_string(),
976 );
977 res.insert(
978 "last_cant_decrypt_outgoing_msgs",
979 self.get_config_int(Config::LastCantDecryptOutgoingMsgs)
980 .await?
981 .to_string(),
982 );
983 res.insert(
984 "scan_all_folders_debounce_secs",
985 self.get_config_int(Config::ScanAllFoldersDebounceSecs)
986 .await?
987 .to_string(),
988 );
989 res.insert(
990 "quota_exceeding",
991 self.get_config_int(Config::QuotaExceeding)
992 .await?
993 .to_string(),
994 );
995 res.insert(
996 "authserv_id_candidates",
997 self.get_config(Config::AuthservIdCandidates)
998 .await?
999 .unwrap_or_default(),
1000 );
1001 res.insert(
1002 "sign_unencrypted",
1003 self.get_config_int(Config::SignUnencrypted)
1004 .await?
1005 .to_string(),
1006 );
1007 res.insert(
1008 "debug_logging",
1009 self.get_config_int(Config::DebugLogging).await?.to_string(),
1010 );
1011 res.insert(
1012 "last_msg_id",
1013 self.get_config_int(Config::LastMsgId).await?.to_string(),
1014 );
1015 res.insert(
1016 "gossip_period",
1017 self.get_config_int(Config::GossipPeriod).await?.to_string(),
1018 );
1019 res.insert(
1020 "webxdc_realtime_enabled",
1021 self.get_config_bool(Config::WebxdcRealtimeEnabled)
1022 .await?
1023 .to_string(),
1024 );
1025 res.insert(
1026 "donation_request_next_check",
1027 self.get_config_i64(Config::DonationRequestNextCheck)
1028 .await?
1029 .to_string(),
1030 );
1031 res.insert(
1032 "first_key_contacts_msg_id",
1033 self.sql
1034 .get_raw_config("first_key_contacts_msg_id")
1035 .await?
1036 .unwrap_or_default(),
1037 );
1038 res.insert(
1039 "stats_id",
1040 self.get_config(Config::StatsId)
1041 .await?
1042 .unwrap_or_else(|| "<unset>".to_string()),
1043 );
1044 res.insert(
1045 "stats_sending",
1046 stats::should_send_stats(self).await?.to_string(),
1047 );
1048 res.insert(
1049 "stats_last_sent",
1050 self.get_config_i64(Config::StatsLastSent)
1051 .await?
1052 .to_string(),
1053 );
1054 res.insert(
1055 "fail_on_receiving_full_msg",
1056 self.sql
1057 .get_raw_config("fail_on_receiving_full_msg")
1058 .await?
1059 .unwrap_or_default(),
1060 );
1061
1062 let elapsed = time_elapsed(&self.creation_time);
1063 res.insert("uptime", duration_to_str(elapsed));
1064
1065 Ok(res)
1066 }
1067
1068 pub async fn get_fresh_msgs(&self) -> Result<Vec<MsgId>> {
1075 let list = self
1076 .sql
1077 .query_map_vec(
1078 concat!(
1079 "SELECT m.id",
1080 " FROM msgs m",
1081 " LEFT JOIN contacts ct",
1082 " ON m.from_id=ct.id",
1083 " LEFT JOIN chats c",
1084 " ON m.chat_id=c.id",
1085 " WHERE m.state=?",
1086 " AND m.hidden=0",
1087 " AND m.chat_id>9",
1088 " AND ct.blocked=0",
1089 " AND c.blocked=0",
1090 " AND NOT(c.muted_until=-1 OR c.muted_until>?)",
1091 " ORDER BY m.timestamp DESC,m.id DESC;"
1092 ),
1093 (MessageState::InFresh, time()),
1094 |row| {
1095 let msg_id: MsgId = row.get(0)?;
1096 Ok(msg_id)
1097 },
1098 )
1099 .await?;
1100 Ok(list)
1101 }
1102
1103 pub async fn get_next_msgs(&self) -> Result<Vec<MsgId>> {
1108 let last_msg_id = match self.get_config(Config::LastMsgId).await? {
1109 Some(s) => MsgId::new(s.parse()?),
1110 None => {
1111 self.sql
1116 .query_row(
1117 "SELECT IFNULL((SELECT MAX(id) - 1 FROM msgs), 0)",
1118 (),
1119 |row| {
1120 let msg_id: MsgId = row.get(0)?;
1121 Ok(msg_id)
1122 },
1123 )
1124 .await?
1125 }
1126 };
1127
1128 let list = self
1129 .sql
1130 .query_map_vec(
1131 "SELECT m.id
1132 FROM msgs m
1133 LEFT JOIN contacts ct
1134 ON m.from_id=ct.id
1135 LEFT JOIN chats c
1136 ON m.chat_id=c.id
1137 WHERE m.id>?
1138 AND m.hidden=0
1139 AND m.chat_id>9
1140 AND ct.blocked=0
1141 AND c.blocked!=1
1142 ORDER BY m.id ASC",
1143 (
1144 last_msg_id.to_u32(), ),
1146 |row| {
1147 let msg_id: MsgId = row.get(0)?;
1148 Ok(msg_id)
1149 },
1150 )
1151 .await?;
1152 Ok(list)
1153 }
1154
1155 pub async fn wait_next_msgs(&self) -> Result<Vec<MsgId>> {
1166 self.new_msgs_notify.notified().await;
1167 let list = self.get_next_msgs().await?;
1168 Ok(list)
1169 }
1170
1171 pub async fn search_msgs(&self, chat_id: Option<ChatId>, query: &str) -> Result<Vec<MsgId>> {
1182 let real_query = query.trim().to_lowercase();
1183 if real_query.is_empty() {
1184 return Ok(Vec::new());
1185 }
1186 let str_like_in_text = format!("%{real_query}%");
1187
1188 let list = if let Some(chat_id) = chat_id {
1189 self.sql
1190 .query_map_vec(
1191 "SELECT m.id AS id
1192 FROM msgs m
1193 LEFT JOIN contacts ct
1194 ON m.from_id=ct.id
1195 WHERE m.chat_id=?
1196 AND m.hidden=0
1197 AND ct.blocked=0
1198 AND IFNULL(txt_normalized, txt) LIKE ?
1199 ORDER BY m.timestamp,m.id;",
1200 (chat_id, str_like_in_text),
1201 |row| {
1202 let msg_id: MsgId = row.get("id")?;
1203 Ok(msg_id)
1204 },
1205 )
1206 .await?
1207 } else {
1208 self.sql
1219 .query_map_vec(
1220 "SELECT m.id AS id
1221 FROM msgs m
1222 LEFT JOIN contacts ct
1223 ON m.from_id=ct.id
1224 LEFT JOIN chats c
1225 ON m.chat_id=c.id
1226 WHERE m.chat_id>9
1227 AND m.hidden=0
1228 AND c.blocked!=1
1229 AND ct.blocked=0
1230 AND IFNULL(txt_normalized, txt) LIKE ?
1231 ORDER BY m.id DESC LIMIT 1000",
1232 (str_like_in_text,),
1233 |row| {
1234 let msg_id: MsgId = row.get("id")?;
1235 Ok(msg_id)
1236 },
1237 )
1238 .await?
1239 };
1240
1241 Ok(list)
1242 }
1243
1244 pub async fn is_inbox(&self, folder_name: &str) -> Result<bool> {
1246 let inbox = self.get_config(Config::ConfiguredInboxFolder).await?;
1247 Ok(inbox.as_deref() == Some(folder_name))
1248 }
1249
1250 pub async fn is_mvbox(&self, folder_name: &str) -> Result<bool> {
1252 let mvbox = self.get_config(Config::ConfiguredMvboxFolder).await?;
1253 Ok(mvbox.as_deref() == Some(folder_name))
1254 }
1255
1256 pub async fn is_trash(&self, folder_name: &str) -> Result<bool> {
1258 let trash = self.get_config(Config::ConfiguredTrashFolder).await?;
1259 Ok(trash.as_deref() == Some(folder_name))
1260 }
1261
1262 pub(crate) async fn should_delete_to_trash(&self) -> Result<bool> {
1263 if let Some(v) = self.get_config_bool_opt(Config::DeleteToTrash).await? {
1264 return Ok(v);
1265 }
1266 if let Some(provider) = self.get_configured_provider().await? {
1267 return Ok(provider.opt.delete_to_trash);
1268 }
1269 Ok(false)
1270 }
1271
1272 pub(crate) async fn get_delete_msgs_target(&self) -> Result<String> {
1275 if !self.should_delete_to_trash().await? {
1276 return Ok("".into());
1277 }
1278 self.get_config(Config::ConfiguredTrashFolder)
1279 .await?
1280 .context("No configured trash folder")
1281 }
1282
1283 pub(crate) fn derive_blobdir(dbfile: &Path) -> PathBuf {
1284 let mut blob_fname = OsString::new();
1285 blob_fname.push(dbfile.file_name().unwrap_or_default());
1286 blob_fname.push("-blobs");
1287 dbfile.with_file_name(blob_fname)
1288 }
1289
1290 pub(crate) fn derive_walfile(dbfile: &Path) -> PathBuf {
1291 let mut wal_fname = OsString::new();
1292 wal_fname.push(dbfile.file_name().unwrap_or_default());
1293 wal_fname.push("-wal");
1294 dbfile.with_file_name(wal_fname)
1295 }
1296}
1297
1298pub fn get_version_str() -> &'static str {
1300 &DC_VERSION_STR
1301}
1302
1303#[cfg(test)]
1304mod context_tests;