1use std::collections::BTreeSet;
4use std::collections::HashSet;
5use std::path::{Path, PathBuf};
6use std::str;
7
8use anyhow::{ensure, format_err, Context as _, Result};
9use deltachat_contact_tools::{parse_vcard, VcardContact};
10use deltachat_derive::{FromSql, ToSql};
11use serde::{Deserialize, Serialize};
12use tokio::{fs, io};
13
14use crate::blob::BlobObject;
15use crate::chat::{send_msg, Chat, ChatId, ChatIdBlocked, ChatVisibility};
16use crate::chatlist_events;
17use crate::config::Config;
18use crate::constants::{
19 Blocked, Chattype, VideochatType, DC_CHAT_ID_TRASH, DC_MSG_ID_LAST_SPECIAL,
20};
21use crate::contact::{self, Contact, ContactId};
22use crate::context::Context;
23use crate::debug_logging::set_debug_logging_xdc;
24use crate::download::DownloadState;
25use crate::ephemeral::{start_ephemeral_timers_msgids, Timer as EphemeralTimer};
26use crate::events::EventType;
27use crate::imap::markseen_on_imap_table;
28use crate::location::delete_poi_location;
29use crate::mimeparser::{parse_message_id, SystemMessage};
30use crate::param::{Param, Params};
31use crate::pgp::split_armored_data;
32use crate::reaction::get_msg_reactions;
33use crate::sql;
34use crate::summary::Summary;
35use crate::sync::SyncData;
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 async fn trash(self, context: &Context, on_server: bool) -> Result<()> {
127 let chat_id = DC_CHAT_ID_TRASH;
128 let deleted_subst = match on_server {
129 true => ", deleted=1",
130 false => "",
131 };
132 context
133 .sql
134 .execute(
135 &format!(
138 "UPDATE msgs SET \
139 chat_id=?, txt='', txt_normalized=NULL, \
140 subject='', txt_raw='', \
141 mime_headers='', \
142 from_id=0, to_id=0, \
143 param=''{deleted_subst} \
144 WHERE id=?"
145 ),
146 (chat_id, self),
147 )
148 .await?;
149
150 Ok(())
151 }
152
153 pub(crate) async fn set_delivered(self, context: &Context) -> Result<()> {
154 update_msg_state(context, self, MessageState::OutDelivered).await?;
155 let chat_id: Option<ChatId> = context
156 .sql
157 .query_get_value("SELECT chat_id FROM msgs WHERE id=?", (self,))
158 .await?;
159 context.emit_event(EventType::MsgDelivered {
160 chat_id: chat_id.unwrap_or_default(),
161 msg_id: self,
162 });
163 if let Some(chat_id) = chat_id {
164 chatlist_events::emit_chatlist_item_changed(context, chat_id);
165 }
166 Ok(())
167 }
168
169 pub fn to_u32(self) -> u32 {
174 self.0
175 }
176
177 pub async fn get_info_server_urls(
179 context: &Context,
180 rfc724_mid: String,
181 ) -> Result<Vec<String>> {
182 context
183 .sql
184 .query_map(
185 "SELECT folder, uid FROM imap WHERE rfc724_mid=?",
186 (rfc724_mid,),
187 |row| {
188 let folder: String = row.get("folder")?;
189 let uid: u32 = row.get("uid")?;
190 Ok(format!("</{folder}/;UID={uid}>"))
191 },
192 |rows| {
193 rows.collect::<std::result::Result<Vec<_>, _>>()
194 .map_err(Into::into)
195 },
196 )
197 .await
198 }
199
200 pub async fn hop_info(self, context: &Context) -> Result<String> {
202 let hop_info = context
203 .sql
204 .query_get_value("SELECT IFNULL(hop_info, '') FROM msgs WHERE id=?", (self,))
205 .await?
206 .with_context(|| format!("Message {self} not found"))?;
207 Ok(hop_info)
208 }
209
210 pub async fn get_info(self, context: &Context) -> Result<String> {
212 let msg = Message::load_from_db(context, self).await?;
213
214 let mut ret = String::new();
215
216 let fts = timestamp_to_str(msg.get_timestamp());
217 ret += &format!("Sent: {fts}");
218
219 let from_contact = Contact::get_by_id(context, msg.from_id).await?;
220 let name = from_contact.get_name_n_addr();
221 if let Some(override_sender_name) = msg.get_override_sender_name() {
222 let addr = from_contact.get_addr();
223 ret += &format!(" by ~{override_sender_name} ({addr})");
224 } else {
225 ret += &format!(" by {name}");
226 }
227 ret += "\n";
228
229 if msg.from_id != ContactId::SELF {
230 let s = timestamp_to_str(if 0 != msg.timestamp_rcvd {
231 msg.timestamp_rcvd
232 } else {
233 msg.timestamp_sort
234 });
235 ret += &format!("Received: {}", &s);
236 ret += "\n";
237 }
238
239 if let EphemeralTimer::Enabled { duration } = msg.ephemeral_timer {
240 ret += &format!("Ephemeral timer: {duration}\n");
241 }
242
243 if msg.ephemeral_timestamp != 0 {
244 ret += &format!("Expires: {}\n", timestamp_to_str(msg.ephemeral_timestamp));
245 }
246
247 if msg.from_id == ContactId::INFO || msg.to_id == ContactId::INFO {
248 return Ok(ret);
250 }
251
252 if let Ok(rows) = context
253 .sql
254 .query_map(
255 "SELECT contact_id, timestamp_sent FROM msgs_mdns WHERE msg_id=?",
256 (self,),
257 |row| {
258 let contact_id: ContactId = row.get(0)?;
259 let ts: i64 = row.get(1)?;
260 Ok((contact_id, ts))
261 },
262 |rows| rows.collect::<Result<Vec<_>, _>>().map_err(Into::into),
263 )
264 .await
265 {
266 for (contact_id, ts) in rows {
267 let fts = timestamp_to_str(ts);
268 ret += &format!("Read: {fts}");
269
270 let name = Contact::get_by_id(context, contact_id)
271 .await
272 .map(|contact| contact.get_name_n_addr())
273 .unwrap_or_default();
274
275 ret += &format!(" by {name}");
276 ret += "\n";
277 }
278 }
279
280 ret += &format!("State: {}", msg.state);
281
282 if msg.has_location() {
283 ret += ", Location sent";
284 }
285
286 if 0 != msg.param.get_int(Param::GuaranteeE2ee).unwrap_or_default() {
287 ret += ", Encrypted";
288 }
289
290 ret += "\n";
291
292 let reactions = get_msg_reactions(context, self).await?;
293 if !reactions.is_empty() {
294 ret += &format!("Reactions: {reactions}\n");
295 }
296
297 if let Some(error) = msg.error.as_ref() {
298 ret += &format!("Error: {error}");
299 }
300
301 if let Some(path) = msg.get_file(context) {
302 let bytes = get_filebytes(context, &path).await?;
303 ret += &format!(
304 "\nFile: {}, name: {}, {} bytes\n",
305 path.display(),
306 msg.get_filename().unwrap_or_default(),
307 bytes
308 );
309 }
310
311 if msg.viewtype != Viewtype::Text {
312 ret += "Type: ";
313 ret += &format!("{}", msg.viewtype);
314 ret += "\n";
315 ret += &format!("Mimetype: {}\n", &msg.get_filemime().unwrap_or_default());
316 }
317 let w = msg.param.get_int(Param::Width).unwrap_or_default();
318 let h = msg.param.get_int(Param::Height).unwrap_or_default();
319 if w != 0 || h != 0 {
320 ret += &format!("Dimension: {w} x {h}\n",);
321 }
322 let duration = msg.param.get_int(Param::Duration).unwrap_or_default();
323 if duration != 0 {
324 ret += &format!("Duration: {duration} ms\n",);
325 }
326 if !msg.rfc724_mid.is_empty() {
327 ret += &format!("\nMessage-ID: {}", msg.rfc724_mid);
328
329 let server_urls = Self::get_info_server_urls(context, msg.rfc724_mid).await?;
330 for server_url in server_urls {
331 ret += &format!("\nServer-URL: {server_url}");
333 }
334 }
335 let hop_info = self.hop_info(context).await?;
336
337 ret += "\n\n";
338 if hop_info.is_empty() {
339 ret += "No Hop Info";
340 } else {
341 ret += &hop_info;
342 }
343
344 Ok(ret)
345 }
346}
347
348impl std::fmt::Display for MsgId {
349 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
350 write!(f, "Msg#{}", self.0)
351 }
352}
353
354impl rusqlite::types::ToSql for MsgId {
363 fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput> {
364 if self.0 <= DC_MSG_ID_LAST_SPECIAL {
365 return Err(rusqlite::Error::ToSqlConversionFailure(
366 format_err!("Invalid MsgId {}", self.0).into(),
367 ));
368 }
369 let val = rusqlite::types::Value::Integer(i64::from(self.0));
370 let out = rusqlite::types::ToSqlOutput::Owned(val);
371 Ok(out)
372 }
373}
374
375impl rusqlite::types::FromSql for MsgId {
377 fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
378 i64::column_result(value).and_then(|val| {
380 if 0 <= val && val <= i64::from(u32::MAX) {
381 Ok(MsgId::new(val as u32))
382 } else {
383 Err(rusqlite::types::FromSqlError::OutOfRange(val))
384 }
385 })
386 }
387}
388
389#[derive(
390 Debug,
391 Copy,
392 Clone,
393 PartialEq,
394 FromPrimitive,
395 ToPrimitive,
396 FromSql,
397 ToSql,
398 Serialize,
399 Deserialize,
400)]
401#[repr(u8)]
402pub(crate) enum MessengerMessage {
403 No = 0,
404 Yes = 1,
405
406 Reply = 2,
408}
409
410impl Default for MessengerMessage {
411 fn default() -> Self {
412 Self::No
413 }
414}
415
416#[derive(Debug, Clone, Default, Serialize, Deserialize)]
420pub struct Message {
421 pub(crate) id: MsgId,
423
424 pub(crate) from_id: ContactId,
426
427 pub(crate) to_id: ContactId,
429
430 pub(crate) chat_id: ChatId,
432
433 pub(crate) viewtype: Viewtype,
435
436 pub(crate) state: MessageState,
438 pub(crate) download_state: DownloadState,
439
440 pub(crate) hidden: bool,
442 pub(crate) timestamp_sort: i64,
443 pub(crate) timestamp_sent: i64,
444 pub(crate) timestamp_rcvd: i64,
445 pub(crate) ephemeral_timer: EphemeralTimer,
446 pub(crate) ephemeral_timestamp: i64,
447 pub(crate) text: String,
448
449 pub(crate) subject: String,
453
454 pub(crate) rfc724_mid: String,
456
457 pub(crate) in_reply_to: Option<String>,
459 pub(crate) is_dc_message: MessengerMessage,
460 pub(crate) original_msg_id: MsgId,
461 pub(crate) mime_modified: bool,
462 pub(crate) chat_blocked: Blocked,
463 pub(crate) location_id: u32,
464 pub(crate) error: Option<String>,
465 pub(crate) param: Params,
466}
467
468impl Message {
469 pub fn new(viewtype: Viewtype) -> Self {
471 Message {
472 viewtype,
473 ..Default::default()
474 }
475 }
476
477 pub fn new_text(text: String) -> Self {
479 Message {
480 viewtype: Viewtype::Text,
481 text,
482 ..Default::default()
483 }
484 }
485
486 pub async fn load_from_db(context: &Context, id: MsgId) -> Result<Message> {
490 let message = Self::load_from_db_optional(context, id)
491 .await?
492 .with_context(|| format!("Message {id} does not exist"))?;
493 Ok(message)
494 }
495
496 pub async fn load_from_db_optional(context: &Context, id: MsgId) -> Result<Option<Message>> {
500 ensure!(
501 !id.is_special(),
502 "Can not load special message ID {} from DB",
503 id
504 );
505 let msg = context
506 .sql
507 .query_row_optional(
508 concat!(
509 "SELECT",
510 " m.id AS id,",
511 " rfc724_mid AS rfc724mid,",
512 " m.mime_in_reply_to AS mime_in_reply_to,",
513 " m.chat_id AS chat_id,",
514 " m.from_id AS from_id,",
515 " m.to_id AS to_id,",
516 " m.timestamp AS timestamp,",
517 " m.timestamp_sent AS timestamp_sent,",
518 " m.timestamp_rcvd AS timestamp_rcvd,",
519 " m.ephemeral_timer AS ephemeral_timer,",
520 " m.ephemeral_timestamp AS ephemeral_timestamp,",
521 " m.type AS type,",
522 " m.state AS state,",
523 " mdns.msg_id AS mdn_msg_id,",
524 " m.download_state AS download_state,",
525 " m.error AS error,",
526 " m.msgrmsg AS msgrmsg,",
527 " m.starred AS original_msg_id,",
528 " m.mime_modified AS mime_modified,",
529 " m.txt AS txt,",
530 " m.subject AS subject,",
531 " m.param AS param,",
532 " m.hidden AS hidden,",
533 " m.location_id AS location,",
534 " c.blocked AS blocked",
535 " FROM msgs m",
536 " LEFT JOIN chats c ON c.id=m.chat_id",
537 " LEFT JOIN msgs_mdns mdns ON mdns.msg_id=m.id",
538 " WHERE m.id=? AND chat_id!=3",
539 " LIMIT 1",
540 ),
541 (id,),
542 |row| {
543 let state: MessageState = row.get("state")?;
544 let mdn_msg_id: Option<MsgId> = row.get("mdn_msg_id")?;
545 let text = match row.get_ref("txt")? {
546 rusqlite::types::ValueRef::Text(buf) => {
547 match String::from_utf8(buf.to_vec()) {
548 Ok(t) => t,
549 Err(_) => {
550 warn!(
551 context,
552 concat!(
553 "dc_msg_load_from_db: could not get ",
554 "text column as non-lossy utf8 id {}"
555 ),
556 id
557 );
558 String::from_utf8_lossy(buf).into_owned()
559 }
560 }
561 }
562 _ => String::new(),
563 };
564 let msg = Message {
565 id: row.get("id")?,
566 rfc724_mid: row.get::<_, String>("rfc724mid")?,
567 in_reply_to: row
568 .get::<_, Option<String>>("mime_in_reply_to")?
569 .and_then(|in_reply_to| parse_message_id(&in_reply_to).ok()),
570 chat_id: row.get("chat_id")?,
571 from_id: row.get("from_id")?,
572 to_id: row.get("to_id")?,
573 timestamp_sort: row.get("timestamp")?,
574 timestamp_sent: row.get("timestamp_sent")?,
575 timestamp_rcvd: row.get("timestamp_rcvd")?,
576 ephemeral_timer: row.get("ephemeral_timer")?,
577 ephemeral_timestamp: row.get("ephemeral_timestamp")?,
578 viewtype: row.get("type")?,
579 state: state.with_mdns(mdn_msg_id.is_some()),
580 download_state: row.get("download_state")?,
581 error: Some(row.get::<_, String>("error")?)
582 .filter(|error| !error.is_empty()),
583 is_dc_message: row.get("msgrmsg")?,
584 original_msg_id: row.get("original_msg_id")?,
585 mime_modified: row.get("mime_modified")?,
586 text,
587 subject: row.get("subject")?,
588 param: row.get::<_, String>("param")?.parse().unwrap_or_default(),
589 hidden: row.get("hidden")?,
590 location_id: row.get("location")?,
591 chat_blocked: row
592 .get::<_, Option<Blocked>>("blocked")?
593 .unwrap_or_default(),
594 };
595 Ok(msg)
596 },
597 )
598 .await
599 .with_context(|| format!("failed to load message {id} from the database"))?;
600
601 Ok(msg)
602 }
603
604 pub fn get_filemime(&self) -> Option<String> {
611 if let Some(m) = self.param.get(Param::MimeType) {
612 return Some(m.to_string());
613 } else if self.param.exists(Param::File) {
614 if let Some((_, mime)) = guess_msgtype_from_suffix(self) {
615 return Some(mime.to_string());
616 }
617 return Some("application/octet-stream".to_string());
619 }
620 None
622 }
623
624 pub fn get_file(&self, context: &Context) -> Option<PathBuf> {
626 self.param.get_file_path(context).unwrap_or(None)
627 }
628
629 pub async fn vcard_contacts(&self, context: &Context) -> Result<Vec<VcardContact>> {
631 if self.viewtype != Viewtype::Vcard {
632 return Ok(Vec::new());
633 }
634
635 let path = self
636 .get_file(context)
637 .context("vCard message does not have an attachment")?;
638 let bytes = tokio::fs::read(path).await?;
639 let vcard_contents = std::str::from_utf8(&bytes).context("vCard is not a valid UTF-8")?;
640 Ok(parse_vcard(vcard_contents))
641 }
642
643 pub async fn save_file(&self, context: &Context, path: &Path) -> Result<()> {
645 let path_src = self.get_file(context).context("No file")?;
646 let mut src = fs::OpenOptions::new().read(true).open(path_src).await?;
647 let mut dst = fs::OpenOptions::new()
648 .write(true)
649 .create_new(true)
650 .open(path)
651 .await?;
652 io::copy(&mut src, &mut dst).await?;
653 Ok(())
654 }
655
656 pub(crate) async fn try_calc_and_set_dimensions(&mut self, context: &Context) -> Result<()> {
658 if self.viewtype.has_file() {
659 let file_param = self.param.get_file_path(context)?;
660 if let Some(path_and_filename) = file_param {
661 if (self.viewtype == Viewtype::Image || self.viewtype == Viewtype::Gif)
662 && !self.param.exists(Param::Width)
663 {
664 let buf = read_file(context, &path_and_filename).await?;
665
666 match get_filemeta(&buf) {
667 Ok((width, height)) => {
668 self.param.set_int(Param::Width, width as i32);
669 self.param.set_int(Param::Height, height as i32);
670 }
671 Err(err) => {
672 self.param.set_int(Param::Width, 0);
673 self.param.set_int(Param::Height, 0);
674 warn!(
675 context,
676 "Failed to get width and height for {}: {err:#}.",
677 path_and_filename.display()
678 );
679 }
680 }
681
682 if !self.id.is_unset() {
683 self.update_param(context).await?;
684 }
685 }
686 }
687 }
688 Ok(())
689 }
690
691 pub fn has_location(&self) -> bool {
697 self.location_id != 0
698 }
699
700 pub fn set_location(&mut self, latitude: f64, longitude: f64) {
717 if latitude == 0.0 && longitude == 0.0 {
718 return;
719 }
720
721 self.param.set_float(Param::SetLatitude, latitude);
722 self.param.set_float(Param::SetLongitude, longitude);
723 }
724
725 pub fn get_timestamp(&self) -> i64 {
728 if 0 != self.timestamp_sent {
729 self.timestamp_sent
730 } else {
731 self.timestamp_sort
732 }
733 }
734
735 pub fn get_id(&self) -> MsgId {
737 self.id
738 }
739
740 pub fn rfc724_mid(&self) -> &str {
743 &self.rfc724_mid
744 }
745
746 pub fn get_from_id(&self) -> ContactId {
748 self.from_id
749 }
750
751 pub fn get_chat_id(&self) -> ChatId {
753 self.chat_id
754 }
755
756 pub fn get_viewtype(&self) -> Viewtype {
758 self.viewtype
759 }
760
761 pub fn force_sticker(&mut self) {
764 self.param.set_int(Param::ForceSticker, 1);
765 }
766
767 pub fn get_state(&self) -> MessageState {
769 self.state
770 }
771
772 pub fn get_received_timestamp(&self) -> i64 {
774 self.timestamp_rcvd
775 }
776
777 pub fn get_sort_timestamp(&self) -> i64 {
779 self.timestamp_sort
780 }
781
782 pub fn get_text(&self) -> String {
784 self.text.clone()
785 }
786
787 pub fn get_subject(&self) -> &str {
789 &self.subject
790 }
791
792 pub fn get_filename(&self) -> Option<String> {
796 if let Some(name) = self.param.get(Param::Filename) {
797 return Some(sanitize_filename(name));
798 }
799 self.param
800 .get(Param::File)
801 .and_then(|file| Path::new(file).file_name())
802 .map(|name| sanitize_filename(&name.to_string_lossy()))
803 }
804
805 pub async fn get_filebytes(&self, context: &Context) -> Result<Option<u64>> {
807 if let Some(path) = self.param.get_file_path(context)? {
808 Ok(Some(get_filebytes(context, &path).await.with_context(
809 || format!("failed to get {} size in bytes", path.display()),
810 )?))
811 } else {
812 Ok(None)
813 }
814 }
815
816 pub fn get_width(&self) -> i32 {
818 self.param.get_int(Param::Width).unwrap_or_default()
819 }
820
821 pub fn get_height(&self) -> i32 {
823 self.param.get_int(Param::Height).unwrap_or_default()
824 }
825
826 pub fn get_duration(&self) -> i32 {
828 self.param.get_int(Param::Duration).unwrap_or_default()
829 }
830
831 pub fn get_showpadlock(&self) -> bool {
833 self.param.get_int(Param::GuaranteeE2ee).unwrap_or_default() != 0
834 }
835
836 pub fn is_bot(&self) -> bool {
838 self.param.get_bool(Param::Bot).unwrap_or_default()
839 }
840
841 pub fn get_ephemeral_timer(&self) -> EphemeralTimer {
843 self.ephemeral_timer
844 }
845
846 pub fn get_ephemeral_timestamp(&self) -> i64 {
848 self.ephemeral_timestamp
849 }
850
851 pub async fn get_summary(&self, context: &Context, chat: Option<&Chat>) -> Result<Summary> {
853 let chat_loaded: Chat;
854 let chat = if let Some(chat) = chat {
855 chat
856 } else {
857 let chat = Chat::load_from_db(context, self.chat_id).await?;
858 chat_loaded = chat;
859 &chat_loaded
860 };
861
862 let contact = if self.from_id != ContactId::SELF {
863 match chat.typ {
864 Chattype::Group | Chattype::Broadcast | Chattype::Mailinglist => {
865 Some(Contact::get_by_id(context, self.from_id).await?)
866 }
867 Chattype::Single => None,
868 }
869 } else {
870 None
871 };
872
873 Summary::new(context, self, chat, contact.as_ref()).await
874 }
875
876 pub fn get_override_sender_name(&self) -> Option<String> {
886 self.param
887 .get(Param::OverrideSenderDisplayname)
888 .map(|name| name.to_string())
889 }
890
891 pub(crate) fn get_sender_name(&self, contact: &Contact) -> String {
894 self.get_override_sender_name()
895 .unwrap_or_else(|| contact.get_display_name().to_string())
896 }
897
898 pub fn has_deviating_timestamp(&self) -> bool {
903 let cnv_to_local = gm2local_offset();
904 let sort_timestamp = self.get_sort_timestamp() + cnv_to_local;
905 let send_timestamp = self.get_timestamp() + cnv_to_local;
906
907 sort_timestamp / 86400 != send_timestamp / 86400
908 }
909
910 pub fn is_sent(&self) -> bool {
913 self.state >= MessageState::OutDelivered
914 }
915
916 pub fn is_forwarded(&self) -> bool {
918 0 != self.param.get_int(Param::Forwarded).unwrap_or_default()
919 }
920
921 pub fn is_edited(&self) -> bool {
923 self.param.get_bool(Param::IsEdited).unwrap_or_default()
924 }
925
926 pub fn is_info(&self) -> bool {
928 let cmd = self.param.get_cmd();
929 self.from_id == ContactId::INFO
930 || self.to_id == ContactId::INFO
931 || cmd != SystemMessage::Unknown && cmd != SystemMessage::AutocryptSetupMessage
932 }
933
934 pub fn get_info_type(&self) -> SystemMessage {
936 self.param.get_cmd()
937 }
938
939 pub async fn get_info_contact_id(&self, context: &Context) -> Result<Option<ContactId>> {
941 match self.param.get_cmd() {
942 SystemMessage::GroupNameChanged
943 | SystemMessage::GroupImageChanged
944 | SystemMessage::EphemeralTimerChanged => {
945 if self.from_id != ContactId::INFO {
946 Ok(Some(self.from_id))
947 } else {
948 Ok(None)
949 }
950 }
951
952 SystemMessage::MemberAddedToGroup | SystemMessage::MemberRemovedFromGroup => {
953 if let Some(contact_i32) = self.param.get_int(Param::ContactAddedRemoved) {
954 let contact_id = ContactId::new(contact_i32.try_into()?);
955 if contact_id == ContactId::SELF
956 || Contact::real_exists_by_id(context, contact_id).await?
957 {
958 Ok(Some(contact_id))
959 } else {
960 Ok(None)
961 }
962 } else {
963 Ok(None)
964 }
965 }
966
967 SystemMessage::AutocryptSetupMessage
968 | SystemMessage::SecurejoinMessage
969 | SystemMessage::LocationStreamingEnabled
970 | SystemMessage::LocationOnly
971 | SystemMessage::ChatProtectionEnabled
972 | SystemMessage::ChatProtectionDisabled
973 | SystemMessage::InvalidUnencryptedMail
974 | SystemMessage::SecurejoinWait
975 | SystemMessage::SecurejoinWaitTimeout
976 | SystemMessage::MultiDeviceSync
977 | SystemMessage::WebxdcStatusUpdate
978 | SystemMessage::WebxdcInfoMessage
979 | SystemMessage::IrohNodeAddr
980 | SystemMessage::Unknown => Ok(None),
981 }
982 }
983
984 pub fn is_system_message(&self) -> bool {
986 let cmd = self.param.get_cmd();
987 cmd != SystemMessage::Unknown
988 }
989
990 pub fn is_setupmessage(&self) -> bool {
992 if self.viewtype != Viewtype::File {
993 return false;
994 }
995
996 self.param.get_cmd() == SystemMessage::AutocryptSetupMessage
997 }
998
999 pub async fn get_setupcodebegin(&self, context: &Context) -> Option<String> {
1003 if !self.is_setupmessage() {
1004 return None;
1005 }
1006
1007 if let Some(filename) = self.get_file(context) {
1008 if let Ok(ref buf) = read_file(context, &filename).await {
1009 if let Ok((typ, headers, _)) = split_armored_data(buf) {
1010 if typ == pgp::armor::BlockType::Message {
1011 return headers.get(crate::pgp::HEADER_SETUPCODE).cloned();
1012 }
1013 }
1014 }
1015 }
1016
1017 None
1018 }
1019
1020 pub(crate) fn create_webrtc_instance(instance: &str, room: &str) -> String {
1023 let (videochat_type, mut url) = Message::parse_webrtc_instance(instance);
1024
1025 if !url.contains(':') {
1027 url = format!("https://{url}");
1028 }
1029
1030 let url = if url.contains("$ROOM") {
1032 url.replace("$ROOM", room)
1033 } else if url.contains("$NOROOM") {
1034 url.replace("$NOROOM", "")
1040 } else {
1041 let maybe_slash = if url.ends_with('/')
1044 || url.ends_with('?')
1045 || url.ends_with('#')
1046 || url.ends_with('=')
1047 {
1048 ""
1049 } else {
1050 "/"
1051 };
1052 format!("{url}{maybe_slash}{room}")
1053 };
1054
1055 match videochat_type {
1057 VideochatType::BasicWebrtc => format!("basicwebrtc:{url}"),
1058 VideochatType::Jitsi => format!("jitsi:{url}"),
1059 VideochatType::Unknown => url,
1060 }
1061 }
1062
1063 pub fn parse_webrtc_instance(instance: &str) -> (VideochatType, String) {
1065 let instance: String = instance.split_whitespace().collect();
1066 let mut split = instance.splitn(2, ':');
1067 let type_str = split.next().unwrap_or_default().to_lowercase();
1068 let url = split.next();
1069 match type_str.as_str() {
1070 "basicwebrtc" => (
1071 VideochatType::BasicWebrtc,
1072 url.unwrap_or_default().to_string(),
1073 ),
1074 "jitsi" => (VideochatType::Jitsi, url.unwrap_or_default().to_string()),
1075 _ => (VideochatType::Unknown, instance.to_string()),
1076 }
1077 }
1078
1079 pub fn get_videochat_url(&self) -> Option<String> {
1081 if self.viewtype == Viewtype::VideochatInvitation {
1082 if let Some(instance) = self.param.get(Param::WebrtcRoom) {
1083 return Some(Message::parse_webrtc_instance(instance).1);
1084 }
1085 }
1086 None
1087 }
1088
1089 pub fn get_videochat_type(&self) -> Option<VideochatType> {
1091 if self.viewtype == Viewtype::VideochatInvitation {
1092 if let Some(instance) = self.param.get(Param::WebrtcRoom) {
1093 return Some(Message::parse_webrtc_instance(instance).0);
1094 }
1095 }
1096 None
1097 }
1098
1099 pub fn set_text(&mut self, text: String) {
1101 self.text = text;
1102 }
1103
1104 pub fn set_subject(&mut self, subject: String) {
1107 self.subject = subject;
1108 }
1109
1110 pub fn set_file_and_deduplicate(
1125 &mut self,
1126 context: &Context,
1127 file: &Path,
1128 name: Option<&str>,
1129 filemime: Option<&str>,
1130 ) -> Result<()> {
1131 let name = if let Some(name) = name {
1132 name.to_string()
1133 } else {
1134 file.file_name()
1135 .map(|s| s.to_string_lossy().to_string())
1136 .unwrap_or_else(|| "unknown_file".to_string())
1137 };
1138
1139 let blob = BlobObject::create_and_deduplicate(context, file, Path::new(&name))?;
1140 self.param.set(Param::File, blob.as_name());
1141
1142 self.param.set(Param::Filename, name);
1143 self.param.set_optional(Param::MimeType, filemime);
1144
1145 Ok(())
1146 }
1147
1148 pub fn set_file_from_bytes(
1155 &mut self,
1156 context: &Context,
1157 name: &str,
1158 data: &[u8],
1159 filemime: Option<&str>,
1160 ) -> Result<()> {
1161 let blob = BlobObject::create_and_deduplicate_from_bytes(context, data, name)?;
1162 self.param.set(Param::Filename, name);
1163 self.param.set(Param::File, blob.as_name());
1164 self.param.set_optional(Param::MimeType, filemime);
1165
1166 Ok(())
1167 }
1168
1169 pub async fn make_vcard(&mut self, context: &Context, contacts: &[ContactId]) -> Result<()> {
1171 ensure!(
1172 matches!(self.viewtype, Viewtype::File | Viewtype::Vcard),
1173 "Wrong viewtype for vCard: {}",
1174 self.viewtype,
1175 );
1176 let vcard = contact::make_vcard(context, contacts).await?;
1177 self.set_file_from_bytes(context, "vcard.vcf", vcard.as_bytes(), None)
1178 }
1179
1180 pub(crate) async fn try_set_vcard(&mut self, context: &Context, path: &Path) -> Result<()> {
1182 let vcard = fs::read(path)
1183 .await
1184 .with_context(|| format!("Could not read {path:?}"))?;
1185 if let Some(summary) = get_vcard_summary(&vcard) {
1186 self.param.set(Param::Summary1, summary);
1187 } else {
1188 warn!(context, "try_set_vcard: Not a valid DeltaChat vCard.");
1189 self.viewtype = Viewtype::File;
1190 }
1191 Ok(())
1192 }
1193
1194 pub fn set_override_sender_name(&mut self, name: Option<String>) {
1197 self.param
1198 .set_optional(Param::OverrideSenderDisplayname, name);
1199 }
1200
1201 pub fn set_dimension(&mut self, width: i32, height: i32) {
1203 self.param.set_int(Param::Width, width);
1204 self.param.set_int(Param::Height, height);
1205 }
1206
1207 pub fn set_duration(&mut self, duration: i32) {
1209 self.param.set_int(Param::Duration, duration);
1210 }
1211
1212 pub(crate) fn set_reaction(&mut self) {
1214 self.param.set_int(Param::Reaction, 1);
1215 }
1216
1217 pub async fn latefiling_mediasize(
1220 &mut self,
1221 context: &Context,
1222 width: i32,
1223 height: i32,
1224 duration: i32,
1225 ) -> Result<()> {
1226 if width > 0 && height > 0 {
1227 self.param.set_int(Param::Width, width);
1228 self.param.set_int(Param::Height, height);
1229 }
1230 if duration > 0 {
1231 self.param.set_int(Param::Duration, duration);
1232 }
1233 self.update_param(context).await?;
1234 Ok(())
1235 }
1236
1237 pub fn set_quote_text(&mut self, text: Option<(String, bool)>) {
1243 let Some((text, protect)) = text else {
1244 self.param.remove(Param::Quote);
1245 self.param.remove(Param::ProtectQuote);
1246 return;
1247 };
1248 self.param.set(Param::Quote, text);
1249 self.param.set_optional(
1250 Param::ProtectQuote,
1251 match protect {
1252 true => Some("1"),
1253 false => None,
1254 },
1255 );
1256 }
1257
1258 pub async fn set_quote(&mut self, context: &Context, quote: Option<&Message>) -> Result<()> {
1267 if let Some(quote) = quote {
1268 ensure!(
1269 !quote.rfc724_mid.is_empty(),
1270 "Message without Message-Id cannot be quoted"
1271 );
1272 self.in_reply_to = Some(quote.rfc724_mid.clone());
1273
1274 let text = quote.get_text();
1275 let text = if text.is_empty() {
1276 quote
1278 .get_summary(context, None)
1279 .await?
1280 .truncated_text(500)
1281 .to_string()
1282 } else {
1283 text
1284 };
1285 self.set_quote_text(Some((
1286 text,
1287 quote
1288 .param
1289 .get_bool(Param::GuaranteeE2ee)
1290 .unwrap_or_default(),
1291 )));
1292 } else {
1293 self.in_reply_to = None;
1294 self.set_quote_text(None);
1295 }
1296
1297 Ok(())
1298 }
1299
1300 pub fn quoted_text(&self) -> Option<String> {
1302 self.param.get(Param::Quote).map(|s| s.to_string())
1303 }
1304
1305 pub async fn quoted_message(&self, context: &Context) -> Result<Option<Message>> {
1307 if self.param.get(Param::Quote).is_some() && !self.is_forwarded() {
1308 return self.parent(context).await;
1309 }
1310 Ok(None)
1311 }
1312
1313 pub async fn parent(&self, context: &Context) -> Result<Option<Message>> {
1318 if let Some(in_reply_to) = &self.in_reply_to {
1319 if let Some((msg_id, _ts_sent)) = rfc724_mid_exists(context, in_reply_to).await? {
1320 let msg = Message::load_from_db_optional(context, msg_id).await?;
1321 return Ok(msg);
1322 }
1323 }
1324 Ok(None)
1325 }
1326
1327 pub async fn get_original_msg_id(&self, context: &Context) -> Result<Option<MsgId>> {
1329 if !self.original_msg_id.is_special() {
1330 if let Some(msg) = Message::load_from_db_optional(context, self.original_msg_id).await?
1331 {
1332 return if msg.chat_id.is_trash() {
1333 Ok(None)
1334 } else {
1335 Ok(Some(msg.id))
1336 };
1337 }
1338 }
1339 Ok(None)
1340 }
1341
1342 pub async fn get_saved_msg_id(&self, context: &Context) -> Result<Option<MsgId>> {
1346 let res: Option<MsgId> = context
1347 .sql
1348 .query_get_value(
1349 "SELECT id FROM msgs WHERE starred=? AND chat_id!=?",
1350 (self.id, DC_CHAT_ID_TRASH),
1351 )
1352 .await?;
1353 Ok(res)
1354 }
1355
1356 pub fn force_plaintext(&mut self) {
1358 self.param.set_int(Param::ForcePlaintext, 1);
1359 }
1360
1361 pub async fn update_param(&self, context: &Context) -> Result<()> {
1363 context
1364 .sql
1365 .execute(
1366 "UPDATE msgs SET param=? WHERE id=?;",
1367 (self.param.to_string(), self.id),
1368 )
1369 .await?;
1370 Ok(())
1371 }
1372
1373 pub(crate) async fn update_subject(&self, context: &Context) -> Result<()> {
1374 context
1375 .sql
1376 .execute(
1377 "UPDATE msgs SET subject=? WHERE id=?;",
1378 (&self.subject, self.id),
1379 )
1380 .await?;
1381 Ok(())
1382 }
1383
1384 pub fn error(&self) -> Option<String> {
1397 self.error.clone()
1398 }
1399}
1400
1401#[derive(
1405 Debug,
1406 Default,
1407 Clone,
1408 Copy,
1409 PartialEq,
1410 Eq,
1411 PartialOrd,
1412 Ord,
1413 FromPrimitive,
1414 ToPrimitive,
1415 ToSql,
1416 FromSql,
1417 Serialize,
1418 Deserialize,
1419)]
1420#[repr(u32)]
1421pub enum MessageState {
1422 #[default]
1424 Undefined = 0,
1425
1426 InFresh = 10,
1429
1430 InNoticed = 13,
1434
1435 InSeen = 16,
1438
1439 OutPreparing = 18,
1443
1444 OutDraft = 19,
1446
1447 OutPending = 20,
1451
1452 OutFailed = 24,
1455
1456 OutDelivered = 26,
1460
1461 OutMdnRcvd = 28,
1464}
1465
1466impl std::fmt::Display for MessageState {
1467 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
1468 write!(
1469 f,
1470 "{}",
1471 match self {
1472 Self::Undefined => "Undefined",
1473 Self::InFresh => "Fresh",
1474 Self::InNoticed => "Noticed",
1475 Self::InSeen => "Seen",
1476 Self::OutPreparing => "Preparing",
1477 Self::OutDraft => "Draft",
1478 Self::OutPending => "Pending",
1479 Self::OutFailed => "Failed",
1480 Self::OutDelivered => "Delivered",
1481 Self::OutMdnRcvd => "Read",
1482 }
1483 )
1484 }
1485}
1486
1487impl MessageState {
1488 pub fn can_fail(self) -> bool {
1490 use MessageState::*;
1491 matches!(
1492 self,
1493 OutPreparing | OutPending | OutDelivered | OutMdnRcvd )
1495 }
1496
1497 pub fn is_outgoing(self) -> bool {
1499 use MessageState::*;
1500 matches!(
1501 self,
1502 OutPreparing | OutDraft | OutPending | OutFailed | OutDelivered | OutMdnRcvd
1503 )
1504 }
1505
1506 pub(crate) fn with_mdns(self, has_mdns: bool) -> Self {
1508 if self == MessageState::OutDelivered && has_mdns {
1509 return MessageState::OutMdnRcvd;
1510 }
1511 self
1512 }
1513}
1514
1515pub async fn get_msg_read_receipts(
1517 context: &Context,
1518 msg_id: MsgId,
1519) -> Result<Vec<(ContactId, i64)>> {
1520 context
1521 .sql
1522 .query_map(
1523 "SELECT contact_id, timestamp_sent FROM msgs_mdns WHERE msg_id=?",
1524 (msg_id,),
1525 |row| {
1526 let contact_id: ContactId = row.get(0)?;
1527 let ts: i64 = row.get(1)?;
1528 Ok((contact_id, ts))
1529 },
1530 |rows| rows.collect::<Result<Vec<_>, _>>().map_err(Into::into),
1531 )
1532 .await
1533}
1534
1535pub(crate) fn guess_msgtype_from_suffix(msg: &Message) -> Option<(Viewtype, &'static str)> {
1536 msg.param
1537 .get(Param::Filename)
1538 .or_else(|| msg.param.get(Param::File))
1539 .and_then(|file| guess_msgtype_from_path_suffix(Path::new(file)))
1540}
1541
1542pub(crate) fn guess_msgtype_from_path_suffix(path: &Path) -> Option<(Viewtype, &'static str)> {
1543 let extension: &str = &path.extension()?.to_str()?.to_lowercase();
1544 let info = match extension {
1545 "3gp" => (Viewtype::Video, "video/3gpp"),
1550 "aac" => (Viewtype::Audio, "audio/aac"),
1551 "avi" => (Viewtype::Video, "video/x-msvideo"),
1552 "avif" => (Viewtype::File, "image/avif"), "doc" => (Viewtype::File, "application/msword"),
1554 "docx" => (
1555 Viewtype::File,
1556 "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
1557 ),
1558 "epub" => (Viewtype::File, "application/epub+zip"),
1559 "flac" => (Viewtype::Audio, "audio/flac"),
1560 "gif" => (Viewtype::Gif, "image/gif"),
1561 "heic" => (Viewtype::File, "image/heic"), "heif" => (Viewtype::File, "image/heif"), "html" => (Viewtype::File, "text/html"),
1564 "htm" => (Viewtype::File, "text/html"),
1565 "ico" => (Viewtype::File, "image/vnd.microsoft.icon"),
1566 "jar" => (Viewtype::File, "application/java-archive"),
1567 "jpeg" => (Viewtype::Image, "image/jpeg"),
1568 "jpe" => (Viewtype::Image, "image/jpeg"),
1569 "jpg" => (Viewtype::Image, "image/jpeg"),
1570 "json" => (Viewtype::File, "application/json"),
1571 "mov" => (Viewtype::Video, "video/quicktime"),
1572 "m4a" => (Viewtype::Audio, "audio/m4a"),
1573 "mp3" => (Viewtype::Audio, "audio/mpeg"),
1574 "mp4" => (Viewtype::Video, "video/mp4"),
1575 "odp" => (
1576 Viewtype::File,
1577 "application/vnd.oasis.opendocument.presentation",
1578 ),
1579 "ods" => (
1580 Viewtype::File,
1581 "application/vnd.oasis.opendocument.spreadsheet",
1582 ),
1583 "odt" => (Viewtype::File, "application/vnd.oasis.opendocument.text"),
1584 "oga" => (Viewtype::Audio, "audio/ogg"),
1585 "ogg" => (Viewtype::Audio, "audio/ogg"),
1586 "ogv" => (Viewtype::File, "video/ogg"),
1587 "opus" => (Viewtype::File, "audio/ogg"), "otf" => (Viewtype::File, "font/otf"),
1589 "pdf" => (Viewtype::File, "application/pdf"),
1590 "png" => (Viewtype::Image, "image/png"),
1591 "ppt" => (Viewtype::File, "application/vnd.ms-powerpoint"),
1592 "pptx" => (
1593 Viewtype::File,
1594 "application/vnd.openxmlformats-officedocument.presentationml.presentation",
1595 ),
1596 "rar" => (Viewtype::File, "application/vnd.rar"),
1597 "rtf" => (Viewtype::File, "application/rtf"),
1598 "spx" => (Viewtype::File, "audio/ogg"), "svg" => (Viewtype::File, "image/svg+xml"),
1600 "tgs" => (Viewtype::Sticker, "application/x-tgsticker"),
1601 "tiff" => (Viewtype::File, "image/tiff"),
1602 "tif" => (Viewtype::File, "image/tiff"),
1603 "ttf" => (Viewtype::File, "font/ttf"),
1604 "txt" => (Viewtype::File, "text/plain"),
1605 "vcard" => (Viewtype::Vcard, "text/vcard"),
1606 "vcf" => (Viewtype::Vcard, "text/vcard"),
1607 "wav" => (Viewtype::Audio, "audio/wav"),
1608 "weba" => (Viewtype::File, "audio/webm"),
1609 "webm" => (Viewtype::Video, "video/webm"),
1610 "webp" => (Viewtype::Image, "image/webp"), "wmv" => (Viewtype::Video, "video/x-ms-wmv"),
1612 "xdc" => (Viewtype::Webxdc, "application/webxdc+zip"),
1613 "xhtml" => (Viewtype::File, "application/xhtml+xml"),
1614 "xls" => (Viewtype::File, "application/vnd.ms-excel"),
1615 "xlsx" => (
1616 Viewtype::File,
1617 "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
1618 ),
1619 "xml" => (Viewtype::File, "application/xml"),
1620 "zip" => (Viewtype::File, "application/zip"),
1621 _ => {
1622 return None;
1623 }
1624 };
1625 Some(info)
1626}
1627
1628pub(crate) async fn get_mime_headers(context: &Context, msg_id: MsgId) -> Result<Vec<u8>> {
1635 let (headers, compressed) = context
1636 .sql
1637 .query_row(
1638 "SELECT mime_headers, mime_compressed FROM msgs WHERE id=?",
1639 (msg_id,),
1640 |row| {
1641 let headers = sql::row_get_vec(row, 0)?;
1642 let compressed: bool = row.get(1)?;
1643 Ok((headers, compressed))
1644 },
1645 )
1646 .await?;
1647 if compressed {
1648 return buf_decompress(&headers);
1649 }
1650
1651 let headers2 = headers.clone();
1652 let compressed = match tokio::task::block_in_place(move || buf_compress(&headers2)) {
1653 Err(e) => {
1654 warn!(context, "get_mime_headers: buf_compress() failed: {}", e);
1655 return Ok(headers);
1656 }
1657 Ok(o) => o,
1658 };
1659 let update = |conn: &mut rusqlite::Connection| {
1660 match conn.execute(
1661 "\
1662 UPDATE msgs SET mime_headers=?, mime_compressed=1 \
1663 WHERE id=? AND mime_headers!='' AND mime_compressed=0",
1664 (compressed, msg_id),
1665 ) {
1666 Ok(rows_updated) => ensure!(rows_updated <= 1),
1667 Err(e) => {
1668 warn!(context, "get_mime_headers: UPDATE failed: {}", e);
1669 return Err(e.into());
1670 }
1671 }
1672 Ok(())
1673 };
1674 if let Err(e) = context.sql.call_write(update).await {
1675 warn!(
1676 context,
1677 "get_mime_headers: failed to update mime_headers: {}", e
1678 );
1679 }
1680
1681 Ok(headers)
1682}
1683
1684pub(crate) async fn delete_msg_locally(context: &Context, msg: &Message) -> Result<()> {
1687 if msg.location_id > 0 {
1688 delete_poi_location(context, msg.location_id).await?;
1689 }
1690 let on_server = true;
1691 msg.id
1692 .trash(context, on_server)
1693 .await
1694 .with_context(|| format!("Unable to trash message {}", msg.id))?;
1695
1696 context.emit_event(EventType::MsgDeleted {
1697 chat_id: msg.chat_id,
1698 msg_id: msg.id,
1699 });
1700
1701 if msg.viewtype == Viewtype::Webxdc {
1702 context.emit_event(EventType::WebxdcInstanceDeleted { msg_id: msg.id });
1703 }
1704
1705 let logging_xdc_id = context
1706 .debug_logging
1707 .read()
1708 .expect("RwLock is poisoned")
1709 .as_ref()
1710 .map(|dl| dl.msg_id);
1711 if let Some(id) = logging_xdc_id {
1712 if id == msg.id {
1713 set_debug_logging_xdc(context, None).await?;
1714 }
1715 }
1716
1717 Ok(())
1718}
1719
1720pub(crate) async fn delete_msgs_locally_done(
1723 context: &Context,
1724 msg_ids: &[MsgId],
1725 modified_chat_ids: HashSet<ChatId>,
1726) -> Result<()> {
1727 for modified_chat_id in modified_chat_ids {
1728 context.emit_msgs_changed_without_msg_id(modified_chat_id);
1729 chatlist_events::emit_chatlist_item_changed(context, modified_chat_id);
1730 }
1731 if !msg_ids.is_empty() {
1732 context.emit_msgs_changed_without_ids();
1733 chatlist_events::emit_chatlist_changed(context);
1734 context
1736 .set_config_internal(Config::LastHousekeeping, None)
1737 .await?;
1738 }
1739 Ok(())
1740}
1741
1742pub async fn delete_msgs(context: &Context, msg_ids: &[MsgId]) -> Result<()> {
1744 delete_msgs_ex(context, msg_ids, false).await
1745}
1746
1747pub async fn delete_msgs_ex(
1751 context: &Context,
1752 msg_ids: &[MsgId],
1753 delete_for_all: bool,
1754) -> Result<()> {
1755 let mut modified_chat_ids = HashSet::new();
1756 let mut deleted_rfc724_mid = Vec::new();
1757 let mut res = Ok(());
1758
1759 for &msg_id in msg_ids {
1760 let msg = Message::load_from_db(context, msg_id).await?;
1761 ensure!(
1762 !delete_for_all || msg.from_id == ContactId::SELF,
1763 "Can delete only own messages for others"
1764 );
1765 ensure!(
1766 !delete_for_all || msg.get_showpadlock(),
1767 "Cannot request deletion of unencrypted message for others"
1768 );
1769
1770 modified_chat_ids.insert(msg.chat_id);
1771 deleted_rfc724_mid.push(msg.rfc724_mid.clone());
1772
1773 let target = context.get_delete_msgs_target().await?;
1774 let update_db = |trans: &mut rusqlite::Transaction| {
1775 trans.execute(
1776 "UPDATE imap SET target=? WHERE rfc724_mid=?",
1777 (target, msg.rfc724_mid),
1778 )?;
1779 trans.execute("DELETE FROM smtp WHERE msg_id=?", (msg_id,))?;
1780 Ok(())
1781 };
1782 if let Err(e) = context.sql.transaction(update_db).await {
1783 error!(context, "delete_msgs: failed to update db: {e:#}.");
1784 res = Err(e);
1785 continue;
1786 }
1787 }
1788 res?;
1789
1790 if delete_for_all {
1791 ensure!(
1792 modified_chat_ids.len() == 1,
1793 "Can delete only from same chat."
1794 );
1795 if let Some(chat_id) = modified_chat_ids.iter().next() {
1796 let mut msg = Message::new_text("🚮".to_owned());
1797 msg.param.set_int(Param::GuaranteeE2ee, 1);
1802 msg.param
1803 .set(Param::DeleteRequestFor, deleted_rfc724_mid.join(" "));
1804 msg.hidden = true;
1805 send_msg(context, *chat_id, &mut msg).await?;
1806 }
1807 } else {
1808 context
1809 .add_sync_item(SyncData::DeleteMessages {
1810 msgs: deleted_rfc724_mid,
1811 })
1812 .await?;
1813 }
1814
1815 for &msg_id in msg_ids {
1816 let msg = Message::load_from_db(context, msg_id).await?;
1817 delete_msg_locally(context, &msg).await?;
1818 }
1819 delete_msgs_locally_done(context, msg_ids, modified_chat_ids).await?;
1820
1821 context.scheduler.interrupt_inbox().await;
1823
1824 Ok(())
1825}
1826
1827pub async fn markseen_msgs(context: &Context, msg_ids: Vec<MsgId>) -> Result<()> {
1829 if msg_ids.is_empty() {
1830 return Ok(());
1831 }
1832
1833 let old_last_msg_id = MsgId::new(context.get_config_u32(Config::LastMsgId).await?);
1834 let last_msg_id = msg_ids.iter().fold(&old_last_msg_id, std::cmp::max);
1835 context
1836 .set_config_internal(Config::LastMsgId, Some(&last_msg_id.to_u32().to_string()))
1837 .await?;
1838
1839 let mut msgs = Vec::with_capacity(msg_ids.len());
1840 for &id in &msg_ids {
1841 if let Some(msg) = context
1842 .sql
1843 .query_row_optional(
1844 "SELECT
1845 m.chat_id AS chat_id,
1846 m.state AS state,
1847 m.download_state as download_state,
1848 m.ephemeral_timer AS ephemeral_timer,
1849 m.param AS param,
1850 m.from_id AS from_id,
1851 m.rfc724_mid AS rfc724_mid,
1852 c.archived AS archived,
1853 c.blocked AS blocked
1854 FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id
1855 WHERE m.id=? AND m.chat_id>9",
1856 (id,),
1857 |row| {
1858 let chat_id: ChatId = row.get("chat_id")?;
1859 let state: MessageState = row.get("state")?;
1860 let download_state: DownloadState = row.get("download_state")?;
1861 let param: Params = row.get::<_, String>("param")?.parse().unwrap_or_default();
1862 let from_id: ContactId = row.get("from_id")?;
1863 let rfc724_mid: String = row.get("rfc724_mid")?;
1864 let visibility: ChatVisibility = row.get("archived")?;
1865 let blocked: Option<Blocked> = row.get("blocked")?;
1866 let ephemeral_timer: EphemeralTimer = row.get("ephemeral_timer")?;
1867 Ok((
1868 (
1869 id,
1870 chat_id,
1871 state,
1872 download_state,
1873 param,
1874 from_id,
1875 rfc724_mid,
1876 visibility,
1877 blocked.unwrap_or_default(),
1878 ),
1879 ephemeral_timer,
1880 ))
1881 },
1882 )
1883 .await?
1884 {
1885 msgs.push(msg);
1886 }
1887 }
1888
1889 if msgs
1890 .iter()
1891 .any(|(_, ephemeral_timer)| *ephemeral_timer != EphemeralTimer::Disabled)
1892 {
1893 start_ephemeral_timers_msgids(context, &msg_ids)
1894 .await
1895 .context("failed to start ephemeral timers")?;
1896 }
1897
1898 let mut updated_chat_ids = BTreeSet::new();
1899 let mut archived_chats_maybe_noticed = false;
1900 for (
1901 (
1902 id,
1903 curr_chat_id,
1904 curr_state,
1905 curr_download_state,
1906 curr_param,
1907 curr_from_id,
1908 curr_rfc724_mid,
1909 curr_visibility,
1910 curr_blocked,
1911 ),
1912 _curr_ephemeral_timer,
1913 ) in msgs
1914 {
1915 if curr_download_state != DownloadState::Done {
1916 if curr_state == MessageState::InFresh {
1917 update_msg_state(context, id, MessageState::InNoticed).await?;
1920 updated_chat_ids.insert(curr_chat_id);
1921 }
1922 } else if curr_state == MessageState::InFresh || curr_state == MessageState::InNoticed {
1923 update_msg_state(context, id, MessageState::InSeen).await?;
1924 info!(context, "Seen message {}.", id);
1925
1926 markseen_on_imap_table(context, &curr_rfc724_mid).await?;
1927
1928 if curr_blocked == Blocked::Not
1938 && curr_param.get_bool(Param::WantsMdn).unwrap_or_default()
1939 && curr_param.get_cmd() == SystemMessage::Unknown
1940 && context.should_send_mdns().await?
1941 {
1942 context
1943 .sql
1944 .execute(
1945 "INSERT INTO smtp_mdns (msg_id, from_id, rfc724_mid) VALUES(?, ?, ?)",
1946 (id, curr_from_id, curr_rfc724_mid),
1947 )
1948 .await
1949 .context("failed to insert into smtp_mdns")?;
1950 context.scheduler.interrupt_smtp().await;
1951 }
1952 updated_chat_ids.insert(curr_chat_id);
1953 }
1954 archived_chats_maybe_noticed |=
1955 curr_state == MessageState::InFresh && curr_visibility == ChatVisibility::Archived;
1956 }
1957
1958 for updated_chat_id in updated_chat_ids {
1959 context.emit_event(EventType::MsgsNoticed(updated_chat_id));
1960 chatlist_events::emit_chatlist_item_changed(context, updated_chat_id);
1961 }
1962 if archived_chats_maybe_noticed {
1963 context.on_archived_chats_maybe_noticed();
1964 }
1965
1966 Ok(())
1967}
1968
1969pub(crate) async fn update_msg_state(
1970 context: &Context,
1971 msg_id: MsgId,
1972 state: MessageState,
1973) -> Result<()> {
1974 ensure!(
1975 state != MessageState::OutMdnRcvd,
1976 "Update msgs_mdns table instead!"
1977 );
1978 ensure!(state != MessageState::OutFailed, "use set_msg_failed()!");
1979 let error_subst = match state >= MessageState::OutPending {
1980 true => ", error=''",
1981 false => "",
1982 };
1983 context
1984 .sql
1985 .execute(
1986 &format!("UPDATE msgs SET state=? {error_subst} WHERE id=?"),
1987 (state, msg_id),
1988 )
1989 .await?;
1990 Ok(())
1991}
1992
1993pub(crate) async fn set_msg_failed(
2001 context: &Context,
2002 msg: &mut Message,
2003 error: &str,
2004) -> Result<()> {
2005 if msg.state.can_fail() {
2006 msg.state = MessageState::OutFailed;
2007 warn!(context, "{} failed: {}", msg.id, error);
2008 } else {
2009 warn!(
2010 context,
2011 "{} seems to have failed ({}), but state is {}", msg.id, error, msg.state
2012 )
2013 }
2014 msg.error = Some(error.to_string());
2015
2016 let exists = context
2017 .sql
2018 .execute(
2019 "UPDATE msgs SET state=?, error=? WHERE id=?;",
2020 (msg.state, error, msg.id),
2021 )
2022 .await?
2023 > 0;
2024 context.emit_event(EventType::MsgFailed {
2025 chat_id: msg.chat_id,
2026 msg_id: msg.id,
2027 });
2028 if exists {
2029 chatlist_events::emit_chatlist_item_changed(context, msg.chat_id);
2030 }
2031 Ok(())
2032}
2033
2034pub async fn get_unblocked_msg_cnt(context: &Context) -> usize {
2036 match context
2037 .sql
2038 .count(
2039 "SELECT COUNT(*) \
2040 FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \
2041 WHERE m.id>9 AND m.chat_id>9 AND c.blocked=0;",
2042 (),
2043 )
2044 .await
2045 {
2046 Ok(res) => res,
2047 Err(err) => {
2048 error!(context, "get_unblocked_msg_cnt() failed. {:#}", err);
2049 0
2050 }
2051 }
2052}
2053
2054pub async fn get_request_msg_cnt(context: &Context) -> usize {
2056 match context
2057 .sql
2058 .count(
2059 "SELECT COUNT(*) \
2060 FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \
2061 WHERE c.blocked=2;",
2062 (),
2063 )
2064 .await
2065 {
2066 Ok(res) => res,
2067 Err(err) => {
2068 error!(context, "get_request_msg_cnt() failed. {:#}", err);
2069 0
2070 }
2071 }
2072}
2073
2074pub async fn estimate_deletion_cnt(
2090 context: &Context,
2091 from_server: bool,
2092 seconds: i64,
2093) -> Result<usize> {
2094 let self_chat_id = ChatIdBlocked::lookup_by_contact(context, ContactId::SELF)
2095 .await?
2096 .map(|c| c.id)
2097 .unwrap_or_default();
2098 let threshold_timestamp = time() - seconds;
2099
2100 let cnt = if from_server {
2101 context
2102 .sql
2103 .count(
2104 "SELECT COUNT(*)
2105 FROM msgs m
2106 WHERE m.id > ?
2107 AND timestamp < ?
2108 AND chat_id != ?
2109 AND EXISTS (SELECT * FROM imap WHERE rfc724_mid=m.rfc724_mid);",
2110 (DC_MSG_ID_LAST_SPECIAL, threshold_timestamp, self_chat_id),
2111 )
2112 .await?
2113 } else {
2114 context
2115 .sql
2116 .count(
2117 "SELECT COUNT(*)
2118 FROM msgs m
2119 WHERE m.id > ?
2120 AND timestamp < ?
2121 AND chat_id != ?
2122 AND chat_id != ? AND hidden = 0;",
2123 (
2124 DC_MSG_ID_LAST_SPECIAL,
2125 threshold_timestamp,
2126 self_chat_id,
2127 DC_CHAT_ID_TRASH,
2128 ),
2129 )
2130 .await?
2131 };
2132 Ok(cnt)
2133}
2134
2135pub(crate) async fn rfc724_mid_exists(
2137 context: &Context,
2138 rfc724_mid: &str,
2139) -> Result<Option<(MsgId, i64)>> {
2140 Ok(rfc724_mid_exists_ex(context, rfc724_mid, "1")
2141 .await?
2142 .map(|(id, ts_sent, _)| (id, ts_sent)))
2143}
2144
2145pub(crate) async fn rfc724_mid_exists_ex(
2151 context: &Context,
2152 rfc724_mid: &str,
2153 expr: &str,
2154) -> Result<Option<(MsgId, i64, bool)>> {
2155 let rfc724_mid = rfc724_mid.trim_start_matches('<').trim_end_matches('>');
2156 if rfc724_mid.is_empty() {
2157 warn!(context, "Empty rfc724_mid passed to rfc724_mid_exists");
2158 return Ok(None);
2159 }
2160
2161 let res = context
2162 .sql
2163 .query_row_optional(
2164 &("SELECT id, timestamp_sent, MIN(".to_string()
2165 + expr
2166 + ") FROM msgs WHERE rfc724_mid=?
2167 HAVING COUNT(*) > 0 -- Prevent MIN(expr) from returning NULL when there are no rows.
2168 ORDER BY timestamp_sent DESC"),
2169 (rfc724_mid,),
2170 |row| {
2171 let msg_id: MsgId = row.get(0)?;
2172 let timestamp_sent: i64 = row.get(1)?;
2173 let expr_res: bool = row.get(2)?;
2174 Ok((msg_id, timestamp_sent, expr_res))
2175 },
2176 )
2177 .await?;
2178
2179 Ok(res)
2180}
2181
2182pub(crate) async fn get_by_rfc724_mids(
2189 context: &Context,
2190 mids: &[String],
2191) -> Result<Option<Message>> {
2192 let mut latest = None;
2193 for id in mids.iter().rev() {
2194 let Some((msg_id, _)) = rfc724_mid_exists(context, id).await? else {
2195 continue;
2196 };
2197 let Some(msg) = Message::load_from_db_optional(context, msg_id).await? else {
2198 continue;
2199 };
2200 if msg.download_state == DownloadState::Done {
2201 return Ok(Some(msg));
2202 }
2203 latest.get_or_insert(msg);
2204 }
2205 Ok(latest)
2206}
2207
2208pub(crate) fn get_vcard_summary(vcard: &[u8]) -> Option<String> {
2210 let vcard = str::from_utf8(vcard).ok()?;
2211 let contacts = deltachat_contact_tools::parse_vcard(vcard);
2212 let [c] = &contacts[..] else {
2213 return None;
2214 };
2215 if !deltachat_contact_tools::may_be_valid_addr(&c.addr) {
2216 return None;
2217 }
2218 Some(c.display_name().to_string())
2219}
2220
2221#[derive(
2223 Debug,
2224 Default,
2225 Display,
2226 Clone,
2227 Copy,
2228 PartialEq,
2229 Eq,
2230 FromPrimitive,
2231 ToPrimitive,
2232 FromSql,
2233 ToSql,
2234 Serialize,
2235 Deserialize,
2236)]
2237#[repr(u32)]
2238pub enum Viewtype {
2239 #[default]
2241 Unknown = 0,
2242
2243 Text = 10,
2246
2247 Image = 20,
2253
2254 Gif = 21,
2258
2259 Sticker = 23,
2266
2267 Audio = 40,
2271
2272 Voice = 41,
2277
2278 Video = 50,
2285
2286 File = 60,
2290
2291 VideochatInvitation = 70,
2293
2294 Webxdc = 80,
2296
2297 Vcard = 90,
2301}
2302
2303impl Viewtype {
2304 pub fn has_file(&self) -> bool {
2306 match self {
2307 Viewtype::Unknown => false,
2308 Viewtype::Text => false,
2309 Viewtype::Image => true,
2310 Viewtype::Gif => true,
2311 Viewtype::Sticker => true,
2312 Viewtype::Audio => true,
2313 Viewtype::Voice => true,
2314 Viewtype::Video => true,
2315 Viewtype::File => true,
2316 Viewtype::VideochatInvitation => false,
2317 Viewtype::Webxdc => true,
2318 Viewtype::Vcard => true,
2319 }
2320 }
2321}
2322
2323pub(crate) fn normalize_text(text: &str) -> Option<String> {
2326 if text.is_ascii() {
2327 return None;
2328 };
2329 Some(text.to_lowercase()).filter(|t| t != text)
2330}
2331
2332#[cfg(test)]
2333mod message_tests;