diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index e03ec6864..f6c4c7e9f 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -1877,13 +1877,10 @@ pub unsafe extern "C" fn dc_get_msg_info( return "".strdup(); } let ctx = &*context; - - block_on(async move { - message::get_msg_info(ctx, MsgId::new(msg_id)) - .await - .unwrap_or_log_default(ctx, "failed to get msg id") - .strdup() - }) + let msg_id = MsgId::new(msg_id); + block_on(msg_id.get_info(ctx)) + .unwrap_or_log_default(ctx, "failed to get msg id") + .strdup() } #[no_mangle] diff --git a/deltachat-jsonrpc/src/api/mod.rs b/deltachat-jsonrpc/src/api/mod.rs index 7caf4030a..617f1d6c4 100644 --- a/deltachat-jsonrpc/src/api/mod.rs +++ b/deltachat-jsonrpc/src/api/mod.rs @@ -19,9 +19,7 @@ use deltachat::{ context::get_info, ephemeral::Timer, imex, location, - message::{ - self, delete_msgs, get_msg_info, markseen_msgs, Message, MessageState, MsgId, Viewtype, - }, + message::{self, delete_msgs, markseen_msgs, Message, MessageState, MsgId, Viewtype}, provider::get_provider_info, qr, qr_code_generator::{generate_backup_qr, get_securejoin_qr_svg}, @@ -1119,7 +1117,7 @@ impl CommandApi { /// max. text returned by dc_msg_get_text() (about 30000 characters). async fn get_message_info(&self, account_id: u32, message_id: u32) -> Result { let ctx = self.get_context(account_id).await?; - get_msg_info(&ctx, MsgId::new(message_id)).await + MsgId::new(message_id).get_info(&ctx).await } /// Returns contacts that sent read receipts and the time of reading. diff --git a/deltachat-repl/src/cmdline.rs b/deltachat-repl/src/cmdline.rs index 6f7a44195..c3e473dbe 100644 --- a/deltachat-repl/src/cmdline.rs +++ b/deltachat-repl/src/cmdline.rs @@ -1088,7 +1088,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu "msginfo" => { ensure!(!arg1.is_empty(), "Argument missing."); let id = MsgId::new(arg1.parse()?); - let res = message::get_msg_info(&context, id).await?; + let res = id.get_info(&context).await?; println!("{res}"); } "download" => { diff --git a/src/authres.rs b/src/authres.rs index 95c5caf20..2192bfbb5 100644 --- a/src/authres.rs +++ b/src/authres.rs @@ -357,7 +357,6 @@ mod tests { use super::*; use crate::aheader::EncryptPreference; use crate::e2ee; - use crate::message; use crate::mimeparser; use crate::peerstate::Peerstate; use crate::securejoin::get_securejoin_qr; @@ -825,7 +824,9 @@ Authentication-Results: dkim="; // Disallowing keychanges is disabled for now: // assert!(rcvd.error.unwrap().contains("DKIM failed")); // The message info should contain a warning: - assert!(message::get_msg_info(&bob, rcvd.id) + assert!(rcvd + .id + .get_info(&bob) .await .unwrap() .contains("KEYCHANGES NOT ALLOWED")); diff --git a/src/message.rs b/src/message.rs index 6c66978ec..990166bc2 100644 --- a/src/message.rs +++ b/src/message.rs @@ -157,6 +157,171 @@ WHERE id=?; pub fn to_u32(self) -> u32 { self.0 } + + /// Returns detailed message information in a multi-line text form. + pub async fn get_info(self, context: &Context) -> Result { + let msg = Message::load_from_db(context, self).await?; + let rawtxt: Option = context + .sql + .query_get_value("SELECT txt_raw FROM msgs WHERE id=?", (self,)) + .await?; + + let mut ret = String::new(); + + if rawtxt.is_none() { + ret += &format!("Cannot load message {self}."); + return Ok(ret); + } + let rawtxt = rawtxt.unwrap_or_default(); + let rawtxt = truncate(rawtxt.trim(), DC_DESIRED_TEXT_LEN); + + let fts = timestamp_to_str(msg.get_timestamp()); + ret += &format!("Sent: {fts}"); + + let name = Contact::get_by_id(context, msg.from_id) + .await + .map(|contact| contact.get_name_n_addr()) + .unwrap_or_default(); + + ret += &format!(" by {name}"); + ret += "\n"; + + if msg.from_id != ContactId::SELF { + let s = timestamp_to_str(if 0 != msg.timestamp_rcvd { + msg.timestamp_rcvd + } else { + msg.timestamp_sort + }); + ret += &format!("Received: {}", &s); + ret += "\n"; + } + + if let EphemeralTimer::Enabled { duration } = msg.ephemeral_timer { + ret += &format!("Ephemeral timer: {duration}\n"); + } + + if msg.ephemeral_timestamp != 0 { + ret += &format!("Expires: {}\n", timestamp_to_str(msg.ephemeral_timestamp)); + } + + if msg.from_id == ContactId::INFO || msg.to_id == ContactId::INFO { + // device-internal message, no further details needed + return Ok(ret); + } + + if let Ok(rows) = context + .sql + .query_map( + "SELECT contact_id, timestamp_sent FROM msgs_mdns WHERE msg_id=?", + (self,), + |row| { + let contact_id: ContactId = row.get(0)?; + let ts: i64 = row.get(1)?; + Ok((contact_id, ts)) + }, + |rows| rows.collect::, _>>().map_err(Into::into), + ) + .await + { + for (contact_id, ts) in rows { + let fts = timestamp_to_str(ts); + ret += &format!("Read: {fts}"); + + let name = Contact::get_by_id(context, contact_id) + .await + .map(|contact| contact.get_name_n_addr()) + .unwrap_or_default(); + + ret += &format!(" by {name}"); + ret += "\n"; + } + } + + ret += &format!("State: {}", msg.state); + + if msg.has_location() { + ret += ", Location sent"; + } + + let e2ee_errors = msg.param.get_int(Param::ErroneousE2ee).unwrap_or_default(); + + if 0 != e2ee_errors { + if 0 != e2ee_errors & 0x2 { + ret += ", Encrypted, no valid signature"; + } + } else if 0 != msg.param.get_int(Param::GuaranteeE2ee).unwrap_or_default() { + ret += ", Encrypted"; + } + + ret += "\n"; + + let reactions = get_msg_reactions(context, self).await?; + if !reactions.is_empty() { + ret += &format!("Reactions: {reactions}\n"); + } + + if let Some(error) = msg.error.as_ref() { + ret += &format!("Error: {error}"); + } + + if let Some(path) = msg.get_file(context) { + let bytes = get_filebytes(context, &path).await?; + ret += &format!("\nFile: {}, {} bytes\n", path.display(), bytes); + } + + if msg.viewtype != Viewtype::Text { + ret += "Type: "; + ret += &format!("{}", msg.viewtype); + ret += "\n"; + ret += &format!("Mimetype: {}\n", &msg.get_filemime().unwrap_or_default()); + } + let w = msg.param.get_int(Param::Width).unwrap_or_default(); + let h = msg.param.get_int(Param::Height).unwrap_or_default(); + if w != 0 || h != 0 { + ret += &format!("Dimension: {w} x {h}\n",); + } + let duration = msg.param.get_int(Param::Duration).unwrap_or_default(); + if duration != 0 { + ret += &format!("Duration: {duration} ms\n",); + } + if !rawtxt.is_empty() { + ret += &format!("\n{rawtxt}\n"); + } + if !msg.rfc724_mid.is_empty() { + ret += &format!("\nMessage-ID: {}", msg.rfc724_mid); + + let server_uids = context + .sql + .query_map( + "SELECT folder, uid FROM imap WHERE rfc724_mid=?", + (msg.rfc724_mid,), + |row| { + let folder: String = row.get("folder")?; + let uid: u32 = row.get("uid")?; + Ok((folder, uid)) + }, + |rows| { + rows.collect::, _>>() + .map_err(Into::into) + }, + ) + .await?; + + for (folder, uid) in server_uids { + // Format as RFC 5092 relative IMAP URL. + ret += &format!("\n"); + } + } + let hop_info: Option = context + .sql + .query_get_value("SELECT hop_info FROM msgs WHERE id=?;", (self,)) + .await?; + + ret += "\n\n"; + ret += &hop_info.unwrap_or_else(|| "No Hop Info".to_owned()); + + Ok(ret) + } } impl std::fmt::Display for MsgId { @@ -1109,171 +1274,6 @@ pub async fn get_msg_read_receipts( .await } -/// Returns detailed message information in a multi-line text form. -pub async fn get_msg_info(context: &Context, msg_id: MsgId) -> Result { - let msg = Message::load_from_db(context, msg_id).await?; - let rawtxt: Option = context - .sql - .query_get_value("SELECT txt_raw FROM msgs WHERE id=?;", (msg_id,)) - .await?; - - let mut ret = String::new(); - - if rawtxt.is_none() { - ret += &format!("Cannot load message {msg_id}."); - return Ok(ret); - } - let rawtxt = rawtxt.unwrap_or_default(); - let rawtxt = truncate(rawtxt.trim(), DC_DESIRED_TEXT_LEN); - - let fts = timestamp_to_str(msg.get_timestamp()); - ret += &format!("Sent: {fts}"); - - let name = Contact::get_by_id(context, msg.from_id) - .await - .map(|contact| contact.get_name_n_addr()) - .unwrap_or_default(); - - ret += &format!(" by {name}"); - ret += "\n"; - - if msg.from_id != ContactId::SELF { - let s = timestamp_to_str(if 0 != msg.timestamp_rcvd { - msg.timestamp_rcvd - } else { - msg.timestamp_sort - }); - ret += &format!("Received: {}", &s); - ret += "\n"; - } - - if let EphemeralTimer::Enabled { duration } = msg.ephemeral_timer { - ret += &format!("Ephemeral timer: {duration}\n"); - } - - if msg.ephemeral_timestamp != 0 { - ret += &format!("Expires: {}\n", timestamp_to_str(msg.ephemeral_timestamp)); - } - - if msg.from_id == ContactId::INFO || msg.to_id == ContactId::INFO { - // device-internal message, no further details needed - return Ok(ret); - } - - if let Ok(rows) = context - .sql - .query_map( - "SELECT contact_id, timestamp_sent FROM msgs_mdns WHERE msg_id=?;", - (msg_id,), - |row| { - let contact_id: ContactId = row.get(0)?; - let ts: i64 = row.get(1)?; - Ok((contact_id, ts)) - }, - |rows| rows.collect::, _>>().map_err(Into::into), - ) - .await - { - for (contact_id, ts) in rows { - let fts = timestamp_to_str(ts); - ret += &format!("Read: {fts}"); - - let name = Contact::get_by_id(context, contact_id) - .await - .map(|contact| contact.get_name_n_addr()) - .unwrap_or_default(); - - ret += &format!(" by {name}"); - ret += "\n"; - } - } - - ret += &format!("State: {}", msg.state); - - if msg.has_location() { - ret += ", Location sent"; - } - - let e2ee_errors = msg.param.get_int(Param::ErroneousE2ee).unwrap_or_default(); - - if 0 != e2ee_errors { - if 0 != e2ee_errors & 0x2 { - ret += ", Encrypted, no valid signature"; - } - } else if 0 != msg.param.get_int(Param::GuaranteeE2ee).unwrap_or_default() { - ret += ", Encrypted"; - } - - ret += "\n"; - - let reactions = get_msg_reactions(context, msg_id).await?; - if !reactions.is_empty() { - ret += &format!("Reactions: {reactions}\n"); - } - - if let Some(error) = msg.error.as_ref() { - ret += &format!("Error: {error}"); - } - - if let Some(path) = msg.get_file(context) { - let bytes = get_filebytes(context, &path).await?; - ret += &format!("\nFile: {}, {} bytes\n", path.display(), bytes); - } - - if msg.viewtype != Viewtype::Text { - ret += "Type: "; - ret += &format!("{}", msg.viewtype); - ret += "\n"; - ret += &format!("Mimetype: {}\n", &msg.get_filemime().unwrap_or_default()); - } - let w = msg.param.get_int(Param::Width).unwrap_or_default(); - let h = msg.param.get_int(Param::Height).unwrap_or_default(); - if w != 0 || h != 0 { - ret += &format!("Dimension: {w} x {h}\n",); - } - let duration = msg.param.get_int(Param::Duration).unwrap_or_default(); - if duration != 0 { - ret += &format!("Duration: {duration} ms\n",); - } - if !rawtxt.is_empty() { - ret += &format!("\n{rawtxt}\n"); - } - if !msg.rfc724_mid.is_empty() { - ret += &format!("\nMessage-ID: {}", msg.rfc724_mid); - - let server_uids = context - .sql - .query_map( - "SELECT folder, uid FROM imap WHERE rfc724_mid=?", - (msg.rfc724_mid,), - |row| { - let folder: String = row.get("folder")?; - let uid: u32 = row.get("uid")?; - Ok((folder, uid)) - }, - |rows| { - rows.collect::, _>>() - .map_err(Into::into) - }, - ) - .await?; - - for (folder, uid) in server_uids { - // Format as RFC 5092 relative IMAP URL. - ret += &format!("\n"); - } - } - let hop_info: Option = context - .sql - .query_get_value("SELECT hop_info FROM msgs WHERE id=?;", (msg_id,)) - .await?; - - ret += "\n\n"; - ret += &hop_info.unwrap_or_else(|| "No Hop Info".to_owned()); - - Ok(ret) -} - pub(crate) fn guess_msgtype_from_suffix(path: &Path) -> Option<(Viewtype, &str)> { let extension: &str = &path.extension()?.to_str()?.to_lowercase(); let info = match extension { diff --git a/src/tools.rs b/src/tools.rs index 048da3a85..8c397255f 100644 --- a/src/tools.rs +++ b/src/tools.rs @@ -713,7 +713,7 @@ mod tests { #![allow(clippy::indexing_slicing)] use super::*; - use crate::{message::get_msg_info, receive_imf::receive_imf, test_utils::TestContext}; + use crate::{receive_imf::receive_imf, test_utils::TestContext}; #[test] fn test_parse_receive_headers() { @@ -786,7 +786,7 @@ DKIM Results: Passed=true, Works=true, Allow_Keychange=true"; let t = TestContext::new_alice().await; receive_imf(&t, raw, false).await.unwrap(); let msg = t.get_last_msg().await; - let msg_info = get_msg_info(&t, msg.id).await.unwrap(); + let msg_info = msg.id.get_info(&t).await.unwrap(); // Ignore the first rows of the msg_info because they contain a // received time that depends on the test time which makes it impossible to