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