1use std::collections::BTreeSet;
4use std::collections::HashSet;
5use std::path::{Path, PathBuf};
6use std::str;
7
8use anyhow::{Context as _, Result, ensure, format_err};
9use deltachat_contact_tools::{VcardContact, parse_vcard};
10use deltachat_derive::{FromSql, ToSql};
11use serde::{Deserialize, Serialize};
12use tokio::{fs, io};
13
14use crate::blob::BlobObject;
15use crate::chat::{Chat, ChatId, ChatIdBlocked, ChatVisibility, send_msg};
16use crate::chatlist_events;
17use crate::config::Config;
18use crate::constants::{Blocked, Chattype, DC_CHAT_ID_TRASH, DC_MSG_ID_LAST_SPECIAL};
19use crate::contact::{self, Contact, ContactId};
20use crate::context::Context;
21use crate::debug_logging::set_debug_logging_xdc;
22use crate::download::DownloadState;
23use crate::ephemeral::{Timer as EphemeralTimer, start_ephemeral_timers_msgids};
24use crate::events::EventType;
25use crate::imap::markseen_on_imap_table;
26use crate::location::delete_poi_location;
27use crate::log::{error, info, warn};
28use crate::mimeparser::{SystemMessage, parse_message_id};
29use crate::param::{Param, Params};
30use crate::pgp::split_armored_data;
31use crate::reaction::get_msg_reactions;
32use crate::sql;
33use crate::summary::Summary;
34use crate::sync::SyncData;
35use crate::tools::create_outgoing_rfc724_mid;
36use crate::tools::{
37 buf_compress, buf_decompress, get_filebytes, get_filemeta, gm2local_offset, read_file,
38 sanitize_filename, time, timestamp_to_str,
39};
40
41#[derive(
47 Debug, Copy, Clone, Default, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize,
48)]
49pub struct MsgId(u32);
50
51impl MsgId {
52 pub fn new(id: u32) -> MsgId {
54 MsgId(id)
55 }
56
57 pub fn new_unset() -> MsgId {
59 MsgId(0)
60 }
61
62 pub fn is_special(self) -> bool {
66 self.0 <= DC_MSG_ID_LAST_SPECIAL
67 }
68
69 pub fn is_unset(self) -> bool {
79 self.0 == 0
80 }
81
82 pub async fn get_state(self, context: &Context) -> Result<MessageState> {
84 let result = context
85 .sql
86 .query_row_optional(
87 concat!(
88 "SELECT m.state, mdns.msg_id",
89 " FROM msgs m LEFT JOIN msgs_mdns mdns ON mdns.msg_id=m.id",
90 " WHERE id=?",
91 " LIMIT 1",
92 ),
93 (self,),
94 |row| {
95 let state: MessageState = row.get(0)?;
96 let mdn_msg_id: Option<MsgId> = row.get(1)?;
97 Ok(state.with_mdns(mdn_msg_id.is_some()))
98 },
99 )
100 .await?
101 .unwrap_or_default();
102 Ok(result)
103 }
104
105 pub(crate) async fn get_param(self, context: &Context) -> Result<Params> {
106 let res: Option<String> = context
107 .sql
108 .query_get_value("SELECT param FROM msgs WHERE id=?", (self,))
109 .await?;
110 Ok(res
111 .map(|s| s.parse().unwrap_or_default())
112 .unwrap_or_default())
113 }
114
115 pub(crate) async fn trash(self, context: &Context, on_server: bool) -> Result<()> {
127 context
128 .sql
129 .execute(
130 "INSERT OR REPLACE INTO msgs (id, rfc724_mid, timestamp, chat_id, deleted)
134 SELECT ?1, rfc724_mid, timestamp, ?, ? FROM msgs WHERE id=?1",
135 (self, DC_CHAT_ID_TRASH, on_server),
136 )
137 .await?;
138
139 Ok(())
140 }
141
142 pub(crate) async fn set_delivered(self, context: &Context) -> Result<()> {
143 update_msg_state(context, self, MessageState::OutDelivered).await?;
144 let chat_id: Option<ChatId> = context
145 .sql
146 .query_get_value("SELECT chat_id FROM msgs WHERE id=?", (self,))
147 .await?;
148 context.emit_event(EventType::MsgDelivered {
149 chat_id: chat_id.unwrap_or_default(),
150 msg_id: self,
151 });
152 if let Some(chat_id) = chat_id {
153 chatlist_events::emit_chatlist_item_changed(context, chat_id);
154 }
155 Ok(())
156 }
157
158 pub fn to_u32(self) -> u32 {
163 self.0
164 }
165
166 pub async fn get_info_server_urls(
168 context: &Context,
169 rfc724_mid: String,
170 ) -> Result<Vec<String>> {
171 context
172 .sql
173 .query_map(
174 "SELECT folder, uid FROM imap WHERE rfc724_mid=?",
175 (rfc724_mid,),
176 |row| {
177 let folder: String = row.get("folder")?;
178 let uid: u32 = row.get("uid")?;
179 Ok(format!("</{folder}/;UID={uid}>"))
180 },
181 |rows| {
182 rows.collect::<std::result::Result<Vec<_>, _>>()
183 .map_err(Into::into)
184 },
185 )
186 .await
187 }
188
189 pub async fn hop_info(self, context: &Context) -> Result<String> {
191 let hop_info = context
192 .sql
193 .query_get_value("SELECT IFNULL(hop_info, '') FROM msgs WHERE id=?", (self,))
194 .await?
195 .with_context(|| format!("Message {self} not found"))?;
196 Ok(hop_info)
197 }
198
199 pub async fn get_info(self, context: &Context) -> Result<String> {
201 let msg = Message::load_from_db(context, self).await?;
202
203 let mut ret = String::new();
204
205 let fts = timestamp_to_str(msg.get_timestamp());
206 ret += &format!("Sent: {fts}");
207
208 let from_contact = Contact::get_by_id(context, msg.from_id).await?;
209 let name = from_contact.get_name_n_addr();
210 if let Some(override_sender_name) = msg.get_override_sender_name() {
211 let addr = from_contact.get_addr();
212 ret += &format!(" by ~{override_sender_name} ({addr})");
213 } else {
214 ret += &format!(" by {name}");
215 }
216 ret += "\n";
217
218 if msg.from_id != ContactId::SELF {
219 let s = timestamp_to_str(if 0 != msg.timestamp_rcvd {
220 msg.timestamp_rcvd
221 } else {
222 msg.timestamp_sort
223 });
224 ret += &format!("Received: {}", &s);
225 ret += "\n";
226 }
227
228 if let EphemeralTimer::Enabled { duration } = msg.ephemeral_timer {
229 ret += &format!("Ephemeral timer: {duration}\n");
230 }
231
232 if msg.ephemeral_timestamp != 0 {
233 ret += &format!("Expires: {}\n", timestamp_to_str(msg.ephemeral_timestamp));
234 }
235
236 if msg.from_id == ContactId::INFO || msg.to_id == ContactId::INFO {
237 return Ok(ret);
239 }
240
241 if let Ok(rows) = context
242 .sql
243 .query_map(
244 "SELECT contact_id, timestamp_sent FROM msgs_mdns WHERE msg_id=?",
245 (self,),
246 |row| {
247 let contact_id: ContactId = row.get(0)?;
248 let ts: i64 = row.get(1)?;
249 Ok((contact_id, ts))
250 },
251 |rows| rows.collect::<Result<Vec<_>, _>>().map_err(Into::into),
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_name_n_addr())
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)]
390#[repr(u8)]
391pub(crate) enum MessengerMessage {
392 No = 0,
393 Yes = 1,
394
395 Reply = 2,
397}
398
399impl Default for MessengerMessage {
400 fn default() -> Self {
401 Self::No
402 }
403}
404
405#[derive(Debug, Clone, Default, Serialize, Deserialize)]
409pub struct Message {
410 pub(crate) id: MsgId,
412
413 pub(crate) from_id: ContactId,
415
416 pub(crate) to_id: ContactId,
418
419 pub(crate) chat_id: ChatId,
421
422 pub(crate) viewtype: Viewtype,
424
425 pub(crate) state: MessageState,
427 pub(crate) download_state: DownloadState,
428
429 pub(crate) hidden: bool,
431 pub(crate) timestamp_sort: i64,
432 pub(crate) timestamp_sent: i64,
433 pub(crate) timestamp_rcvd: i64,
434 pub(crate) ephemeral_timer: EphemeralTimer,
435 pub(crate) ephemeral_timestamp: i64,
436 pub(crate) text: String,
437
438 pub(crate) subject: String,
442
443 pub(crate) rfc724_mid: String,
445
446 pub(crate) in_reply_to: Option<String>,
448 pub(crate) is_dc_message: MessengerMessage,
449 pub(crate) original_msg_id: MsgId,
450 pub(crate) mime_modified: bool,
451 pub(crate) chat_blocked: Blocked,
452 pub(crate) location_id: u32,
453 pub(crate) error: Option<String>,
454 pub(crate) param: Params,
455}
456
457impl Message {
458 pub fn new(viewtype: Viewtype) -> Self {
460 Message {
461 viewtype,
462 rfc724_mid: create_outgoing_rfc724_mid(),
463 ..Default::default()
464 }
465 }
466
467 pub fn new_text(text: String) -> Self {
469 Message {
470 viewtype: Viewtype::Text,
471 text,
472 rfc724_mid: create_outgoing_rfc724_mid(),
473 ..Default::default()
474 }
475 }
476
477 pub async fn load_from_db(context: &Context, id: MsgId) -> Result<Message> {
481 let message = Self::load_from_db_optional(context, id)
482 .await?
483 .with_context(|| format!("Message {id} does not exist"))?;
484 Ok(message)
485 }
486
487 pub async fn load_from_db_optional(context: &Context, id: MsgId) -> Result<Option<Message>> {
491 ensure!(
492 !id.is_special(),
493 "Can not load special message ID {} from DB",
494 id
495 );
496 let msg = context
497 .sql
498 .query_row_optional(
499 concat!(
500 "SELECT",
501 " m.id AS id,",
502 " rfc724_mid AS rfc724mid,",
503 " m.mime_in_reply_to AS mime_in_reply_to,",
504 " m.chat_id AS chat_id,",
505 " m.from_id AS from_id,",
506 " m.to_id AS to_id,",
507 " m.timestamp AS timestamp,",
508 " m.timestamp_sent AS timestamp_sent,",
509 " m.timestamp_rcvd AS timestamp_rcvd,",
510 " m.ephemeral_timer AS ephemeral_timer,",
511 " m.ephemeral_timestamp AS ephemeral_timestamp,",
512 " m.type AS type,",
513 " m.state AS state,",
514 " mdns.msg_id AS mdn_msg_id,",
515 " m.download_state AS download_state,",
516 " m.error AS error,",
517 " m.msgrmsg AS msgrmsg,",
518 " m.starred AS original_msg_id,",
519 " m.mime_modified AS mime_modified,",
520 " m.txt AS txt,",
521 " m.subject AS subject,",
522 " m.param AS param,",
523 " m.hidden AS hidden,",
524 " m.location_id AS location,",
525 " c.blocked AS blocked",
526 " FROM msgs m",
527 " LEFT JOIN chats c ON c.id=m.chat_id",
528 " LEFT JOIN msgs_mdns mdns ON mdns.msg_id=m.id",
529 " WHERE m.id=? AND chat_id!=3",
530 " LIMIT 1",
531 ),
532 (id,),
533 |row| {
534 let state: MessageState = row.get("state")?;
535 let mdn_msg_id: Option<MsgId> = row.get("mdn_msg_id")?;
536 let text = match row.get_ref("txt")? {
537 rusqlite::types::ValueRef::Text(buf) => {
538 match String::from_utf8(buf.to_vec()) {
539 Ok(t) => t,
540 Err(_) => {
541 warn!(
542 context,
543 concat!(
544 "dc_msg_load_from_db: could not get ",
545 "text column as non-lossy utf8 id {}"
546 ),
547 id
548 );
549 String::from_utf8_lossy(buf).into_owned()
550 }
551 }
552 }
553 _ => String::new(),
554 };
555 let msg = Message {
556 id: row.get("id")?,
557 rfc724_mid: row.get::<_, String>("rfc724mid")?,
558 in_reply_to: row
559 .get::<_, Option<String>>("mime_in_reply_to")?
560 .and_then(|in_reply_to| parse_message_id(&in_reply_to).ok()),
561 chat_id: row.get("chat_id")?,
562 from_id: row.get("from_id")?,
563 to_id: row.get("to_id")?,
564 timestamp_sort: row.get("timestamp")?,
565 timestamp_sent: row.get("timestamp_sent")?,
566 timestamp_rcvd: row.get("timestamp_rcvd")?,
567 ephemeral_timer: row.get("ephemeral_timer")?,
568 ephemeral_timestamp: row.get("ephemeral_timestamp")?,
569 viewtype: row.get("type").unwrap_or_default(),
570 state: state.with_mdns(mdn_msg_id.is_some()),
571 download_state: row.get("download_state")?,
572 error: Some(row.get::<_, String>("error")?)
573 .filter(|error| !error.is_empty()),
574 is_dc_message: row.get("msgrmsg")?,
575 original_msg_id: row.get("original_msg_id")?,
576 mime_modified: row.get("mime_modified")?,
577 text,
578 subject: row.get("subject")?,
579 param: row.get::<_, String>("param")?.parse().unwrap_or_default(),
580 hidden: row.get("hidden")?,
581 location_id: row.get("location")?,
582 chat_blocked: row
583 .get::<_, Option<Blocked>>("blocked")?
584 .unwrap_or_default(),
585 };
586 Ok(msg)
587 },
588 )
589 .await
590 .with_context(|| format!("failed to load message {id} from the database"))?;
591
592 Ok(msg)
593 }
594
595 pub fn get_filemime(&self) -> Option<String> {
602 if let Some(m) = self.param.get(Param::MimeType) {
603 return Some(m.to_string());
604 } else if self.param.exists(Param::File) {
605 if let Some((_, mime)) = guess_msgtype_from_suffix(self) {
606 return Some(mime.to_string());
607 }
608 return Some("application/octet-stream".to_string());
610 }
611 None
613 }
614
615 pub fn get_file(&self, context: &Context) -> Option<PathBuf> {
617 self.param.get_file_path(context).unwrap_or(None)
618 }
619
620 pub async fn vcard_contacts(&self, context: &Context) -> Result<Vec<VcardContact>> {
622 if self.viewtype != Viewtype::Vcard {
623 return Ok(Vec::new());
624 }
625
626 let path = self
627 .get_file(context)
628 .context("vCard message does not have an attachment")?;
629 let bytes = tokio::fs::read(path).await?;
630 let vcard_contents = std::str::from_utf8(&bytes).context("vCard is not a valid UTF-8")?;
631 Ok(parse_vcard(vcard_contents))
632 }
633
634 pub async fn save_file(&self, context: &Context, path: &Path) -> Result<()> {
636 let path_src = self.get_file(context).context("No file")?;
637 let mut src = fs::OpenOptions::new().read(true).open(path_src).await?;
638 let mut dst = fs::OpenOptions::new()
639 .write(true)
640 .create_new(true)
641 .open(path)
642 .await?;
643 io::copy(&mut src, &mut dst).await?;
644 Ok(())
645 }
646
647 pub(crate) async fn try_calc_and_set_dimensions(&mut self, context: &Context) -> Result<()> {
649 if self.viewtype.has_file() {
650 let file_param = self.param.get_file_path(context)?;
651 if let Some(path_and_filename) = file_param {
652 if matches!(
653 self.viewtype,
654 Viewtype::Image | Viewtype::Gif | Viewtype::Sticker
655 ) && !self.param.exists(Param::Width)
656 {
657 let buf = read_file(context, &path_and_filename).await?;
658
659 match get_filemeta(&buf) {
660 Ok((width, height)) => {
661 self.param.set_int(Param::Width, width as i32);
662 self.param.set_int(Param::Height, height as i32);
663 }
664 Err(err) => {
665 self.param.set_int(Param::Width, 0);
666 self.param.set_int(Param::Height, 0);
667 warn!(
668 context,
669 "Failed to get width and height for {}: {err:#}.",
670 path_and_filename.display()
671 );
672 }
673 }
674
675 if !self.id.is_unset() {
676 self.update_param(context).await?;
677 }
678 }
679 }
680 }
681 Ok(())
682 }
683
684 pub fn has_location(&self) -> bool {
690 self.location_id != 0
691 }
692
693 pub fn set_location(&mut self, latitude: f64, longitude: f64) {
710 if latitude == 0.0 && longitude == 0.0 {
711 return;
712 }
713
714 self.param.set_float(Param::SetLatitude, latitude);
715 self.param.set_float(Param::SetLongitude, longitude);
716 }
717
718 pub fn get_timestamp(&self) -> i64 {
721 if 0 != self.timestamp_sent {
722 self.timestamp_sent
723 } else {
724 self.timestamp_sort
725 }
726 }
727
728 pub fn get_id(&self) -> MsgId {
730 self.id
731 }
732
733 pub fn rfc724_mid(&self) -> &str {
736 &self.rfc724_mid
737 }
738
739 pub fn get_from_id(&self) -> ContactId {
741 self.from_id
742 }
743
744 pub fn get_chat_id(&self) -> ChatId {
746 self.chat_id
747 }
748
749 pub fn get_viewtype(&self) -> Viewtype {
751 self.viewtype
752 }
753
754 pub fn force_sticker(&mut self) {
757 self.param.set_int(Param::ForceSticker, 1);
758 }
759
760 pub fn get_state(&self) -> MessageState {
762 self.state
763 }
764
765 pub fn get_received_timestamp(&self) -> i64 {
767 self.timestamp_rcvd
768 }
769
770 pub fn get_sort_timestamp(&self) -> i64 {
772 self.timestamp_sort
773 }
774
775 pub fn get_text(&self) -> String {
777 self.text.clone()
778 }
779
780 pub fn get_subject(&self) -> &str {
782 &self.subject
783 }
784
785 pub fn get_filename(&self) -> Option<String> {
789 if let Some(name) = self.param.get(Param::Filename) {
790 return Some(sanitize_filename(name));
791 }
792 self.param
793 .get(Param::File)
794 .and_then(|file| Path::new(file).file_name())
795 .map(|name| sanitize_filename(&name.to_string_lossy()))
796 }
797
798 pub async fn get_filebytes(&self, context: &Context) -> Result<Option<u64>> {
800 if let Some(path) = self.param.get_file_path(context)? {
801 Ok(Some(get_filebytes(context, &path).await.with_context(
802 || format!("failed to get {} size in bytes", path.display()),
803 )?))
804 } else {
805 Ok(None)
806 }
807 }
808
809 pub fn get_width(&self) -> i32 {
811 self.param.get_int(Param::Width).unwrap_or_default()
812 }
813
814 pub fn get_height(&self) -> i32 {
816 self.param.get_int(Param::Height).unwrap_or_default()
817 }
818
819 pub fn get_duration(&self) -> i32 {
821 self.param.get_int(Param::Duration).unwrap_or_default()
822 }
823
824 pub fn get_showpadlock(&self) -> bool {
826 self.param.get_int(Param::GuaranteeE2ee).unwrap_or_default() != 0
827 || self.from_id == ContactId::DEVICE
828 }
829
830 pub fn is_bot(&self) -> bool {
832 self.param.get_bool(Param::Bot).unwrap_or_default()
833 }
834
835 pub fn get_ephemeral_timer(&self) -> EphemeralTimer {
837 self.ephemeral_timer
838 }
839
840 pub fn get_ephemeral_timestamp(&self) -> i64 {
842 self.ephemeral_timestamp
843 }
844
845 pub async fn get_summary(&self, context: &Context, chat: Option<&Chat>) -> Result<Summary> {
847 let chat_loaded: Chat;
848 let chat = if let Some(chat) = chat {
849 chat
850 } else {
851 let chat = Chat::load_from_db(context, self.chat_id).await?;
852 chat_loaded = chat;
853 &chat_loaded
854 };
855
856 let contact = if self.from_id != ContactId::SELF {
857 match chat.typ {
858 Chattype::Group
859 | Chattype::OutBroadcast
860 | Chattype::InBroadcast
861 | Chattype::Mailinglist => Some(Contact::get_by_id(context, self.from_id).await?),
862 Chattype::Single => None,
863 }
864 } else {
865 None
866 };
867
868 Summary::new(context, self, chat, contact.as_ref()).await
869 }
870
871 pub fn get_override_sender_name(&self) -> Option<String> {
881 self.param
882 .get(Param::OverrideSenderDisplayname)
883 .map(|name| name.to_string())
884 }
885
886 pub(crate) fn get_sender_name(&self, contact: &Contact) -> String {
889 self.get_override_sender_name()
890 .unwrap_or_else(|| contact.get_display_name().to_string())
891 }
892
893 pub fn has_deviating_timestamp(&self) -> bool {
898 let cnv_to_local = gm2local_offset();
899 let sort_timestamp = self.get_sort_timestamp() + cnv_to_local;
900 let send_timestamp = self.get_timestamp() + cnv_to_local;
901
902 sort_timestamp / 86400 != send_timestamp / 86400
903 }
904
905 pub fn is_sent(&self) -> bool {
908 self.state >= MessageState::OutDelivered
909 }
910
911 pub fn is_forwarded(&self) -> bool {
913 0 != self.param.get_int(Param::Forwarded).unwrap_or_default()
914 }
915
916 pub fn is_edited(&self) -> bool {
918 self.param.get_bool(Param::IsEdited).unwrap_or_default()
919 }
920
921 pub fn is_info(&self) -> bool {
923 let cmd = self.param.get_cmd();
924 self.from_id == ContactId::INFO
925 || self.to_id == ContactId::INFO
926 || cmd != SystemMessage::Unknown && cmd != SystemMessage::AutocryptSetupMessage
927 }
928
929 pub fn get_info_type(&self) -> SystemMessage {
931 self.param.get_cmd()
932 }
933
934 pub async fn get_info_contact_id(&self, context: &Context) -> Result<Option<ContactId>> {
936 match self.param.get_cmd() {
937 SystemMessage::GroupNameChanged
938 | SystemMessage::GroupImageChanged
939 | SystemMessage::EphemeralTimerChanged => {
940 if self.from_id != ContactId::INFO {
941 Ok(Some(self.from_id))
942 } else {
943 Ok(None)
944 }
945 }
946
947 SystemMessage::MemberAddedToGroup | SystemMessage::MemberRemovedFromGroup => {
948 if let Some(contact_i32) = self.param.get_int(Param::ContactAddedRemoved) {
949 let contact_id = ContactId::new(contact_i32.try_into()?);
950 if contact_id == ContactId::SELF
951 || Contact::real_exists_by_id(context, contact_id).await?
952 {
953 Ok(Some(contact_id))
954 } else {
955 Ok(None)
956 }
957 } else {
958 Ok(None)
959 }
960 }
961
962 SystemMessage::AutocryptSetupMessage
963 | SystemMessage::SecurejoinMessage
964 | SystemMessage::LocationStreamingEnabled
965 | SystemMessage::LocationOnly
966 | SystemMessage::ChatE2ee
967 | SystemMessage::ChatProtectionEnabled
968 | SystemMessage::ChatProtectionDisabled
969 | SystemMessage::InvalidUnencryptedMail
970 | SystemMessage::SecurejoinWait
971 | SystemMessage::SecurejoinWaitTimeout
972 | SystemMessage::MultiDeviceSync
973 | SystemMessage::WebxdcStatusUpdate
974 | SystemMessage::WebxdcInfoMessage
975 | SystemMessage::IrohNodeAddr
976 | SystemMessage::CallAccepted
977 | SystemMessage::CallEnded
978 | SystemMessage::Unknown => Ok(None),
979 }
980 }
981
982 pub fn is_system_message(&self) -> bool {
984 let cmd = self.param.get_cmd();
985 cmd != SystemMessage::Unknown
986 }
987
988 pub fn is_setupmessage(&self) -> bool {
990 if self.viewtype != Viewtype::File {
991 return false;
992 }
993
994 self.param.get_cmd() == SystemMessage::AutocryptSetupMessage
995 }
996
997 pub async fn get_setupcodebegin(&self, context: &Context) -> Option<String> {
1001 if !self.is_setupmessage() {
1002 return None;
1003 }
1004
1005 if let Some(filename) = self.get_file(context) {
1006 if let Ok(ref buf) = read_file(context, &filename).await {
1007 if let Ok((typ, headers, _)) = split_armored_data(buf) {
1008 if typ == pgp::armor::BlockType::Message {
1009 return headers.get(crate::pgp::HEADER_SETUPCODE).cloned();
1010 }
1011 }
1012 }
1013 }
1014
1015 None
1016 }
1017
1018 pub fn set_text(&mut self, text: String) {
1020 self.text = text;
1021 }
1022
1023 pub fn set_subject(&mut self, subject: String) {
1026 self.subject = subject;
1027 }
1028
1029 pub fn set_file_and_deduplicate(
1044 &mut self,
1045 context: &Context,
1046 file: &Path,
1047 name: Option<&str>,
1048 filemime: Option<&str>,
1049 ) -> Result<()> {
1050 let name = if let Some(name) = name {
1051 name.to_string()
1052 } else {
1053 file.file_name()
1054 .map(|s| s.to_string_lossy().to_string())
1055 .unwrap_or_else(|| "unknown_file".to_string())
1056 };
1057
1058 let blob = BlobObject::create_and_deduplicate(context, file, Path::new(&name))?;
1059 self.param.set(Param::File, blob.as_name());
1060
1061 self.param.set(Param::Filename, name);
1062 self.param.set_optional(Param::MimeType, filemime);
1063
1064 Ok(())
1065 }
1066
1067 pub fn set_file_from_bytes(
1074 &mut self,
1075 context: &Context,
1076 name: &str,
1077 data: &[u8],
1078 filemime: Option<&str>,
1079 ) -> Result<()> {
1080 let blob = BlobObject::create_and_deduplicate_from_bytes(context, data, name)?;
1081 self.param.set(Param::Filename, name);
1082 self.param.set(Param::File, blob.as_name());
1083 self.param.set_optional(Param::MimeType, filemime);
1084
1085 Ok(())
1086 }
1087
1088 pub async fn make_vcard(&mut self, context: &Context, contacts: &[ContactId]) -> Result<()> {
1090 ensure!(
1091 matches!(self.viewtype, Viewtype::File | Viewtype::Vcard),
1092 "Wrong viewtype for vCard: {}",
1093 self.viewtype,
1094 );
1095 let vcard = contact::make_vcard(context, contacts).await?;
1096 self.set_file_from_bytes(context, "vcard.vcf", vcard.as_bytes(), None)
1097 }
1098
1099 pub(crate) async fn try_set_vcard(&mut self, context: &Context, path: &Path) -> Result<()> {
1101 let vcard = fs::read(path)
1102 .await
1103 .with_context(|| format!("Could not read {path:?}"))?;
1104 if let Some(summary) = get_vcard_summary(&vcard) {
1105 self.param.set(Param::Summary1, summary);
1106 } else {
1107 warn!(context, "try_set_vcard: Not a valid DeltaChat vCard.");
1108 self.viewtype = Viewtype::File;
1109 }
1110 Ok(())
1111 }
1112
1113 pub fn set_override_sender_name(&mut self, name: Option<String>) {
1116 self.param
1117 .set_optional(Param::OverrideSenderDisplayname, name);
1118 }
1119
1120 pub fn set_dimension(&mut self, width: i32, height: i32) {
1122 self.param.set_int(Param::Width, width);
1123 self.param.set_int(Param::Height, height);
1124 }
1125
1126 pub fn set_duration(&mut self, duration: i32) {
1128 self.param.set_int(Param::Duration, duration);
1129 }
1130
1131 pub(crate) fn set_reaction(&mut self) {
1133 self.param.set_int(Param::Reaction, 1);
1134 }
1135
1136 pub async fn latefiling_mediasize(
1139 &mut self,
1140 context: &Context,
1141 width: i32,
1142 height: i32,
1143 duration: i32,
1144 ) -> Result<()> {
1145 if width > 0 && height > 0 {
1146 self.param.set_int(Param::Width, width);
1147 self.param.set_int(Param::Height, height);
1148 }
1149 if duration > 0 {
1150 self.param.set_int(Param::Duration, duration);
1151 }
1152 self.update_param(context).await?;
1153 Ok(())
1154 }
1155
1156 pub fn set_quote_text(&mut self, text: Option<(String, bool)>) {
1162 let Some((text, protect)) = text else {
1163 self.param.remove(Param::Quote);
1164 self.param.remove(Param::ProtectQuote);
1165 return;
1166 };
1167 self.param.set(Param::Quote, text);
1168 self.param.set_optional(
1169 Param::ProtectQuote,
1170 match protect {
1171 true => Some("1"),
1172 false => None,
1173 },
1174 );
1175 }
1176
1177 pub async fn set_quote(&mut self, context: &Context, quote: Option<&Message>) -> Result<()> {
1186 if let Some(quote) = quote {
1187 ensure!(
1188 !quote.rfc724_mid.is_empty(),
1189 "Message without Message-Id cannot be quoted"
1190 );
1191 self.in_reply_to = Some(quote.rfc724_mid.clone());
1192
1193 let text = quote.get_text();
1194 let text = if text.is_empty() {
1195 quote
1197 .get_summary(context, None)
1198 .await?
1199 .truncated_text(500)
1200 .to_string()
1201 } else {
1202 text
1203 };
1204 self.set_quote_text(Some((
1205 text,
1206 quote
1207 .param
1208 .get_bool(Param::GuaranteeE2ee)
1209 .unwrap_or_default(),
1210 )));
1211 } else {
1212 self.in_reply_to = None;
1213 self.set_quote_text(None);
1214 }
1215
1216 Ok(())
1217 }
1218
1219 pub fn quoted_text(&self) -> Option<String> {
1221 self.param.get(Param::Quote).map(|s| s.to_string())
1222 }
1223
1224 pub async fn quoted_message(&self, context: &Context) -> Result<Option<Message>> {
1226 if self.param.get(Param::Quote).is_some() && !self.is_forwarded() {
1227 return self.parent(context).await;
1228 }
1229 Ok(None)
1230 }
1231
1232 pub async fn parent(&self, context: &Context) -> Result<Option<Message>> {
1237 if let Some(in_reply_to) = &self.in_reply_to {
1238 if let Some((msg_id, _ts_sent)) = rfc724_mid_exists(context, in_reply_to).await? {
1239 let msg = Message::load_from_db_optional(context, msg_id).await?;
1240 return Ok(msg);
1241 }
1242 }
1243 Ok(None)
1244 }
1245
1246 pub async fn get_original_msg_id(&self, context: &Context) -> Result<Option<MsgId>> {
1248 if !self.original_msg_id.is_special() {
1249 if let Some(msg) = Message::load_from_db_optional(context, self.original_msg_id).await?
1250 {
1251 return if msg.chat_id.is_trash() {
1252 Ok(None)
1253 } else {
1254 Ok(Some(msg.id))
1255 };
1256 }
1257 }
1258 Ok(None)
1259 }
1260
1261 pub async fn get_saved_msg_id(&self, context: &Context) -> Result<Option<MsgId>> {
1265 let res: Option<MsgId> = context
1266 .sql
1267 .query_get_value(
1268 "SELECT id FROM msgs WHERE starred=? AND chat_id!=?",
1269 (self.id, DC_CHAT_ID_TRASH),
1270 )
1271 .await?;
1272 Ok(res)
1273 }
1274
1275 pub fn force_plaintext(&mut self) {
1277 self.param.set_int(Param::ForcePlaintext, 1);
1278 }
1279
1280 pub async fn update_param(&self, context: &Context) -> Result<()> {
1282 context
1283 .sql
1284 .execute(
1285 "UPDATE msgs SET param=? WHERE id=?;",
1286 (self.param.to_string(), self.id),
1287 )
1288 .await?;
1289 Ok(())
1290 }
1291
1292 pub fn error(&self) -> Option<String> {
1305 self.error.clone()
1306 }
1307}
1308
1309#[derive(
1313 Debug,
1314 Default,
1315 Clone,
1316 Copy,
1317 PartialEq,
1318 Eq,
1319 PartialOrd,
1320 Ord,
1321 FromPrimitive,
1322 ToPrimitive,
1323 ToSql,
1324 FromSql,
1325 Serialize,
1326 Deserialize,
1327)]
1328#[repr(u32)]
1329pub enum MessageState {
1330 #[default]
1332 Undefined = 0,
1333
1334 InFresh = 10,
1337
1338 InNoticed = 13,
1342
1343 InSeen = 16,
1346
1347 OutPreparing = 18,
1351
1352 OutDraft = 19,
1354
1355 OutPending = 20,
1359
1360 OutFailed = 24,
1363
1364 OutDelivered = 26,
1368
1369 OutMdnRcvd = 28,
1372}
1373
1374impl std::fmt::Display for MessageState {
1375 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
1376 write!(
1377 f,
1378 "{}",
1379 match self {
1380 Self::Undefined => "Undefined",
1381 Self::InFresh => "Fresh",
1382 Self::InNoticed => "Noticed",
1383 Self::InSeen => "Seen",
1384 Self::OutPreparing => "Preparing",
1385 Self::OutDraft => "Draft",
1386 Self::OutPending => "Pending",
1387 Self::OutFailed => "Failed",
1388 Self::OutDelivered => "Delivered",
1389 Self::OutMdnRcvd => "Read",
1390 }
1391 )
1392 }
1393}
1394
1395impl MessageState {
1396 pub fn can_fail(self) -> bool {
1398 use MessageState::*;
1399 matches!(
1400 self,
1401 OutPreparing | OutPending | OutDelivered | OutMdnRcvd )
1403 }
1404
1405 pub fn is_outgoing(self) -> bool {
1407 use MessageState::*;
1408 matches!(
1409 self,
1410 OutPreparing | OutDraft | OutPending | OutFailed | OutDelivered | OutMdnRcvd
1411 )
1412 }
1413
1414 pub(crate) fn with_mdns(self, has_mdns: bool) -> Self {
1416 if self == MessageState::OutDelivered && has_mdns {
1417 return MessageState::OutMdnRcvd;
1418 }
1419 self
1420 }
1421}
1422
1423pub async fn get_msg_read_receipts(
1425 context: &Context,
1426 msg_id: MsgId,
1427) -> Result<Vec<(ContactId, i64)>> {
1428 context
1429 .sql
1430 .query_map(
1431 "SELECT contact_id, timestamp_sent FROM msgs_mdns WHERE msg_id=?",
1432 (msg_id,),
1433 |row| {
1434 let contact_id: ContactId = row.get(0)?;
1435 let ts: i64 = row.get(1)?;
1436 Ok((contact_id, ts))
1437 },
1438 |rows| rows.collect::<Result<Vec<_>, _>>().map_err(Into::into),
1439 )
1440 .await
1441}
1442
1443pub(crate) fn guess_msgtype_from_suffix(msg: &Message) -> Option<(Viewtype, &'static str)> {
1444 msg.param
1445 .get(Param::Filename)
1446 .or_else(|| msg.param.get(Param::File))
1447 .and_then(|file| guess_msgtype_from_path_suffix(Path::new(file)))
1448}
1449
1450pub(crate) fn guess_msgtype_from_path_suffix(path: &Path) -> Option<(Viewtype, &'static str)> {
1451 let extension: &str = &path.extension()?.to_str()?.to_lowercase();
1452 let info = match extension {
1453 "3gp" => (Viewtype::Video, "video/3gpp"),
1458 "aac" => (Viewtype::Audio, "audio/aac"),
1459 "avi" => (Viewtype::Video, "video/x-msvideo"),
1460 "avif" => (Viewtype::File, "image/avif"), "doc" => (Viewtype::File, "application/msword"),
1462 "docx" => (
1463 Viewtype::File,
1464 "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
1465 ),
1466 "epub" => (Viewtype::File, "application/epub+zip"),
1467 "flac" => (Viewtype::Audio, "audio/flac"),
1468 "gif" => (Viewtype::Gif, "image/gif"),
1469 "heic" => (Viewtype::File, "image/heic"), "heif" => (Viewtype::File, "image/heif"), "html" => (Viewtype::File, "text/html"),
1472 "htm" => (Viewtype::File, "text/html"),
1473 "ico" => (Viewtype::File, "image/vnd.microsoft.icon"),
1474 "jar" => (Viewtype::File, "application/java-archive"),
1475 "jpeg" => (Viewtype::Image, "image/jpeg"),
1476 "jpe" => (Viewtype::Image, "image/jpeg"),
1477 "jpg" => (Viewtype::Image, "image/jpeg"),
1478 "json" => (Viewtype::File, "application/json"),
1479 "mov" => (Viewtype::Video, "video/quicktime"),
1480 "m4a" => (Viewtype::Audio, "audio/m4a"),
1481 "mp3" => (Viewtype::Audio, "audio/mpeg"),
1482 "mp4" => (Viewtype::Video, "video/mp4"),
1483 "odp" => (
1484 Viewtype::File,
1485 "application/vnd.oasis.opendocument.presentation",
1486 ),
1487 "ods" => (
1488 Viewtype::File,
1489 "application/vnd.oasis.opendocument.spreadsheet",
1490 ),
1491 "odt" => (Viewtype::File, "application/vnd.oasis.opendocument.text"),
1492 "oga" => (Viewtype::Audio, "audio/ogg"),
1493 "ogg" => (Viewtype::Audio, "audio/ogg"),
1494 "ogv" => (Viewtype::File, "video/ogg"),
1495 "opus" => (Viewtype::File, "audio/ogg"), "otf" => (Viewtype::File, "font/otf"),
1497 "pdf" => (Viewtype::File, "application/pdf"),
1498 "png" => (Viewtype::Image, "image/png"),
1499 "ppt" => (Viewtype::File, "application/vnd.ms-powerpoint"),
1500 "pptx" => (
1501 Viewtype::File,
1502 "application/vnd.openxmlformats-officedocument.presentationml.presentation",
1503 ),
1504 "rar" => (Viewtype::File, "application/vnd.rar"),
1505 "rtf" => (Viewtype::File, "application/rtf"),
1506 "spx" => (Viewtype::File, "audio/ogg"), "svg" => (Viewtype::File, "image/svg+xml"),
1508 "tgs" => (Viewtype::File, "application/x-tgsticker"),
1509 "tiff" => (Viewtype::File, "image/tiff"),
1510 "tif" => (Viewtype::File, "image/tiff"),
1511 "ttf" => (Viewtype::File, "font/ttf"),
1512 "txt" => (Viewtype::File, "text/plain"),
1513 "vcard" => (Viewtype::Vcard, "text/vcard"),
1514 "vcf" => (Viewtype::Vcard, "text/vcard"),
1515 "wav" => (Viewtype::Audio, "audio/wav"),
1516 "weba" => (Viewtype::File, "audio/webm"),
1517 "webm" => (Viewtype::Video, "video/webm"),
1518 "webp" => (Viewtype::Image, "image/webp"), "wmv" => (Viewtype::Video, "video/x-ms-wmv"),
1520 "xdc" => (Viewtype::Webxdc, "application/webxdc+zip"),
1521 "xhtml" => (Viewtype::File, "application/xhtml+xml"),
1522 "xls" => (Viewtype::File, "application/vnd.ms-excel"),
1523 "xlsx" => (
1524 Viewtype::File,
1525 "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
1526 ),
1527 "xml" => (Viewtype::File, "application/xml"),
1528 "zip" => (Viewtype::File, "application/zip"),
1529 _ => {
1530 return None;
1531 }
1532 };
1533 Some(info)
1534}
1535
1536pub(crate) async fn get_mime_headers(context: &Context, msg_id: MsgId) -> Result<Vec<u8>> {
1543 let (headers, compressed) = context
1544 .sql
1545 .query_row(
1546 "SELECT mime_headers, mime_compressed FROM msgs WHERE id=?",
1547 (msg_id,),
1548 |row| {
1549 let headers = sql::row_get_vec(row, 0)?;
1550 let compressed: bool = row.get(1)?;
1551 Ok((headers, compressed))
1552 },
1553 )
1554 .await?;
1555 if compressed {
1556 return buf_decompress(&headers);
1557 }
1558
1559 let headers2 = headers.clone();
1560 let compressed = match tokio::task::block_in_place(move || buf_compress(&headers2)) {
1561 Err(e) => {
1562 warn!(context, "get_mime_headers: buf_compress() failed: {}", e);
1563 return Ok(headers);
1564 }
1565 Ok(o) => o,
1566 };
1567 let update = |conn: &mut rusqlite::Connection| {
1568 match conn.execute(
1569 "\
1570 UPDATE msgs SET mime_headers=?, mime_compressed=1 \
1571 WHERE id=? AND mime_headers!='' AND mime_compressed=0",
1572 (compressed, msg_id),
1573 ) {
1574 Ok(rows_updated) => ensure!(rows_updated <= 1),
1575 Err(e) => {
1576 warn!(context, "get_mime_headers: UPDATE failed: {}", e);
1577 return Err(e.into());
1578 }
1579 }
1580 Ok(())
1581 };
1582 if let Err(e) = context.sql.call_write(update).await {
1583 warn!(
1584 context,
1585 "get_mime_headers: failed to update mime_headers: {}", e
1586 );
1587 }
1588
1589 Ok(headers)
1590}
1591
1592pub(crate) async fn delete_msg_locally(context: &Context, msg: &Message) -> Result<()> {
1595 if msg.location_id > 0 {
1596 delete_poi_location(context, msg.location_id).await?;
1597 }
1598 let on_server = true;
1599 msg.id
1600 .trash(context, on_server)
1601 .await
1602 .with_context(|| format!("Unable to trash message {}", msg.id))?;
1603
1604 context.emit_event(EventType::MsgDeleted {
1605 chat_id: msg.chat_id,
1606 msg_id: msg.id,
1607 });
1608
1609 if msg.viewtype == Viewtype::Webxdc {
1610 context.emit_event(EventType::WebxdcInstanceDeleted { msg_id: msg.id });
1611 }
1612
1613 let logging_xdc_id = context
1614 .debug_logging
1615 .read()
1616 .expect("RwLock is poisoned")
1617 .as_ref()
1618 .map(|dl| dl.msg_id);
1619 if let Some(id) = logging_xdc_id {
1620 if id == msg.id {
1621 set_debug_logging_xdc(context, None).await?;
1622 }
1623 }
1624
1625 Ok(())
1626}
1627
1628pub(crate) async fn delete_msgs_locally_done(
1631 context: &Context,
1632 msg_ids: &[MsgId],
1633 modified_chat_ids: HashSet<ChatId>,
1634) -> Result<()> {
1635 for modified_chat_id in modified_chat_ids {
1636 context.emit_msgs_changed_without_msg_id(modified_chat_id);
1637 chatlist_events::emit_chatlist_item_changed(context, modified_chat_id);
1638 }
1639 if !msg_ids.is_empty() {
1640 context.emit_msgs_changed_without_ids();
1641 chatlist_events::emit_chatlist_changed(context);
1642 context
1644 .set_config_internal(Config::LastHousekeeping, None)
1645 .await?;
1646 }
1647 Ok(())
1648}
1649
1650pub async fn delete_msgs(context: &Context, msg_ids: &[MsgId]) -> Result<()> {
1652 delete_msgs_ex(context, msg_ids, false).await
1653}
1654
1655pub async fn delete_msgs_ex(
1659 context: &Context,
1660 msg_ids: &[MsgId],
1661 delete_for_all: bool,
1662) -> Result<()> {
1663 let mut modified_chat_ids = HashSet::new();
1664 let mut deleted_rfc724_mid = Vec::new();
1665 let mut res = Ok(());
1666
1667 for &msg_id in msg_ids {
1668 let msg = Message::load_from_db(context, msg_id).await?;
1669 ensure!(
1670 !delete_for_all || msg.from_id == ContactId::SELF,
1671 "Can delete only own messages for others"
1672 );
1673 ensure!(
1674 !delete_for_all || msg.get_showpadlock(),
1675 "Cannot request deletion of unencrypted message for others"
1676 );
1677
1678 modified_chat_ids.insert(msg.chat_id);
1679 deleted_rfc724_mid.push(msg.rfc724_mid.clone());
1680
1681 let target = context.get_delete_msgs_target().await?;
1682 let update_db = |trans: &mut rusqlite::Transaction| {
1683 trans.execute(
1684 "UPDATE imap SET target=? WHERE rfc724_mid=?",
1685 (target, msg.rfc724_mid),
1686 )?;
1687 trans.execute("DELETE FROM smtp WHERE msg_id=?", (msg_id,))?;
1688 Ok(())
1689 };
1690 if let Err(e) = context.sql.transaction(update_db).await {
1691 error!(context, "delete_msgs: failed to update db: {e:#}.");
1692 res = Err(e);
1693 continue;
1694 }
1695 }
1696 res?;
1697
1698 if delete_for_all {
1699 ensure!(
1700 modified_chat_ids.len() == 1,
1701 "Can delete only from same chat."
1702 );
1703 if let Some(chat_id) = modified_chat_ids.iter().next() {
1704 let mut msg = Message::new_text("🚮".to_owned());
1705 msg.param.set_int(Param::GuaranteeE2ee, 1);
1710 msg.param
1711 .set(Param::DeleteRequestFor, deleted_rfc724_mid.join(" "));
1712 msg.hidden = true;
1713 send_msg(context, *chat_id, &mut msg).await?;
1714 }
1715 } else {
1716 context
1717 .add_sync_item(SyncData::DeleteMessages {
1718 msgs: deleted_rfc724_mid,
1719 })
1720 .await?;
1721 }
1722
1723 for &msg_id in msg_ids {
1724 let msg = Message::load_from_db(context, msg_id).await?;
1725 delete_msg_locally(context, &msg).await?;
1726 }
1727 delete_msgs_locally_done(context, msg_ids, modified_chat_ids).await?;
1728
1729 context.scheduler.interrupt_inbox().await;
1731
1732 Ok(())
1733}
1734
1735pub async fn markseen_msgs(context: &Context, msg_ids: Vec<MsgId>) -> Result<()> {
1737 if msg_ids.is_empty() {
1738 return Ok(());
1739 }
1740
1741 let old_last_msg_id = MsgId::new(context.get_config_u32(Config::LastMsgId).await?);
1742 let last_msg_id = msg_ids.iter().fold(&old_last_msg_id, std::cmp::max);
1743 context
1744 .set_config_internal(Config::LastMsgId, Some(&last_msg_id.to_u32().to_string()))
1745 .await?;
1746
1747 let mut msgs = Vec::with_capacity(msg_ids.len());
1748 for &id in &msg_ids {
1749 if let Some(msg) = context
1750 .sql
1751 .query_row_optional(
1752 "SELECT
1753 m.chat_id AS chat_id,
1754 m.state AS state,
1755 m.download_state as download_state,
1756 m.ephemeral_timer AS ephemeral_timer,
1757 m.param AS param,
1758 m.from_id AS from_id,
1759 m.rfc724_mid AS rfc724_mid,
1760 c.archived AS archived,
1761 c.blocked AS blocked
1762 FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id
1763 WHERE m.id=? AND m.chat_id>9",
1764 (id,),
1765 |row| {
1766 let chat_id: ChatId = row.get("chat_id")?;
1767 let state: MessageState = row.get("state")?;
1768 let download_state: DownloadState = row.get("download_state")?;
1769 let param: Params = row.get::<_, String>("param")?.parse().unwrap_or_default();
1770 let from_id: ContactId = row.get("from_id")?;
1771 let rfc724_mid: String = row.get("rfc724_mid")?;
1772 let visibility: ChatVisibility = row.get("archived")?;
1773 let blocked: Option<Blocked> = row.get("blocked")?;
1774 let ephemeral_timer: EphemeralTimer = row.get("ephemeral_timer")?;
1775 Ok((
1776 (
1777 id,
1778 chat_id,
1779 state,
1780 download_state,
1781 param,
1782 from_id,
1783 rfc724_mid,
1784 visibility,
1785 blocked.unwrap_or_default(),
1786 ),
1787 ephemeral_timer,
1788 ))
1789 },
1790 )
1791 .await?
1792 {
1793 msgs.push(msg);
1794 }
1795 }
1796
1797 if msgs
1798 .iter()
1799 .any(|(_, ephemeral_timer)| *ephemeral_timer != EphemeralTimer::Disabled)
1800 {
1801 start_ephemeral_timers_msgids(context, &msg_ids)
1802 .await
1803 .context("failed to start ephemeral timers")?;
1804 }
1805
1806 let mut updated_chat_ids = BTreeSet::new();
1807 let mut archived_chats_maybe_noticed = false;
1808 for (
1809 (
1810 id,
1811 curr_chat_id,
1812 curr_state,
1813 curr_download_state,
1814 curr_param,
1815 curr_from_id,
1816 curr_rfc724_mid,
1817 curr_visibility,
1818 curr_blocked,
1819 ),
1820 _curr_ephemeral_timer,
1821 ) in msgs
1822 {
1823 if curr_download_state != DownloadState::Done {
1824 if curr_state == MessageState::InFresh {
1825 update_msg_state(context, id, MessageState::InNoticed).await?;
1828 updated_chat_ids.insert(curr_chat_id);
1829 }
1830 } else if curr_state == MessageState::InFresh || curr_state == MessageState::InNoticed {
1831 update_msg_state(context, id, MessageState::InSeen).await?;
1832 info!(context, "Seen message {}.", id);
1833
1834 markseen_on_imap_table(context, &curr_rfc724_mid).await?;
1835
1836 if curr_blocked == Blocked::Not
1846 && curr_param.get_bool(Param::WantsMdn).unwrap_or_default()
1847 && curr_param.get_cmd() == SystemMessage::Unknown
1848 && context.should_send_mdns().await?
1849 {
1850 context
1851 .sql
1852 .execute(
1853 "INSERT INTO smtp_mdns (msg_id, from_id, rfc724_mid) VALUES(?, ?, ?)",
1854 (id, curr_from_id, curr_rfc724_mid),
1855 )
1856 .await
1857 .context("failed to insert into smtp_mdns")?;
1858 context.scheduler.interrupt_smtp().await;
1859 }
1860 updated_chat_ids.insert(curr_chat_id);
1861 }
1862 archived_chats_maybe_noticed |=
1863 curr_state == MessageState::InFresh && curr_visibility == ChatVisibility::Archived;
1864 }
1865
1866 for updated_chat_id in updated_chat_ids {
1867 context.emit_event(EventType::MsgsNoticed(updated_chat_id));
1868 chatlist_events::emit_chatlist_item_changed(context, updated_chat_id);
1869 }
1870 if archived_chats_maybe_noticed {
1871 context.on_archived_chats_maybe_noticed();
1872 }
1873
1874 Ok(())
1875}
1876
1877pub(crate) async fn update_msg_state(
1878 context: &Context,
1879 msg_id: MsgId,
1880 state: MessageState,
1881) -> Result<()> {
1882 ensure!(
1883 state != MessageState::OutMdnRcvd,
1884 "Update msgs_mdns table instead!"
1885 );
1886 ensure!(state != MessageState::OutFailed, "use set_msg_failed()!");
1887 let error_subst = match state >= MessageState::OutPending {
1888 true => ", error=''",
1889 false => "",
1890 };
1891 context
1892 .sql
1893 .execute(
1894 &format!("UPDATE msgs SET state=? {error_subst} WHERE id=?"),
1895 (state, msg_id),
1896 )
1897 .await?;
1898 Ok(())
1899}
1900
1901pub(crate) async fn set_msg_failed(
1909 context: &Context,
1910 msg: &mut Message,
1911 error: &str,
1912) -> Result<()> {
1913 if msg.state.can_fail() {
1914 msg.state = MessageState::OutFailed;
1915 warn!(context, "{} failed: {}", msg.id, error);
1916 } else {
1917 warn!(
1918 context,
1919 "{} seems to have failed ({}), but state is {}", msg.id, error, msg.state
1920 )
1921 }
1922 msg.error = Some(error.to_string());
1923
1924 let exists = context
1925 .sql
1926 .execute(
1927 "UPDATE msgs SET state=?, error=? WHERE id=?;",
1928 (msg.state, error, msg.id),
1929 )
1930 .await?
1931 > 0;
1932 context.emit_event(EventType::MsgFailed {
1933 chat_id: msg.chat_id,
1934 msg_id: msg.id,
1935 });
1936 if exists {
1937 chatlist_events::emit_chatlist_item_changed(context, msg.chat_id);
1938 }
1939 Ok(())
1940}
1941
1942pub async fn get_unblocked_msg_cnt(context: &Context) -> usize {
1944 match context
1945 .sql
1946 .count(
1947 "SELECT COUNT(*) \
1948 FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \
1949 WHERE m.id>9 AND m.chat_id>9 AND c.blocked=0;",
1950 (),
1951 )
1952 .await
1953 {
1954 Ok(res) => res,
1955 Err(err) => {
1956 error!(context, "get_unblocked_msg_cnt() failed. {:#}", err);
1957 0
1958 }
1959 }
1960}
1961
1962pub async fn get_request_msg_cnt(context: &Context) -> usize {
1964 match context
1965 .sql
1966 .count(
1967 "SELECT COUNT(*) \
1968 FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \
1969 WHERE c.blocked=2;",
1970 (),
1971 )
1972 .await
1973 {
1974 Ok(res) => res,
1975 Err(err) => {
1976 error!(context, "get_request_msg_cnt() failed. {:#}", err);
1977 0
1978 }
1979 }
1980}
1981
1982pub async fn estimate_deletion_cnt(
1998 context: &Context,
1999 from_server: bool,
2000 seconds: i64,
2001) -> Result<usize> {
2002 let self_chat_id = ChatIdBlocked::lookup_by_contact(context, ContactId::SELF)
2003 .await?
2004 .map(|c| c.id)
2005 .unwrap_or_default();
2006 let threshold_timestamp = time() - seconds;
2007
2008 let cnt = if from_server {
2009 context
2010 .sql
2011 .count(
2012 "SELECT COUNT(*)
2013 FROM msgs m
2014 WHERE m.id > ?
2015 AND timestamp < ?
2016 AND chat_id != ?
2017 AND EXISTS (SELECT * FROM imap WHERE rfc724_mid=m.rfc724_mid);",
2018 (DC_MSG_ID_LAST_SPECIAL, threshold_timestamp, self_chat_id),
2019 )
2020 .await?
2021 } else {
2022 context
2023 .sql
2024 .count(
2025 "SELECT COUNT(*)
2026 FROM msgs m
2027 WHERE m.id > ?
2028 AND timestamp < ?
2029 AND chat_id != ?
2030 AND chat_id != ? AND hidden = 0;",
2031 (
2032 DC_MSG_ID_LAST_SPECIAL,
2033 threshold_timestamp,
2034 self_chat_id,
2035 DC_CHAT_ID_TRASH,
2036 ),
2037 )
2038 .await?
2039 };
2040 Ok(cnt)
2041}
2042
2043pub(crate) async fn rfc724_mid_exists(
2045 context: &Context,
2046 rfc724_mid: &str,
2047) -> Result<Option<(MsgId, i64)>> {
2048 Ok(rfc724_mid_exists_ex(context, rfc724_mid, "1")
2049 .await?
2050 .map(|(id, ts_sent, _)| (id, ts_sent)))
2051}
2052
2053pub(crate) async fn rfc724_mid_exists_ex(
2059 context: &Context,
2060 rfc724_mid: &str,
2061 expr: &str,
2062) -> Result<Option<(MsgId, i64, bool)>> {
2063 let rfc724_mid = rfc724_mid.trim_start_matches('<').trim_end_matches('>');
2064 if rfc724_mid.is_empty() {
2065 warn!(context, "Empty rfc724_mid passed to rfc724_mid_exists");
2066 return Ok(None);
2067 }
2068
2069 let res = context
2070 .sql
2071 .query_row_optional(
2072 &("SELECT id, timestamp_sent, MIN(".to_string()
2073 + expr
2074 + ") FROM msgs WHERE rfc724_mid=?
2075 HAVING COUNT(*) > 0 -- Prevent MIN(expr) from returning NULL when there are no rows.
2076 ORDER BY timestamp_sent DESC"),
2077 (rfc724_mid,),
2078 |row| {
2079 let msg_id: MsgId = row.get(0)?;
2080 let timestamp_sent: i64 = row.get(1)?;
2081 let expr_res: bool = row.get(2)?;
2082 Ok((msg_id, timestamp_sent, expr_res))
2083 },
2084 )
2085 .await?;
2086
2087 Ok(res)
2088}
2089
2090pub(crate) async fn get_by_rfc724_mids(
2097 context: &Context,
2098 mids: &[String],
2099) -> Result<Option<Message>> {
2100 let mut latest = None;
2101 for id in mids.iter().rev() {
2102 let Some((msg_id, _)) = rfc724_mid_exists(context, id).await? else {
2103 continue;
2104 };
2105 let Some(msg) = Message::load_from_db_optional(context, msg_id).await? else {
2106 continue;
2107 };
2108 if msg.download_state == DownloadState::Done {
2109 return Ok(Some(msg));
2110 }
2111 latest.get_or_insert(msg);
2112 }
2113 Ok(latest)
2114}
2115
2116pub(crate) fn get_vcard_summary(vcard: &[u8]) -> Option<String> {
2118 let vcard = str::from_utf8(vcard).ok()?;
2119 let contacts = deltachat_contact_tools::parse_vcard(vcard);
2120 let [c] = &contacts[..] else {
2121 return None;
2122 };
2123 if !deltachat_contact_tools::may_be_valid_addr(&c.addr) {
2124 return None;
2125 }
2126 Some(c.display_name().to_string())
2127}
2128
2129#[derive(
2131 Debug,
2132 Default,
2133 Display,
2134 Clone,
2135 Copy,
2136 PartialEq,
2137 Eq,
2138 FromPrimitive,
2139 ToPrimitive,
2140 FromSql,
2141 ToSql,
2142 Serialize,
2143 Deserialize,
2144)]
2145#[repr(u32)]
2146pub enum Viewtype {
2147 #[default]
2149 Unknown = 0,
2150
2151 Text = 10,
2154
2155 Image = 20,
2161
2162 Gif = 21,
2166
2167 Sticker = 23,
2174
2175 Audio = 40,
2179
2180 Voice = 41,
2185
2186 Video = 50,
2193
2194 File = 60,
2198
2199 Call = 71,
2201
2202 Webxdc = 80,
2204
2205 Vcard = 90,
2209}
2210
2211impl Viewtype {
2212 pub fn has_file(&self) -> bool {
2214 match self {
2215 Viewtype::Unknown => false,
2216 Viewtype::Text => false,
2217 Viewtype::Image => true,
2218 Viewtype::Gif => true,
2219 Viewtype::Sticker => true,
2220 Viewtype::Audio => true,
2221 Viewtype::Voice => true,
2222 Viewtype::Video => true,
2223 Viewtype::File => true,
2224 Viewtype::Call => false,
2225 Viewtype::Webxdc => true,
2226 Viewtype::Vcard => true,
2227 }
2228 }
2229}
2230
2231pub(crate) fn normalize_text(text: &str) -> Option<String> {
2234 if text.is_ascii() {
2235 return None;
2236 };
2237 Some(text.to_lowercase()).filter(|t| t != text)
2238}
2239
2240#[cfg(test)]
2241mod message_tests;