1use std::collections::BTreeSet;
4use std::collections::HashSet;
5use std::path::{Path, PathBuf};
6use std::str;
7
8use anyhow::{Context as _, Result, ensure, format_err};
9use deltachat_contact_tools::{VcardContact, parse_vcard};
10use deltachat_derive::{FromSql, ToSql};
11use serde::{Deserialize, Serialize};
12use tokio::{fs, io};
13
14use crate::blob::BlobObject;
15use crate::chat::{Chat, ChatId, ChatIdBlocked, ChatVisibility, send_msg};
16use crate::chatlist_events;
17use crate::config::Config;
18use crate::constants::{Blocked, Chattype, DC_CHAT_ID_TRASH, DC_MSG_ID_LAST_SPECIAL};
19use crate::contact::{self, Contact, ContactId};
20use crate::context::Context;
21use crate::debug_logging::set_debug_logging_xdc;
22use crate::download::DownloadState;
23use crate::ephemeral::{Timer as EphemeralTimer, start_ephemeral_timers_msgids};
24use crate::events::EventType;
25use crate::imap::markseen_on_imap_table;
26use crate::location::delete_poi_location;
27use crate::log::warn;
28use crate::mimeparser::{SystemMessage, parse_message_id};
29use crate::param::{Param, Params};
30use crate::pgp::split_armored_data;
31use crate::reaction::get_msg_reactions;
32use crate::sql;
33use crate::summary::Summary;
34use crate::sync::SyncData;
35use crate::tools::create_outgoing_rfc724_mid;
36use crate::tools::{
37 buf_compress, buf_decompress, get_filebytes, get_filemeta, gm2local_offset, read_file,
38 sanitize_filename, time, timestamp_to_str,
39};
40
41#[derive(
47 Debug, Copy, Clone, Default, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize,
48)]
49pub struct MsgId(u32);
50
51impl MsgId {
52 pub fn new(id: u32) -> MsgId {
54 MsgId(id)
55 }
56
57 pub fn new_unset() -> MsgId {
59 MsgId(0)
60 }
61
62 pub fn is_special(self) -> bool {
66 self.0 <= DC_MSG_ID_LAST_SPECIAL
67 }
68
69 pub fn is_unset(self) -> bool {
79 self.0 == 0
80 }
81
82 pub async fn get_state(self, context: &Context) -> Result<MessageState> {
84 let result = context
85 .sql
86 .query_row_optional(
87 concat!(
88 "SELECT m.state, mdns.msg_id",
89 " FROM msgs m LEFT JOIN msgs_mdns mdns ON mdns.msg_id=m.id",
90 " WHERE id=?",
91 " LIMIT 1",
92 ),
93 (self,),
94 |row| {
95 let state: MessageState = row.get(0)?;
96 let mdn_msg_id: Option<MsgId> = row.get(1)?;
97 Ok(state.with_mdns(mdn_msg_id.is_some()))
98 },
99 )
100 .await?
101 .unwrap_or_default();
102 Ok(result)
103 }
104
105 pub(crate) async fn get_param(self, context: &Context) -> Result<Params> {
106 let res: Option<String> = context
107 .sql
108 .query_get_value("SELECT param FROM msgs WHERE id=?", (self,))
109 .await?;
110 Ok(res
111 .map(|s| s.parse().unwrap_or_default())
112 .unwrap_or_default())
113 }
114
115 pub(crate) async fn trash(self, context: &Context, on_server: bool) -> Result<()> {
127 context
128 .sql
129 .execute(
130 "INSERT OR REPLACE INTO msgs (id, rfc724_mid, timestamp, chat_id, deleted)
134 SELECT ?1, rfc724_mid, timestamp, ?, ? FROM msgs WHERE id=?1",
135 (self, DC_CHAT_ID_TRASH, on_server),
136 )
137 .await?;
138
139 Ok(())
140 }
141
142 pub(crate) async fn set_delivered(self, context: &Context) -> Result<()> {
143 update_msg_state(context, self, MessageState::OutDelivered).await?;
144 let chat_id: Option<ChatId> = context
145 .sql
146 .query_get_value("SELECT chat_id FROM msgs WHERE id=?", (self,))
147 .await?;
148 context.emit_event(EventType::MsgDelivered {
149 chat_id: chat_id.unwrap_or_default(),
150 msg_id: self,
151 });
152 if let Some(chat_id) = chat_id {
153 chatlist_events::emit_chatlist_item_changed(context, chat_id);
154 }
155 Ok(())
156 }
157
158 pub fn to_u32(self) -> u32 {
163 self.0
164 }
165
166 pub async fn get_info_server_urls(
168 context: &Context,
169 rfc724_mid: String,
170 ) -> Result<Vec<String>> {
171 context
172 .sql
173 .query_map_vec(
174 "SELECT folder, uid FROM imap WHERE rfc724_mid=?",
175 (rfc724_mid,),
176 |row| {
177 let folder: String = row.get("folder")?;
178 let uid: u32 = row.get("uid")?;
179 Ok(format!("</{folder}/;UID={uid}>"))
180 },
181 )
182 .await
183 }
184
185 pub async fn hop_info(self, context: &Context) -> Result<String> {
187 let hop_info = context
188 .sql
189 .query_get_value("SELECT IFNULL(hop_info, '') FROM msgs WHERE id=?", (self,))
190 .await?
191 .with_context(|| format!("Message {self} not found"))?;
192 Ok(hop_info)
193 }
194
195 pub async fn get_info(self, context: &Context) -> Result<String> {
197 let msg = Message::load_from_db(context, self).await?;
198
199 let mut ret = String::new();
200
201 let fts = timestamp_to_str(msg.get_timestamp());
202 ret += &format!("Sent: {fts}");
203
204 let from_contact = Contact::get_by_id(context, msg.from_id).await?;
205 let name = from_contact.get_name_n_addr();
206 if let Some(override_sender_name) = msg.get_override_sender_name() {
207 let addr = from_contact.get_addr();
208 ret += &format!(" by ~{override_sender_name} ({addr})");
209 } else {
210 ret += &format!(" by {name}");
211 }
212 ret += "\n";
213
214 if msg.from_id != ContactId::SELF {
215 let s = timestamp_to_str(if 0 != msg.timestamp_rcvd {
216 msg.timestamp_rcvd
217 } else {
218 msg.timestamp_sort
219 });
220 ret += &format!("Received: {}", &s);
221 ret += "\n";
222 }
223
224 if let EphemeralTimer::Enabled { duration } = msg.ephemeral_timer {
225 ret += &format!("Ephemeral timer: {duration}\n");
226 }
227
228 if msg.ephemeral_timestamp != 0 {
229 ret += &format!("Expires: {}\n", timestamp_to_str(msg.ephemeral_timestamp));
230 }
231
232 if msg.from_id == ContactId::INFO || msg.to_id == ContactId::INFO {
233 return Ok(ret);
235 }
236
237 if let Ok(rows) = context
238 .sql
239 .query_map_vec(
240 "SELECT contact_id, timestamp_sent FROM msgs_mdns WHERE msg_id=?",
241 (self,),
242 |row| {
243 let contact_id: ContactId = row.get(0)?;
244 let ts: i64 = row.get(1)?;
245 Ok((contact_id, ts))
246 },
247 )
248 .await
249 {
250 for (contact_id, ts) in rows {
251 let fts = timestamp_to_str(ts);
252 ret += &format!("Read: {fts}");
253
254 let name = Contact::get_by_id(context, contact_id)
255 .await
256 .map(|contact| contact.get_name_n_addr())
257 .unwrap_or_default();
258
259 ret += &format!(" by {name}");
260 ret += "\n";
261 }
262 }
263
264 ret += &format!("State: {}", msg.state);
265
266 if msg.has_location() {
267 ret += ", Location sent";
268 }
269
270 if 0 != msg.param.get_int(Param::GuaranteeE2ee).unwrap_or_default() {
271 ret += ", Encrypted";
272 }
273
274 ret += "\n";
275
276 let reactions = get_msg_reactions(context, self).await?;
277 if !reactions.is_empty() {
278 ret += &format!("Reactions: {reactions}\n");
279 }
280
281 if let Some(error) = msg.error.as_ref() {
282 ret += &format!("Error: {error}");
283 }
284
285 if let Some(path) = msg.get_file(context) {
286 let bytes = get_filebytes(context, &path).await?;
287 ret += &format!(
288 "\nFile: {}, name: {}, {} bytes\n",
289 path.display(),
290 msg.get_filename().unwrap_or_default(),
291 bytes
292 );
293 }
294
295 if msg.viewtype != Viewtype::Text {
296 ret += "Type: ";
297 ret += &format!("{}", msg.viewtype);
298 ret += "\n";
299 ret += &format!("Mimetype: {}\n", &msg.get_filemime().unwrap_or_default());
300 }
301 let w = msg.param.get_int(Param::Width).unwrap_or_default();
302 let h = msg.param.get_int(Param::Height).unwrap_or_default();
303 if w != 0 || h != 0 {
304 ret += &format!("Dimension: {w} x {h}\n",);
305 }
306 let duration = msg.param.get_int(Param::Duration).unwrap_or_default();
307 if duration != 0 {
308 ret += &format!("Duration: {duration} ms\n",);
309 }
310 if !msg.rfc724_mid.is_empty() {
311 ret += &format!("\nMessage-ID: {}", msg.rfc724_mid);
312
313 let server_urls = Self::get_info_server_urls(context, msg.rfc724_mid).await?;
314 for server_url in server_urls {
315 ret += &format!("\nServer-URL: {server_url}");
317 }
318 }
319 let hop_info = self.hop_info(context).await?;
320
321 ret += "\n\n";
322 if hop_info.is_empty() {
323 ret += "No Hop Info";
324 } else {
325 ret += &hop_info;
326 }
327
328 Ok(ret)
329 }
330}
331
332impl std::fmt::Display for MsgId {
333 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
334 write!(f, "Msg#{}", self.0)
335 }
336}
337
338impl rusqlite::types::ToSql for MsgId {
347 fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
348 if self.0 <= DC_MSG_ID_LAST_SPECIAL {
349 return Err(rusqlite::Error::ToSqlConversionFailure(
350 format_err!("Invalid MsgId {}", self.0).into(),
351 ));
352 }
353 let val = rusqlite::types::Value::Integer(i64::from(self.0));
354 let out = rusqlite::types::ToSqlOutput::Owned(val);
355 Ok(out)
356 }
357}
358
359impl rusqlite::types::FromSql for MsgId {
361 fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
362 i64::column_result(value).and_then(|val| {
364 if 0 <= val && val <= i64::from(u32::MAX) {
365 Ok(MsgId::new(val as u32))
366 } else {
367 Err(rusqlite::types::FromSqlError::OutOfRange(val))
368 }
369 })
370 }
371}
372
373#[derive(
374 Debug,
375 Copy,
376 Clone,
377 PartialEq,
378 FromPrimitive,
379 ToPrimitive,
380 FromSql,
381 ToSql,
382 Serialize,
383 Deserialize,
384 Default,
385)]
386#[repr(u8)]
387pub(crate) enum MessengerMessage {
388 #[default]
389 No = 0,
390 Yes = 1,
391
392 Reply = 2,
394}
395
396#[derive(Debug, Clone, Default, Serialize, Deserialize)]
400pub struct Message {
401 pub(crate) id: MsgId,
403
404 pub(crate) from_id: ContactId,
406
407 pub(crate) to_id: ContactId,
409
410 pub(crate) chat_id: ChatId,
412
413 pub(crate) viewtype: Viewtype,
415
416 pub(crate) state: MessageState,
418 pub(crate) download_state: DownloadState,
419
420 pub(crate) hidden: bool,
422 pub(crate) timestamp_sort: i64,
423 pub(crate) timestamp_sent: i64,
424 pub(crate) timestamp_rcvd: i64,
425 pub(crate) ephemeral_timer: EphemeralTimer,
426 pub(crate) ephemeral_timestamp: i64,
427 pub(crate) text: String,
428
429 pub(crate) subject: String,
433
434 pub(crate) rfc724_mid: String,
436
437 pub(crate) in_reply_to: Option<String>,
439 pub(crate) is_dc_message: MessengerMessage,
440 pub(crate) original_msg_id: MsgId,
441 pub(crate) mime_modified: bool,
442 pub(crate) chat_blocked: Blocked,
443 pub(crate) location_id: u32,
444 pub(crate) error: Option<String>,
445 pub(crate) param: Params,
446}
447
448impl Message {
449 pub fn new(viewtype: Viewtype) -> Self {
451 Message {
452 viewtype,
453 rfc724_mid: create_outgoing_rfc724_mid(),
454 ..Default::default()
455 }
456 }
457
458 pub fn new_text(text: String) -> Self {
460 Message {
461 viewtype: Viewtype::Text,
462 text,
463 rfc724_mid: create_outgoing_rfc724_mid(),
464 ..Default::default()
465 }
466 }
467
468 pub async fn load_from_db(context: &Context, id: MsgId) -> Result<Message> {
472 let message = Self::load_from_db_optional(context, id)
473 .await?
474 .with_context(|| format!("Message {id} does not exist"))?;
475 Ok(message)
476 }
477
478 pub async fn load_from_db_optional(context: &Context, id: MsgId) -> Result<Option<Message>> {
482 ensure!(
483 !id.is_special(),
484 "Can not load special message ID {id} from DB"
485 );
486 let msg = context
487 .sql
488 .query_row_optional(
489 concat!(
490 "SELECT",
491 " m.id AS id,",
492 " rfc724_mid AS rfc724mid,",
493 " m.mime_in_reply_to AS mime_in_reply_to,",
494 " m.chat_id AS chat_id,",
495 " m.from_id AS from_id,",
496 " m.to_id AS to_id,",
497 " m.timestamp AS timestamp,",
498 " m.timestamp_sent AS timestamp_sent,",
499 " m.timestamp_rcvd AS timestamp_rcvd,",
500 " m.ephemeral_timer AS ephemeral_timer,",
501 " m.ephemeral_timestamp AS ephemeral_timestamp,",
502 " m.type AS type,",
503 " m.state AS state,",
504 " mdns.msg_id AS mdn_msg_id,",
505 " m.download_state AS download_state,",
506 " m.error AS error,",
507 " m.msgrmsg AS msgrmsg,",
508 " m.starred AS original_msg_id,",
509 " m.mime_modified AS mime_modified,",
510 " m.txt AS txt,",
511 " m.subject AS subject,",
512 " m.param AS param,",
513 " m.hidden AS hidden,",
514 " m.location_id AS location,",
515 " c.blocked AS blocked",
516 " FROM msgs m",
517 " LEFT JOIN chats c ON c.id=m.chat_id",
518 " LEFT JOIN msgs_mdns mdns ON mdns.msg_id=m.id",
519 " WHERE m.id=? AND chat_id!=3",
520 " LIMIT 1",
521 ),
522 (id,),
523 |row| {
524 let state: MessageState = row.get("state")?;
525 let mdn_msg_id: Option<MsgId> = row.get("mdn_msg_id")?;
526 let text = match row.get_ref("txt")? {
527 rusqlite::types::ValueRef::Text(buf) => {
528 match String::from_utf8(buf.to_vec()) {
529 Ok(t) => t,
530 Err(_) => {
531 warn!(
532 context,
533 concat!(
534 "dc_msg_load_from_db: could not get ",
535 "text column as non-lossy utf8 id {}"
536 ),
537 id
538 );
539 String::from_utf8_lossy(buf).into_owned()
540 }
541 }
542 }
543 _ => String::new(),
544 };
545 let msg = Message {
546 id: row.get("id")?,
547 rfc724_mid: row.get::<_, String>("rfc724mid")?,
548 in_reply_to: row
549 .get::<_, Option<String>>("mime_in_reply_to")?
550 .and_then(|in_reply_to| parse_message_id(&in_reply_to).ok()),
551 chat_id: row.get("chat_id")?,
552 from_id: row.get("from_id")?,
553 to_id: row.get("to_id")?,
554 timestamp_sort: row.get("timestamp")?,
555 timestamp_sent: row.get("timestamp_sent")?,
556 timestamp_rcvd: row.get("timestamp_rcvd")?,
557 ephemeral_timer: row.get("ephemeral_timer")?,
558 ephemeral_timestamp: row.get("ephemeral_timestamp")?,
559 viewtype: row.get("type").unwrap_or_default(),
560 state: state.with_mdns(mdn_msg_id.is_some()),
561 download_state: row.get("download_state")?,
562 error: Some(row.get::<_, String>("error")?)
563 .filter(|error| !error.is_empty()),
564 is_dc_message: row.get("msgrmsg")?,
565 original_msg_id: row.get("original_msg_id")?,
566 mime_modified: row.get("mime_modified")?,
567 text,
568 subject: row.get("subject")?,
569 param: row.get::<_, String>("param")?.parse().unwrap_or_default(),
570 hidden: row.get("hidden")?,
571 location_id: row.get("location")?,
572 chat_blocked: row
573 .get::<_, Option<Blocked>>("blocked")?
574 .unwrap_or_default(),
575 };
576 Ok(msg)
577 },
578 )
579 .await
580 .with_context(|| format!("failed to load message {id} from the database"))?;
581
582 Ok(msg)
583 }
584
585 pub fn get_filemime(&self) -> Option<String> {
592 if let Some(m) = self.param.get(Param::MimeType) {
593 return Some(m.to_string());
594 } else if self.param.exists(Param::File) {
595 if let Some((_, mime)) = guess_msgtype_from_suffix(self) {
596 return Some(mime.to_string());
597 }
598 return Some("application/octet-stream".to_string());
600 }
601 None
603 }
604
605 pub fn get_file(&self, context: &Context) -> Option<PathBuf> {
607 self.param.get_file_path(context).unwrap_or(None)
608 }
609
610 pub async fn vcard_contacts(&self, context: &Context) -> Result<Vec<VcardContact>> {
612 if self.viewtype != Viewtype::Vcard {
613 return Ok(Vec::new());
614 }
615
616 let path = self
617 .get_file(context)
618 .context("vCard message does not have an attachment")?;
619 let bytes = tokio::fs::read(path).await?;
620 let vcard_contents = std::str::from_utf8(&bytes).context("vCard is not a valid UTF-8")?;
621 Ok(parse_vcard(vcard_contents))
622 }
623
624 pub async fn save_file(&self, context: &Context, path: &Path) -> Result<()> {
626 let path_src = self.get_file(context).context("No file")?;
627 let mut src = fs::OpenOptions::new().read(true).open(path_src).await?;
628 let mut dst = fs::OpenOptions::new()
629 .write(true)
630 .create_new(true)
631 .open(path)
632 .await?;
633 io::copy(&mut src, &mut dst).await?;
634 Ok(())
635 }
636
637 pub(crate) async fn try_calc_and_set_dimensions(&mut self, context: &Context) -> Result<()> {
639 if self.viewtype.has_file() {
640 let file_param = self.param.get_file_path(context)?;
641 if let Some(path_and_filename) = file_param
642 && matches!(
643 self.viewtype,
644 Viewtype::Image | Viewtype::Gif | Viewtype::Sticker
645 )
646 && !self.param.exists(Param::Width)
647 {
648 let buf = read_file(context, &path_and_filename).await?;
649
650 match get_filemeta(&buf) {
651 Ok((width, height)) => {
652 self.param.set_int(Param::Width, width as i32);
653 self.param.set_int(Param::Height, height as i32);
654 }
655 Err(err) => {
656 self.param.set_int(Param::Width, 0);
657 self.param.set_int(Param::Height, 0);
658 warn!(
659 context,
660 "Failed to get width and height for {}: {err:#}.",
661 path_and_filename.display()
662 );
663 }
664 }
665
666 if !self.id.is_unset() {
667 self.update_param(context).await?;
668 }
669 }
670 }
671 Ok(())
672 }
673
674 pub fn has_location(&self) -> bool {
680 self.location_id != 0
681 }
682
683 pub fn set_location(&mut self, latitude: f64, longitude: f64) {
700 if latitude == 0.0 && longitude == 0.0 {
701 return;
702 }
703
704 self.param.set_float(Param::SetLatitude, latitude);
705 self.param.set_float(Param::SetLongitude, longitude);
706 }
707
708 pub fn get_timestamp(&self) -> i64 {
711 if 0 != self.timestamp_sent {
712 self.timestamp_sent
713 } else {
714 self.timestamp_sort
715 }
716 }
717
718 pub fn get_id(&self) -> MsgId {
720 self.id
721 }
722
723 pub fn rfc724_mid(&self) -> &str {
726 &self.rfc724_mid
727 }
728
729 pub fn get_from_id(&self) -> ContactId {
731 self.from_id
732 }
733
734 pub fn get_chat_id(&self) -> ChatId {
736 self.chat_id
737 }
738
739 pub fn get_viewtype(&self) -> Viewtype {
741 self.viewtype
742 }
743
744 pub fn force_sticker(&mut self) {
747 self.param.set_int(Param::ForceSticker, 1);
748 }
749
750 pub fn get_state(&self) -> MessageState {
752 self.state
753 }
754
755 pub fn get_received_timestamp(&self) -> i64 {
757 self.timestamp_rcvd
758 }
759
760 pub fn get_sort_timestamp(&self) -> i64 {
762 self.timestamp_sort
763 }
764
765 pub fn get_text(&self) -> String {
767 self.text.clone()
768 }
769
770 pub fn get_subject(&self) -> &str {
772 &self.subject
773 }
774
775 pub fn get_filename(&self) -> Option<String> {
779 if let Some(name) = self.param.get(Param::Filename) {
780 return Some(sanitize_filename(name));
781 }
782 self.param
783 .get(Param::File)
784 .and_then(|file| Path::new(file).file_name())
785 .map(|name| sanitize_filename(&name.to_string_lossy()))
786 }
787
788 pub async fn get_filebytes(&self, context: &Context) -> Result<Option<u64>> {
790 if let Some(path) = self.param.get_file_path(context)? {
791 Ok(Some(get_filebytes(context, &path).await.with_context(
792 || format!("failed to get {} size in bytes", path.display()),
793 )?))
794 } else {
795 Ok(None)
796 }
797 }
798
799 pub fn get_width(&self) -> i32 {
801 self.param.get_int(Param::Width).unwrap_or_default()
802 }
803
804 pub fn get_height(&self) -> i32 {
806 self.param.get_int(Param::Height).unwrap_or_default()
807 }
808
809 pub fn get_duration(&self) -> i32 {
811 self.param.get_int(Param::Duration).unwrap_or_default()
812 }
813
814 pub fn get_showpadlock(&self) -> bool {
816 self.param.get_int(Param::GuaranteeE2ee).unwrap_or_default() != 0
817 || self.from_id == ContactId::DEVICE
818 }
819
820 pub fn is_bot(&self) -> bool {
822 self.param.get_bool(Param::Bot).unwrap_or_default()
823 }
824
825 pub fn get_ephemeral_timer(&self) -> EphemeralTimer {
827 self.ephemeral_timer
828 }
829
830 pub fn get_ephemeral_timestamp(&self) -> i64 {
832 self.ephemeral_timestamp
833 }
834
835 pub async fn get_summary(&self, context: &Context, chat: Option<&Chat>) -> Result<Summary> {
837 let chat_loaded: Chat;
838 let chat = if let Some(chat) = chat {
839 chat
840 } else {
841 let chat = Chat::load_from_db(context, self.chat_id).await?;
842 chat_loaded = chat;
843 &chat_loaded
844 };
845
846 let contact = if self.from_id != ContactId::SELF {
847 match chat.typ {
848 Chattype::Group
849 | Chattype::OutBroadcast
850 | Chattype::InBroadcast
851 | Chattype::Mailinglist => Some(Contact::get_by_id(context, self.from_id).await?),
852 Chattype::Single => None,
853 }
854 } else {
855 None
856 };
857
858 Summary::new(context, self, chat, contact.as_ref()).await
859 }
860
861 pub fn get_override_sender_name(&self) -> Option<String> {
871 self.param
872 .get(Param::OverrideSenderDisplayname)
873 .map(|name| name.to_string())
874 }
875
876 pub(crate) fn get_sender_name(&self, contact: &Contact) -> String {
879 self.get_override_sender_name()
880 .unwrap_or_else(|| contact.get_display_name().to_string())
881 }
882
883 pub fn has_deviating_timestamp(&self) -> bool {
888 let cnv_to_local = gm2local_offset();
889 let sort_timestamp = self.get_sort_timestamp() + cnv_to_local;
890 let send_timestamp = self.get_timestamp() + cnv_to_local;
891
892 sort_timestamp / 86400 != send_timestamp / 86400
893 }
894
895 pub fn is_sent(&self) -> bool {
898 self.state >= MessageState::OutDelivered
899 }
900
901 pub fn is_forwarded(&self) -> bool {
903 0 != self.param.get_int(Param::Forwarded).unwrap_or_default()
904 }
905
906 pub fn is_edited(&self) -> bool {
908 self.param.get_bool(Param::IsEdited).unwrap_or_default()
909 }
910
911 pub fn is_info(&self) -> bool {
913 let cmd = self.param.get_cmd();
914 self.from_id == ContactId::INFO
915 || self.to_id == ContactId::INFO
916 || cmd != SystemMessage::Unknown && cmd != SystemMessage::AutocryptSetupMessage
917 }
918
919 pub fn get_info_type(&self) -> SystemMessage {
921 self.param.get_cmd()
922 }
923
924 pub async fn get_info_contact_id(&self, context: &Context) -> Result<Option<ContactId>> {
926 match self.param.get_cmd() {
927 SystemMessage::GroupNameChanged
928 | SystemMessage::GroupImageChanged
929 | SystemMessage::EphemeralTimerChanged => {
930 if self.from_id != ContactId::INFO {
931 Ok(Some(self.from_id))
932 } else {
933 Ok(None)
934 }
935 }
936
937 SystemMessage::MemberAddedToGroup | SystemMessage::MemberRemovedFromGroup => {
938 if let Some(contact_i32) = self.param.get_int(Param::ContactAddedRemoved) {
939 let contact_id = ContactId::new(contact_i32.try_into()?);
940 if contact_id == ContactId::SELF
941 || Contact::real_exists_by_id(context, contact_id).await?
942 {
943 Ok(Some(contact_id))
944 } else {
945 Ok(None)
946 }
947 } else {
948 Ok(None)
949 }
950 }
951
952 SystemMessage::AutocryptSetupMessage
953 | SystemMessage::SecurejoinMessage
954 | SystemMessage::LocationStreamingEnabled
955 | SystemMessage::LocationOnly
956 | SystemMessage::ChatE2ee
957 | SystemMessage::ChatProtectionEnabled
958 | SystemMessage::ChatProtectionDisabled
959 | SystemMessage::InvalidUnencryptedMail
960 | SystemMessage::SecurejoinWait
961 | SystemMessage::SecurejoinWaitTimeout
962 | SystemMessage::MultiDeviceSync
963 | SystemMessage::WebxdcStatusUpdate
964 | SystemMessage::WebxdcInfoMessage
965 | SystemMessage::IrohNodeAddr
966 | SystemMessage::CallAccepted
967 | SystemMessage::CallEnded
968 | SystemMessage::Unknown => Ok(None),
969 }
970 }
971
972 pub fn is_system_message(&self) -> bool {
974 let cmd = self.param.get_cmd();
975 cmd != SystemMessage::Unknown
976 }
977
978 pub fn is_setupmessage(&self) -> bool {
980 if self.viewtype != Viewtype::File {
981 return false;
982 }
983
984 self.param.get_cmd() == SystemMessage::AutocryptSetupMessage
985 }
986
987 pub async fn get_setupcodebegin(&self, context: &Context) -> Option<String> {
991 if !self.is_setupmessage() {
992 return None;
993 }
994
995 if let Some(filename) = self.get_file(context)
996 && let Ok(ref buf) = read_file(context, &filename).await
997 && let Ok((typ, headers, _)) = split_armored_data(buf)
998 && typ == pgp::armor::BlockType::Message
999 {
1000 return headers.get(crate::pgp::HEADER_SETUPCODE).cloned();
1001 }
1002
1003 None
1004 }
1005
1006 pub fn set_text(&mut self, text: String) {
1008 self.text = text;
1009 }
1010
1011 pub fn set_subject(&mut self, subject: String) {
1014 self.subject = subject;
1015 }
1016
1017 pub fn set_file_and_deduplicate(
1032 &mut self,
1033 context: &Context,
1034 file: &Path,
1035 name: Option<&str>,
1036 filemime: Option<&str>,
1037 ) -> Result<()> {
1038 let name = if let Some(name) = name {
1039 name.to_string()
1040 } else {
1041 file.file_name()
1042 .map(|s| s.to_string_lossy().to_string())
1043 .unwrap_or_else(|| "unknown_file".to_string())
1044 };
1045
1046 let blob = BlobObject::create_and_deduplicate(context, file, Path::new(&name))?;
1047 self.param.set(Param::File, blob.as_name());
1048
1049 self.param.set(Param::Filename, name);
1050 self.param.set_optional(Param::MimeType, filemime);
1051
1052 Ok(())
1053 }
1054
1055 pub fn set_file_from_bytes(
1062 &mut self,
1063 context: &Context,
1064 name: &str,
1065 data: &[u8],
1066 filemime: Option<&str>,
1067 ) -> Result<()> {
1068 let blob = BlobObject::create_and_deduplicate_from_bytes(context, data, name)?;
1069 self.param.set(Param::Filename, name);
1070 self.param.set(Param::File, blob.as_name());
1071 self.param.set_optional(Param::MimeType, filemime);
1072
1073 Ok(())
1074 }
1075
1076 pub async fn make_vcard(&mut self, context: &Context, contacts: &[ContactId]) -> Result<()> {
1078 ensure!(
1079 matches!(self.viewtype, Viewtype::File | Viewtype::Vcard),
1080 "Wrong viewtype for vCard: {}",
1081 self.viewtype,
1082 );
1083 let vcard = contact::make_vcard(context, contacts).await?;
1084 self.set_file_from_bytes(context, "vcard.vcf", vcard.as_bytes(), None)
1085 }
1086
1087 pub(crate) async fn try_set_vcard(&mut self, context: &Context, path: &Path) -> Result<()> {
1089 let vcard = fs::read(path)
1090 .await
1091 .with_context(|| format!("Could not read {path:?}"))?;
1092 if let Some(summary) = get_vcard_summary(&vcard) {
1093 self.param.set(Param::Summary1, summary);
1094 } else {
1095 warn!(context, "try_set_vcard: Not a valid DeltaChat vCard.");
1096 self.viewtype = Viewtype::File;
1097 }
1098 Ok(())
1099 }
1100
1101 pub fn set_override_sender_name(&mut self, name: Option<String>) {
1104 self.param
1105 .set_optional(Param::OverrideSenderDisplayname, name);
1106 }
1107
1108 pub fn set_dimension(&mut self, width: i32, height: i32) {
1110 self.param.set_int(Param::Width, width);
1111 self.param.set_int(Param::Height, height);
1112 }
1113
1114 pub fn set_duration(&mut self, duration: i32) {
1116 self.param.set_int(Param::Duration, duration);
1117 }
1118
1119 pub(crate) fn set_reaction(&mut self) {
1121 self.param.set_int(Param::Reaction, 1);
1122 }
1123
1124 pub async fn latefiling_mediasize(
1127 &mut self,
1128 context: &Context,
1129 width: i32,
1130 height: i32,
1131 duration: i32,
1132 ) -> Result<()> {
1133 if width > 0 && height > 0 {
1134 self.param.set_int(Param::Width, width);
1135 self.param.set_int(Param::Height, height);
1136 }
1137 if duration > 0 {
1138 self.param.set_int(Param::Duration, duration);
1139 }
1140 self.update_param(context).await?;
1141 Ok(())
1142 }
1143
1144 pub fn set_quote_text(&mut self, text: Option<(String, bool)>) {
1150 let Some((text, protect)) = text else {
1151 self.param.remove(Param::Quote);
1152 self.param.remove(Param::ProtectQuote);
1153 return;
1154 };
1155 self.param.set(Param::Quote, text);
1156 self.param.set_optional(
1157 Param::ProtectQuote,
1158 match protect {
1159 true => Some("1"),
1160 false => None,
1161 },
1162 );
1163 }
1164
1165 pub async fn set_quote(&mut self, context: &Context, quote: Option<&Message>) -> Result<()> {
1174 if let Some(quote) = quote {
1175 ensure!(
1176 !quote.rfc724_mid.is_empty(),
1177 "Message without Message-Id cannot be quoted"
1178 );
1179 self.in_reply_to = Some(quote.rfc724_mid.clone());
1180
1181 let text = quote.get_text();
1182 let text = if text.is_empty() {
1183 quote
1185 .get_summary(context, None)
1186 .await?
1187 .truncated_text(500)
1188 .to_string()
1189 } else {
1190 text
1191 };
1192 self.set_quote_text(Some((
1193 text,
1194 quote
1195 .param
1196 .get_bool(Param::GuaranteeE2ee)
1197 .unwrap_or_default(),
1198 )));
1199 } else {
1200 self.in_reply_to = None;
1201 self.set_quote_text(None);
1202 }
1203
1204 Ok(())
1205 }
1206
1207 pub fn quoted_text(&self) -> Option<String> {
1209 self.param.get(Param::Quote).map(|s| s.to_string())
1210 }
1211
1212 pub async fn quoted_message(&self, context: &Context) -> Result<Option<Message>> {
1214 if self.param.get(Param::Quote).is_some() && !self.is_forwarded() {
1215 return self.parent(context).await;
1216 }
1217 Ok(None)
1218 }
1219
1220 pub async fn parent(&self, context: &Context) -> Result<Option<Message>> {
1225 if let Some(in_reply_to) = &self.in_reply_to
1226 && let Some(msg_id) = rfc724_mid_exists(context, in_reply_to).await?
1227 {
1228 let msg = Message::load_from_db_optional(context, msg_id).await?;
1229 return Ok(msg);
1230 }
1231 Ok(None)
1232 }
1233
1234 pub async fn get_original_msg_id(&self, context: &Context) -> Result<Option<MsgId>> {
1236 if !self.original_msg_id.is_special()
1237 && let Some(msg) = Message::load_from_db_optional(context, self.original_msg_id).await?
1238 {
1239 return if msg.chat_id.is_trash() {
1240 Ok(None)
1241 } else {
1242 Ok(Some(msg.id))
1243 };
1244 }
1245 Ok(None)
1246 }
1247
1248 pub async fn get_saved_msg_id(&self, context: &Context) -> Result<Option<MsgId>> {
1252 let res: Option<MsgId> = context
1253 .sql
1254 .query_get_value(
1255 "SELECT id FROM msgs WHERE starred=? AND chat_id!=?",
1256 (self.id, DC_CHAT_ID_TRASH),
1257 )
1258 .await?;
1259 Ok(res)
1260 }
1261
1262 pub fn force_plaintext(&mut self) {
1264 self.param.set_int(Param::ForcePlaintext, 1);
1265 }
1266
1267 pub async fn update_param(&self, context: &Context) -> Result<()> {
1269 context
1270 .sql
1271 .execute(
1272 "UPDATE msgs SET param=? WHERE id=?;",
1273 (self.param.to_string(), self.id),
1274 )
1275 .await?;
1276 Ok(())
1277 }
1278
1279 pub fn error(&self) -> Option<String> {
1292 self.error.clone()
1293 }
1294}
1295
1296#[derive(
1300 Debug,
1301 Default,
1302 Clone,
1303 Copy,
1304 PartialEq,
1305 Eq,
1306 PartialOrd,
1307 Ord,
1308 FromPrimitive,
1309 ToPrimitive,
1310 ToSql,
1311 FromSql,
1312 Serialize,
1313 Deserialize,
1314)]
1315#[repr(u32)]
1316pub enum MessageState {
1317 #[default]
1319 Undefined = 0,
1320
1321 InFresh = 10,
1324
1325 InNoticed = 13,
1329
1330 InSeen = 16,
1333
1334 OutPreparing = 18,
1338
1339 OutDraft = 19,
1341
1342 OutPending = 20,
1346
1347 OutFailed = 24,
1350
1351 OutDelivered = 26,
1355
1356 OutMdnRcvd = 28,
1359}
1360
1361impl std::fmt::Display for MessageState {
1362 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
1363 write!(
1364 f,
1365 "{}",
1366 match self {
1367 Self::Undefined => "Undefined",
1368 Self::InFresh => "Fresh",
1369 Self::InNoticed => "Noticed",
1370 Self::InSeen => "Seen",
1371 Self::OutPreparing => "Preparing",
1372 Self::OutDraft => "Draft",
1373 Self::OutPending => "Pending",
1374 Self::OutFailed => "Failed",
1375 Self::OutDelivered => "Delivered",
1376 Self::OutMdnRcvd => "Read",
1377 }
1378 )
1379 }
1380}
1381
1382impl MessageState {
1383 pub fn can_fail(self) -> bool {
1385 use MessageState::*;
1386 matches!(
1387 self,
1388 OutPreparing | OutPending | OutDelivered | OutMdnRcvd )
1390 }
1391
1392 pub fn is_outgoing(self) -> bool {
1394 use MessageState::*;
1395 matches!(
1396 self,
1397 OutPreparing | OutDraft | OutPending | OutFailed | OutDelivered | OutMdnRcvd
1398 )
1399 }
1400
1401 pub(crate) fn with_mdns(self, has_mdns: bool) -> Self {
1403 if self == MessageState::OutDelivered && has_mdns {
1404 return MessageState::OutMdnRcvd;
1405 }
1406 self
1407 }
1408}
1409
1410pub async fn get_msg_read_receipts(
1412 context: &Context,
1413 msg_id: MsgId,
1414) -> Result<Vec<(ContactId, i64)>> {
1415 context
1416 .sql
1417 .query_map_vec(
1418 "SELECT contact_id, timestamp_sent FROM msgs_mdns WHERE msg_id=?",
1419 (msg_id,),
1420 |row| {
1421 let contact_id: ContactId = row.get(0)?;
1422 let ts: i64 = row.get(1)?;
1423 Ok((contact_id, ts))
1424 },
1425 )
1426 .await
1427}
1428
1429pub(crate) fn guess_msgtype_from_suffix(msg: &Message) -> Option<(Viewtype, &'static str)> {
1430 msg.param
1431 .get(Param::Filename)
1432 .or_else(|| msg.param.get(Param::File))
1433 .and_then(|file| guess_msgtype_from_path_suffix(Path::new(file)))
1434}
1435
1436pub(crate) fn guess_msgtype_from_path_suffix(path: &Path) -> Option<(Viewtype, &'static str)> {
1437 let extension: &str = &path.extension()?.to_str()?.to_lowercase();
1438 let info = match extension {
1439 "3gp" => (Viewtype::Video, "video/3gpp"),
1452 "aac" => (Viewtype::Audio, "audio/aac"),
1453 "avi" => (Viewtype::Video, "video/x-msvideo"),
1454 "avif" => (Viewtype::File, "image/avif"), "doc" => (Viewtype::File, "application/msword"),
1456 "docx" => (
1457 Viewtype::File,
1458 "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
1459 ),
1460 "epub" => (Viewtype::File, "application/epub+zip"),
1461 "flac" => (Viewtype::Audio, "audio/flac"),
1462 "gif" => (Viewtype::Gif, "image/gif"),
1463 "heic" => (Viewtype::File, "image/heic"), "heif" => (Viewtype::File, "image/heif"), "html" => (Viewtype::File, "text/html"),
1466 "htm" => (Viewtype::File, "text/html"),
1467 "ico" => (Viewtype::File, "image/vnd.microsoft.icon"),
1468 "jar" => (Viewtype::File, "application/java-archive"),
1469 "jpeg" => (Viewtype::Image, "image/jpeg"),
1470 "jpe" => (Viewtype::Image, "image/jpeg"),
1471 "jpg" => (Viewtype::Image, "image/jpeg"),
1472 "json" => (Viewtype::File, "application/json"),
1473 "mov" => (Viewtype::Video, "video/quicktime"),
1474 "m4a" => (Viewtype::Audio, "audio/m4a"),
1475 "mp3" => (Viewtype::Audio, "audio/mpeg"),
1476 "mp4" => (Viewtype::Video, "video/mp4"),
1477 "odp" => (
1478 Viewtype::File,
1479 "application/vnd.oasis.opendocument.presentation",
1480 ),
1481 "ods" => (
1482 Viewtype::File,
1483 "application/vnd.oasis.opendocument.spreadsheet",
1484 ),
1485 "odt" => (Viewtype::File, "application/vnd.oasis.opendocument.text"),
1486 "oga" => (Viewtype::Audio, "audio/ogg"),
1487 "ogg" => (Viewtype::Audio, "audio/ogg"),
1488 "ogv" => (Viewtype::File, "video/ogg"),
1489 "opus" => (Viewtype::File, "audio/ogg"), "otf" => (Viewtype::File, "font/otf"),
1491 "pdf" => (Viewtype::File, "application/pdf"),
1492 "png" => (Viewtype::Image, "image/png"),
1493 "ppt" => (Viewtype::File, "application/vnd.ms-powerpoint"),
1494 "pptx" => (
1495 Viewtype::File,
1496 "application/vnd.openxmlformats-officedocument.presentationml.presentation",
1497 ),
1498 "rar" => (Viewtype::File, "application/vnd.rar"),
1499 "rtf" => (Viewtype::File, "application/rtf"),
1500 "spx" => (Viewtype::File, "audio/ogg"), "svg" => (Viewtype::File, "image/svg+xml"),
1502 "tgs" => (Viewtype::File, "application/x-tgsticker"),
1503 "tiff" => (Viewtype::File, "image/tiff"),
1504 "tif" => (Viewtype::File, "image/tiff"),
1505 "ttf" => (Viewtype::File, "font/ttf"),
1506 "txt" => (Viewtype::File, "text/plain"),
1507 "vcard" => (Viewtype::Vcard, "text/vcard"),
1508 "vcf" => (Viewtype::Vcard, "text/vcard"),
1509 "wav" => (Viewtype::Audio, "audio/wav"),
1510 "weba" => (Viewtype::File, "audio/webm"),
1511 "webm" => (Viewtype::File, "video/webm"), "webp" => (Viewtype::Image, "image/webp"), "wmv" => (Viewtype::Video, "video/x-ms-wmv"),
1514 "xdc" => (Viewtype::Webxdc, "application/webxdc+zip"),
1515 "xhtml" => (Viewtype::File, "application/xhtml+xml"),
1516 "xls" => (Viewtype::File, "application/vnd.ms-excel"),
1517 "xlsx" => (
1518 Viewtype::File,
1519 "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
1520 ),
1521 "xml" => (Viewtype::File, "application/xml"),
1522 "zip" => (Viewtype::File, "application/zip"),
1523 _ => {
1524 return None;
1525 }
1526 };
1527 Some(info)
1528}
1529
1530pub(crate) async fn get_mime_headers(context: &Context, msg_id: MsgId) -> Result<Vec<u8>> {
1537 let (headers, compressed) = context
1538 .sql
1539 .query_row(
1540 "SELECT mime_headers, mime_compressed FROM msgs WHERE id=?",
1541 (msg_id,),
1542 |row| {
1543 let headers = sql::row_get_vec(row, 0)?;
1544 let compressed: bool = row.get(1)?;
1545 Ok((headers, compressed))
1546 },
1547 )
1548 .await?;
1549 if compressed {
1550 return buf_decompress(&headers);
1551 }
1552
1553 let headers2 = headers.clone();
1554 let compressed = match tokio::task::block_in_place(move || buf_compress(&headers2)) {
1555 Err(e) => {
1556 warn!(context, "get_mime_headers: buf_compress() failed: {}", e);
1557 return Ok(headers);
1558 }
1559 Ok(o) => o,
1560 };
1561 let update = |conn: &mut rusqlite::Connection| {
1562 match conn.execute(
1563 "\
1564 UPDATE msgs SET mime_headers=?, mime_compressed=1 \
1565 WHERE id=? AND mime_headers!='' AND mime_compressed=0",
1566 (compressed, msg_id),
1567 ) {
1568 Ok(rows_updated) => ensure!(rows_updated <= 1),
1569 Err(e) => {
1570 warn!(context, "get_mime_headers: UPDATE failed: {}", e);
1571 return Err(e.into());
1572 }
1573 }
1574 Ok(())
1575 };
1576 if let Err(e) = context.sql.call_write(update).await {
1577 warn!(
1578 context,
1579 "get_mime_headers: failed to update mime_headers: {}", e
1580 );
1581 }
1582
1583 Ok(headers)
1584}
1585
1586pub(crate) async fn delete_msg_locally(context: &Context, msg: &Message) -> Result<()> {
1589 if msg.location_id > 0 {
1590 delete_poi_location(context, msg.location_id).await?;
1591 }
1592 let on_server = true;
1593 msg.id
1594 .trash(context, on_server)
1595 .await
1596 .with_context(|| format!("Unable to trash message {}", msg.id))?;
1597
1598 context.emit_event(EventType::MsgDeleted {
1599 chat_id: msg.chat_id,
1600 msg_id: msg.id,
1601 });
1602
1603 if msg.viewtype == Viewtype::Webxdc {
1604 context.emit_event(EventType::WebxdcInstanceDeleted { msg_id: msg.id });
1605 }
1606
1607 let logging_xdc_id = context
1608 .debug_logging
1609 .read()
1610 .expect("RwLock is poisoned")
1611 .as_ref()
1612 .map(|dl| dl.msg_id);
1613 if let Some(id) = logging_xdc_id
1614 && id == msg.id
1615 {
1616 set_debug_logging_xdc(context, None).await?;
1617 }
1618
1619 Ok(())
1620}
1621
1622pub(crate) async fn delete_msgs_locally_done(
1625 context: &Context,
1626 msg_ids: &[MsgId],
1627 modified_chat_ids: HashSet<ChatId>,
1628) -> Result<()> {
1629 for modified_chat_id in modified_chat_ids {
1630 context.emit_msgs_changed_without_msg_id(modified_chat_id);
1631 chatlist_events::emit_chatlist_item_changed(context, modified_chat_id);
1632 }
1633 if !msg_ids.is_empty() {
1634 context.emit_msgs_changed_without_ids();
1635 chatlist_events::emit_chatlist_changed(context);
1636 context
1638 .set_config_internal(Config::LastHousekeeping, None)
1639 .await?;
1640 }
1641 Ok(())
1642}
1643
1644pub async fn delete_msgs(context: &Context, msg_ids: &[MsgId]) -> Result<()> {
1646 delete_msgs_ex(context, msg_ids, false).await
1647}
1648
1649pub async fn delete_msgs_ex(
1653 context: &Context,
1654 msg_ids: &[MsgId],
1655 delete_for_all: bool,
1656) -> Result<()> {
1657 let mut modified_chat_ids = HashSet::new();
1658 let mut deleted_rfc724_mid = Vec::new();
1659 let mut res = Ok(());
1660
1661 for &msg_id in msg_ids {
1662 let msg = Message::load_from_db(context, msg_id).await?;
1663 ensure!(
1664 !delete_for_all || msg.from_id == ContactId::SELF,
1665 "Can delete only own messages for others"
1666 );
1667 ensure!(
1668 !delete_for_all || msg.get_showpadlock(),
1669 "Cannot request deletion of unencrypted message for others"
1670 );
1671
1672 modified_chat_ids.insert(msg.chat_id);
1673 deleted_rfc724_mid.push(msg.rfc724_mid.clone());
1674
1675 let target = context.get_delete_msgs_target().await?;
1676 let update_db = |trans: &mut rusqlite::Transaction| {
1677 trans.execute(
1678 "UPDATE imap SET target=? WHERE rfc724_mid=?",
1679 (target, msg.rfc724_mid),
1680 )?;
1681 trans.execute("DELETE FROM smtp WHERE msg_id=?", (msg_id,))?;
1682 Ok(())
1683 };
1684 if let Err(e) = context.sql.transaction(update_db).await {
1685 error!(context, "delete_msgs: failed to update db: {e:#}.");
1686 res = Err(e);
1687 continue;
1688 }
1689 }
1690 res?;
1691
1692 if delete_for_all {
1693 ensure!(
1694 modified_chat_ids.len() == 1,
1695 "Can delete only from same chat."
1696 );
1697 if let Some(chat_id) = modified_chat_ids.iter().next() {
1698 let mut msg = Message::new_text("🚮".to_owned());
1699 msg.param.set_int(Param::GuaranteeE2ee, 1);
1704 msg.param
1705 .set(Param::DeleteRequestFor, deleted_rfc724_mid.join(" "));
1706 msg.hidden = true;
1707 send_msg(context, *chat_id, &mut msg).await?;
1708 }
1709 } else {
1710 context
1711 .add_sync_item(SyncData::DeleteMessages {
1712 msgs: deleted_rfc724_mid,
1713 })
1714 .await?;
1715 }
1716
1717 for &msg_id in msg_ids {
1718 let msg = Message::load_from_db(context, msg_id).await?;
1719 delete_msg_locally(context, &msg).await?;
1720 }
1721 delete_msgs_locally_done(context, msg_ids, modified_chat_ids).await?;
1722
1723 context.scheduler.interrupt_inbox().await;
1725
1726 Ok(())
1727}
1728
1729pub async fn markseen_msgs(context: &Context, msg_ids: Vec<MsgId>) -> Result<()> {
1731 if msg_ids.is_empty() {
1732 return Ok(());
1733 }
1734
1735 let old_last_msg_id = MsgId::new(context.get_config_u32(Config::LastMsgId).await?);
1736 let last_msg_id = msg_ids.iter().fold(&old_last_msg_id, std::cmp::max);
1737 context
1738 .set_config_internal(Config::LastMsgId, Some(&last_msg_id.to_u32().to_string()))
1739 .await?;
1740
1741 let mut msgs = Vec::with_capacity(msg_ids.len());
1742 for &id in &msg_ids {
1743 if let Some(msg) = context
1744 .sql
1745 .query_row_optional(
1746 "SELECT
1747 m.chat_id AS chat_id,
1748 m.state AS state,
1749 m.download_state as download_state,
1750 m.ephemeral_timer AS ephemeral_timer,
1751 m.param AS param,
1752 m.from_id AS from_id,
1753 m.rfc724_mid AS rfc724_mid,
1754 c.archived AS archived,
1755 c.blocked AS blocked
1756 FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id
1757 WHERE m.id=? AND m.chat_id>9",
1758 (id,),
1759 |row| {
1760 let chat_id: ChatId = row.get("chat_id")?;
1761 let state: MessageState = row.get("state")?;
1762 let download_state: DownloadState = row.get("download_state")?;
1763 let param: Params = row.get::<_, String>("param")?.parse().unwrap_or_default();
1764 let from_id: ContactId = row.get("from_id")?;
1765 let rfc724_mid: String = row.get("rfc724_mid")?;
1766 let visibility: ChatVisibility = row.get("archived")?;
1767 let blocked: Option<Blocked> = row.get("blocked")?;
1768 let ephemeral_timer: EphemeralTimer = row.get("ephemeral_timer")?;
1769 Ok((
1770 (
1771 id,
1772 chat_id,
1773 state,
1774 download_state,
1775 param,
1776 from_id,
1777 rfc724_mid,
1778 visibility,
1779 blocked.unwrap_or_default(),
1780 ),
1781 ephemeral_timer,
1782 ))
1783 },
1784 )
1785 .await?
1786 {
1787 msgs.push(msg);
1788 }
1789 }
1790
1791 if msgs
1792 .iter()
1793 .any(|(_, ephemeral_timer)| *ephemeral_timer != EphemeralTimer::Disabled)
1794 {
1795 start_ephemeral_timers_msgids(context, &msg_ids)
1796 .await
1797 .context("failed to start ephemeral timers")?;
1798 }
1799
1800 let mut updated_chat_ids = BTreeSet::new();
1801 let mut archived_chats_maybe_noticed = false;
1802 for (
1803 (
1804 id,
1805 curr_chat_id,
1806 curr_state,
1807 curr_download_state,
1808 curr_param,
1809 curr_from_id,
1810 curr_rfc724_mid,
1811 curr_visibility,
1812 curr_blocked,
1813 ),
1814 _curr_ephemeral_timer,
1815 ) in msgs
1816 {
1817 if curr_download_state != DownloadState::Done {
1818 if curr_state == MessageState::InFresh {
1819 update_msg_state(context, id, MessageState::InNoticed).await?;
1822 updated_chat_ids.insert(curr_chat_id);
1823 }
1824 } else if curr_state == MessageState::InFresh || curr_state == MessageState::InNoticed {
1825 update_msg_state(context, id, MessageState::InSeen).await?;
1826 info!(context, "Seen message {}.", id);
1827
1828 markseen_on_imap_table(context, &curr_rfc724_mid).await?;
1829
1830 if curr_blocked == Blocked::Not
1840 && curr_param.get_bool(Param::WantsMdn).unwrap_or_default()
1841 && curr_param.get_cmd() == SystemMessage::Unknown
1842 && context.should_send_mdns().await?
1843 {
1844 context
1845 .sql
1846 .execute(
1847 "INSERT INTO smtp_mdns (msg_id, from_id, rfc724_mid) VALUES(?, ?, ?)",
1848 (id, curr_from_id, curr_rfc724_mid),
1849 )
1850 .await
1851 .context("failed to insert into smtp_mdns")?;
1852 context.scheduler.interrupt_smtp().await;
1853 }
1854 updated_chat_ids.insert(curr_chat_id);
1855 }
1856 archived_chats_maybe_noticed |=
1857 curr_state == MessageState::InFresh && curr_visibility == ChatVisibility::Archived;
1858 }
1859
1860 for updated_chat_id in updated_chat_ids {
1861 context.emit_event(EventType::MsgsNoticed(updated_chat_id));
1862 chatlist_events::emit_chatlist_item_changed(context, updated_chat_id);
1863 }
1864 if archived_chats_maybe_noticed {
1865 context.on_archived_chats_maybe_noticed();
1866 }
1867
1868 Ok(())
1869}
1870
1871pub(crate) async fn update_msg_state(
1872 context: &Context,
1873 msg_id: MsgId,
1874 state: MessageState,
1875) -> Result<()> {
1876 ensure!(
1877 state != MessageState::OutMdnRcvd,
1878 "Update msgs_mdns table instead!"
1879 );
1880 ensure!(state != MessageState::OutFailed, "use set_msg_failed()!");
1881 let error_subst = match state >= MessageState::OutPending {
1882 true => ", error=''",
1883 false => "",
1884 };
1885 context
1886 .sql
1887 .execute(
1888 &format!("UPDATE msgs SET state=? {error_subst} WHERE id=?"),
1889 (state, msg_id),
1890 )
1891 .await?;
1892 Ok(())
1893}
1894
1895pub(crate) async fn set_msg_failed(
1903 context: &Context,
1904 msg: &mut Message,
1905 error: &str,
1906) -> Result<()> {
1907 if msg.state.can_fail() {
1908 msg.state = MessageState::OutFailed;
1909 warn!(context, "{} failed: {}", msg.id, error);
1910 } else {
1911 warn!(
1912 context,
1913 "{} seems to have failed ({}), but state is {}", msg.id, error, msg.state
1914 )
1915 }
1916 msg.error = Some(error.to_string());
1917
1918 let exists = context
1919 .sql
1920 .execute(
1921 "UPDATE msgs SET state=?, error=? WHERE id=?;",
1922 (msg.state, error, msg.id),
1923 )
1924 .await?
1925 > 0;
1926 context.emit_event(EventType::MsgFailed {
1927 chat_id: msg.chat_id,
1928 msg_id: msg.id,
1929 });
1930 if exists {
1931 chatlist_events::emit_chatlist_item_changed(context, msg.chat_id);
1932 }
1933 Ok(())
1934}
1935
1936pub async fn get_unblocked_msg_cnt(context: &Context) -> usize {
1938 match context
1939 .sql
1940 .count(
1941 "SELECT COUNT(*) \
1942 FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \
1943 WHERE m.id>9 AND m.chat_id>9 AND c.blocked=0;",
1944 (),
1945 )
1946 .await
1947 {
1948 Ok(res) => res,
1949 Err(err) => {
1950 error!(context, "get_unblocked_msg_cnt() failed. {:#}", err);
1951 0
1952 }
1953 }
1954}
1955
1956pub async fn get_request_msg_cnt(context: &Context) -> usize {
1958 match context
1959 .sql
1960 .count(
1961 "SELECT COUNT(*) \
1962 FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \
1963 WHERE c.blocked=2;",
1964 (),
1965 )
1966 .await
1967 {
1968 Ok(res) => res,
1969 Err(err) => {
1970 error!(context, "get_request_msg_cnt() failed. {:#}", err);
1971 0
1972 }
1973 }
1974}
1975
1976pub async fn estimate_deletion_cnt(
1992 context: &Context,
1993 from_server: bool,
1994 seconds: i64,
1995) -> Result<usize> {
1996 let self_chat_id = ChatIdBlocked::lookup_by_contact(context, ContactId::SELF)
1997 .await?
1998 .map(|c| c.id)
1999 .unwrap_or_default();
2000 let threshold_timestamp = time() - seconds;
2001
2002 let cnt = if from_server {
2003 context
2004 .sql
2005 .count(
2006 "SELECT COUNT(*)
2007 FROM msgs m
2008 WHERE m.id > ?
2009 AND timestamp < ?
2010 AND chat_id != ?
2011 AND EXISTS (SELECT * FROM imap WHERE rfc724_mid=m.rfc724_mid);",
2012 (DC_MSG_ID_LAST_SPECIAL, threshold_timestamp, self_chat_id),
2013 )
2014 .await?
2015 } else {
2016 context
2017 .sql
2018 .count(
2019 "SELECT COUNT(*)
2020 FROM msgs m
2021 WHERE m.id > ?
2022 AND timestamp < ?
2023 AND chat_id != ?
2024 AND chat_id != ? AND hidden = 0;",
2025 (
2026 DC_MSG_ID_LAST_SPECIAL,
2027 threshold_timestamp,
2028 self_chat_id,
2029 DC_CHAT_ID_TRASH,
2030 ),
2031 )
2032 .await?
2033 };
2034 Ok(cnt)
2035}
2036
2037pub(crate) async fn rfc724_mid_exists(
2039 context: &Context,
2040 rfc724_mid: &str,
2041) -> Result<Option<MsgId>> {
2042 Ok(rfc724_mid_exists_ex(context, rfc724_mid, "1")
2043 .await?
2044 .map(|(id, _)| id))
2045}
2046
2047pub(crate) async fn rfc724_mid_exists_ex(
2053 context: &Context,
2054 rfc724_mid: &str,
2055 expr: &str,
2056) -> Result<Option<(MsgId, bool)>> {
2057 let rfc724_mid = rfc724_mid.trim_start_matches('<').trim_end_matches('>');
2058 if rfc724_mid.is_empty() {
2059 warn!(context, "Empty rfc724_mid passed to rfc724_mid_exists");
2060 return Ok(None);
2061 }
2062
2063 let res = context
2064 .sql
2065 .query_row_optional(
2066 &("SELECT id, timestamp_sent, MIN(".to_string()
2067 + expr
2068 + ") FROM msgs WHERE rfc724_mid=?
2069 HAVING COUNT(*) > 0 -- Prevent MIN(expr) from returning NULL when there are no rows.
2070 ORDER BY timestamp_sent DESC"),
2071 (rfc724_mid,),
2072 |row| {
2073 let msg_id: MsgId = row.get(0)?;
2074 let expr_res: bool = row.get(2)?;
2075 Ok((msg_id, expr_res))
2076 },
2077 )
2078 .await?;
2079
2080 Ok(res)
2081}
2082
2083pub(crate) async fn get_by_rfc724_mids(
2090 context: &Context,
2091 mids: &[String],
2092) -> Result<Option<Message>> {
2093 let mut latest = None;
2094 for id in mids.iter().rev() {
2095 let Some(msg_id) = rfc724_mid_exists(context, id).await? else {
2096 continue;
2097 };
2098 let Some(msg) = Message::load_from_db_optional(context, msg_id).await? else {
2099 continue;
2100 };
2101 if msg.download_state == DownloadState::Done {
2102 return Ok(Some(msg));
2103 }
2104 latest.get_or_insert(msg);
2105 }
2106 Ok(latest)
2107}
2108
2109pub(crate) fn get_vcard_summary(vcard: &[u8]) -> Option<String> {
2111 let vcard = str::from_utf8(vcard).ok()?;
2112 let contacts = deltachat_contact_tools::parse_vcard(vcard);
2113 let [c] = &contacts[..] else {
2114 return None;
2115 };
2116 if !deltachat_contact_tools::may_be_valid_addr(&c.addr) {
2117 return None;
2118 }
2119 Some(c.display_name().to_string())
2120}
2121
2122#[derive(
2124 Debug,
2125 Default,
2126 Display,
2127 Clone,
2128 Copy,
2129 PartialEq,
2130 Eq,
2131 FromPrimitive,
2132 ToPrimitive,
2133 FromSql,
2134 ToSql,
2135 Serialize,
2136 Deserialize,
2137)]
2138#[repr(u32)]
2139pub enum Viewtype {
2140 #[default]
2142 Unknown = 0,
2143
2144 Text = 10,
2147
2148 Image = 20,
2154
2155 Gif = 21,
2159
2160 Sticker = 23,
2167
2168 Audio = 40,
2172
2173 Voice = 41,
2178
2179 Video = 50,
2186
2187 File = 60,
2191
2192 Call = 71,
2194
2195 Webxdc = 80,
2197
2198 Vcard = 90,
2202}
2203
2204impl Viewtype {
2205 pub fn has_file(&self) -> bool {
2207 match self {
2208 Viewtype::Unknown => false,
2209 Viewtype::Text => false,
2210 Viewtype::Image => true,
2211 Viewtype::Gif => true,
2212 Viewtype::Sticker => true,
2213 Viewtype::Audio => true,
2214 Viewtype::Voice => true,
2215 Viewtype::Video => true,
2216 Viewtype::File => true,
2217 Viewtype::Call => false,
2218 Viewtype::Webxdc => true,
2219 Viewtype::Vcard => true,
2220 }
2221 }
2222}
2223
2224pub(crate) fn normalize_text(text: &str) -> Option<String> {
2227 if text.is_ascii() {
2228 return None;
2229 };
2230 Some(text.to_lowercase()).filter(|t| t != text)
2231}
2232
2233#[cfg(test)]
2234mod message_tests;