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