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, Weak};
9use std::time::Duration;
10
11use anyhow::{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::message::{self, MessageState, MsgId};
27use crate::net::tls::TlsSessionStore;
28use crate::peer_channels::Iroh;
29use crate::push::PushSubscriber;
30use crate::quota::QuotaInfo;
31use crate::scheduler::{ConnectivityStore, SchedulerState, convert_folder_meaning};
32use crate::sql::Sql;
33use crate::stock_str::StockStrings;
34use crate::timesmearing::SmearedTimestamp;
35use crate::tools::{self, duration_to_str, time, time_elapsed};
36use crate::transport::ConfiguredLoginParam;
37use crate::{chatlist_events, stats};
38
39pub use crate::scheduler::connectivity::Connectivity;
40
41#[derive(Clone, Debug)]
66pub struct ContextBuilder {
67 dbfile: PathBuf,
68 id: u32,
69 events: Events,
70 stock_strings: StockStrings,
71 password: Option<String>,
72
73 push_subscriber: Option<PushSubscriber>,
74}
75
76impl ContextBuilder {
77 pub fn new(dbfile: PathBuf) -> Self {
83 ContextBuilder {
84 dbfile,
85 id: rand::random(),
86 events: Events::new(),
87 stock_strings: StockStrings::new(),
88 password: None,
89 push_subscriber: None,
90 }
91 }
92
93 pub fn with_id(mut self, id: u32) -> Self {
103 self.id = id;
104 self
105 }
106
107 pub fn with_events(mut self, events: Events) -> Self {
116 self.events = events;
117 self
118 }
119
120 pub fn with_stock_strings(mut self, stock_strings: StockStrings) -> Self {
131 self.stock_strings = stock_strings;
132 self
133 }
134
135 #[deprecated(since = "TBD")]
143 pub fn with_password(mut self, password: String) -> Self {
144 self.password = Some(password);
145 self
146 }
147
148 pub(crate) fn with_push_subscriber(mut self, push_subscriber: PushSubscriber) -> Self {
150 self.push_subscriber = Some(push_subscriber);
151 self
152 }
153
154 pub async fn build(self) -> Result<Context> {
156 let push_subscriber = self.push_subscriber.unwrap_or_default();
157 let context = Context::new_closed(
158 &self.dbfile,
159 self.id,
160 self.events,
161 self.stock_strings,
162 push_subscriber,
163 )
164 .await?;
165 Ok(context)
166 }
167
168 pub async fn open(self) -> Result<Context> {
172 let password = self.password.clone().unwrap_or_default();
173 let context = self.build().await?;
174 match context.open(password).await? {
175 true => Ok(context),
176 false => bail!("database could not be decrypted, incorrect or missing password"),
177 }
178 }
179}
180
181#[derive(Clone, Debug)]
193pub struct Context {
194 pub(crate) inner: Arc<InnerContext>,
195}
196
197impl Deref for Context {
198 type Target = InnerContext;
199
200 fn deref(&self) -> &Self::Target {
201 &self.inner
202 }
203}
204
205#[derive(Clone, Debug)]
209pub(crate) struct WeakContext {
210 inner: Weak<InnerContext>,
211}
212
213impl WeakContext {
214 pub(crate) fn upgrade(&self) -> Result<Context> {
216 let inner = self
217 .inner
218 .upgrade()
219 .ok_or_else(|| anyhow::anyhow!("Inner struct has been dropped"))?;
220 Ok(Context { inner })
221 }
222}
223
224#[derive(Debug)]
226pub struct InnerContext {
227 pub(crate) blobdir: PathBuf,
229 pub(crate) sql: Sql,
230 pub(crate) smeared_timestamp: SmearedTimestamp,
231 running_state: RwLock<RunningState>,
236 pub(crate) generating_key_mutex: Mutex<()>,
238 pub(crate) oauth2_mutex: Mutex<()>,
240 pub(crate) wrong_pw_warning_mutex: Mutex<()>,
242 pub(crate) translated_stockstrings: StockStrings,
243 pub(crate) events: Events,
244
245 pub(crate) scheduler: SchedulerState,
246 pub(crate) ratelimit: RwLock<Ratelimit>,
247
248 pub(crate) quota: RwLock<BTreeMap<u32, QuotaInfo>>,
251
252 pub(crate) new_msgs_notify: Notify,
256
257 pub(crate) server_id: RwLock<Option<HashMap<String, String>>>,
261
262 pub(crate) metadata: RwLock<Option<ServerMetadata>>,
264
265 pub(crate) id: u32,
270
271 creation_time: tools::Time,
272
273 pub(crate) last_error: parking_lot::RwLock<String>,
277
278 pub(crate) migration_error: parking_lot::RwLock<Option<String>>,
284
285 pub(crate) debug_logging: std::sync::RwLock<Option<DebugLogging>>,
290
291 pub(crate) push_subscriber: PushSubscriber,
294
295 pub(crate) push_subscribed: AtomicBool,
297
298 pub(crate) tls_session_store: TlsSessionStore,
300
301 pub(crate) iroh: Arc<RwLock<Option<Iroh>>>,
303
304 pub(crate) self_fingerprint: OnceLock<String>,
308
309 pub(crate) connectivities: parking_lot::Mutex<Vec<ConnectivityStore>>,
312
313 #[expect(clippy::type_complexity)]
314 pub(crate) pre_encrypt_mime_hook: parking_lot::Mutex<
316 Option<
317 for<'a> fn(
318 &Context,
319 mail_builder::mime::MimePart<'a>,
320 ) -> mail_builder::mime::MimePart<'a>,
321 >,
322 >,
323}
324
325#[derive(Debug, Default)]
327enum RunningState {
328 Running { cancel_sender: Sender<()> },
330
331 ShallStop { request: tools::Time },
333
334 #[default]
336 Stopped,
337}
338
339#[expect(clippy::arithmetic_side_effects)]
346pub fn get_info() -> BTreeMap<&'static str, String> {
347 let mut res = BTreeMap::new();
348
349 #[cfg(debug_assertions)]
350 res.insert(
351 "debug_assertions",
352 "On - DO NOT RELEASE THIS BUILD".to_string(),
353 );
354 #[cfg(not(debug_assertions))]
355 res.insert("debug_assertions", "Off".to_string());
356
357 res.insert("deltachat_core_version", format!("v{DC_VERSION_STR}"));
358 res.insert("sqlite_version", rusqlite::version().to_string());
359 res.insert("arch", (std::mem::size_of::<usize>() * 8).to_string());
360 res.insert("num_cpus", num_cpus::get().to_string());
361 res.insert("level", "awesome".into());
362 res
363}
364
365impl Context {
366 pub async fn new(
368 dbfile: &Path,
369 id: u32,
370 events: Events,
371 stock_strings: StockStrings,
372 ) -> Result<Context> {
373 let context =
374 Self::new_closed(dbfile, id, events, stock_strings, Default::default()).await?;
375
376 if context.check_passphrase("".to_string()).await? {
378 context.sql.open(&context, "".to_string()).await?;
379 }
380 Ok(context)
381 }
382
383 pub async fn new_closed(
385 dbfile: &Path,
386 id: u32,
387 events: Events,
388 stockstrings: StockStrings,
389 push_subscriber: PushSubscriber,
390 ) -> Result<Context> {
391 let mut blob_fname = OsString::new();
392 blob_fname.push(dbfile.file_name().unwrap_or_default());
393 blob_fname.push("-blobs");
394 let blobdir = dbfile.with_file_name(blob_fname);
395 if !blobdir.exists() {
396 tokio::fs::create_dir_all(&blobdir).await?;
397 }
398 let context = Context::with_blobdir(
399 dbfile.into(),
400 blobdir,
401 id,
402 events,
403 stockstrings,
404 push_subscriber,
405 )?;
406 Ok(context)
407 }
408
409 pub(crate) fn get_weak_context(&self) -> WeakContext {
411 WeakContext {
412 inner: Arc::downgrade(&self.inner),
413 }
414 }
415
416 #[deprecated(since = "TBD")]
423 pub async fn open(&self, passphrase: String) -> Result<bool> {
424 if self.sql.check_passphrase(passphrase.clone()).await? {
425 self.sql.open(self, passphrase).await?;
426 Ok(true)
427 } else {
428 Ok(false)
429 }
430 }
431
432 pub async fn change_passphrase(&self, passphrase: String) -> Result<()> {
435 self.sql.change_passphrase(passphrase).await?;
436 Ok(())
437 }
438
439 pub async fn is_open(&self) -> bool {
441 self.sql.is_open().await
442 }
443
444 pub(crate) async fn check_passphrase(&self, passphrase: String) -> Result<bool> {
450 self.sql.check_passphrase(passphrase).await
451 }
452
453 pub(crate) fn with_blobdir(
454 dbfile: PathBuf,
455 blobdir: PathBuf,
456 id: u32,
457 events: Events,
458 stockstrings: StockStrings,
459 push_subscriber: PushSubscriber,
460 ) -> Result<Context> {
461 ensure!(
462 blobdir.is_dir(),
463 "Blobdir does not exist: {}",
464 blobdir.display()
465 );
466
467 let new_msgs_notify = Notify::new();
468 new_msgs_notify.notify_one();
471
472 let inner = InnerContext {
473 id,
474 blobdir,
475 running_state: RwLock::new(Default::default()),
476 sql: Sql::new(dbfile),
477 smeared_timestamp: SmearedTimestamp::new(),
478 generating_key_mutex: Mutex::new(()),
479 oauth2_mutex: Mutex::new(()),
480 wrong_pw_warning_mutex: Mutex::new(()),
481 translated_stockstrings: stockstrings,
482 events,
483 scheduler: SchedulerState::new(),
484 ratelimit: RwLock::new(Ratelimit::new(Duration::new(3, 0), 3.0)), quota: RwLock::new(BTreeMap::new()),
486 new_msgs_notify,
487 server_id: RwLock::new(None),
488 metadata: RwLock::new(None),
489 creation_time: tools::Time::now(),
490 last_error: parking_lot::RwLock::new("".to_string()),
491 migration_error: parking_lot::RwLock::new(None),
492 debug_logging: std::sync::RwLock::new(None),
493 push_subscriber,
494 push_subscribed: AtomicBool::new(false),
495 tls_session_store: TlsSessionStore::new(),
496 iroh: Arc::new(RwLock::new(None)),
497 self_fingerprint: OnceLock::new(),
498 connectivities: parking_lot::Mutex::new(Vec::new()),
499 pre_encrypt_mime_hook: None.into(),
500 };
501
502 let ctx = Context {
503 inner: Arc::new(inner),
504 };
505
506 Ok(ctx)
507 }
508
509 pub async fn start_io(&self) {
511 if !self.is_configured().await.unwrap_or_default() {
512 warn!(self, "can not start io on a context that is not configured");
513 return;
514 }
515
516 self.sql.config_cache.write().await.clear();
522
523 self.scheduler.start(self).await;
524 }
525
526 pub async fn stop_io(&self) {
528 self.scheduler.stop(self).await;
529 if let Some(iroh) = self.iroh.write().await.take() {
530 tokio::spawn(async move {
537 let _ = tokio::time::timeout(Duration::from_secs(60), iroh.close()).await;
540 });
541 }
542 }
543
544 pub async fn restart_io_if_running(&self) {
547 self.scheduler.restart(self).await;
548 }
549
550 pub async fn maybe_network(&self) {
552 if let Some(ref iroh) = *self.iroh.read().await {
553 iroh.network_change().await;
554 }
555 self.scheduler.maybe_network().await;
556 }
557
558 pub async fn is_chatmail(&self) -> Result<bool> {
560 self.get_config_bool(Config::IsChatmail).await
561 }
562
563 pub(crate) async fn get_max_smtp_rcpt_to(&self) -> Result<usize> {
565 let is_chatmail = self.is_chatmail().await?;
566 let val = self
567 .get_configured_provider()
568 .await?
569 .and_then(|provider| provider.opt.max_smtp_rcpt_to)
570 .map_or_else(
571 || match is_chatmail {
572 true => constants::DEFAULT_CHATMAIL_MAX_SMTP_RCPT_TO,
573 false => constants::DEFAULT_MAX_SMTP_RCPT_TO,
574 },
575 usize::from,
576 );
577 Ok(val)
578 }
579
580 pub async fn background_fetch(&self) -> Result<()> {
586 if !(self.is_configured().await?) {
587 return Ok(());
588 }
589
590 let address = self.get_primary_self_addr().await?;
591 let time_start = tools::Time::now();
592 info!(self, "background_fetch started fetching {address}.");
593
594 if self.scheduler.is_running().await {
595 self.scheduler.maybe_network().await;
596 self.wait_for_all_work_done().await;
597 } else {
598 let _pause_guard = self.scheduler.pause(self).await?;
601
602 let mut connection = Imap::new_configured(self, channel::bounded(1).1).await?;
604 let mut session = connection.prepare(self).await?;
605
606 for folder_meaning in [FolderMeaning::Inbox, FolderMeaning::Mvbox] {
610 if let Some((_folder_config, watch_folder)) =
611 convert_folder_meaning(self, folder_meaning).await?
612 {
613 connection
614 .fetch_move_delete(self, &mut session, &watch_folder, folder_meaning)
615 .await?;
616 }
617 }
618
619 if self
623 .quota_needs_update(
624 session.transport_id(),
625 DC_BACKGROUND_FETCH_QUOTA_CHECK_RATELIMIT,
626 )
627 .await
628 && let Err(err) = self.update_recent_quota(&mut session).await
629 {
630 warn!(self, "Failed to update quota: {err:#}.");
631 }
632 }
633
634 info!(
635 self,
636 "background_fetch done for {address} took {:?}.",
637 time_elapsed(&time_start),
638 );
639
640 Ok(())
641 }
642
643 #[cfg(feature = "internals")]
647 pub fn sql(&self) -> &Sql {
648 &self.inner.sql
649 }
650
651 pub fn get_dbfile(&self) -> &Path {
653 self.sql.dbfile.as_path()
654 }
655
656 pub fn get_blobdir(&self) -> &Path {
658 self.blobdir.as_path()
659 }
660
661 pub fn emit_event(&self, event: EventType) {
663 {
664 let lock = self.debug_logging.read().expect("RwLock is poisoned");
665 if let Some(debug_logging) = &*lock {
666 debug_logging.log_event(event.clone());
667 }
668 }
669 self.events.emit(Event {
670 id: self.id,
671 typ: event,
672 });
673 }
674
675 pub fn emit_msgs_changed_without_ids(&self) {
677 self.emit_event(EventType::MsgsChanged {
678 chat_id: ChatId::new(0),
679 msg_id: MsgId::new(0),
680 });
681 }
682
683 pub fn emit_msgs_changed(&self, chat_id: ChatId, msg_id: MsgId) {
689 logged_debug_assert!(
690 self,
691 !chat_id.is_unset(),
692 "emit_msgs_changed: chat_id is unset."
693 );
694 logged_debug_assert!(
695 self,
696 !msg_id.is_unset(),
697 "emit_msgs_changed: msg_id is unset."
698 );
699
700 self.emit_event(EventType::MsgsChanged { chat_id, msg_id });
701 chatlist_events::emit_chatlist_changed(self);
702 chatlist_events::emit_chatlist_item_changed(self, chat_id);
703 }
704
705 pub fn emit_msgs_changed_without_msg_id(&self, chat_id: ChatId) {
707 logged_debug_assert!(
708 self,
709 !chat_id.is_unset(),
710 "emit_msgs_changed_without_msg_id: chat_id is unset."
711 );
712
713 self.emit_event(EventType::MsgsChanged {
714 chat_id,
715 msg_id: MsgId::new(0),
716 });
717 chatlist_events::emit_chatlist_changed(self);
718 chatlist_events::emit_chatlist_item_changed(self, chat_id);
719 }
720
721 pub fn emit_incoming_msg(&self, chat_id: ChatId, msg_id: MsgId) {
723 debug_assert!(!chat_id.is_unset());
724 debug_assert!(!msg_id.is_unset());
725
726 self.emit_event(EventType::IncomingMsg { chat_id, msg_id });
727 chatlist_events::emit_chatlist_changed(self);
728 chatlist_events::emit_chatlist_item_changed(self, chat_id);
729 }
730
731 pub async fn emit_location_changed(&self, contact_id: Option<ContactId>) -> Result<()> {
733 self.emit_event(EventType::LocationChanged(contact_id));
734
735 if let Some(msg_id) = self
736 .get_config_parsed::<u32>(Config::WebxdcIntegration)
737 .await?
738 {
739 self.emit_event(EventType::WebxdcStatusUpdate {
740 msg_id: MsgId::new(msg_id),
741 status_update_serial: Default::default(),
742 })
743 }
744
745 Ok(())
746 }
747
748 pub fn get_event_emitter(&self) -> EventEmitter {
753 self.events.get_emitter()
754 }
755
756 pub fn get_id(&self) -> u32 {
758 self.id
759 }
760
761 pub(crate) async fn alloc_ongoing(&self) -> Result<Receiver<()>> {
771 let mut s = self.running_state.write().await;
772 ensure!(
773 matches!(*s, RunningState::Stopped),
774 "There is already another ongoing process running."
775 );
776
777 let (sender, receiver) = channel::bounded(1);
778 *s = RunningState::Running {
779 cancel_sender: sender,
780 };
781
782 Ok(receiver)
783 }
784
785 pub(crate) async fn free_ongoing(&self) {
786 let mut s = self.running_state.write().await;
787 if let RunningState::ShallStop { request } = *s {
788 info!(self, "Ongoing stopped in {:?}", time_elapsed(&request));
789 }
790 *s = RunningState::Stopped;
791 }
792
793 pub async fn stop_ongoing(&self) {
795 let mut s = self.running_state.write().await;
796 match &*s {
797 RunningState::Running { cancel_sender } => {
798 if let Err(err) = cancel_sender.send(()).await {
799 warn!(self, "could not cancel ongoing: {:#}", err);
800 }
801 info!(self, "Signaling the ongoing process to stop ASAP.",);
802 *s = RunningState::ShallStop {
803 request: tools::Time::now(),
804 };
805 }
806 RunningState::ShallStop { .. } | RunningState::Stopped => {
807 info!(self, "No ongoing process to stop.",);
808 }
809 }
810 }
811
812 #[allow(unused)]
813 pub(crate) async fn shall_stop_ongoing(&self) -> bool {
814 match &*self.running_state.read().await {
815 RunningState::Running { .. } => false,
816 RunningState::ShallStop { .. } | RunningState::Stopped => true,
817 }
818 }
819
820 pub async fn get_info(&self) -> Result<BTreeMap<&'static str, String>> {
826 let secondary_addrs = self.get_secondary_self_addrs().await?.join(", ");
827 let all_transports: Vec<String> = ConfiguredLoginParam::load_all(self)
828 .await?
829 .into_iter()
830 .map(|(transport_id, param)| format!("{transport_id}: {param}"))
831 .collect();
832 let all_transports = if all_transports.is_empty() {
833 "Not configured".to_string()
834 } else {
835 all_transports.join(",")
836 };
837 let chats = get_chat_cnt(self).await?;
838 let unblocked_msgs = message::get_unblocked_msg_cnt(self).await;
839 let request_msgs = message::get_request_msg_cnt(self).await;
840 let contacts = Contact::get_real_cnt(self).await?;
841 let proxy_enabled = self.get_config_int(Config::ProxyEnabled).await?;
842 let dbversion = self
843 .sql
844 .get_raw_config_int("dbversion")
845 .await?
846 .unwrap_or_default();
847 let journal_mode = self
848 .sql
849 .query_get_value("PRAGMA journal_mode;", ())
850 .await?
851 .unwrap_or_else(|| "unknown".to_string());
852 let mdns_enabled = self.get_config_int(Config::MdnsEnabled).await?;
853 let bcc_self = self.get_config_int(Config::BccSelf).await?;
854 let sync_msgs = self.get_config_int(Config::SyncMsgs).await?;
855 let disable_idle = self.get_config_bool(Config::DisableIdle).await?;
856
857 let prv_key_cnt = self.sql.count("SELECT COUNT(*) FROM keypairs;", ()).await?;
858
859 let pub_key_cnt = self
860 .sql
861 .count("SELECT COUNT(*) FROM public_keys;", ())
862 .await?;
863 let fingerprint_str = match self_fingerprint(self).await {
864 Ok(fp) => fp.to_string(),
865 Err(err) => format!("<key failure: {err}>"),
866 };
867
868 let mvbox_move = self.get_config_int(Config::MvboxMove).await?;
869 let only_fetch_mvbox = self.get_config_int(Config::OnlyFetchMvbox).await?;
870 let folders_configured = self
871 .sql
872 .get_raw_config_int(constants::DC_FOLDERS_CONFIGURED_KEY)
873 .await?
874 .unwrap_or_default();
875
876 let configured_inbox_folder = self
877 .get_config(Config::ConfiguredInboxFolder)
878 .await?
879 .unwrap_or_else(|| "<unset>".to_string());
880 let configured_mvbox_folder = self
881 .get_config(Config::ConfiguredMvboxFolder)
882 .await?
883 .unwrap_or_else(|| "<unset>".to_string());
884
885 let mut res = get_info();
886
887 res.insert("bot", self.get_config_int(Config::Bot).await?.to_string());
889 res.insert("number_of_chats", chats.to_string());
890 res.insert("number_of_chat_messages", unblocked_msgs.to_string());
891 res.insert("messages_in_contact_requests", request_msgs.to_string());
892 res.insert("number_of_contacts", contacts.to_string());
893 res.insert("database_dir", self.get_dbfile().display().to_string());
894 res.insert("database_version", dbversion.to_string());
895 res.insert(
896 "database_encrypted",
897 self.sql
898 .is_encrypted()
899 .await
900 .map_or_else(|| "closed".to_string(), |b| b.to_string()),
901 );
902 res.insert("journal_mode", journal_mode);
903 res.insert("blobdir", self.get_blobdir().display().to_string());
904 res.insert(
905 "selfavatar",
906 self.get_config(Config::Selfavatar)
907 .await?
908 .unwrap_or_else(|| "<unset>".to_string()),
909 );
910 res.insert("proxy_enabled", proxy_enabled.to_string());
911 res.insert("used_transport_settings", all_transports);
912
913 if let Some(server_id) = &*self.server_id.read().await {
914 res.insert("imap_server_id", format!("{server_id:?}"));
915 }
916
917 res.insert("is_chatmail", self.is_chatmail().await?.to_string());
918 res.insert(
919 "fix_is_chatmail",
920 self.get_config_bool(Config::FixIsChatmail)
921 .await?
922 .to_string(),
923 );
924 res.insert(
925 "is_muted",
926 self.get_config_bool(Config::IsMuted).await?.to_string(),
927 );
928 res.insert(
929 "private_tag",
930 self.get_config(Config::PrivateTag)
931 .await?
932 .unwrap_or_else(|| "<unset>".to_string()),
933 );
934
935 if let Some(metadata) = &*self.metadata.read().await {
936 if let Some(comment) = &metadata.comment {
937 res.insert("imap_server_comment", format!("{comment:?}"));
938 }
939
940 if let Some(admin) = &metadata.admin {
941 res.insert("imap_server_admin", format!("{admin:?}"));
942 }
943 }
944
945 res.insert("secondary_addrs", secondary_addrs);
946 res.insert(
947 "show_emails",
948 self.get_config_int(Config::ShowEmails).await?.to_string(),
949 );
950 res.insert(
951 "who_can_call_me",
952 self.get_config_int(Config::WhoCanCallMe).await?.to_string(),
953 );
954 res.insert(
955 "download_limit",
956 self.get_config_int(Config::DownloadLimit)
957 .await?
958 .to_string(),
959 );
960 res.insert("mvbox_move", mvbox_move.to_string());
961 res.insert("only_fetch_mvbox", only_fetch_mvbox.to_string());
962 res.insert(
963 constants::DC_FOLDERS_CONFIGURED_KEY,
964 folders_configured.to_string(),
965 );
966 res.insert("configured_inbox_folder", configured_inbox_folder);
967 res.insert("configured_mvbox_folder", configured_mvbox_folder);
968 res.insert("mdns_enabled", mdns_enabled.to_string());
969 res.insert("bcc_self", bcc_self.to_string());
970 res.insert("sync_msgs", sync_msgs.to_string());
971 res.insert("disable_idle", disable_idle.to_string());
972 res.insert("private_key_count", prv_key_cnt.to_string());
973 res.insert("public_key_count", pub_key_cnt.to_string());
974 res.insert("fingerprint", fingerprint_str);
975 res.insert(
976 "media_quality",
977 self.get_config_int(Config::MediaQuality).await?.to_string(),
978 );
979 res.insert(
980 "delete_device_after",
981 self.get_config_int(Config::DeleteDeviceAfter)
982 .await?
983 .to_string(),
984 );
985 res.insert(
986 "delete_server_after",
987 self.get_config_int(Config::DeleteServerAfter)
988 .await?
989 .to_string(),
990 );
991 res.insert(
992 "last_housekeeping",
993 self.get_config_int(Config::LastHousekeeping)
994 .await?
995 .to_string(),
996 );
997 res.insert(
998 "last_cant_decrypt_outgoing_msgs",
999 self.get_config_int(Config::LastCantDecryptOutgoingMsgs)
1000 .await?
1001 .to_string(),
1002 );
1003 res.insert(
1004 "quota_exceeding",
1005 self.get_config_int(Config::QuotaExceeding)
1006 .await?
1007 .to_string(),
1008 );
1009 res.insert(
1010 "authserv_id_candidates",
1011 self.get_config(Config::AuthservIdCandidates)
1012 .await?
1013 .unwrap_or_default(),
1014 );
1015 res.insert(
1016 "sign_unencrypted",
1017 self.get_config_int(Config::SignUnencrypted)
1018 .await?
1019 .to_string(),
1020 );
1021 res.insert(
1022 "debug_logging",
1023 self.get_config_int(Config::DebugLogging).await?.to_string(),
1024 );
1025 res.insert(
1026 "last_msg_id",
1027 self.get_config_int(Config::LastMsgId).await?.to_string(),
1028 );
1029 res.insert(
1030 "gossip_period",
1031 self.get_config_int(Config::GossipPeriod).await?.to_string(),
1032 );
1033 res.insert(
1034 "webxdc_realtime_enabled",
1035 self.get_config_bool(Config::WebxdcRealtimeEnabled)
1036 .await?
1037 .to_string(),
1038 );
1039 res.insert(
1040 "donation_request_next_check",
1041 self.get_config_i64(Config::DonationRequestNextCheck)
1042 .await?
1043 .to_string(),
1044 );
1045 res.insert(
1046 "first_key_contacts_msg_id",
1047 self.sql
1048 .get_raw_config("first_key_contacts_msg_id")
1049 .await?
1050 .unwrap_or_default(),
1051 );
1052 res.insert(
1053 "stats_id",
1054 self.get_config(Config::StatsId)
1055 .await?
1056 .unwrap_or_else(|| "<unset>".to_string()),
1057 );
1058 res.insert(
1059 "stats_sending",
1060 stats::should_send_stats(self).await?.to_string(),
1061 );
1062 res.insert(
1063 "stats_last_sent",
1064 self.get_config_i64(Config::StatsLastSent)
1065 .await?
1066 .to_string(),
1067 );
1068 res.insert(
1069 "test_hooks",
1070 self.sql
1071 .get_raw_config("test_hooks")
1072 .await?
1073 .unwrap_or_default(),
1074 );
1075 res.insert(
1076 "std_header_protection_composing",
1077 self.sql
1078 .get_raw_config("std_header_protection_composing")
1079 .await?
1080 .unwrap_or_default(),
1081 );
1082 res.insert(
1083 "team_profile",
1084 self.get_config_bool(Config::TeamProfile).await?.to_string(),
1085 );
1086
1087 let elapsed = time_elapsed(&self.creation_time);
1088 res.insert("uptime", duration_to_str(elapsed));
1089
1090 Ok(res)
1091 }
1092
1093 pub async fn get_fresh_msgs(&self) -> Result<Vec<MsgId>> {
1100 let list = self
1101 .sql
1102 .query_map_vec(
1103 "SELECT m.id
1104FROM msgs m
1105LEFT JOIN contacts ct
1106 ON m.from_id=ct.id
1107LEFT JOIN chats c
1108 ON m.chat_id=c.id
1109WHERE m.state=?
1110AND m.hidden=0
1111AND m.chat_id>9
1112AND ct.blocked=0
1113AND c.blocked=0
1114AND NOT(c.muted_until=-1 OR c.muted_until>?)
1115ORDER BY m.timestamp DESC,m.id DESC",
1116 (MessageState::InFresh, time()),
1117 |row| {
1118 let msg_id: MsgId = row.get(0)?;
1119 Ok(msg_id)
1120 },
1121 )
1122 .await?;
1123 Ok(list)
1124 }
1125
1126 pub async fn get_next_msgs(&self) -> Result<Vec<MsgId>> {
1131 let last_msg_id = match self.get_config(Config::LastMsgId).await? {
1132 Some(s) => MsgId::new(s.parse()?),
1133 None => {
1134 self.sql
1139 .query_row(
1140 "SELECT IFNULL((SELECT MAX(id) - 1 FROM msgs), 0)",
1141 (),
1142 |row| {
1143 let msg_id: MsgId = row.get(0)?;
1144 Ok(msg_id)
1145 },
1146 )
1147 .await?
1148 }
1149 };
1150
1151 let list = self
1152 .sql
1153 .query_map_vec(
1154 "SELECT m.id
1155 FROM msgs m
1156 LEFT JOIN contacts ct
1157 ON m.from_id=ct.id
1158 LEFT JOIN chats c
1159 ON m.chat_id=c.id
1160 WHERE m.id>?
1161 AND m.hidden=0
1162 AND m.chat_id>9
1163 AND ct.blocked=0
1164 AND c.blocked!=1
1165 ORDER BY m.id ASC",
1166 (
1167 last_msg_id.to_u32(), ),
1169 |row| {
1170 let msg_id: MsgId = row.get(0)?;
1171 Ok(msg_id)
1172 },
1173 )
1174 .await?;
1175 Ok(list)
1176 }
1177
1178 pub async fn wait_next_msgs(&self) -> Result<Vec<MsgId>> {
1189 self.new_msgs_notify.notified().await;
1190 let list = self.get_next_msgs().await?;
1191 Ok(list)
1192 }
1193
1194 pub async fn search_msgs(&self, chat_id: Option<ChatId>, query: &str) -> Result<Vec<MsgId>> {
1205 let real_query = query.trim().to_lowercase();
1206 if real_query.is_empty() {
1207 return Ok(Vec::new());
1208 }
1209 let str_like_in_text = format!("%{real_query}%");
1210
1211 let list = if let Some(chat_id) = chat_id {
1212 self.sql
1213 .query_map_vec(
1214 "SELECT m.id AS id
1215 FROM msgs m
1216 LEFT JOIN contacts ct
1217 ON m.from_id=ct.id
1218 WHERE m.chat_id=?
1219 AND m.hidden=0
1220 AND ct.blocked=0
1221 AND IFNULL(txt_normalized, txt) LIKE ?
1222 ORDER BY m.timestamp,m.id;",
1223 (chat_id, str_like_in_text),
1224 |row| {
1225 let msg_id: MsgId = row.get("id")?;
1226 Ok(msg_id)
1227 },
1228 )
1229 .await?
1230 } else {
1231 self.sql
1242 .query_map_vec(
1243 "SELECT m.id AS id
1244 FROM msgs m
1245 LEFT JOIN contacts ct
1246 ON m.from_id=ct.id
1247 LEFT JOIN chats c
1248 ON m.chat_id=c.id
1249 WHERE m.chat_id>9
1250 AND m.hidden=0
1251 AND c.blocked!=1
1252 AND ct.blocked=0
1253 AND IFNULL(txt_normalized, txt) LIKE ?
1254 ORDER BY m.id DESC LIMIT 1000",
1255 (str_like_in_text,),
1256 |row| {
1257 let msg_id: MsgId = row.get("id")?;
1258 Ok(msg_id)
1259 },
1260 )
1261 .await?
1262 };
1263
1264 Ok(list)
1265 }
1266
1267 pub async fn is_mvbox(&self, folder_name: &str) -> Result<bool> {
1269 let mvbox = self.get_config(Config::ConfiguredMvboxFolder).await?;
1270 Ok(mvbox.as_deref() == Some(folder_name))
1271 }
1272
1273 pub(crate) fn derive_blobdir(dbfile: &Path) -> PathBuf {
1274 let mut blob_fname = OsString::new();
1275 blob_fname.push(dbfile.file_name().unwrap_or_default());
1276 blob_fname.push("-blobs");
1277 dbfile.with_file_name(blob_fname)
1278 }
1279
1280 pub(crate) fn derive_walfile(dbfile: &Path) -> PathBuf {
1281 let mut wal_fname = OsString::new();
1282 wal_fname.push(dbfile.file_name().unwrap_or_default());
1283 wal_fname.push("-wal");
1284 dbfile.with_file_name(wal_fname)
1285 }
1286}
1287
1288#[cfg(test)]
1289mod context_tests;