1use std::collections::BTreeSet;
4use std::path::{Path, PathBuf};
5use std::str;
6
7use anyhow::{Context as _, Result, ensure, format_err};
8use deltachat_contact_tools::{VcardContact, parse_vcard};
9use deltachat_derive::{FromSql, ToSql};
10use humansize::BINARY;
11use humansize::format_size;
12use num_traits::FromPrimitive;
13use serde::{Deserialize, Serialize};
14use tokio::{fs, io};
15
16use crate::blob::BlobObject;
17use crate::chat::{Chat, ChatId, ChatIdBlocked, ChatVisibility, send_msg};
18use crate::chatlist_events;
19use crate::config::Config;
20use crate::constants::{Blocked, Chattype, DC_CHAT_ID_TRASH, DC_MSG_ID_LAST_SPECIAL};
21use crate::contact::{self, Contact, ContactId};
22use crate::context::Context;
23use crate::debug_logging::set_debug_logging_xdc;
24use crate::download::DownloadState;
25use crate::ephemeral::{Timer as EphemeralTimer, start_ephemeral_timers_msgids};
26use crate::events::EventType;
27use crate::imap::markseen_on_imap_table;
28use crate::location;
29use crate::log::warn;
30use crate::mimeparser::{SystemMessage, parse_message_id};
31use crate::param::{Param, Params};
32use crate::reaction::get_msg_reactions;
33use crate::sql;
34use crate::summary::Summary;
35use crate::sync::SyncData;
36use crate::tools::create_outgoing_rfc724_mid;
37use crate::tools::{
38 buf_compress, buf_decompress, get_filebytes, get_filemeta, gm2local_offset, read_file,
39 sanitize_filename, time, timestamp_to_str,
40};
41
42#[derive(
48 Debug, Copy, Clone, Default, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize,
49)]
50pub struct MsgId(u32);
51
52impl MsgId {
53 pub fn new(id: u32) -> MsgId {
55 MsgId(id)
56 }
57
58 pub fn new_unset() -> MsgId {
60 MsgId(0)
61 }
62
63 pub fn is_special(self) -> bool {
67 self.0 <= DC_MSG_ID_LAST_SPECIAL
68 }
69
70 pub fn is_unset(self) -> bool {
80 self.0 == 0
81 }
82
83 pub async fn get_state(self, context: &Context) -> Result<MessageState> {
85 let result = context
86 .sql
87 .query_row_optional(
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 (self,),
93 |row| {
94 let state: MessageState = row.get(0)?;
95 let mdn_msg_id: Option<MsgId> = row.get(1)?;
96 Ok(state.with_mdns(mdn_msg_id.is_some()))
97 },
98 )
99 .await?
100 .unwrap_or_default();
101 Ok(result)
102 }
103
104 pub(crate) async fn get_param(self, context: &Context) -> Result<Params> {
105 let res: Option<String> = context
106 .sql
107 .query_get_value("SELECT param FROM msgs WHERE id=?", (self,))
108 .await?;
109 Ok(res
110 .map(|s| s.parse().unwrap_or_default())
111 .unwrap_or_default())
112 }
113
114 pub(crate) async fn trash(self, context: &Context, on_server: bool) -> Result<()> {
126 context
127 .sql
128 .execute(
129 "
133INSERT OR REPLACE INTO msgs (id, rfc724_mid, pre_rfc724_mid, timestamp, chat_id, deleted)
134SELECT ?1, rfc724_mid, pre_rfc724_mid, timestamp, ?, ? FROM msgs WHERE id=?1
135 ",
136 (self, DC_CHAT_ID_TRASH, on_server),
137 )
138 .await?;
139
140 Ok(())
141 }
142
143 pub(crate) async fn set_delivered(self, context: &Context) -> Result<()> {
144 update_msg_state(context, self, MessageState::OutDelivered).await?;
145 let chat_id: Option<ChatId> = context
146 .sql
147 .query_get_value("SELECT chat_id FROM msgs WHERE id=?", (self,))
148 .await?;
149 context.emit_event(EventType::MsgDelivered {
150 chat_id: chat_id.unwrap_or_default(),
151 msg_id: self,
152 });
153 if let Some(chat_id) = chat_id {
154 chatlist_events::emit_chatlist_item_changed(context, chat_id);
155 }
156 Ok(())
157 }
158
159 pub fn to_u32(self) -> u32 {
164 self.0
165 }
166
167 pub async fn get_info_server_urls(
169 context: &Context,
170 rfc724_mid: String,
171 ) -> Result<Vec<String>> {
172 context
173 .sql
174 .query_map_vec(
175 "SELECT transports.addr, imap.folder, imap.uid
176 FROM imap
177 LEFT JOIN transports
178 ON transports.id = imap.transport_id
179 WHERE imap.rfc724_mid=?",
180 (rfc724_mid,),
181 |row| {
182 let addr: String = row.get(0)?;
183 let folder: String = row.get(1)?;
184 let uid: u32 = row.get(2)?;
185 Ok(format!("<{addr}/{folder}/;UID={uid}>"))
186 },
187 )
188 .await
189 }
190
191 pub async fn hop_info(self, context: &Context) -> Result<String> {
193 let hop_info = context
194 .sql
195 .query_get_value("SELECT IFNULL(hop_info, '') FROM msgs WHERE id=?", (self,))
196 .await?
197 .with_context(|| format!("Message {self} not found"))?;
198 Ok(hop_info)
199 }
200
201 pub async fn get_info(self, context: &Context) -> Result<String> {
203 let msg = Message::load_from_db(context, self).await?;
204
205 let mut ret = String::new();
206
207 let fts = timestamp_to_str(msg.get_timestamp());
208 ret += &format!("Sent: {fts}");
209
210 let from_contact = Contact::get_by_id(context, msg.from_id).await?;
211 let name = from_contact.get_display_name();
212 if let Some(override_sender_name) = msg.get_override_sender_name() {
213 ret += &format!(" by ~{override_sender_name}");
214 } else {
215 ret += &format!(" by {name}");
216 }
217 ret += "\n";
218
219 if msg.from_id != ContactId::SELF {
220 let s = timestamp_to_str(if 0 != msg.timestamp_rcvd {
221 msg.timestamp_rcvd
222 } else {
223 msg.timestamp_sort
224 });
225 ret += &format!("Received: {s}");
226 ret += "\n";
227 }
228
229 if let EphemeralTimer::Enabled { duration } = msg.ephemeral_timer {
230 ret += &format!("Ephemeral timer: {duration}\n");
231 }
232
233 if msg.ephemeral_timestamp != 0 {
234 ret += &format!("Expires: {}\n", timestamp_to_str(msg.ephemeral_timestamp));
235 }
236
237 if msg.from_id == ContactId::INFO || msg.to_id == ContactId::INFO {
238 return Ok(ret);
240 }
241
242 if let Ok(rows) = context
243 .sql
244 .query_map_vec(
245 "SELECT contact_id, timestamp_sent FROM msgs_mdns WHERE msg_id=?",
246 (self,),
247 |row| {
248 let contact_id: ContactId = row.get(0)?;
249 let ts: i64 = row.get(1)?;
250 Ok((contact_id, ts))
251 },
252 )
253 .await
254 {
255 for (contact_id, ts) in rows {
256 let fts = timestamp_to_str(ts);
257 ret += &format!("Read: {fts}");
258
259 let name = Contact::get_by_id(context, contact_id)
260 .await
261 .map(|contact| contact.get_display_name().to_owned())
262 .unwrap_or_default();
263
264 ret += &format!(" by {name}");
265 ret += "\n";
266 }
267 }
268
269 ret += &format!("State: {}", msg.state);
270
271 if msg.has_location() {
272 ret += ", Location sent";
273 }
274
275 if 0 != msg.param.get_int(Param::GuaranteeE2ee).unwrap_or_default() {
276 ret += ", Encrypted";
277 }
278
279 ret += "\n";
280
281 let reactions = get_msg_reactions(context, self).await?;
282 if !reactions.is_empty() {
283 ret += &format!("Reactions: {reactions}\n");
284 }
285
286 if let Some(error) = msg.error.as_ref() {
287 ret += &format!("Error: {error}");
288 }
289
290 if let Some(path) = msg.get_file(context) {
291 let bytes = get_filebytes(context, &path).await?;
292 ret += &format!(
293 "\nFile: {}, name: {}, {} bytes\n",
294 path.display(),
295 msg.get_filename().unwrap_or_default(),
296 bytes
297 );
298 }
299
300 if msg.viewtype != Viewtype::Text {
301 ret += "Type: ";
302 ret += &format!("{}", msg.viewtype);
303 ret += "\n";
304 ret += &format!("Mimetype: {}\n", msg.get_filemime().unwrap_or_default());
305 }
306 let w = msg.param.get_int(Param::Width).unwrap_or_default();
307 let h = msg.param.get_int(Param::Height).unwrap_or_default();
308 if w != 0 || h != 0 {
309 ret += &format!("Dimension: {w} x {h}\n",);
310 }
311 let duration = msg.param.get_int(Param::Duration).unwrap_or_default();
312 if duration != 0 {
313 ret += &format!("Duration: {duration} ms\n",);
314 }
315 if !msg.rfc724_mid.is_empty() {
316 ret += &format!("\nMessage-ID: {}", msg.rfc724_mid);
317
318 let server_urls = Self::get_info_server_urls(context, msg.rfc724_mid).await?;
319 for server_url in server_urls {
320 ret += &format!("\nServer-URL: {server_url}");
322 }
323 }
324 let hop_info = self.hop_info(context).await?;
325
326 ret += "\n\n";
327 if hop_info.is_empty() {
328 ret += "No Hop Info";
329 } else {
330 ret += &hop_info;
331 }
332
333 Ok(ret)
334 }
335}
336
337impl std::fmt::Display for MsgId {
338 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
339 write!(f, "Msg#{}", self.0)
340 }
341}
342
343impl rusqlite::types::ToSql for MsgId {
352 fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
353 if self.0 <= DC_MSG_ID_LAST_SPECIAL {
354 return Err(rusqlite::Error::ToSqlConversionFailure(
355 format_err!("Invalid MsgId {}", self.0).into(),
356 ));
357 }
358 let val = rusqlite::types::Value::Integer(i64::from(self.0));
359 let out = rusqlite::types::ToSqlOutput::Owned(val);
360 Ok(out)
361 }
362}
363
364impl rusqlite::types::FromSql for MsgId {
366 fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
367 i64::column_result(value).and_then(|val| {
369 if 0 <= val && val <= i64::from(u32::MAX) {
370 Ok(MsgId::new(val as u32))
371 } else {
372 Err(rusqlite::types::FromSqlError::OutOfRange(val))
373 }
374 })
375 }
376}
377
378#[derive(
379 Debug,
380 Copy,
381 Clone,
382 PartialEq,
383 FromPrimitive,
384 ToPrimitive,
385 FromSql,
386 ToSql,
387 Serialize,
388 Deserialize,
389 Default,
390)]
391#[repr(u8)]
392pub(crate) enum MessengerMessage {
393 #[default]
394 No = 0,
395 Yes = 1,
396
397 Reply = 2,
399}
400
401#[derive(Debug, Clone, Default, Serialize, Deserialize)]
405pub struct Message {
406 pub(crate) id: MsgId,
408
409 pub(crate) from_id: ContactId,
411
412 pub(crate) to_id: ContactId,
414
415 pub(crate) chat_id: ChatId,
417
418 pub(crate) viewtype: Viewtype,
420
421 pub(crate) state: MessageState,
423 pub(crate) download_state: DownloadState,
424
425 pub(crate) hidden: bool,
427 pub(crate) timestamp_sort: i64,
428 pub(crate) timestamp_sent: i64,
429 pub(crate) timestamp_rcvd: i64,
430 pub(crate) ephemeral_timer: EphemeralTimer,
431 pub(crate) ephemeral_timestamp: i64,
432 pub(crate) text: String,
433 pub(crate) additional_text: String,
437
438 pub(crate) subject: String,
442
443 pub(crate) rfc724_mid: String,
445 pub(crate) pre_rfc724_mid: String,
447
448 pub(crate) in_reply_to: Option<String>,
450 pub(crate) is_dc_message: MessengerMessage,
451 pub(crate) original_msg_id: MsgId,
452 pub(crate) mime_modified: bool,
453 pub(crate) chat_visibility: ChatVisibility,
454 pub(crate) chat_blocked: Blocked,
455 pub(crate) location_id: u32,
456 pub(crate) error: Option<String>,
457 pub(crate) param: Params,
458}
459
460impl Message {
461 pub fn new(viewtype: Viewtype) -> Self {
463 Message {
464 viewtype,
465 rfc724_mid: create_outgoing_rfc724_mid(),
466 ..Default::default()
467 }
468 }
469
470 pub fn new_text(text: String) -> Self {
472 Message {
473 viewtype: Viewtype::Text,
474 text,
475 rfc724_mid: create_outgoing_rfc724_mid(),
476 ..Default::default()
477 }
478 }
479
480 pub async fn load_from_db(context: &Context, id: MsgId) -> Result<Message> {
484 let message = Self::load_from_db_optional(context, id)
485 .await?
486 .with_context(|| format!("Message {id} does not exist"))?;
487 Ok(message)
488 }
489
490 pub async fn load_from_db_optional(context: &Context, id: MsgId) -> Result<Option<Message>> {
494 ensure!(
495 !id.is_special(),
496 "Can not load special message ID {id} from DB"
497 );
498 let mut msg = context
499 .sql
500 .query_row_optional(
501 "SELECT
502 m.id AS id,
503 rfc724_mid AS rfc724mid,
504 pre_rfc724_mid AS pre_rfc724mid,
505 m.mime_in_reply_to AS mime_in_reply_to,
506 m.chat_id AS chat_id,
507 m.from_id AS from_id,
508 m.to_id AS to_id,
509 m.timestamp AS timestamp,
510 m.timestamp_sent AS timestamp_sent,
511 m.timestamp_rcvd AS timestamp_rcvd,
512 m.ephemeral_timer AS ephemeral_timer,
513 m.ephemeral_timestamp AS ephemeral_timestamp,
514 m.type AS type,
515 m.state AS state,
516 mdns.msg_id AS mdn_msg_id,
517 m.download_state AS download_state,
518 m.error AS error,
519 m.msgrmsg AS msgrmsg,
520 m.starred AS original_msg_id,
521 m.mime_modified AS mime_modified,
522 m.txt AS txt,
523 m.subject AS subject,
524 m.param AS param,
525 m.hidden AS hidden,
526 m.location_id AS location,
527 c.archived AS visibility,
528 c.blocked AS blocked
529 FROM msgs m
530 LEFT JOIN chats c ON c.id=m.chat_id
531 LEFT JOIN msgs_mdns mdns ON mdns.msg_id=m.id
532 WHERE m.id=? AND chat_id!=3 -- DC_CHAT_ID_TRASH
533 LIMIT 1",
534 (id,),
535 |row| {
536 let state: MessageState = row.get("state")?;
537 let mdn_msg_id: Option<MsgId> = row.get("mdn_msg_id")?;
538 let text = match row.get_ref("txt")? {
539 rusqlite::types::ValueRef::Text(buf) => {
540 match String::from_utf8(buf.to_vec()) {
541 Ok(t) => t,
542 Err(_) => {
543 warn!(
544 context,
545 concat!(
546 "dc_msg_load_from_db: could not get ",
547 "text column as non-lossy utf8 id {}"
548 ),
549 id
550 );
551 String::from_utf8_lossy(buf).into_owned()
552 }
553 }
554 }
555 _ => String::new(),
556 };
557 let msg = Message {
558 id: row.get("id")?,
559 rfc724_mid: row.get::<_, String>("rfc724mid")?,
560 pre_rfc724_mid: row.get::<_, String>("pre_rfc724mid")?,
561 in_reply_to: row
562 .get::<_, Option<String>>("mime_in_reply_to")?
563 .and_then(|in_reply_to| parse_message_id(&in_reply_to).ok()),
564 chat_id: row.get("chat_id")?,
565 from_id: row.get("from_id")?,
566 to_id: row.get("to_id")?,
567 timestamp_sort: row.get("timestamp")?,
568 timestamp_sent: row.get("timestamp_sent")?,
569 timestamp_rcvd: row.get("timestamp_rcvd")?,
570 ephemeral_timer: row.get("ephemeral_timer")?,
571 ephemeral_timestamp: row.get("ephemeral_timestamp")?,
572 viewtype: row.get("type").unwrap_or_default(),
573 state: state.with_mdns(mdn_msg_id.is_some()),
574 download_state: row.get("download_state")?,
575 error: Some(row.get::<_, String>("error")?)
576 .filter(|error| !error.is_empty()),
577 is_dc_message: row.get("msgrmsg")?,
578 original_msg_id: row.get("original_msg_id")?,
579 mime_modified: row.get("mime_modified")?,
580 text,
581 additional_text: String::new(),
582 subject: row.get("subject")?,
583 param: row.get::<_, String>("param")?.parse().unwrap_or_default(),
584 hidden: row.get("hidden")?,
585 location_id: row.get("location")?,
586 chat_visibility: row.get::<_, Option<_>>("visibility")?.unwrap_or_default(),
587 chat_blocked: row
588 .get::<_, Option<Blocked>>("blocked")?
589 .unwrap_or_default(),
590 };
591 Ok(msg)
592 },
593 )
594 .await
595 .with_context(|| format!("failed to load message {id} from the database"))?;
596
597 if let Some(msg) = &mut msg {
598 msg.additional_text =
599 Self::get_additional_text(context, msg.download_state, &msg.param)?;
600 }
601
602 Ok(msg)
603 }
604
605 pub async fn load_by_rfc724_mid_optional(
609 context: &Context,
610 rfc724_mid: &str,
611 ) -> Result<Option<Message>> {
612 if let Some(msg_id) = context
613 .sql
614 .query_row_optional(
615 "SELECT id FROM msgs WHERE rfc724_mid=? AND chat_id != ?",
616 (rfc724_mid, DC_CHAT_ID_TRASH),
617 |row| {
618 let msg_id: MsgId = row.get(0)?;
619 Ok(msg_id)
620 },
621 )
622 .await?
623 {
624 Self::load_from_db_optional(context, msg_id).await
625 } else {
626 Ok(None)
627 }
628 }
629
630 fn get_additional_text(
634 context: &Context,
635 download_state: DownloadState,
636 param: &Params,
637 ) -> Result<String> {
638 if download_state != DownloadState::Done {
639 let file_size = param
640 .get(Param::PostMessageFileBytes)
641 .and_then(|s| s.parse().ok())
642 .map(|file_size: usize| format_size(file_size, BINARY))
643 .unwrap_or("?".to_owned());
644 let viewtype = param
645 .get_i64(Param::PostMessageViewtype)
646 .and_then(Viewtype::from_i64)
647 .unwrap_or(Viewtype::Unknown);
648 let file_name = param
649 .get(Param::Filename)
650 .map(sanitize_filename)
651 .unwrap_or("?".to_owned());
652
653 return match viewtype {
654 Viewtype::File => Ok(format!(" [{file_name} – {file_size}]")),
655 _ => {
656 let translated_viewtype = viewtype.to_locale_string(context);
657 Ok(format!(" [{translated_viewtype} – {file_size}]"))
658 }
659 };
660 }
661 Ok(String::new())
662 }
663
664 pub fn get_filemime(&self) -> Option<String> {
671 if let Some(m) = self.param.get(Param::MimeType) {
672 return Some(m.to_string());
673 } else if self.param.exists(Param::File) {
674 if let Some((_, mime)) = guess_msgtype_from_suffix(self) {
675 return Some(mime.to_string());
676 }
677 return Some("application/octet-stream".to_string());
679 }
680 None
682 }
683
684 pub fn get_file(&self, context: &Context) -> Option<PathBuf> {
686 self.param.get_file_path(context).unwrap_or(None)
687 }
688
689 pub async fn vcard_contacts(&self, context: &Context) -> Result<Vec<VcardContact>> {
691 if self.viewtype != Viewtype::Vcard {
692 return Ok(Vec::new());
693 }
694
695 let path = self
696 .get_file(context)
697 .context("vCard message does not have an attachment")?;
698 let bytes = tokio::fs::read(path).await?;
699 let vcard_contents = std::str::from_utf8(&bytes).context("vCard is not a valid UTF-8")?;
700 Ok(parse_vcard(vcard_contents))
701 }
702
703 pub async fn save_file(&self, context: &Context, path: &Path) -> Result<()> {
705 let path_src = self.get_file(context).context("No file")?;
706 let mut src = fs::OpenOptions::new().read(true).open(path_src).await?;
707 let mut dst = fs::OpenOptions::new()
708 .write(true)
709 .create_new(true)
710 .open(path)
711 .await?;
712 io::copy(&mut src, &mut dst).await?;
713 Ok(())
714 }
715
716 pub(crate) async fn try_calc_and_set_dimensions(&mut self, context: &Context) -> Result<()> {
718 if self.viewtype.has_file() {
719 let file_param = self.param.get_file_path(context)?;
720 if let Some(path_and_filename) = file_param
721 && matches!(
722 self.viewtype,
723 Viewtype::Image | Viewtype::Gif | Viewtype::Sticker
724 )
725 && !self.param.exists(Param::Width)
726 {
727 let buf = read_file(context, &path_and_filename).await?;
728
729 match get_filemeta(&buf) {
730 Ok((width, height)) => {
731 self.param.set_int(Param::Width, width as i32);
732 self.param.set_int(Param::Height, height as i32);
733 }
734 Err(err) => {
735 self.param.set_int(Param::Width, 0);
736 self.param.set_int(Param::Height, 0);
737 warn!(
738 context,
739 "Failed to get width and height for {}: {err:#}.",
740 path_and_filename.display()
741 );
742 }
743 }
744
745 if !self.id.is_unset() {
746 self.update_param(context).await?;
747 }
748 }
749 }
750 Ok(())
751 }
752
753 pub fn has_location(&self) -> bool {
759 self.location_id != 0
760 }
761
762 pub fn set_location(&mut self, latitude: f64, longitude: f64) {
779 if latitude == 0.0 && longitude == 0.0 {
780 return;
781 }
782
783 self.param.set_float(Param::SetLatitude, latitude);
784 self.param.set_float(Param::SetLongitude, longitude);
785 }
786
787 pub fn get_timestamp(&self) -> i64 {
790 if 0 != self.timestamp_sent {
791 self.timestamp_sent
792 } else {
793 self.timestamp_sort
794 }
795 }
796
797 pub fn get_id(&self) -> MsgId {
799 self.id
800 }
801
802 pub fn rfc724_mid(&self) -> &str {
805 &self.rfc724_mid
806 }
807
808 pub fn get_from_id(&self) -> ContactId {
810 self.from_id
811 }
812
813 pub fn get_chat_id(&self) -> ChatId {
815 self.chat_id
816 }
817
818 pub fn get_viewtype(&self) -> Viewtype {
820 self.viewtype
821 }
822
823 pub fn get_state(&self) -> MessageState {
825 self.state
826 }
827
828 pub fn get_received_timestamp(&self) -> i64 {
830 self.timestamp_rcvd
831 }
832
833 pub fn get_sort_timestamp(&self) -> i64 {
835 if self.timestamp_sort != 0 {
836 self.timestamp_sort
837 } else {
838 self.timestamp_sent
839 }
840 }
841
842 pub fn get_text(&self) -> String {
847 self.text.clone() + &self.additional_text
848 }
849
850 pub fn get_subject(&self) -> &str {
852 &self.subject
853 }
854
855 pub fn get_filename(&self) -> Option<String> {
859 if let Some(name) = self.param.get(Param::Filename) {
860 return Some(sanitize_filename(name));
861 }
862 self.param
863 .get(Param::File)
864 .and_then(|file| Path::new(file).file_name())
865 .map(|name| sanitize_filename(&name.to_string_lossy()))
866 }
867
868 pub async fn get_filebytes(&self, context: &Context) -> Result<Option<u64>> {
871 if self.download_state != DownloadState::Done
872 && let Some(file_size) = self
873 .param
874 .get(Param::PostMessageFileBytes)
875 .and_then(|s| s.parse().ok())
876 {
877 return Ok(Some(file_size));
878 }
879 if let Some(path) = self.param.get_file_path(context)? {
880 Ok(Some(get_filebytes(context, &path).await.with_context(
881 || format!("failed to get {} size in bytes", path.display()),
882 )?))
883 } else {
884 Ok(None)
885 }
886 }
887
888 #[cfg(test)]
891 pub(crate) fn get_post_message_viewtype(&self) -> Option<Viewtype> {
892 if self.download_state != DownloadState::Done {
893 return self
894 .param
895 .get_i64(Param::PostMessageViewtype)
896 .and_then(Viewtype::from_i64);
897 }
898 None
899 }
900
901 pub fn get_width(&self) -> i32 {
903 self.param.get_int(Param::Width).unwrap_or_default()
904 }
905
906 pub fn get_height(&self) -> i32 {
908 self.param.get_int(Param::Height).unwrap_or_default()
909 }
910
911 pub fn get_duration(&self) -> i32 {
913 self.param.get_int(Param::Duration).unwrap_or_default()
914 }
915
916 pub fn get_showpadlock(&self) -> bool {
918 self.param.get_int(Param::GuaranteeE2ee).unwrap_or_default() != 0
919 || self.from_id == ContactId::DEVICE
920 }
921
922 pub fn is_bot(&self) -> bool {
924 self.param.get_bool(Param::Bot).unwrap_or_default()
925 }
926
927 pub fn get_ephemeral_timer(&self) -> EphemeralTimer {
929 self.ephemeral_timer
930 }
931
932 pub fn get_ephemeral_timestamp(&self) -> i64 {
934 self.ephemeral_timestamp
935 }
936
937 pub async fn get_summary(&self, context: &Context, chat: Option<&Chat>) -> Result<Summary> {
939 let chat_loaded: Chat;
940 let chat = if let Some(chat) = chat {
941 chat
942 } else {
943 let chat = Chat::load_from_db(context, self.chat_id).await?;
944 chat_loaded = chat;
945 &chat_loaded
946 };
947
948 let contact = if self.from_id != ContactId::SELF {
949 match chat.typ {
950 Chattype::Group | Chattype::Mailinglist => {
951 Some(Contact::get_by_id(context, self.from_id).await?)
952 }
953 Chattype::Single | Chattype::OutBroadcast | Chattype::InBroadcast => None,
954 }
955 } else {
956 None
957 };
958
959 Summary::new(context, self, chat, contact.as_ref()).await
960 }
961
962 pub fn get_override_sender_name(&self) -> Option<String> {
972 self.param
973 .get(Param::OverrideSenderDisplayname)
974 .map(|name| name.to_string())
975 }
976
977 pub(crate) fn get_sender_name(&self, contact: &Contact) -> String {
980 self.get_override_sender_name()
981 .unwrap_or_else(|| contact.get_display_name().to_string())
982 }
983
984 #[expect(clippy::arithmetic_side_effects)]
989 pub fn has_deviating_timestamp(&self) -> bool {
990 let cnv_to_local = gm2local_offset();
991 let sort_timestamp = self.get_sort_timestamp() + cnv_to_local;
992 let send_timestamp = self.get_timestamp() + cnv_to_local;
993
994 sort_timestamp / 86400 != send_timestamp / 86400
995 }
996
997 pub fn is_sent(&self) -> bool {
1000 self.state >= MessageState::OutDelivered
1001 }
1002
1003 pub fn is_forwarded(&self) -> bool {
1005 self.param.get_int(Param::Forwarded).is_some()
1006 }
1007
1008 pub fn is_edited(&self) -> bool {
1010 self.param.get_bool(Param::IsEdited).unwrap_or_default()
1011 }
1012
1013 pub fn is_info(&self) -> bool {
1015 let cmd = self.param.get_cmd();
1016 self.from_id == ContactId::INFO
1017 || self.to_id == ContactId::INFO
1018 || cmd != SystemMessage::Unknown && cmd != SystemMessage::AutocryptSetupMessage
1019 }
1020
1021 pub fn get_info_type(&self) -> SystemMessage {
1023 self.param.get_cmd()
1024 }
1025
1026 pub async fn get_info_contact_id(&self, context: &Context) -> Result<Option<ContactId>> {
1028 match self.param.get_cmd() {
1029 SystemMessage::GroupNameChanged
1030 | SystemMessage::GroupDescriptionChanged
1031 | SystemMessage::GroupImageChanged
1032 | SystemMessage::EphemeralTimerChanged => {
1033 if self.from_id != ContactId::INFO {
1034 Ok(Some(self.from_id))
1035 } else {
1036 Ok(None)
1037 }
1038 }
1039
1040 SystemMessage::MemberAddedToGroup | SystemMessage::MemberRemovedFromGroup => {
1041 if let Some(contact_i32) = self.param.get_int(Param::ContactAddedRemoved) {
1042 let contact_id = ContactId::new(contact_i32.try_into()?);
1043 if contact_id == ContactId::SELF
1044 || Contact::real_exists_by_id(context, contact_id).await?
1045 {
1046 Ok(Some(contact_id))
1047 } else {
1048 Ok(None)
1049 }
1050 } else {
1051 Ok(None)
1052 }
1053 }
1054
1055 SystemMessage::AutocryptSetupMessage
1056 | SystemMessage::SecurejoinMessage
1057 | SystemMessage::LocationStreamingEnabled
1058 | SystemMessage::LocationOnly
1059 | SystemMessage::ChatE2ee
1060 | SystemMessage::ChatProtectionEnabled
1061 | SystemMessage::ChatProtectionDisabled
1062 | SystemMessage::InvalidUnencryptedMail
1063 | SystemMessage::SecurejoinWait
1064 | SystemMessage::SecurejoinWaitTimeout
1065 | SystemMessage::MultiDeviceSync
1066 | SystemMessage::WebxdcStatusUpdate
1067 | SystemMessage::WebxdcInfoMessage
1068 | SystemMessage::IrohNodeAddr
1069 | SystemMessage::CallAccepted
1070 | SystemMessage::CallEnded
1071 | SystemMessage::Unknown => Ok(None),
1072 }
1073 }
1074
1075 pub fn is_system_message(&self) -> bool {
1077 let cmd = self.param.get_cmd();
1078 cmd != SystemMessage::Unknown
1079 }
1080
1081 pub fn set_text(&mut self, text: String) {
1083 self.text = text;
1084 }
1085
1086 pub fn set_subject(&mut self, subject: String) {
1089 self.subject = subject;
1090 }
1091
1092 pub fn set_file_and_deduplicate(
1107 &mut self,
1108 context: &Context,
1109 file: &Path,
1110 name: Option<&str>,
1111 filemime: Option<&str>,
1112 ) -> Result<()> {
1113 let name = if let Some(name) = name {
1114 name.to_string()
1115 } else {
1116 file.file_name()
1117 .map(|s| s.to_string_lossy().to_string())
1118 .unwrap_or_else(|| "unknown_file".to_string())
1119 };
1120
1121 let blob = BlobObject::create_and_deduplicate(context, file, Path::new(&name))?;
1122 self.param.set(Param::File, blob.as_name());
1123
1124 self.param.set(Param::Filename, name);
1125 self.param.set_optional(Param::MimeType, filemime);
1126
1127 Ok(())
1128 }
1129
1130 pub fn set_file_from_bytes(
1137 &mut self,
1138 context: &Context,
1139 name: &str,
1140 data: &[u8],
1141 filemime: Option<&str>,
1142 ) -> Result<()> {
1143 let blob = BlobObject::create_and_deduplicate_from_bytes(context, data, name)?;
1144 self.param.set(Param::Filename, name);
1145 self.param.set(Param::File, blob.as_name());
1146 self.param.set_optional(Param::MimeType, filemime);
1147
1148 Ok(())
1149 }
1150
1151 pub async fn make_vcard(&mut self, context: &Context, contacts: &[ContactId]) -> Result<()> {
1153 ensure!(
1154 matches!(self.viewtype, Viewtype::File | Viewtype::Vcard),
1155 "Wrong viewtype for vCard: {}",
1156 self.viewtype,
1157 );
1158 let vcard = contact::make_vcard(context, contacts).await?;
1159 self.set_file_from_bytes(context, "vcard.vcf", vcard.as_bytes(), None)
1160 }
1161
1162 pub(crate) async fn try_set_vcard(&mut self, context: &Context, path: &Path) -> Result<()> {
1164 let vcard = fs::read(path)
1165 .await
1166 .with_context(|| format!("Could not read {path:?}"))?;
1167 if let Some(summary) = get_vcard_summary(&vcard) {
1168 self.param.set(Param::Summary1, summary);
1169 } else {
1170 warn!(context, "try_set_vcard: Not a valid DeltaChat vCard.");
1171 self.viewtype = Viewtype::File;
1172 }
1173 Ok(())
1174 }
1175
1176 pub fn set_override_sender_name(&mut self, name: Option<String>) {
1179 self.param
1180 .set_optional(Param::OverrideSenderDisplayname, name);
1181 }
1182
1183 pub fn set_dimension(&mut self, width: i32, height: i32) {
1185 self.param.set_int(Param::Width, width);
1186 self.param.set_int(Param::Height, height);
1187 }
1188
1189 pub fn set_duration(&mut self, duration: i32) {
1191 self.param.set_int(Param::Duration, duration);
1192 }
1193
1194 pub(crate) fn set_reaction(&mut self) {
1196 self.param.set_int(Param::Reaction, 1);
1197 }
1198
1199 pub async fn latefiling_mediasize(
1202 &mut self,
1203 context: &Context,
1204 width: i32,
1205 height: i32,
1206 duration: i32,
1207 ) -> Result<()> {
1208 if width > 0 && height > 0 {
1209 self.param.set_int(Param::Width, width);
1210 self.param.set_int(Param::Height, height);
1211 }
1212 if duration > 0 {
1213 self.param.set_int(Param::Duration, duration);
1214 }
1215 self.update_param(context).await?;
1216 Ok(())
1217 }
1218
1219 pub fn set_quote_text(&mut self, text: Option<(String, bool)>) {
1225 let Some((text, protect)) = text else {
1226 self.param.remove(Param::Quote);
1227 self.param.remove(Param::ProtectQuote);
1228 return;
1229 };
1230 self.param.set(Param::Quote, text);
1231 self.param.set_optional(
1232 Param::ProtectQuote,
1233 match protect {
1234 true => Some("1"),
1235 false => None,
1236 },
1237 );
1238 }
1239
1240 pub async fn set_quote(&mut self, context: &Context, quote: Option<&Message>) -> Result<()> {
1249 if let Some(quote) = quote {
1250 ensure!(
1251 !quote.rfc724_mid.is_empty(),
1252 "Message without Message-Id cannot be quoted"
1253 );
1254 self.in_reply_to = Some(quote.rfc724_mid.clone());
1255
1256 let text = quote.get_text();
1257 let text = if text.is_empty() {
1258 quote
1260 .get_summary(context, None)
1261 .await?
1262 .truncated_text(500)
1263 .to_string()
1264 } else {
1265 text
1266 };
1267 self.set_quote_text(Some((
1268 text,
1269 quote
1270 .param
1271 .get_bool(Param::GuaranteeE2ee)
1272 .unwrap_or_default(),
1273 )));
1274 } else {
1275 self.in_reply_to = None;
1276 self.set_quote_text(None);
1277 }
1278
1279 Ok(())
1280 }
1281
1282 pub fn quoted_text(&self) -> Option<String> {
1284 self.param.get(Param::Quote).map(|s| s.to_string())
1285 }
1286
1287 pub async fn quoted_message(&self, context: &Context) -> Result<Option<Message>> {
1289 if self.param.get(Param::Quote).is_some() && !self.is_forwarded() {
1290 return self.parent(context).await;
1291 }
1292 Ok(None)
1293 }
1294
1295 pub async fn parent(&self, context: &Context) -> Result<Option<Message>> {
1300 if let Some(in_reply_to) = &self.in_reply_to
1301 && let Some(msg_id) = rfc724_mid_exists(context, in_reply_to).await?
1302 {
1303 let msg = Message::load_from_db_optional(context, msg_id).await?;
1304 return Ok(msg);
1305 }
1306 Ok(None)
1307 }
1308
1309 pub async fn get_original_msg_id(&self, context: &Context) -> Result<Option<MsgId>> {
1311 if !self.original_msg_id.is_special()
1312 && let Some(msg) = Message::load_from_db_optional(context, self.original_msg_id).await?
1313 {
1314 return if msg.chat_id.is_trash() {
1315 Ok(None)
1316 } else {
1317 Ok(Some(msg.id))
1318 };
1319 }
1320 Ok(None)
1321 }
1322
1323 pub async fn get_saved_msg_id(&self, context: &Context) -> Result<Option<MsgId>> {
1327 let res: Option<MsgId> = context
1328 .sql
1329 .query_get_value(
1330 "SELECT id FROM msgs WHERE starred=? AND chat_id!=?",
1331 (self.id, DC_CHAT_ID_TRASH),
1332 )
1333 .await?;
1334 Ok(res)
1335 }
1336
1337 pub(crate) fn force_plaintext(&mut self) {
1339 self.param.set_int(Param::ForcePlaintext, 1);
1340 }
1341
1342 pub async fn update_param(&self, context: &Context) -> Result<()> {
1344 context
1345 .sql
1346 .execute(
1347 "UPDATE msgs SET param=? WHERE id=?;",
1348 (self.param.to_string(), self.id),
1349 )
1350 .await?;
1351 Ok(())
1352 }
1353
1354 pub fn error(&self) -> Option<String> {
1367 self.error.clone()
1368 }
1369}
1370
1371#[derive(
1375 Debug,
1376 Default,
1377 Clone,
1378 Copy,
1379 PartialEq,
1380 Eq,
1381 PartialOrd,
1382 Ord,
1383 FromPrimitive,
1384 ToPrimitive,
1385 ToSql,
1386 FromSql,
1387 Serialize,
1388 Deserialize,
1389)]
1390#[repr(u32)]
1391pub enum MessageState {
1392 #[default]
1394 Undefined = 0,
1395
1396 InFresh = 10,
1399
1400 InNoticed = 13,
1404
1405 InSeen = 16,
1408
1409 OutDraft = 19,
1413
1414 OutPending = 20,
1418
1419 OutFailed = 24,
1422
1423 OutDelivered = 26,
1427
1428 OutMdnRcvd = 28,
1431}
1432
1433impl std::fmt::Display for MessageState {
1434 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
1435 write!(
1436 f,
1437 "{}",
1438 match self {
1439 Self::Undefined => "Undefined",
1440 Self::InFresh => "Fresh",
1441 Self::InNoticed => "Noticed",
1442 Self::InSeen => "Seen",
1443 Self::OutDraft => "Draft",
1444 Self::OutPending => "Pending",
1445 Self::OutFailed => "Failed",
1446 Self::OutDelivered => "Delivered",
1447 Self::OutMdnRcvd => "Read",
1448 }
1449 )
1450 }
1451}
1452
1453impl MessageState {
1454 pub fn can_fail(self) -> bool {
1456 use MessageState::*;
1457 matches!(
1458 self,
1459 OutPending | OutDelivered | OutMdnRcvd )
1461 }
1462
1463 pub fn is_outgoing(self) -> bool {
1465 use MessageState::*;
1466 matches!(
1467 self,
1468 OutDraft | OutPending | OutFailed | OutDelivered | OutMdnRcvd
1469 )
1470 }
1471
1472 pub(crate) fn with_mdns(self, has_mdns: bool) -> Self {
1474 if self == MessageState::OutDelivered && has_mdns {
1475 return MessageState::OutMdnRcvd;
1476 }
1477 self
1478 }
1479}
1480
1481pub async fn get_msg_read_receipts(
1483 context: &Context,
1484 msg_id: MsgId,
1485) -> Result<Vec<(ContactId, i64)>> {
1486 context
1487 .sql
1488 .query_map_vec(
1489 "SELECT contact_id, timestamp_sent FROM msgs_mdns WHERE msg_id=?",
1490 (msg_id,),
1491 |row| {
1492 let contact_id: ContactId = row.get(0)?;
1493 let ts: i64 = row.get(1)?;
1494 Ok((contact_id, ts))
1495 },
1496 )
1497 .await
1498}
1499
1500pub async fn get_msg_read_receipt_count(context: &Context, msg_id: MsgId) -> Result<usize> {
1504 context
1505 .sql
1506 .count("SELECT COUNT(*) FROM msgs_mdns WHERE msg_id=?", (msg_id,))
1507 .await
1508}
1509
1510pub(crate) fn guess_msgtype_from_suffix(msg: &Message) -> Option<(Viewtype, &'static str)> {
1511 msg.param
1512 .get(Param::Filename)
1513 .or_else(|| msg.param.get(Param::File))
1514 .and_then(|file| guess_msgtype_from_path_suffix(Path::new(file)))
1515}
1516
1517pub(crate) fn guess_msgtype_from_path_suffix(path: &Path) -> Option<(Viewtype, &'static str)> {
1518 let extension: &str = &path.extension()?.to_str()?.to_lowercase();
1519 let info = match extension {
1520 "3gp" => (Viewtype::Video, "video/3gpp"),
1533 "aac" => (Viewtype::Audio, "audio/aac"),
1534 "avi" => (Viewtype::Video, "video/x-msvideo"),
1535 "avif" => (Viewtype::File, "image/avif"), "doc" => (Viewtype::File, "application/msword"),
1537 "docx" => (
1538 Viewtype::File,
1539 "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
1540 ),
1541 "epub" => (Viewtype::File, "application/epub+zip"),
1542 "flac" => (Viewtype::Audio, "audio/flac"),
1543 "gif" => (Viewtype::Gif, "image/gif"),
1544 "heic" => (Viewtype::File, "image/heic"), "heif" => (Viewtype::File, "image/heif"), "html" => (Viewtype::File, "text/html"),
1547 "htm" => (Viewtype::File, "text/html"),
1548 "ico" => (Viewtype::File, "image/vnd.microsoft.icon"),
1549 "jar" => (Viewtype::File, "application/java-archive"),
1550 "jpeg" => (Viewtype::Image, "image/jpeg"),
1551 "jpe" => (Viewtype::Image, "image/jpeg"),
1552 "jpg" => (Viewtype::Image, "image/jpeg"),
1553 "json" => (Viewtype::File, "application/json"),
1554 "mov" => (Viewtype::Video, "video/quicktime"),
1555 "m4a" => (Viewtype::Audio, "audio/m4a"),
1556 "mp3" => (Viewtype::Audio, "audio/mpeg"),
1557 "mp4" => (Viewtype::Video, "video/mp4"),
1558 "odp" => (
1559 Viewtype::File,
1560 "application/vnd.oasis.opendocument.presentation",
1561 ),
1562 "ods" => (
1563 Viewtype::File,
1564 "application/vnd.oasis.opendocument.spreadsheet",
1565 ),
1566 "odt" => (Viewtype::File, "application/vnd.oasis.opendocument.text"),
1567 "oga" => (Viewtype::Audio, "audio/ogg"),
1568 "ogg" => (Viewtype::Audio, "audio/ogg"),
1569 "ogv" => (Viewtype::File, "video/ogg"),
1570 "opus" => (Viewtype::File, "audio/ogg"), "otf" => (Viewtype::File, "font/otf"),
1572 "pdf" => (Viewtype::File, "application/pdf"),
1573 "png" => (Viewtype::Image, "image/png"),
1574 "ppt" => (Viewtype::File, "application/vnd.ms-powerpoint"),
1575 "pptx" => (
1576 Viewtype::File,
1577 "application/vnd.openxmlformats-officedocument.presentationml.presentation",
1578 ),
1579 "rar" => (Viewtype::File, "application/vnd.rar"),
1580 "rtf" => (Viewtype::File, "application/rtf"),
1581 "spx" => (Viewtype::File, "audio/ogg"), "svg" => (Viewtype::File, "image/svg+xml"),
1583 "tgs" => (Viewtype::File, "application/x-tgsticker"),
1584 "tiff" => (Viewtype::File, "image/tiff"),
1585 "tif" => (Viewtype::File, "image/tiff"),
1586 "ttf" => (Viewtype::File, "font/ttf"),
1587 "txt" => (Viewtype::File, "text/plain"),
1588 "vcard" => (Viewtype::Vcard, "text/vcard"),
1589 "vcf" => (Viewtype::Vcard, "text/vcard"),
1590 "wav" => (Viewtype::Audio, "audio/wav"),
1591 "weba" => (Viewtype::File, "audio/webm"),
1592 "webm" => (Viewtype::File, "video/webm"), "webp" => (Viewtype::Image, "image/webp"), "wmv" => (Viewtype::Video, "video/x-ms-wmv"),
1595 "xdc" => (Viewtype::Webxdc, "application/webxdc+zip"),
1596 "xhtml" => (Viewtype::File, "application/xhtml+xml"),
1597 "xls" => (Viewtype::File, "application/vnd.ms-excel"),
1598 "xlsx" => (
1599 Viewtype::File,
1600 "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
1601 ),
1602 "xml" => (Viewtype::File, "application/xml"),
1603 "zip" => (Viewtype::File, "application/zip"),
1604 _ => {
1605 return None;
1606 }
1607 };
1608 Some(info)
1609}
1610
1611pub(crate) async fn get_mime_headers(context: &Context, msg_id: MsgId) -> Result<Vec<u8>> {
1618 let (headers, compressed) = context
1619 .sql
1620 .query_row(
1621 "SELECT mime_headers, mime_compressed FROM msgs WHERE id=?",
1622 (msg_id,),
1623 |row| {
1624 let headers = sql::row_get_vec(row, 0)?;
1625 let compressed: bool = row.get(1)?;
1626 Ok((headers, compressed))
1627 },
1628 )
1629 .await?;
1630 if compressed {
1631 return buf_decompress(&headers);
1632 }
1633
1634 let headers2 = headers.clone();
1635 let compressed = match tokio::task::block_in_place(move || buf_compress(&headers2)) {
1636 Err(e) => {
1637 warn!(context, "get_mime_headers: buf_compress() failed: {}", e);
1638 return Ok(headers);
1639 }
1640 Ok(o) => o,
1641 };
1642 let update = |conn: &mut rusqlite::Connection| {
1643 match conn.execute(
1644 "\
1645 UPDATE msgs SET mime_headers=?, mime_compressed=1 \
1646 WHERE id=? AND mime_headers!='' AND mime_compressed=0",
1647 (compressed, msg_id),
1648 ) {
1649 Ok(rows_updated) => ensure!(rows_updated <= 1),
1650 Err(e) => {
1651 warn!(context, "get_mime_headers: UPDATE failed: {}", e);
1652 return Err(e.into());
1653 }
1654 }
1655 Ok(())
1656 };
1657 if let Err(e) = context.sql.call_write(update).await {
1658 warn!(
1659 context,
1660 "get_mime_headers: failed to update mime_headers: {}", e
1661 );
1662 }
1663
1664 Ok(headers)
1665}
1666
1667pub(crate) async fn delete_msg_locally(context: &Context, msg: &Message) -> Result<()> {
1670 if msg.location_id > 0 {
1671 location::delete_poi(context, msg.location_id).await?;
1672 }
1673 let on_server = true;
1674 msg.id
1675 .trash(context, on_server)
1676 .await
1677 .with_context(|| format!("Unable to trash message {}", msg.id))?;
1678
1679 context.emit_event(EventType::MsgDeleted {
1680 chat_id: msg.chat_id,
1681 msg_id: msg.id,
1682 });
1683
1684 if msg.viewtype == Viewtype::Webxdc {
1685 context.emit_event(EventType::WebxdcInstanceDeleted { msg_id: msg.id });
1686 }
1687
1688 let logging_xdc_id = context
1689 .debug_logging
1690 .read()
1691 .expect("RwLock is poisoned")
1692 .as_ref()
1693 .map(|dl| dl.msg_id);
1694 if let Some(id) = logging_xdc_id
1695 && id == msg.id
1696 {
1697 set_debug_logging_xdc(context, None).await?;
1698 }
1699
1700 Ok(())
1701}
1702
1703pub(crate) async fn delete_msgs_locally_done(
1706 context: &Context,
1707 msg_ids: &[MsgId],
1708 modified_chat_ids: BTreeSet<ChatId>,
1709) -> Result<()> {
1710 for modified_chat_id in modified_chat_ids {
1711 context.emit_msgs_changed_without_msg_id(modified_chat_id);
1712 chatlist_events::emit_chatlist_item_changed(context, modified_chat_id);
1713 }
1714 if !msg_ids.is_empty() {
1715 context.emit_msgs_changed_without_ids();
1716 chatlist_events::emit_chatlist_changed(context);
1717 context
1719 .set_config_internal(Config::LastHousekeeping, None)
1720 .await?;
1721 }
1722 Ok(())
1723}
1724
1725pub async fn delete_msgs(context: &Context, msg_ids: &[MsgId]) -> Result<()> {
1727 delete_msgs_ex(context, msg_ids, false).await
1728}
1729
1730pub async fn delete_msgs_ex(
1734 context: &Context,
1735 msg_ids: &[MsgId],
1736 delete_for_all: bool,
1737) -> Result<()> {
1738 let mut modified_chat_ids = BTreeSet::new();
1739 let mut deleted_rfc724_mid = Vec::new();
1740 let mut res = Ok(());
1741
1742 for &msg_id in msg_ids {
1743 let msg = Message::load_from_db(context, msg_id).await?;
1744 ensure!(
1745 !delete_for_all || msg.from_id == ContactId::SELF,
1746 "Can delete only own messages for others"
1747 );
1748 ensure!(
1749 !delete_for_all || msg.get_showpadlock(),
1750 "Cannot request deletion of unencrypted message for others"
1751 );
1752
1753 modified_chat_ids.insert(msg.chat_id);
1754 deleted_rfc724_mid.push(msg.rfc724_mid.clone());
1755
1756 let update_db = |trans: &mut rusqlite::Transaction| {
1757 let mut stmt = trans.prepare("UPDATE imap SET target='' WHERE rfc724_mid=?")?;
1758 stmt.execute((&msg.rfc724_mid,))?;
1759 if !msg.pre_rfc724_mid.is_empty() {
1760 stmt.execute((&msg.pre_rfc724_mid,))?;
1761 }
1762 trans.execute("DELETE FROM smtp WHERE msg_id=?", (msg_id,))?;
1763 trans.execute(
1764 "DELETE FROM download WHERE rfc724_mid=?",
1765 (&msg.rfc724_mid,),
1766 )?;
1767 trans.execute(
1768 "DELETE FROM available_post_msgs WHERE rfc724_mid=?",
1769 (&msg.rfc724_mid,),
1770 )?;
1771 Ok(())
1772 };
1773 if let Err(e) = context.sql.transaction(update_db).await {
1774 error!(context, "delete_msgs: failed to update db: {e:#}.");
1775 res = Err(e);
1776 continue;
1777 }
1778 }
1779 res?;
1780
1781 if delete_for_all {
1782 ensure!(
1783 modified_chat_ids.len() == 1,
1784 "Can delete only from same chat."
1785 );
1786 if let Some(chat_id) = modified_chat_ids.iter().next() {
1787 let mut msg = Message::new_text("🚮".to_owned());
1788 msg.param.set_int(Param::GuaranteeE2ee, 1);
1793 msg.param
1794 .set(Param::DeleteRequestFor, deleted_rfc724_mid.join(" "));
1795 msg.hidden = true;
1796 send_msg(context, *chat_id, &mut msg).await?;
1797 }
1798 } else {
1799 context
1800 .add_sync_item(SyncData::DeleteMessages {
1801 msgs: deleted_rfc724_mid,
1802 })
1803 .await?;
1804 context.scheduler.interrupt_smtp().await;
1805 }
1806
1807 for &msg_id in msg_ids {
1808 let msg = Message::load_from_db(context, msg_id).await?;
1809 delete_msg_locally(context, &msg).await?;
1810 }
1811 delete_msgs_locally_done(context, msg_ids, modified_chat_ids).await?;
1812
1813 context.scheduler.interrupt_inbox().await;
1815
1816 Ok(())
1817}
1818
1819pub async fn markseen_msgs(context: &Context, msg_ids: Vec<MsgId>) -> Result<()> {
1821 if msg_ids.is_empty() {
1822 return Ok(());
1823 }
1824
1825 let old_last_msg_id = MsgId::new(context.get_config_u32(Config::LastMsgId).await?);
1826 let last_msg_id = msg_ids.iter().fold(&old_last_msg_id, std::cmp::max);
1827 context
1828 .set_config_internal(Config::LastMsgId, Some(&last_msg_id.to_u32().to_string()))
1829 .await?;
1830
1831 let mut msgs = Vec::with_capacity(msg_ids.len());
1832 for &id in &msg_ids {
1833 if let Some(msg) = context
1834 .sql
1835 .query_row_optional(
1836 "SELECT
1837 m.chat_id AS chat_id,
1838 m.state AS state,
1839 m.ephemeral_timer AS ephemeral_timer,
1840 m.param AS param,
1841 m.from_id AS from_id,
1842 m.rfc724_mid AS rfc724_mid,
1843 m.hidden AS hidden,
1844 c.archived AS archived,
1845 c.blocked AS blocked
1846 FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id
1847 WHERE m.id=? AND m.chat_id>9",
1848 (id,),
1849 |row| {
1850 let chat_id: ChatId = row.get("chat_id")?;
1851 let state: MessageState = row.get("state")?;
1852 let param: Params = row.get::<_, String>("param")?.parse().unwrap_or_default();
1853 let from_id: ContactId = row.get("from_id")?;
1854 let rfc724_mid: String = row.get("rfc724_mid")?;
1855 let hidden: bool = row.get("hidden")?;
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 hidden,
1868 visibility,
1869 blocked.unwrap_or_default(),
1870 ),
1871 ephemeral_timer,
1872 ))
1873 },
1874 )
1875 .await?
1876 {
1877 msgs.push(msg);
1878 }
1879 }
1880
1881 if msgs
1882 .iter()
1883 .any(|(_, ephemeral_timer)| *ephemeral_timer != EphemeralTimer::Disabled)
1884 {
1885 start_ephemeral_timers_msgids(context, &msg_ids)
1886 .await
1887 .context("failed to start ephemeral timers")?;
1888 }
1889
1890 let mut updated_chat_ids = BTreeSet::new();
1891 let mut archived_chats_maybe_noticed = false;
1892 for (
1893 (
1894 id,
1895 curr_chat_id,
1896 curr_state,
1897 curr_param,
1898 curr_from_id,
1899 curr_rfc724_mid,
1900 curr_hidden,
1901 curr_visibility,
1902 curr_blocked,
1903 ),
1904 _curr_ephemeral_timer,
1905 ) in msgs
1906 {
1907 if curr_state == MessageState::InFresh || curr_state == MessageState::InNoticed {
1908 update_msg_state(context, id, MessageState::InSeen).await?;
1909 info!(context, "Seen message {}.", id);
1910
1911 markseen_on_imap_table(context, &curr_rfc724_mid).await?;
1912
1913 let to_id = if curr_blocked == Blocked::Not
1924 && !curr_hidden
1925 && curr_param.get_bool(Param::WantsMdn).unwrap_or_default()
1926 && curr_param.get_cmd() == SystemMessage::Unknown
1927 && context.should_send_mdns().await?
1928 {
1929 context
1933 .sql
1934 .execute(
1935 "UPDATE msgs SET param=? WHERE id=?",
1936 (curr_param.clone().remove(Param::WantsMdn).to_string(), id),
1937 )
1938 .await
1939 .context("failed to clear WantsMdn")?;
1940 Some(curr_from_id)
1941 } else if context.get_config_bool(Config::BccSelf).await? {
1942 Some(ContactId::SELF)
1943 } else {
1944 None
1945 };
1946 if let Some(to_id) = to_id {
1947 context
1948 .sql
1949 .execute(
1950 "INSERT INTO smtp_mdns (msg_id, from_id, rfc724_mid) VALUES(?, ?, ?)",
1951 (id, to_id, curr_rfc724_mid),
1952 )
1953 .await
1954 .context("failed to insert into smtp_mdns")?;
1955 context.scheduler.interrupt_smtp().await;
1956 }
1957
1958 if !curr_hidden {
1959 updated_chat_ids.insert(curr_chat_id);
1960 }
1961 }
1962 archived_chats_maybe_noticed |= curr_state == MessageState::InFresh
1963 && !curr_hidden
1964 && curr_visibility == ChatVisibility::Archived;
1965 }
1966
1967 for updated_chat_id in updated_chat_ids {
1968 context.emit_event(EventType::MsgsNoticed(updated_chat_id));
1969 chatlist_events::emit_chatlist_item_changed(context, updated_chat_id);
1970 }
1971 if archived_chats_maybe_noticed {
1972 context.on_archived_chats_maybe_noticed();
1973 }
1974
1975 Ok(())
1976}
1977
1978pub async fn get_existing_msg_ids(context: &Context, ids: &[MsgId]) -> Result<Vec<MsgId>> {
1982 let query_only = true;
1983 let res = context
1984 .sql
1985 .transaction_ex(query_only, |transaction| {
1986 let mut res: Vec<MsgId> = Vec::new();
1987 for id in ids {
1988 if transaction.query_one(
1989 "SELECT COUNT(*) > 0 FROM msgs WHERE id=? AND chat_id!=3",
1990 (id,),
1991 |row| {
1992 let exists: bool = row.get(0)?;
1993 Ok(exists)
1994 },
1995 )? {
1996 res.push(*id);
1997 }
1998 }
1999 Ok(res)
2000 })
2001 .await?;
2002 Ok(res)
2003}
2004
2005pub(crate) async fn update_msg_state(
2006 context: &Context,
2007 msg_id: MsgId,
2008 state: MessageState,
2009) -> Result<()> {
2010 ensure!(
2011 state != MessageState::OutMdnRcvd,
2012 "Update msgs_mdns table instead!"
2013 );
2014 ensure!(state != MessageState::OutFailed, "use set_msg_failed()!");
2015 let error_subst = match state >= MessageState::OutPending {
2016 true => ", error=''",
2017 false => "",
2018 };
2019 context
2020 .sql
2021 .execute(
2022 &format!("UPDATE msgs SET state=? {error_subst} WHERE id=?"),
2023 (state, msg_id),
2024 )
2025 .await?;
2026 Ok(())
2027}
2028
2029pub(crate) async fn set_msg_failed(
2037 context: &Context,
2038 msg: &mut Message,
2039 error: &str,
2040) -> Result<()> {
2041 if msg.state.can_fail() {
2042 msg.state = MessageState::OutFailed;
2043 warn!(context, "{} failed: {}", msg.id, error);
2044 } else {
2045 warn!(
2046 context,
2047 "{} seems to have failed ({}), but state is {}", msg.id, error, msg.state
2048 )
2049 }
2050 msg.error = Some(error.to_string());
2051
2052 let exists = context
2053 .sql
2054 .execute(
2055 "UPDATE msgs SET state=?, error=? WHERE id=?;",
2056 (msg.state, error, msg.id),
2057 )
2058 .await?
2059 > 0;
2060 context.emit_event(EventType::MsgFailed {
2061 chat_id: msg.chat_id,
2062 msg_id: msg.id,
2063 });
2064 if exists {
2065 chatlist_events::emit_chatlist_item_changed(context, msg.chat_id);
2066 }
2067 Ok(())
2068}
2069
2070pub(crate) async fn insert_tombstone(context: &Context, rfc724_mid: &str) -> Result<MsgId> {
2075 let row_id = context
2076 .sql
2077 .insert(
2078 "INSERT INTO msgs(rfc724_mid, chat_id) VALUES (?,?)",
2079 (rfc724_mid, DC_CHAT_ID_TRASH),
2080 )
2081 .await?;
2082 let msg_id = MsgId::new(u32::try_from(row_id)?);
2083 Ok(msg_id)
2084}
2085
2086pub async fn get_unblocked_msg_cnt(context: &Context) -> usize {
2088 match context
2089 .sql
2090 .count(
2091 "SELECT COUNT(*) \
2092 FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \
2093 WHERE m.id>9 AND m.chat_id>9 AND c.blocked=0;",
2094 (),
2095 )
2096 .await
2097 {
2098 Ok(res) => res,
2099 Err(err) => {
2100 error!(context, "get_unblocked_msg_cnt() failed. {:#}", err);
2101 0
2102 }
2103 }
2104}
2105
2106pub async fn get_request_msg_cnt(context: &Context) -> usize {
2108 match context
2109 .sql
2110 .count(
2111 "SELECT COUNT(*) \
2112 FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \
2113 WHERE c.blocked=2;",
2114 (),
2115 )
2116 .await
2117 {
2118 Ok(res) => res,
2119 Err(err) => {
2120 error!(context, "get_request_msg_cnt() failed. {:#}", err);
2121 0
2122 }
2123 }
2124}
2125
2126#[expect(clippy::arithmetic_side_effects)]
2140pub async fn estimate_deletion_cnt(
2141 context: &Context,
2142 from_server: bool,
2143 seconds: i64,
2144) -> Result<usize> {
2145 ensure!(
2146 !from_server,
2147 "The `delete_server_after` config option was removed. You need to pass `false` for `from_server`"
2148 );
2149
2150 let self_chat_id = ChatIdBlocked::lookup_by_contact(context, ContactId::SELF)
2151 .await?
2152 .map(|c| c.id)
2153 .unwrap_or_default();
2154 let threshold_timestamp = time() - seconds;
2155
2156 let cnt = context
2157 .sql
2158 .count(
2159 "SELECT COUNT(*)
2160 FROM msgs m
2161 WHERE m.id > ?
2162 AND timestamp < ?
2163 AND chat_id != ?
2164 AND chat_id != ? AND hidden = 0;",
2165 (
2166 DC_MSG_ID_LAST_SPECIAL,
2167 threshold_timestamp,
2168 self_chat_id,
2169 DC_CHAT_ID_TRASH,
2170 ),
2171 )
2172 .await?;
2173 Ok(cnt)
2174}
2175
2176pub(crate) async fn rfc724_mid_exists(
2178 context: &Context,
2179 rfc724_mid: &str,
2180) -> Result<Option<MsgId>> {
2181 Ok(rfc724_mid_exists_ex(context, rfc724_mid, "1")
2182 .await?
2183 .map(|(id, _)| id))
2184}
2185
2186pub(crate) async fn rfc724_mid_exists_ex(
2192 context: &Context,
2193 rfc724_mid: &str,
2194 expr: &str,
2195) -> Result<Option<(MsgId, bool)>> {
2196 let rfc724_mid = rfc724_mid.trim_start_matches('<').trim_end_matches('>');
2197 if rfc724_mid.is_empty() {
2198 warn!(context, "Empty rfc724_mid passed to rfc724_mid_exists");
2199 return Ok(None);
2200 }
2201
2202 let res = context
2203 .sql
2204 .query_row_optional(
2205 &("SELECT id, timestamp_sent, MIN(".to_string()
2206 + expr
2207 + ") FROM msgs WHERE rfc724_mid=?1 OR pre_rfc724_mid=?1
2208 HAVING COUNT(*) > 0 -- Prevent MIN(expr) from returning NULL when there are no rows.
2209 ORDER BY timestamp_sent DESC"),
2210 (rfc724_mid,),
2211 |row| {
2212 let msg_id: MsgId = row.get(0)?;
2213 let expr_res: bool = row.get(2)?;
2214 Ok((msg_id, expr_res))
2215 },
2216 )
2217 .await?;
2218
2219 Ok(res)
2220}
2221
2222pub(crate) async fn rfc724_mid_download_tried(context: &Context, rfc724_mid: &str) -> Result<bool> {
2227 let rfc724_mid = rfc724_mid.trim_start_matches('<').trim_end_matches('>');
2228 if rfc724_mid.is_empty() {
2229 warn!(
2230 context,
2231 "Empty rfc724_mid passed to rfc724_mid_download_tried"
2232 );
2233 return Ok(false);
2234 }
2235
2236 let res = context
2237 .sql
2238 .exists(
2239 "SELECT COUNT(*) FROM msgs
2240 WHERE rfc724_mid=? AND download_state<>?",
2241 (rfc724_mid, DownloadState::Available),
2242 )
2243 .await?;
2244
2245 Ok(res)
2246}
2247
2248pub(crate) async fn get_by_rfc724_mids(
2255 context: &Context,
2256 mids: &[String],
2257) -> Result<Option<Message>> {
2258 let mut latest = None;
2259 for id in mids.iter().rev() {
2260 let Some(msg_id) = rfc724_mid_exists(context, id).await? else {
2261 continue;
2262 };
2263 let Some(msg) = Message::load_from_db_optional(context, msg_id).await? else {
2264 continue;
2265 };
2266 if msg.download_state == DownloadState::Done {
2267 return Ok(Some(msg));
2268 }
2269 latest.get_or_insert(msg);
2270 }
2271 Ok(latest)
2272}
2273
2274pub(crate) fn get_vcard_summary(vcard: &[u8]) -> Option<String> {
2276 let vcard = str::from_utf8(vcard).ok()?;
2277 let contacts = deltachat_contact_tools::parse_vcard(vcard);
2278 let [c] = &contacts[..] else {
2279 return None;
2280 };
2281 if !deltachat_contact_tools::may_be_valid_addr(&c.addr) {
2282 return None;
2283 }
2284 Some(c.display_name().to_string())
2285}
2286
2287#[derive(
2289 Debug,
2290 Default,
2291 Display,
2292 Clone,
2293 Copy,
2294 PartialEq,
2295 Eq,
2296 FromPrimitive,
2297 ToPrimitive,
2298 FromSql,
2299 ToSql,
2300 Serialize,
2301 Deserialize,
2302)]
2303#[repr(u32)]
2304pub enum Viewtype {
2305 #[default]
2307 Unknown = 0,
2308
2309 Text = 10,
2312
2313 Image = 20,
2319
2320 Gif = 21,
2324
2325 Sticker = 23,
2330
2331 Audio = 40,
2335
2336 Voice = 41,
2341
2342 Video = 50,
2349
2350 File = 60,
2354
2355 Call = 71,
2357
2358 Webxdc = 80,
2360
2361 Vcard = 90,
2365}
2366
2367impl Viewtype {
2368 pub fn has_file(&self) -> bool {
2370 match self {
2371 Viewtype::Unknown => false,
2372 Viewtype::Text => false,
2373 Viewtype::Image => true,
2374 Viewtype::Gif => true,
2375 Viewtype::Sticker => true,
2376 Viewtype::Audio => true,
2377 Viewtype::Voice => true,
2378 Viewtype::Video => true,
2379 Viewtype::File => true,
2380 Viewtype::Call => false,
2381 Viewtype::Webxdc => true,
2382 Viewtype::Vcard => true,
2383 }
2384 }
2385}
2386
2387#[cfg(test)]
2388mod message_tests;