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 humansize::BINARY;
12use humansize::format_size;
13use num_traits::FromPrimitive;
14use serde::{Deserialize, Serialize};
15use tokio::{fs, io};
16
17use crate::blob::BlobObject;
18use crate::chat::{Chat, ChatId, ChatIdBlocked, ChatVisibility, send_msg};
19use crate::chatlist_events;
20use crate::config::Config;
21use crate::constants::{Blocked, Chattype, DC_CHAT_ID_TRASH, DC_MSG_ID_LAST_SPECIAL};
22use crate::contact::{self, Contact, ContactId};
23use crate::context::Context;
24use crate::debug_logging::set_debug_logging_xdc;
25use crate::download::DownloadState;
26use crate::ephemeral::{Timer as EphemeralTimer, start_ephemeral_timers_msgids};
27use crate::events::EventType;
28use crate::imap::markseen_on_imap_table;
29use crate::location::delete_poi_location;
30use crate::log::warn;
31use crate::mimeparser::{SystemMessage, parse_message_id};
32use crate::param::{Param, Params};
33use crate::pgp::split_armored_data;
34use crate::reaction::get_msg_reactions;
35use crate::sql;
36use crate::summary::Summary;
37use crate::sync::SyncData;
38use crate::tools::create_outgoing_rfc724_mid;
39use crate::tools::{
40 buf_compress, buf_decompress, get_filebytes, get_filemeta, gm2local_offset, read_file,
41 sanitize_filename, time, timestamp_to_str,
42};
43
44#[derive(
50 Debug, Copy, Clone, Default, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize,
51)]
52pub struct MsgId(u32);
53
54impl MsgId {
55 pub fn new(id: u32) -> MsgId {
57 MsgId(id)
58 }
59
60 pub fn new_unset() -> MsgId {
62 MsgId(0)
63 }
64
65 pub fn is_special(self) -> bool {
69 self.0 <= DC_MSG_ID_LAST_SPECIAL
70 }
71
72 pub fn is_unset(self) -> bool {
82 self.0 == 0
83 }
84
85 pub async fn get_state(self, context: &Context) -> Result<MessageState> {
87 let result = context
88 .sql
89 .query_row_optional(
90 concat!(
91 "SELECT m.state, mdns.msg_id",
92 " FROM msgs m LEFT JOIN msgs_mdns mdns ON mdns.msg_id=m.id",
93 " WHERE id=?",
94 " LIMIT 1",
95 ),
96 (self,),
97 |row| {
98 let state: MessageState = row.get(0)?;
99 let mdn_msg_id: Option<MsgId> = row.get(1)?;
100 Ok(state.with_mdns(mdn_msg_id.is_some()))
101 },
102 )
103 .await?
104 .unwrap_or_default();
105 Ok(result)
106 }
107
108 pub(crate) async fn get_param(self, context: &Context) -> Result<Params> {
109 let res: Option<String> = context
110 .sql
111 .query_get_value("SELECT param FROM msgs WHERE id=?", (self,))
112 .await?;
113 Ok(res
114 .map(|s| s.parse().unwrap_or_default())
115 .unwrap_or_default())
116 }
117
118 pub(crate) async fn trash(self, context: &Context, on_server: bool) -> Result<()> {
130 context
131 .sql
132 .execute(
133 "
137INSERT OR REPLACE INTO msgs (id, rfc724_mid, pre_rfc724_mid, timestamp, chat_id, deleted)
138SELECT ?1, rfc724_mid, pre_rfc724_mid, timestamp, ?, ? FROM msgs WHERE id=?1
139 ",
140 (self, DC_CHAT_ID_TRASH, on_server),
141 )
142 .await?;
143
144 Ok(())
145 }
146
147 pub(crate) async fn set_delivered(self, context: &Context) -> Result<()> {
148 update_msg_state(context, self, MessageState::OutDelivered).await?;
149 let chat_id: Option<ChatId> = context
150 .sql
151 .query_get_value("SELECT chat_id FROM msgs WHERE id=?", (self,))
152 .await?;
153 context.emit_event(EventType::MsgDelivered {
154 chat_id: chat_id.unwrap_or_default(),
155 msg_id: self,
156 });
157 if let Some(chat_id) = chat_id {
158 chatlist_events::emit_chatlist_item_changed(context, chat_id);
159 }
160 Ok(())
161 }
162
163 pub fn to_u32(self) -> u32 {
168 self.0
169 }
170
171 pub async fn get_info_server_urls(
173 context: &Context,
174 rfc724_mid: String,
175 ) -> Result<Vec<String>> {
176 context
177 .sql
178 .query_map_vec(
179 "SELECT transports.addr, imap.folder, imap.uid
180 FROM imap
181 LEFT JOIN transports
182 ON transports.id = imap.transport_id
183 WHERE imap.rfc724_mid=?",
184 (rfc724_mid,),
185 |row| {
186 let addr: String = row.get(0)?;
187 let folder: String = row.get(1)?;
188 let uid: u32 = row.get(2)?;
189 Ok(format!("<{addr}/{folder}/;UID={uid}>"))
190 },
191 )
192 .await
193 }
194
195 pub async fn hop_info(self, context: &Context) -> Result<String> {
197 let hop_info = context
198 .sql
199 .query_get_value("SELECT IFNULL(hop_info, '') FROM msgs WHERE id=?", (self,))
200 .await?
201 .with_context(|| format!("Message {self} not found"))?;
202 Ok(hop_info)
203 }
204
205 pub async fn get_info(self, context: &Context) -> Result<String> {
207 let msg = Message::load_from_db(context, self).await?;
208
209 let mut ret = String::new();
210
211 let fts = timestamp_to_str(msg.get_timestamp());
212 ret += &format!("Sent: {fts}");
213
214 let from_contact = Contact::get_by_id(context, msg.from_id).await?;
215 let name = from_contact.get_name_n_addr();
216 if let Some(override_sender_name) = msg.get_override_sender_name() {
217 let addr = from_contact.get_addr();
218 ret += &format!(" by ~{override_sender_name} ({addr})");
219 } else {
220 ret += &format!(" by {name}");
221 }
222 ret += "\n";
223
224 if msg.from_id != ContactId::SELF {
225 let s = timestamp_to_str(if 0 != msg.timestamp_rcvd {
226 msg.timestamp_rcvd
227 } else {
228 msg.timestamp_sort
229 });
230 ret += &format!("Received: {}", &s);
231 ret += "\n";
232 }
233
234 if let EphemeralTimer::Enabled { duration } = msg.ephemeral_timer {
235 ret += &format!("Ephemeral timer: {duration}\n");
236 }
237
238 if msg.ephemeral_timestamp != 0 {
239 ret += &format!("Expires: {}\n", timestamp_to_str(msg.ephemeral_timestamp));
240 }
241
242 if msg.from_id == ContactId::INFO || msg.to_id == ContactId::INFO {
243 return Ok(ret);
245 }
246
247 if let Ok(rows) = context
248 .sql
249 .query_map_vec(
250 "SELECT contact_id, timestamp_sent FROM msgs_mdns WHERE msg_id=?",
251 (self,),
252 |row| {
253 let contact_id: ContactId = row.get(0)?;
254 let ts: i64 = row.get(1)?;
255 Ok((contact_id, ts))
256 },
257 )
258 .await
259 {
260 for (contact_id, ts) in rows {
261 let fts = timestamp_to_str(ts);
262 ret += &format!("Read: {fts}");
263
264 let name = Contact::get_by_id(context, contact_id)
265 .await
266 .map(|contact| contact.get_name_n_addr())
267 .unwrap_or_default();
268
269 ret += &format!(" by {name}");
270 ret += "\n";
271 }
272 }
273
274 ret += &format!("State: {}", msg.state);
275
276 if msg.has_location() {
277 ret += ", Location sent";
278 }
279
280 if 0 != msg.param.get_int(Param::GuaranteeE2ee).unwrap_or_default() {
281 ret += ", Encrypted";
282 }
283
284 ret += "\n";
285
286 let reactions = get_msg_reactions(context, self).await?;
287 if !reactions.is_empty() {
288 ret += &format!("Reactions: {reactions}\n");
289 }
290
291 if let Some(error) = msg.error.as_ref() {
292 ret += &format!("Error: {error}");
293 }
294
295 if let Some(path) = msg.get_file(context) {
296 let bytes = get_filebytes(context, &path).await?;
297 ret += &format!(
298 "\nFile: {}, name: {}, {} bytes\n",
299 path.display(),
300 msg.get_filename().unwrap_or_default(),
301 bytes
302 );
303 }
304
305 if msg.viewtype != Viewtype::Text {
306 ret += "Type: ";
307 ret += &format!("{}", msg.viewtype);
308 ret += "\n";
309 ret += &format!("Mimetype: {}\n", &msg.get_filemime().unwrap_or_default());
310 }
311 let w = msg.param.get_int(Param::Width).unwrap_or_default();
312 let h = msg.param.get_int(Param::Height).unwrap_or_default();
313 if w != 0 || h != 0 {
314 ret += &format!("Dimension: {w} x {h}\n",);
315 }
316 let duration = msg.param.get_int(Param::Duration).unwrap_or_default();
317 if duration != 0 {
318 ret += &format!("Duration: {duration} ms\n",);
319 }
320 if !msg.rfc724_mid.is_empty() {
321 ret += &format!("\nMessage-ID: {}", msg.rfc724_mid);
322
323 let server_urls = Self::get_info_server_urls(context, msg.rfc724_mid).await?;
324 for server_url in server_urls {
325 ret += &format!("\nServer-URL: {server_url}");
327 }
328 }
329 let hop_info = self.hop_info(context).await?;
330
331 ret += "\n\n";
332 if hop_info.is_empty() {
333 ret += "No Hop Info";
334 } else {
335 ret += &hop_info;
336 }
337
338 Ok(ret)
339 }
340}
341
342impl std::fmt::Display for MsgId {
343 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
344 write!(f, "Msg#{}", self.0)
345 }
346}
347
348impl rusqlite::types::ToSql for MsgId {
357 fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
358 if self.0 <= DC_MSG_ID_LAST_SPECIAL {
359 return Err(rusqlite::Error::ToSqlConversionFailure(
360 format_err!("Invalid MsgId {}", self.0).into(),
361 ));
362 }
363 let val = rusqlite::types::Value::Integer(i64::from(self.0));
364 let out = rusqlite::types::ToSqlOutput::Owned(val);
365 Ok(out)
366 }
367}
368
369impl rusqlite::types::FromSql for MsgId {
371 fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
372 i64::column_result(value).and_then(|val| {
374 if 0 <= val && val <= i64::from(u32::MAX) {
375 Ok(MsgId::new(val as u32))
376 } else {
377 Err(rusqlite::types::FromSqlError::OutOfRange(val))
378 }
379 })
380 }
381}
382
383#[derive(
384 Debug,
385 Copy,
386 Clone,
387 PartialEq,
388 FromPrimitive,
389 ToPrimitive,
390 FromSql,
391 ToSql,
392 Serialize,
393 Deserialize,
394 Default,
395)]
396#[repr(u8)]
397pub(crate) enum MessengerMessage {
398 #[default]
399 No = 0,
400 Yes = 1,
401
402 Reply = 2,
404}
405
406#[derive(Debug, Clone, Default, Serialize, Deserialize)]
410pub struct Message {
411 pub(crate) id: MsgId,
413
414 pub(crate) from_id: ContactId,
416
417 pub(crate) to_id: ContactId,
419
420 pub(crate) chat_id: ChatId,
422
423 pub(crate) viewtype: Viewtype,
425
426 pub(crate) state: MessageState,
428 pub(crate) download_state: DownloadState,
429
430 pub(crate) hidden: bool,
432 pub(crate) timestamp_sort: i64,
433 pub(crate) timestamp_sent: i64,
434 pub(crate) timestamp_rcvd: i64,
435 pub(crate) ephemeral_timer: EphemeralTimer,
436 pub(crate) ephemeral_timestamp: i64,
437 pub(crate) text: String,
438 pub(crate) additional_text: String,
442
443 pub(crate) subject: String,
447
448 pub(crate) rfc724_mid: String,
450 pub(crate) pre_rfc724_mid: String,
452
453 pub(crate) in_reply_to: Option<String>,
455 pub(crate) is_dc_message: MessengerMessage,
456 pub(crate) original_msg_id: MsgId,
457 pub(crate) mime_modified: bool,
458 pub(crate) chat_blocked: Blocked,
459 pub(crate) location_id: u32,
460 pub(crate) error: Option<String>,
461 pub(crate) param: Params,
462}
463
464impl Message {
465 pub fn new(viewtype: Viewtype) -> Self {
467 Message {
468 viewtype,
469 rfc724_mid: create_outgoing_rfc724_mid(),
470 ..Default::default()
471 }
472 }
473
474 pub fn new_text(text: String) -> Self {
476 Message {
477 viewtype: Viewtype::Text,
478 text,
479 rfc724_mid: create_outgoing_rfc724_mid(),
480 ..Default::default()
481 }
482 }
483
484 pub async fn load_from_db(context: &Context, id: MsgId) -> Result<Message> {
488 let message = Self::load_from_db_optional(context, id)
489 .await?
490 .with_context(|| format!("Message {id} does not exist"))?;
491 Ok(message)
492 }
493
494 pub async fn load_from_db_optional(context: &Context, id: MsgId) -> Result<Option<Message>> {
498 ensure!(
499 !id.is_special(),
500 "Can not load special message ID {id} from DB"
501 );
502 let mut msg = context
503 .sql
504 .query_row_optional(
505 concat!(
506 "SELECT",
507 " m.id AS id,",
508 " rfc724_mid AS rfc724mid,",
509 " pre_rfc724_mid AS pre_rfc724mid,",
510 " m.mime_in_reply_to AS mime_in_reply_to,",
511 " m.chat_id AS chat_id,",
512 " m.from_id AS from_id,",
513 " m.to_id AS to_id,",
514 " m.timestamp AS timestamp,",
515 " m.timestamp_sent AS timestamp_sent,",
516 " m.timestamp_rcvd AS timestamp_rcvd,",
517 " m.ephemeral_timer AS ephemeral_timer,",
518 " m.ephemeral_timestamp AS ephemeral_timestamp,",
519 " m.type AS type,",
520 " m.state AS state,",
521 " mdns.msg_id AS mdn_msg_id,",
522 " m.download_state AS download_state,",
523 " m.error AS error,",
524 " m.msgrmsg AS msgrmsg,",
525 " m.starred AS original_msg_id,",
526 " m.mime_modified AS mime_modified,",
527 " m.txt AS txt,",
528 " m.subject AS subject,",
529 " m.param AS param,",
530 " m.hidden AS hidden,",
531 " m.location_id AS location,",
532 " c.blocked AS blocked",
533 " FROM msgs m",
534 " LEFT JOIN chats c ON c.id=m.chat_id",
535 " LEFT JOIN msgs_mdns mdns ON mdns.msg_id=m.id",
536 " WHERE m.id=? AND chat_id!=3",
537 " LIMIT 1",
538 ),
539 (id,),
540 |row| {
541 let state: MessageState = row.get("state")?;
542 let mdn_msg_id: Option<MsgId> = row.get("mdn_msg_id")?;
543 let text = match row.get_ref("txt")? {
544 rusqlite::types::ValueRef::Text(buf) => {
545 match String::from_utf8(buf.to_vec()) {
546 Ok(t) => t,
547 Err(_) => {
548 warn!(
549 context,
550 concat!(
551 "dc_msg_load_from_db: could not get ",
552 "text column as non-lossy utf8 id {}"
553 ),
554 id
555 );
556 String::from_utf8_lossy(buf).into_owned()
557 }
558 }
559 }
560 _ => String::new(),
561 };
562 let msg = Message {
563 id: row.get("id")?,
564 rfc724_mid: row.get::<_, String>("rfc724mid")?,
565 pre_rfc724_mid: row.get::<_, String>("pre_rfc724mid")?,
566 in_reply_to: row
567 .get::<_, Option<String>>("mime_in_reply_to")?
568 .and_then(|in_reply_to| parse_message_id(&in_reply_to).ok()),
569 chat_id: row.get("chat_id")?,
570 from_id: row.get("from_id")?,
571 to_id: row.get("to_id")?,
572 timestamp_sort: row.get("timestamp")?,
573 timestamp_sent: row.get("timestamp_sent")?,
574 timestamp_rcvd: row.get("timestamp_rcvd")?,
575 ephemeral_timer: row.get("ephemeral_timer")?,
576 ephemeral_timestamp: row.get("ephemeral_timestamp")?,
577 viewtype: row.get("type").unwrap_or_default(),
578 state: state.with_mdns(mdn_msg_id.is_some()),
579 download_state: row.get("download_state")?,
580 error: Some(row.get::<_, String>("error")?)
581 .filter(|error| !error.is_empty()),
582 is_dc_message: row.get("msgrmsg")?,
583 original_msg_id: row.get("original_msg_id")?,
584 mime_modified: row.get("mime_modified")?,
585 text,
586 additional_text: String::new(),
587 subject: row.get("subject")?,
588 param: row.get::<_, String>("param")?.parse().unwrap_or_default(),
589 hidden: row.get("hidden")?,
590 location_id: row.get("location")?,
591 chat_blocked: row
592 .get::<_, Option<Blocked>>("blocked")?
593 .unwrap_or_default(),
594 };
595 Ok(msg)
596 },
597 )
598 .await
599 .with_context(|| format!("failed to load message {id} from the database"))?;
600
601 if let Some(msg) = &mut msg {
602 msg.additional_text =
603 Self::get_additional_text(context, msg.download_state, &msg.param).await?;
604 }
605
606 Ok(msg)
607 }
608
609 async fn get_additional_text(
613 context: &Context,
614 download_state: DownloadState,
615 param: &Params,
616 ) -> Result<String> {
617 if download_state != DownloadState::Done {
618 let file_size = param
619 .get(Param::PostMessageFileBytes)
620 .and_then(|s| s.parse().ok())
621 .map(|file_size: usize| format_size(file_size, BINARY))
622 .unwrap_or("?".to_owned());
623 let viewtype = param
624 .get_i64(Param::PostMessageViewtype)
625 .and_then(Viewtype::from_i64)
626 .unwrap_or(Viewtype::Unknown);
627 let file_name = param
628 .get(Param::Filename)
629 .map(sanitize_filename)
630 .unwrap_or("?".to_owned());
631
632 return match viewtype {
633 Viewtype::File => Ok(format!(" [{file_name} – {file_size}]")),
634 _ => {
635 let translated_viewtype = viewtype.to_locale_string(context).await;
636 Ok(format!(" [{translated_viewtype} – {file_size}]"))
637 }
638 };
639 }
640 Ok(String::new())
641 }
642
643 pub fn get_filemime(&self) -> Option<String> {
650 if let Some(m) = self.param.get(Param::MimeType) {
651 return Some(m.to_string());
652 } else if self.param.exists(Param::File) {
653 if let Some((_, mime)) = guess_msgtype_from_suffix(self) {
654 return Some(mime.to_string());
655 }
656 return Some("application/octet-stream".to_string());
658 }
659 None
661 }
662
663 pub fn get_file(&self, context: &Context) -> Option<PathBuf> {
665 self.param.get_file_path(context).unwrap_or(None)
666 }
667
668 pub async fn vcard_contacts(&self, context: &Context) -> Result<Vec<VcardContact>> {
670 if self.viewtype != Viewtype::Vcard {
671 return Ok(Vec::new());
672 }
673
674 let path = self
675 .get_file(context)
676 .context("vCard message does not have an attachment")?;
677 let bytes = tokio::fs::read(path).await?;
678 let vcard_contents = std::str::from_utf8(&bytes).context("vCard is not a valid UTF-8")?;
679 Ok(parse_vcard(vcard_contents))
680 }
681
682 pub async fn save_file(&self, context: &Context, path: &Path) -> Result<()> {
684 let path_src = self.get_file(context).context("No file")?;
685 let mut src = fs::OpenOptions::new().read(true).open(path_src).await?;
686 let mut dst = fs::OpenOptions::new()
687 .write(true)
688 .create_new(true)
689 .open(path)
690 .await?;
691 io::copy(&mut src, &mut dst).await?;
692 Ok(())
693 }
694
695 pub(crate) async fn try_calc_and_set_dimensions(&mut self, context: &Context) -> Result<()> {
697 if self.viewtype.has_file() {
698 let file_param = self.param.get_file_path(context)?;
699 if let Some(path_and_filename) = file_param
700 && matches!(
701 self.viewtype,
702 Viewtype::Image | Viewtype::Gif | Viewtype::Sticker
703 )
704 && !self.param.exists(Param::Width)
705 {
706 let buf = read_file(context, &path_and_filename).await?;
707
708 match get_filemeta(&buf) {
709 Ok((width, height)) => {
710 self.param.set_int(Param::Width, width as i32);
711 self.param.set_int(Param::Height, height as i32);
712 }
713 Err(err) => {
714 self.param.set_int(Param::Width, 0);
715 self.param.set_int(Param::Height, 0);
716 warn!(
717 context,
718 "Failed to get width and height for {}: {err:#}.",
719 path_and_filename.display()
720 );
721 }
722 }
723
724 if !self.id.is_unset() {
725 self.update_param(context).await?;
726 }
727 }
728 }
729 Ok(())
730 }
731
732 pub fn has_location(&self) -> bool {
738 self.location_id != 0
739 }
740
741 pub fn set_location(&mut self, latitude: f64, longitude: f64) {
758 if latitude == 0.0 && longitude == 0.0 {
759 return;
760 }
761
762 self.param.set_float(Param::SetLatitude, latitude);
763 self.param.set_float(Param::SetLongitude, longitude);
764 }
765
766 pub fn get_timestamp(&self) -> i64 {
769 if 0 != self.timestamp_sent {
770 self.timestamp_sent
771 } else {
772 self.timestamp_sort
773 }
774 }
775
776 pub fn get_id(&self) -> MsgId {
778 self.id
779 }
780
781 pub fn rfc724_mid(&self) -> &str {
784 &self.rfc724_mid
785 }
786
787 pub fn get_from_id(&self) -> ContactId {
789 self.from_id
790 }
791
792 pub fn get_chat_id(&self) -> ChatId {
794 self.chat_id
795 }
796
797 pub fn get_viewtype(&self) -> Viewtype {
799 self.viewtype
800 }
801
802 pub fn force_sticker(&mut self) {
805 self.param.set_int(Param::ForceSticker, 1);
806 }
807
808 pub fn get_state(&self) -> MessageState {
810 self.state
811 }
812
813 pub fn get_received_timestamp(&self) -> i64 {
815 self.timestamp_rcvd
816 }
817
818 pub fn get_sort_timestamp(&self) -> i64 {
820 self.timestamp_sort
821 }
822
823 pub fn get_text(&self) -> String {
828 self.text.clone() + &self.additional_text
829 }
830
831 pub fn get_subject(&self) -> &str {
833 &self.subject
834 }
835
836 pub fn get_filename(&self) -> Option<String> {
840 if let Some(name) = self.param.get(Param::Filename) {
841 return Some(sanitize_filename(name));
842 }
843 self.param
844 .get(Param::File)
845 .and_then(|file| Path::new(file).file_name())
846 .map(|name| sanitize_filename(&name.to_string_lossy()))
847 }
848
849 pub async fn get_filebytes(&self, context: &Context) -> Result<Option<u64>> {
852 if self.download_state != DownloadState::Done
853 && let Some(file_size) = self
854 .param
855 .get(Param::PostMessageFileBytes)
856 .and_then(|s| s.parse().ok())
857 {
858 return Ok(Some(file_size));
859 }
860 if let Some(path) = self.param.get_file_path(context)? {
861 Ok(Some(get_filebytes(context, &path).await.with_context(
862 || format!("failed to get {} size in bytes", path.display()),
863 )?))
864 } else {
865 Ok(None)
866 }
867 }
868
869 #[cfg(test)]
872 pub(crate) fn get_post_message_viewtype(&self) -> Option<Viewtype> {
873 if self.download_state != DownloadState::Done {
874 return self
875 .param
876 .get_i64(Param::PostMessageViewtype)
877 .and_then(Viewtype::from_i64);
878 }
879 None
880 }
881
882 pub fn get_width(&self) -> i32 {
884 self.param.get_int(Param::Width).unwrap_or_default()
885 }
886
887 pub fn get_height(&self) -> i32 {
889 self.param.get_int(Param::Height).unwrap_or_default()
890 }
891
892 pub fn get_duration(&self) -> i32 {
894 self.param.get_int(Param::Duration).unwrap_or_default()
895 }
896
897 pub fn get_showpadlock(&self) -> bool {
899 self.param.get_int(Param::GuaranteeE2ee).unwrap_or_default() != 0
900 || self.from_id == ContactId::DEVICE
901 }
902
903 pub fn is_bot(&self) -> bool {
905 self.param.get_bool(Param::Bot).unwrap_or_default()
906 }
907
908 pub fn get_ephemeral_timer(&self) -> EphemeralTimer {
910 self.ephemeral_timer
911 }
912
913 pub fn get_ephemeral_timestamp(&self) -> i64 {
915 self.ephemeral_timestamp
916 }
917
918 pub async fn get_summary(&self, context: &Context, chat: Option<&Chat>) -> Result<Summary> {
920 let chat_loaded: Chat;
921 let chat = if let Some(chat) = chat {
922 chat
923 } else {
924 let chat = Chat::load_from_db(context, self.chat_id).await?;
925 chat_loaded = chat;
926 &chat_loaded
927 };
928
929 let contact = if self.from_id != ContactId::SELF {
930 match chat.typ {
931 Chattype::Group | Chattype::Mailinglist => {
932 Some(Contact::get_by_id(context, self.from_id).await?)
933 }
934 Chattype::Single | Chattype::OutBroadcast | Chattype::InBroadcast => None,
935 }
936 } else {
937 None
938 };
939
940 Summary::new(context, self, chat, contact.as_ref()).await
941 }
942
943 pub fn get_override_sender_name(&self) -> Option<String> {
953 self.param
954 .get(Param::OverrideSenderDisplayname)
955 .map(|name| name.to_string())
956 }
957
958 pub(crate) fn get_sender_name(&self, contact: &Contact) -> String {
961 self.get_override_sender_name()
962 .unwrap_or_else(|| contact.get_display_name().to_string())
963 }
964
965 pub fn has_deviating_timestamp(&self) -> bool {
970 let cnv_to_local = gm2local_offset();
971 let sort_timestamp = self.get_sort_timestamp() + cnv_to_local;
972 let send_timestamp = self.get_timestamp() + cnv_to_local;
973
974 sort_timestamp / 86400 != send_timestamp / 86400
975 }
976
977 pub fn is_sent(&self) -> bool {
980 self.state >= MessageState::OutDelivered
981 }
982
983 pub fn is_forwarded(&self) -> bool {
985 0 != self.param.get_int(Param::Forwarded).unwrap_or_default()
986 }
987
988 pub fn is_edited(&self) -> bool {
990 self.param.get_bool(Param::IsEdited).unwrap_or_default()
991 }
992
993 pub fn is_info(&self) -> bool {
995 let cmd = self.param.get_cmd();
996 self.from_id == ContactId::INFO
997 || self.to_id == ContactId::INFO
998 || cmd != SystemMessage::Unknown && cmd != SystemMessage::AutocryptSetupMessage
999 }
1000
1001 pub fn get_info_type(&self) -> SystemMessage {
1003 self.param.get_cmd()
1004 }
1005
1006 pub async fn get_info_contact_id(&self, context: &Context) -> Result<Option<ContactId>> {
1008 match self.param.get_cmd() {
1009 SystemMessage::GroupNameChanged
1010 | SystemMessage::GroupImageChanged
1011 | SystemMessage::EphemeralTimerChanged => {
1012 if self.from_id != ContactId::INFO {
1013 Ok(Some(self.from_id))
1014 } else {
1015 Ok(None)
1016 }
1017 }
1018
1019 SystemMessage::MemberAddedToGroup | SystemMessage::MemberRemovedFromGroup => {
1020 if let Some(contact_i32) = self.param.get_int(Param::ContactAddedRemoved) {
1021 let contact_id = ContactId::new(contact_i32.try_into()?);
1022 if contact_id == ContactId::SELF
1023 || Contact::real_exists_by_id(context, contact_id).await?
1024 {
1025 Ok(Some(contact_id))
1026 } else {
1027 Ok(None)
1028 }
1029 } else {
1030 Ok(None)
1031 }
1032 }
1033
1034 SystemMessage::AutocryptSetupMessage
1035 | SystemMessage::SecurejoinMessage
1036 | SystemMessage::LocationStreamingEnabled
1037 | SystemMessage::LocationOnly
1038 | SystemMessage::ChatE2ee
1039 | SystemMessage::ChatProtectionEnabled
1040 | SystemMessage::ChatProtectionDisabled
1041 | SystemMessage::InvalidUnencryptedMail
1042 | SystemMessage::SecurejoinWait
1043 | SystemMessage::SecurejoinWaitTimeout
1044 | SystemMessage::MultiDeviceSync
1045 | SystemMessage::WebxdcStatusUpdate
1046 | SystemMessage::WebxdcInfoMessage
1047 | SystemMessage::IrohNodeAddr
1048 | SystemMessage::CallAccepted
1049 | SystemMessage::CallEnded
1050 | SystemMessage::Unknown => Ok(None),
1051 }
1052 }
1053
1054 pub fn is_system_message(&self) -> bool {
1056 let cmd = self.param.get_cmd();
1057 cmd != SystemMessage::Unknown
1058 }
1059
1060 pub fn is_setupmessage(&self) -> bool {
1062 if self.viewtype != Viewtype::File {
1063 return false;
1064 }
1065
1066 self.param.get_cmd() == SystemMessage::AutocryptSetupMessage
1067 }
1068
1069 pub async fn get_setupcodebegin(&self, context: &Context) -> Option<String> {
1073 if !self.is_setupmessage() {
1074 return None;
1075 }
1076
1077 if let Some(filename) = self.get_file(context)
1078 && let Ok(ref buf) = read_file(context, &filename).await
1079 && let Ok((typ, headers, _)) = split_armored_data(buf)
1080 && typ == pgp::armor::BlockType::Message
1081 {
1082 return headers.get(crate::pgp::HEADER_SETUPCODE).cloned();
1083 }
1084
1085 None
1086 }
1087
1088 pub fn set_text(&mut self, text: String) {
1090 self.text = text;
1091 }
1092
1093 pub fn set_subject(&mut self, subject: String) {
1096 self.subject = subject;
1097 }
1098
1099 pub fn set_file_and_deduplicate(
1114 &mut self,
1115 context: &Context,
1116 file: &Path,
1117 name: Option<&str>,
1118 filemime: Option<&str>,
1119 ) -> Result<()> {
1120 let name = if let Some(name) = name {
1121 name.to_string()
1122 } else {
1123 file.file_name()
1124 .map(|s| s.to_string_lossy().to_string())
1125 .unwrap_or_else(|| "unknown_file".to_string())
1126 };
1127
1128 let blob = BlobObject::create_and_deduplicate(context, file, Path::new(&name))?;
1129 self.param.set(Param::File, blob.as_name());
1130
1131 self.param.set(Param::Filename, name);
1132 self.param.set_optional(Param::MimeType, filemime);
1133
1134 Ok(())
1135 }
1136
1137 pub fn set_file_from_bytes(
1144 &mut self,
1145 context: &Context,
1146 name: &str,
1147 data: &[u8],
1148 filemime: Option<&str>,
1149 ) -> Result<()> {
1150 let blob = BlobObject::create_and_deduplicate_from_bytes(context, data, name)?;
1151 self.param.set(Param::Filename, name);
1152 self.param.set(Param::File, blob.as_name());
1153 self.param.set_optional(Param::MimeType, filemime);
1154
1155 Ok(())
1156 }
1157
1158 pub async fn make_vcard(&mut self, context: &Context, contacts: &[ContactId]) -> Result<()> {
1160 ensure!(
1161 matches!(self.viewtype, Viewtype::File | Viewtype::Vcard),
1162 "Wrong viewtype for vCard: {}",
1163 self.viewtype,
1164 );
1165 let vcard = contact::make_vcard(context, contacts).await?;
1166 self.set_file_from_bytes(context, "vcard.vcf", vcard.as_bytes(), None)
1167 }
1168
1169 pub(crate) async fn try_set_vcard(&mut self, context: &Context, path: &Path) -> Result<()> {
1171 let vcard = fs::read(path)
1172 .await
1173 .with_context(|| format!("Could not read {path:?}"))?;
1174 if let Some(summary) = get_vcard_summary(&vcard) {
1175 self.param.set(Param::Summary1, summary);
1176 } else {
1177 warn!(context, "try_set_vcard: Not a valid DeltaChat vCard.");
1178 self.viewtype = Viewtype::File;
1179 }
1180 Ok(())
1181 }
1182
1183 pub fn set_override_sender_name(&mut self, name: Option<String>) {
1186 self.param
1187 .set_optional(Param::OverrideSenderDisplayname, name);
1188 }
1189
1190 pub fn set_dimension(&mut self, width: i32, height: i32) {
1192 self.param.set_int(Param::Width, width);
1193 self.param.set_int(Param::Height, height);
1194 }
1195
1196 pub fn set_duration(&mut self, duration: i32) {
1198 self.param.set_int(Param::Duration, duration);
1199 }
1200
1201 pub(crate) fn set_reaction(&mut self) {
1203 self.param.set_int(Param::Reaction, 1);
1204 }
1205
1206 pub async fn latefiling_mediasize(
1209 &mut self,
1210 context: &Context,
1211 width: i32,
1212 height: i32,
1213 duration: i32,
1214 ) -> Result<()> {
1215 if width > 0 && height > 0 {
1216 self.param.set_int(Param::Width, width);
1217 self.param.set_int(Param::Height, height);
1218 }
1219 if duration > 0 {
1220 self.param.set_int(Param::Duration, duration);
1221 }
1222 self.update_param(context).await?;
1223 Ok(())
1224 }
1225
1226 pub fn set_quote_text(&mut self, text: Option<(String, bool)>) {
1232 let Some((text, protect)) = text else {
1233 self.param.remove(Param::Quote);
1234 self.param.remove(Param::ProtectQuote);
1235 return;
1236 };
1237 self.param.set(Param::Quote, text);
1238 self.param.set_optional(
1239 Param::ProtectQuote,
1240 match protect {
1241 true => Some("1"),
1242 false => None,
1243 },
1244 );
1245 }
1246
1247 pub async fn set_quote(&mut self, context: &Context, quote: Option<&Message>) -> Result<()> {
1256 if let Some(quote) = quote {
1257 ensure!(
1258 !quote.rfc724_mid.is_empty(),
1259 "Message without Message-Id cannot be quoted"
1260 );
1261 self.in_reply_to = Some(quote.rfc724_mid.clone());
1262
1263 let text = quote.get_text();
1264 let text = if text.is_empty() {
1265 quote
1267 .get_summary(context, None)
1268 .await?
1269 .truncated_text(500)
1270 .to_string()
1271 } else {
1272 text
1273 };
1274 self.set_quote_text(Some((
1275 text,
1276 quote
1277 .param
1278 .get_bool(Param::GuaranteeE2ee)
1279 .unwrap_or_default(),
1280 )));
1281 } else {
1282 self.in_reply_to = None;
1283 self.set_quote_text(None);
1284 }
1285
1286 Ok(())
1287 }
1288
1289 pub fn quoted_text(&self) -> Option<String> {
1291 self.param.get(Param::Quote).map(|s| s.to_string())
1292 }
1293
1294 pub async fn quoted_message(&self, context: &Context) -> Result<Option<Message>> {
1296 if self.param.get(Param::Quote).is_some() && !self.is_forwarded() {
1297 return self.parent(context).await;
1298 }
1299 Ok(None)
1300 }
1301
1302 pub async fn parent(&self, context: &Context) -> Result<Option<Message>> {
1307 if let Some(in_reply_to) = &self.in_reply_to
1308 && let Some(msg_id) = rfc724_mid_exists(context, in_reply_to).await?
1309 {
1310 let msg = Message::load_from_db_optional(context, msg_id).await?;
1311 return Ok(msg);
1312 }
1313 Ok(None)
1314 }
1315
1316 pub async fn get_original_msg_id(&self, context: &Context) -> Result<Option<MsgId>> {
1318 if !self.original_msg_id.is_special()
1319 && let Some(msg) = Message::load_from_db_optional(context, self.original_msg_id).await?
1320 {
1321 return if msg.chat_id.is_trash() {
1322 Ok(None)
1323 } else {
1324 Ok(Some(msg.id))
1325 };
1326 }
1327 Ok(None)
1328 }
1329
1330 pub async fn get_saved_msg_id(&self, context: &Context) -> Result<Option<MsgId>> {
1334 let res: Option<MsgId> = context
1335 .sql
1336 .query_get_value(
1337 "SELECT id FROM msgs WHERE starred=? AND chat_id!=?",
1338 (self.id, DC_CHAT_ID_TRASH),
1339 )
1340 .await?;
1341 Ok(res)
1342 }
1343
1344 pub fn force_plaintext(&mut self) {
1346 self.param.set_int(Param::ForcePlaintext, 1);
1347 }
1348
1349 pub async fn update_param(&self, context: &Context) -> Result<()> {
1351 context
1352 .sql
1353 .execute(
1354 "UPDATE msgs SET param=? WHERE id=?;",
1355 (self.param.to_string(), self.id),
1356 )
1357 .await?;
1358 Ok(())
1359 }
1360
1361 pub fn error(&self) -> Option<String> {
1374 self.error.clone()
1375 }
1376}
1377
1378#[derive(
1382 Debug,
1383 Default,
1384 Clone,
1385 Copy,
1386 PartialEq,
1387 Eq,
1388 PartialOrd,
1389 Ord,
1390 FromPrimitive,
1391 ToPrimitive,
1392 ToSql,
1393 FromSql,
1394 Serialize,
1395 Deserialize,
1396)]
1397#[repr(u32)]
1398pub enum MessageState {
1399 #[default]
1401 Undefined = 0,
1402
1403 InFresh = 10,
1406
1407 InNoticed = 13,
1411
1412 InSeen = 16,
1415
1416 OutPreparing = 18,
1420
1421 OutDraft = 19,
1423
1424 OutPending = 20,
1428
1429 OutFailed = 24,
1432
1433 OutDelivered = 26,
1437
1438 OutMdnRcvd = 28,
1441}
1442
1443impl std::fmt::Display for MessageState {
1444 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
1445 write!(
1446 f,
1447 "{}",
1448 match self {
1449 Self::Undefined => "Undefined",
1450 Self::InFresh => "Fresh",
1451 Self::InNoticed => "Noticed",
1452 Self::InSeen => "Seen",
1453 Self::OutPreparing => "Preparing",
1454 Self::OutDraft => "Draft",
1455 Self::OutPending => "Pending",
1456 Self::OutFailed => "Failed",
1457 Self::OutDelivered => "Delivered",
1458 Self::OutMdnRcvd => "Read",
1459 }
1460 )
1461 }
1462}
1463
1464impl MessageState {
1465 pub fn can_fail(self) -> bool {
1467 use MessageState::*;
1468 matches!(
1469 self,
1470 OutPreparing | OutPending | OutDelivered | OutMdnRcvd )
1472 }
1473
1474 pub fn is_outgoing(self) -> bool {
1476 use MessageState::*;
1477 matches!(
1478 self,
1479 OutPreparing | OutDraft | OutPending | OutFailed | OutDelivered | OutMdnRcvd
1480 )
1481 }
1482
1483 pub(crate) fn with_mdns(self, has_mdns: bool) -> Self {
1485 if self == MessageState::OutDelivered && has_mdns {
1486 return MessageState::OutMdnRcvd;
1487 }
1488 self
1489 }
1490}
1491
1492pub async fn get_msg_read_receipts(
1494 context: &Context,
1495 msg_id: MsgId,
1496) -> Result<Vec<(ContactId, i64)>> {
1497 context
1498 .sql
1499 .query_map_vec(
1500 "SELECT contact_id, timestamp_sent FROM msgs_mdns WHERE msg_id=?",
1501 (msg_id,),
1502 |row| {
1503 let contact_id: ContactId = row.get(0)?;
1504 let ts: i64 = row.get(1)?;
1505 Ok((contact_id, ts))
1506 },
1507 )
1508 .await
1509}
1510
1511pub(crate) fn guess_msgtype_from_suffix(msg: &Message) -> Option<(Viewtype, &'static str)> {
1512 msg.param
1513 .get(Param::Filename)
1514 .or_else(|| msg.param.get(Param::File))
1515 .and_then(|file| guess_msgtype_from_path_suffix(Path::new(file)))
1516}
1517
1518pub(crate) fn guess_msgtype_from_path_suffix(path: &Path) -> Option<(Viewtype, &'static str)> {
1519 let extension: &str = &path.extension()?.to_str()?.to_lowercase();
1520 let info = match extension {
1521 "3gp" => (Viewtype::Video, "video/3gpp"),
1534 "aac" => (Viewtype::Audio, "audio/aac"),
1535 "avi" => (Viewtype::Video, "video/x-msvideo"),
1536 "avif" => (Viewtype::File, "image/avif"), "doc" => (Viewtype::File, "application/msword"),
1538 "docx" => (
1539 Viewtype::File,
1540 "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
1541 ),
1542 "epub" => (Viewtype::File, "application/epub+zip"),
1543 "flac" => (Viewtype::Audio, "audio/flac"),
1544 "gif" => (Viewtype::Gif, "image/gif"),
1545 "heic" => (Viewtype::File, "image/heic"), "heif" => (Viewtype::File, "image/heif"), "html" => (Viewtype::File, "text/html"),
1548 "htm" => (Viewtype::File, "text/html"),
1549 "ico" => (Viewtype::File, "image/vnd.microsoft.icon"),
1550 "jar" => (Viewtype::File, "application/java-archive"),
1551 "jpeg" => (Viewtype::Image, "image/jpeg"),
1552 "jpe" => (Viewtype::Image, "image/jpeg"),
1553 "jpg" => (Viewtype::Image, "image/jpeg"),
1554 "json" => (Viewtype::File, "application/json"),
1555 "mov" => (Viewtype::Video, "video/quicktime"),
1556 "m4a" => (Viewtype::Audio, "audio/m4a"),
1557 "mp3" => (Viewtype::Audio, "audio/mpeg"),
1558 "mp4" => (Viewtype::Video, "video/mp4"),
1559 "odp" => (
1560 Viewtype::File,
1561 "application/vnd.oasis.opendocument.presentation",
1562 ),
1563 "ods" => (
1564 Viewtype::File,
1565 "application/vnd.oasis.opendocument.spreadsheet",
1566 ),
1567 "odt" => (Viewtype::File, "application/vnd.oasis.opendocument.text"),
1568 "oga" => (Viewtype::Audio, "audio/ogg"),
1569 "ogg" => (Viewtype::Audio, "audio/ogg"),
1570 "ogv" => (Viewtype::File, "video/ogg"),
1571 "opus" => (Viewtype::File, "audio/ogg"), "otf" => (Viewtype::File, "font/otf"),
1573 "pdf" => (Viewtype::File, "application/pdf"),
1574 "png" => (Viewtype::Image, "image/png"),
1575 "ppt" => (Viewtype::File, "application/vnd.ms-powerpoint"),
1576 "pptx" => (
1577 Viewtype::File,
1578 "application/vnd.openxmlformats-officedocument.presentationml.presentation",
1579 ),
1580 "rar" => (Viewtype::File, "application/vnd.rar"),
1581 "rtf" => (Viewtype::File, "application/rtf"),
1582 "spx" => (Viewtype::File, "audio/ogg"), "svg" => (Viewtype::File, "image/svg+xml"),
1584 "tgs" => (Viewtype::File, "application/x-tgsticker"),
1585 "tiff" => (Viewtype::File, "image/tiff"),
1586 "tif" => (Viewtype::File, "image/tiff"),
1587 "ttf" => (Viewtype::File, "font/ttf"),
1588 "txt" => (Viewtype::File, "text/plain"),
1589 "vcard" => (Viewtype::Vcard, "text/vcard"),
1590 "vcf" => (Viewtype::Vcard, "text/vcard"),
1591 "wav" => (Viewtype::Audio, "audio/wav"),
1592 "weba" => (Viewtype::File, "audio/webm"),
1593 "webm" => (Viewtype::File, "video/webm"), "webp" => (Viewtype::Image, "image/webp"), "wmv" => (Viewtype::Video, "video/x-ms-wmv"),
1596 "xdc" => (Viewtype::Webxdc, "application/webxdc+zip"),
1597 "xhtml" => (Viewtype::File, "application/xhtml+xml"),
1598 "xls" => (Viewtype::File, "application/vnd.ms-excel"),
1599 "xlsx" => (
1600 Viewtype::File,
1601 "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
1602 ),
1603 "xml" => (Viewtype::File, "application/xml"),
1604 "zip" => (Viewtype::File, "application/zip"),
1605 _ => {
1606 return None;
1607 }
1608 };
1609 Some(info)
1610}
1611
1612pub(crate) async fn get_mime_headers(context: &Context, msg_id: MsgId) -> Result<Vec<u8>> {
1619 let (headers, compressed) = context
1620 .sql
1621 .query_row(
1622 "SELECT mime_headers, mime_compressed FROM msgs WHERE id=?",
1623 (msg_id,),
1624 |row| {
1625 let headers = sql::row_get_vec(row, 0)?;
1626 let compressed: bool = row.get(1)?;
1627 Ok((headers, compressed))
1628 },
1629 )
1630 .await?;
1631 if compressed {
1632 return buf_decompress(&headers);
1633 }
1634
1635 let headers2 = headers.clone();
1636 let compressed = match tokio::task::block_in_place(move || buf_compress(&headers2)) {
1637 Err(e) => {
1638 warn!(context, "get_mime_headers: buf_compress() failed: {}", e);
1639 return Ok(headers);
1640 }
1641 Ok(o) => o,
1642 };
1643 let update = |conn: &mut rusqlite::Connection| {
1644 match conn.execute(
1645 "\
1646 UPDATE msgs SET mime_headers=?, mime_compressed=1 \
1647 WHERE id=? AND mime_headers!='' AND mime_compressed=0",
1648 (compressed, msg_id),
1649 ) {
1650 Ok(rows_updated) => ensure!(rows_updated <= 1),
1651 Err(e) => {
1652 warn!(context, "get_mime_headers: UPDATE failed: {}", e);
1653 return Err(e.into());
1654 }
1655 }
1656 Ok(())
1657 };
1658 if let Err(e) = context.sql.call_write(update).await {
1659 warn!(
1660 context,
1661 "get_mime_headers: failed to update mime_headers: {}", e
1662 );
1663 }
1664
1665 Ok(headers)
1666}
1667
1668pub(crate) async fn delete_msg_locally(context: &Context, msg: &Message) -> Result<()> {
1671 if msg.location_id > 0 {
1672 delete_poi_location(context, msg.location_id).await?;
1673 }
1674 let on_server = true;
1675 msg.id
1676 .trash(context, on_server)
1677 .await
1678 .with_context(|| format!("Unable to trash message {}", msg.id))?;
1679
1680 context.emit_event(EventType::MsgDeleted {
1681 chat_id: msg.chat_id,
1682 msg_id: msg.id,
1683 });
1684
1685 if msg.viewtype == Viewtype::Webxdc {
1686 context.emit_event(EventType::WebxdcInstanceDeleted { msg_id: msg.id });
1687 }
1688
1689 let logging_xdc_id = context
1690 .debug_logging
1691 .read()
1692 .expect("RwLock is poisoned")
1693 .as_ref()
1694 .map(|dl| dl.msg_id);
1695 if let Some(id) = logging_xdc_id
1696 && id == msg.id
1697 {
1698 set_debug_logging_xdc(context, None).await?;
1699 }
1700
1701 Ok(())
1702}
1703
1704pub(crate) async fn delete_msgs_locally_done(
1707 context: &Context,
1708 msg_ids: &[MsgId],
1709 modified_chat_ids: HashSet<ChatId>,
1710) -> Result<()> {
1711 for modified_chat_id in modified_chat_ids {
1712 context.emit_msgs_changed_without_msg_id(modified_chat_id);
1713 chatlist_events::emit_chatlist_item_changed(context, modified_chat_id);
1714 }
1715 if !msg_ids.is_empty() {
1716 context.emit_msgs_changed_without_ids();
1717 chatlist_events::emit_chatlist_changed(context);
1718 context
1720 .set_config_internal(Config::LastHousekeeping, None)
1721 .await?;
1722 }
1723 Ok(())
1724}
1725
1726pub async fn delete_msgs(context: &Context, msg_ids: &[MsgId]) -> Result<()> {
1728 delete_msgs_ex(context, msg_ids, false).await
1729}
1730
1731pub async fn delete_msgs_ex(
1735 context: &Context,
1736 msg_ids: &[MsgId],
1737 delete_for_all: bool,
1738) -> Result<()> {
1739 let mut modified_chat_ids = HashSet::new();
1740 let mut deleted_rfc724_mid = Vec::new();
1741 let mut res = Ok(());
1742
1743 for &msg_id in msg_ids {
1744 let msg = Message::load_from_db(context, msg_id).await?;
1745 ensure!(
1746 !delete_for_all || msg.from_id == ContactId::SELF,
1747 "Can delete only own messages for others"
1748 );
1749 ensure!(
1750 !delete_for_all || msg.get_showpadlock(),
1751 "Cannot request deletion of unencrypted message for others"
1752 );
1753
1754 modified_chat_ids.insert(msg.chat_id);
1755 deleted_rfc724_mid.push(msg.rfc724_mid.clone());
1756
1757 let target = context.get_delete_msgs_target().await?;
1758 let update_db = |trans: &mut rusqlite::Transaction| {
1759 let mut stmt = trans.prepare("UPDATE imap SET target=? WHERE rfc724_mid=?")?;
1760 stmt.execute((&target, &msg.rfc724_mid))?;
1761 if !msg.pre_rfc724_mid.is_empty() {
1762 stmt.execute((&target, &msg.pre_rfc724_mid))?;
1763 }
1764 trans.execute("DELETE FROM smtp WHERE msg_id=?", (msg_id,))?;
1765 trans.execute(
1766 "DELETE FROM download WHERE rfc724_mid=?",
1767 (&msg.rfc724_mid,),
1768 )?;
1769 trans.execute(
1770 "DELETE FROM available_post_msgs WHERE rfc724_mid=?",
1771 (&msg.rfc724_mid,),
1772 )?;
1773 Ok(())
1774 };
1775 if let Err(e) = context.sql.transaction(update_db).await {
1776 error!(context, "delete_msgs: failed to update db: {e:#}.");
1777 res = Err(e);
1778 continue;
1779 }
1780 }
1781 res?;
1782
1783 if delete_for_all {
1784 ensure!(
1785 modified_chat_ids.len() == 1,
1786 "Can delete only from same chat."
1787 );
1788 if let Some(chat_id) = modified_chat_ids.iter().next() {
1789 let mut msg = Message::new_text("🚮".to_owned());
1790 msg.param.set_int(Param::GuaranteeE2ee, 1);
1795 msg.param
1796 .set(Param::DeleteRequestFor, deleted_rfc724_mid.join(" "));
1797 msg.hidden = true;
1798 send_msg(context, *chat_id, &mut msg).await?;
1799 }
1800 } else {
1801 context
1802 .add_sync_item(SyncData::DeleteMessages {
1803 msgs: deleted_rfc724_mid,
1804 })
1805 .await?;
1806 context.scheduler.interrupt_smtp().await;
1807 }
1808
1809 for &msg_id in msg_ids {
1810 let msg = Message::load_from_db(context, msg_id).await?;
1811 delete_msg_locally(context, &msg).await?;
1812 }
1813 delete_msgs_locally_done(context, msg_ids, modified_chat_ids).await?;
1814
1815 context.scheduler.interrupt_inbox().await;
1817
1818 Ok(())
1819}
1820
1821pub async fn markseen_msgs(context: &Context, msg_ids: Vec<MsgId>) -> Result<()> {
1823 if msg_ids.is_empty() {
1824 return Ok(());
1825 }
1826
1827 let old_last_msg_id = MsgId::new(context.get_config_u32(Config::LastMsgId).await?);
1828 let last_msg_id = msg_ids.iter().fold(&old_last_msg_id, std::cmp::max);
1829 context
1830 .set_config_internal(Config::LastMsgId, Some(&last_msg_id.to_u32().to_string()))
1831 .await?;
1832
1833 let mut msgs = Vec::with_capacity(msg_ids.len());
1834 for &id in &msg_ids {
1835 if let Some(msg) = context
1836 .sql
1837 .query_row_optional(
1838 "SELECT
1839 m.chat_id AS chat_id,
1840 m.state AS state,
1841 m.ephemeral_timer AS ephemeral_timer,
1842 m.param AS param,
1843 m.from_id AS from_id,
1844 m.rfc724_mid AS rfc724_mid,
1845 c.archived AS archived,
1846 c.blocked AS blocked
1847 FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id
1848 WHERE m.id=? AND m.chat_id>9",
1849 (id,),
1850 |row| {
1851 let chat_id: ChatId = row.get("chat_id")?;
1852 let state: MessageState = row.get("state")?;
1853 let param: Params = row.get::<_, String>("param")?.parse().unwrap_or_default();
1854 let from_id: ContactId = row.get("from_id")?;
1855 let rfc724_mid: String = row.get("rfc724_mid")?;
1856 let visibility: ChatVisibility = row.get("archived")?;
1857 let blocked: Option<Blocked> = row.get("blocked")?;
1858 let ephemeral_timer: EphemeralTimer = row.get("ephemeral_timer")?;
1859 Ok((
1860 (
1861 id,
1862 chat_id,
1863 state,
1864 param,
1865 from_id,
1866 rfc724_mid,
1867 visibility,
1868 blocked.unwrap_or_default(),
1869 ),
1870 ephemeral_timer,
1871 ))
1872 },
1873 )
1874 .await?
1875 {
1876 msgs.push(msg);
1877 }
1878 }
1879
1880 if msgs
1881 .iter()
1882 .any(|(_, ephemeral_timer)| *ephemeral_timer != EphemeralTimer::Disabled)
1883 {
1884 start_ephemeral_timers_msgids(context, &msg_ids)
1885 .await
1886 .context("failed to start ephemeral timers")?;
1887 }
1888
1889 let mut updated_chat_ids = BTreeSet::new();
1890 let mut archived_chats_maybe_noticed = false;
1891 for (
1892 (
1893 id,
1894 curr_chat_id,
1895 curr_state,
1896 curr_param,
1897 curr_from_id,
1898 curr_rfc724_mid,
1899 curr_visibility,
1900 curr_blocked,
1901 ),
1902 _curr_ephemeral_timer,
1903 ) in msgs
1904 {
1905 if curr_state == MessageState::InFresh || curr_state == MessageState::InNoticed {
1906 update_msg_state(context, id, MessageState::InSeen).await?;
1907 info!(context, "Seen message {}.", id);
1908
1909 markseen_on_imap_table(context, &curr_rfc724_mid).await?;
1910
1911 if curr_blocked == Blocked::Not
1921 && curr_param.get_bool(Param::WantsMdn).unwrap_or_default()
1922 && curr_param.get_cmd() == SystemMessage::Unknown
1923 && context.should_send_mdns().await?
1924 {
1925 context
1926 .sql
1927 .execute(
1928 "INSERT INTO smtp_mdns (msg_id, from_id, rfc724_mid) VALUES(?, ?, ?)",
1929 (id, curr_from_id, curr_rfc724_mid),
1930 )
1931 .await
1932 .context("failed to insert into smtp_mdns")?;
1933 context.scheduler.interrupt_smtp().await;
1934 }
1935 updated_chat_ids.insert(curr_chat_id);
1936 }
1937 archived_chats_maybe_noticed |=
1938 curr_state == MessageState::InFresh && curr_visibility == ChatVisibility::Archived;
1939 }
1940
1941 for updated_chat_id in updated_chat_ids {
1942 context.emit_event(EventType::MsgsNoticed(updated_chat_id));
1943 chatlist_events::emit_chatlist_item_changed(context, updated_chat_id);
1944 }
1945 if archived_chats_maybe_noticed {
1946 context.on_archived_chats_maybe_noticed();
1947 }
1948
1949 Ok(())
1950}
1951
1952pub async fn get_existing_msg_ids(context: &Context, ids: &[MsgId]) -> Result<Vec<MsgId>> {
1956 let query_only = true;
1957 let res = context
1958 .sql
1959 .transaction_ex(query_only, |transaction| {
1960 let mut res: Vec<MsgId> = Vec::new();
1961 for id in ids {
1962 if transaction.query_one(
1963 "SELECT COUNT(*) > 0 FROM msgs WHERE id=? AND chat_id!=3",
1964 (id,),
1965 |row| {
1966 let exists: bool = row.get(0)?;
1967 Ok(exists)
1968 },
1969 )? {
1970 res.push(*id);
1971 }
1972 }
1973 Ok(res)
1974 })
1975 .await?;
1976 Ok(res)
1977}
1978
1979pub(crate) async fn update_msg_state(
1980 context: &Context,
1981 msg_id: MsgId,
1982 state: MessageState,
1983) -> Result<()> {
1984 ensure!(
1985 state != MessageState::OutMdnRcvd,
1986 "Update msgs_mdns table instead!"
1987 );
1988 ensure!(state != MessageState::OutFailed, "use set_msg_failed()!");
1989 let error_subst = match state >= MessageState::OutPending {
1990 true => ", error=''",
1991 false => "",
1992 };
1993 context
1994 .sql
1995 .execute(
1996 &format!("UPDATE msgs SET state=? {error_subst} WHERE id=?"),
1997 (state, msg_id),
1998 )
1999 .await?;
2000 Ok(())
2001}
2002
2003pub(crate) async fn set_msg_failed(
2011 context: &Context,
2012 msg: &mut Message,
2013 error: &str,
2014) -> Result<()> {
2015 if msg.state.can_fail() {
2016 msg.state = MessageState::OutFailed;
2017 warn!(context, "{} failed: {}", msg.id, error);
2018 } else {
2019 warn!(
2020 context,
2021 "{} seems to have failed ({}), but state is {}", msg.id, error, msg.state
2022 )
2023 }
2024 msg.error = Some(error.to_string());
2025
2026 let exists = context
2027 .sql
2028 .execute(
2029 "UPDATE msgs SET state=?, error=? WHERE id=?;",
2030 (msg.state, error, msg.id),
2031 )
2032 .await?
2033 > 0;
2034 context.emit_event(EventType::MsgFailed {
2035 chat_id: msg.chat_id,
2036 msg_id: msg.id,
2037 });
2038 if exists {
2039 chatlist_events::emit_chatlist_item_changed(context, msg.chat_id);
2040 }
2041 Ok(())
2042}
2043
2044pub async fn get_unblocked_msg_cnt(context: &Context) -> usize {
2046 match context
2047 .sql
2048 .count(
2049 "SELECT COUNT(*) \
2050 FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \
2051 WHERE m.id>9 AND m.chat_id>9 AND c.blocked=0;",
2052 (),
2053 )
2054 .await
2055 {
2056 Ok(res) => res,
2057 Err(err) => {
2058 error!(context, "get_unblocked_msg_cnt() failed. {:#}", err);
2059 0
2060 }
2061 }
2062}
2063
2064pub async fn get_request_msg_cnt(context: &Context) -> usize {
2066 match context
2067 .sql
2068 .count(
2069 "SELECT COUNT(*) \
2070 FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \
2071 WHERE c.blocked=2;",
2072 (),
2073 )
2074 .await
2075 {
2076 Ok(res) => res,
2077 Err(err) => {
2078 error!(context, "get_request_msg_cnt() failed. {:#}", err);
2079 0
2080 }
2081 }
2082}
2083
2084pub async fn estimate_deletion_cnt(
2100 context: &Context,
2101 from_server: bool,
2102 seconds: i64,
2103) -> Result<usize> {
2104 let self_chat_id = ChatIdBlocked::lookup_by_contact(context, ContactId::SELF)
2105 .await?
2106 .map(|c| c.id)
2107 .unwrap_or_default();
2108 let threshold_timestamp = time() - seconds;
2109
2110 let cnt = if from_server {
2111 context
2112 .sql
2113 .count(
2114 "SELECT COUNT(*)
2115 FROM msgs m
2116 WHERE m.id > ?
2117 AND timestamp < ?
2118 AND chat_id != ?
2119 AND EXISTS (SELECT * FROM imap WHERE rfc724_mid=m.rfc724_mid);",
2120 (DC_MSG_ID_LAST_SPECIAL, threshold_timestamp, self_chat_id),
2121 )
2122 .await?
2123 } else {
2124 context
2125 .sql
2126 .count(
2127 "SELECT COUNT(*)
2128 FROM msgs m
2129 WHERE m.id > ?
2130 AND timestamp < ?
2131 AND chat_id != ?
2132 AND chat_id != ? AND hidden = 0;",
2133 (
2134 DC_MSG_ID_LAST_SPECIAL,
2135 threshold_timestamp,
2136 self_chat_id,
2137 DC_CHAT_ID_TRASH,
2138 ),
2139 )
2140 .await?
2141 };
2142 Ok(cnt)
2143}
2144
2145pub(crate) async fn rfc724_mid_exists(
2147 context: &Context,
2148 rfc724_mid: &str,
2149) -> Result<Option<MsgId>> {
2150 Ok(rfc724_mid_exists_ex(context, rfc724_mid, "1")
2151 .await?
2152 .map(|(id, _)| id))
2153}
2154
2155pub(crate) async fn rfc724_mid_exists_ex(
2161 context: &Context,
2162 rfc724_mid: &str,
2163 expr: &str,
2164) -> Result<Option<(MsgId, bool)>> {
2165 let rfc724_mid = rfc724_mid.trim_start_matches('<').trim_end_matches('>');
2166 if rfc724_mid.is_empty() {
2167 warn!(context, "Empty rfc724_mid passed to rfc724_mid_exists");
2168 return Ok(None);
2169 }
2170
2171 let res = context
2172 .sql
2173 .query_row_optional(
2174 &("SELECT id, timestamp_sent, MIN(".to_string()
2175 + expr
2176 + ") FROM msgs WHERE rfc724_mid=?1 OR pre_rfc724_mid=?1
2177 HAVING COUNT(*) > 0 -- Prevent MIN(expr) from returning NULL when there are no rows.
2178 ORDER BY timestamp_sent DESC"),
2179 (rfc724_mid,),
2180 |row| {
2181 let msg_id: MsgId = row.get(0)?;
2182 let expr_res: bool = row.get(2)?;
2183 Ok((msg_id, expr_res))
2184 },
2185 )
2186 .await?;
2187
2188 Ok(res)
2189}
2190
2191pub(crate) async fn rfc724_mid_download_tried(context: &Context, rfc724_mid: &str) -> Result<bool> {
2196 let rfc724_mid = rfc724_mid.trim_start_matches('<').trim_end_matches('>');
2197 if rfc724_mid.is_empty() {
2198 warn!(
2199 context,
2200 "Empty rfc724_mid passed to rfc724_mid_download_tried"
2201 );
2202 return Ok(false);
2203 }
2204
2205 let res = context
2206 .sql
2207 .exists(
2208 "SELECT COUNT(*) FROM msgs
2209 WHERE rfc724_mid=? AND download_state<>?",
2210 (rfc724_mid, DownloadState::Available),
2211 )
2212 .await?;
2213
2214 Ok(res)
2215}
2216
2217pub(crate) async fn get_by_rfc724_mids(
2224 context: &Context,
2225 mids: &[String],
2226) -> Result<Option<Message>> {
2227 let mut latest = None;
2228 for id in mids.iter().rev() {
2229 let Some(msg_id) = rfc724_mid_exists(context, id).await? else {
2230 continue;
2231 };
2232 let Some(msg) = Message::load_from_db_optional(context, msg_id).await? else {
2233 continue;
2234 };
2235 if msg.download_state == DownloadState::Done {
2236 return Ok(Some(msg));
2237 }
2238 latest.get_or_insert(msg);
2239 }
2240 Ok(latest)
2241}
2242
2243pub(crate) fn get_vcard_summary(vcard: &[u8]) -> Option<String> {
2245 let vcard = str::from_utf8(vcard).ok()?;
2246 let contacts = deltachat_contact_tools::parse_vcard(vcard);
2247 let [c] = &contacts[..] else {
2248 return None;
2249 };
2250 if !deltachat_contact_tools::may_be_valid_addr(&c.addr) {
2251 return None;
2252 }
2253 Some(c.display_name().to_string())
2254}
2255
2256#[derive(
2258 Debug,
2259 Default,
2260 Display,
2261 Clone,
2262 Copy,
2263 PartialEq,
2264 Eq,
2265 FromPrimitive,
2266 ToPrimitive,
2267 FromSql,
2268 ToSql,
2269 Serialize,
2270 Deserialize,
2271)]
2272#[repr(u32)]
2273pub enum Viewtype {
2274 #[default]
2276 Unknown = 0,
2277
2278 Text = 10,
2281
2282 Image = 20,
2288
2289 Gif = 21,
2293
2294 Sticker = 23,
2301
2302 Audio = 40,
2306
2307 Voice = 41,
2312
2313 Video = 50,
2320
2321 File = 60,
2325
2326 Call = 71,
2328
2329 Webxdc = 80,
2331
2332 Vcard = 90,
2336}
2337
2338impl Viewtype {
2339 pub fn has_file(&self) -> bool {
2341 match self {
2342 Viewtype::Unknown => false,
2343 Viewtype::Text => false,
2344 Viewtype::Image => true,
2345 Viewtype::Gif => true,
2346 Viewtype::Sticker => true,
2347 Viewtype::Audio => true,
2348 Viewtype::Voice => true,
2349 Viewtype::Video => true,
2350 Viewtype::File => true,
2351 Viewtype::Call => false,
2352 Viewtype::Webxdc => true,
2353 Viewtype::Vcard => true,
2354 }
2355 }
2356}
2357
2358#[cfg(test)]
2359mod message_tests;