diff --git a/src/html.rs b/src/html.rs index fec82589f0..20e1ba319f 100644 --- a/src/html.rs +++ b/src/html.rs @@ -9,7 +9,7 @@ use std::mem; -use anyhow::{Context as _, Result}; +use anyhow::{Context as _, Result, ensure}; use base64::Engine as _; use mailparse::ParsedContentType; use mime::Mime; @@ -17,10 +17,12 @@ use mime::Mime; use crate::context::Context; use crate::headerdef::{HeaderDef, HeaderDefMap}; use crate::log::warn; -use crate::message::{self, Message, MsgId}; +use crate::message::{Message, MsgId}; use crate::mimeparser::parse_message_id; -use crate::param::Param::SendHtml; +use crate::param::{Param::SendHtml, Params}; use crate::plaintext::PlainText; +use crate::sql; +use crate::tools::{buf_compress, buf_decompress}; impl Message { /// Check if the message can be retrieved as HTML. @@ -258,28 +260,71 @@ impl MsgId { /// NB: we do not save raw mime unconditionally in the database to save space. /// The corresponding ffi-function is `dc_get_msg_html()`. pub async fn get_html(self, context: &Context) -> Result> { - // If there are many concurrent db readers, going to the queue earlier makes sense. - let (param, rawmime) = tokio::join!( - self.get_param(context), - message::get_mime_headers(context, self) - ); - if let Some(html) = param?.get(SendHtml) { + let (param, headers, compressed) = context + .sql + .query_row( + "SELECT param, mime_headers, mime_compressed FROM msgs WHERE id=?", + (self,), + |row| { + let param: String = row.get(0)?; + let param: Params = param.parse().unwrap_or_default(); + let headers = sql::row_get_vec(row, 1)?; + let compressed: bool = row.get(2)?; + Ok((param, headers, compressed)) + }, + ) + .await?; + if let Some(html) = param.get(SendHtml) { return Ok(Some(html.to_string())); } + let from_rawmime = |rawmime: Vec| async move { + if !rawmime.is_empty() { + match HtmlMsgParser::from_bytes(context, &rawmime).await { + Err(err) => { + warn!(context, "get_html: parser error: {:#}", err); + Ok(None) + } + Ok((parser, _)) => Ok(Some(parser.html)), + } + } else { + warn!(context, "get_html: no mime for {}", self); + Ok(None) + } + }; - let rawmime = rawmime?; - if !rawmime.is_empty() { - match HtmlMsgParser::from_bytes(context, &rawmime).await { - Err(err) => { - warn!(context, "get_html: parser error: {:#}", err); - Ok(None) + if compressed { + return from_rawmime(buf_decompress(&headers)?).await; + } + let headers2 = headers.clone(); + let compressed = match tokio::task::block_in_place(move || buf_compress(&headers2)) { + Err(e) => { + warn!(context, "get_mime_headers: buf_compress() failed: {}", e); + return from_rawmime(headers).await; + } + Ok(o) => o, + }; + let update = |conn: &mut rusqlite::Connection| { + match conn.execute( + " +UPDATE msgs SET mime_headers=?, mime_compressed=1 +WHERE id=? AND mime_headers!='' AND mime_compressed=0", + (compressed, self), + ) { + Ok(rows_updated) => ensure!(rows_updated <= 1), + Err(e) => { + warn!(context, "get_mime_headers: UPDATE failed: {}", e); + return Err(e.into()); } - Ok((parser, _)) => Ok(Some(parser.html)), } - } else { - warn!(context, "get_html: no mime for {}", self); - Ok(None) + Ok(()) + }; + if let Err(e) = context.sql.call_write(update).await { + warn!( + context, + "get_mime_headers: failed to update mime_headers: {}", e + ); } + return from_rawmime(headers).await; } } diff --git a/src/message.rs b/src/message.rs index 0676daf7d8..f97f6ed956 100644 --- a/src/message.rs +++ b/src/message.rs @@ -32,13 +32,12 @@ use crate::mimeparser::{SystemMessage, parse_message_id}; use crate::param::{Param, Params}; use crate::pgp::split_armored_data; use crate::reaction::get_msg_reactions; -use crate::sql; use crate::summary::Summary; use crate::sync::SyncData; use crate::tools::create_outgoing_rfc724_mid; use crate::tools::{ - buf_compress, buf_decompress, get_filebytes, get_filemeta, gm2local_offset, read_file, - sanitize_filename, time, timestamp_to_str, + get_filebytes, get_filemeta, gm2local_offset, read_file, sanitize_filename, time, + timestamp_to_str, }; /// Message ID, including reserved IDs. @@ -1617,62 +1616,6 @@ pub(crate) fn guess_msgtype_from_path_suffix(path: &Path) -> Option<(Viewtype, & Some(info) } -/// Get the raw mime-headers of the given message. -/// Raw headers are saved for large messages -/// that need a "Show full message..." -/// to see HTML part. -/// -/// Returns an empty vector if there are no headers saved for the given message. -pub(crate) async fn get_mime_headers(context: &Context, msg_id: MsgId) -> Result> { - let (headers, compressed) = context - .sql - .query_row( - "SELECT mime_headers, mime_compressed FROM msgs WHERE id=?", - (msg_id,), - |row| { - let headers = sql::row_get_vec(row, 0)?; - let compressed: bool = row.get(1)?; - Ok((headers, compressed)) - }, - ) - .await?; - if compressed { - return buf_decompress(&headers); - } - - let headers2 = headers.clone(); - let compressed = match tokio::task::block_in_place(move || buf_compress(&headers2)) { - Err(e) => { - warn!(context, "get_mime_headers: buf_compress() failed: {}", e); - return Ok(headers); - } - Ok(o) => o, - }; - let update = |conn: &mut rusqlite::Connection| { - match conn.execute( - "\ - UPDATE msgs SET mime_headers=?, mime_compressed=1 \ - WHERE id=? AND mime_headers!='' AND mime_compressed=0", - (compressed, msg_id), - ) { - Ok(rows_updated) => ensure!(rows_updated <= 1), - Err(e) => { - warn!(context, "get_mime_headers: UPDATE failed: {}", e); - return Err(e.into()); - } - } - Ok(()) - }; - if let Err(e) = context.sql.call_write(update).await { - warn!( - context, - "get_mime_headers: failed to update mime_headers: {}", e - ); - } - - Ok(headers) -} - /// Delete a single message from the database, including references in other tables. /// This may be called in batches; the final events are emitted in delete_msgs_locally_done() then. pub(crate) async fn delete_msg_locally(context: &Context, msg: &Message) -> Result<()> { diff --git a/src/receive_imf/receive_imf_tests.rs b/src/receive_imf/receive_imf_tests.rs index 8209de31b9..4325a766e0 100644 --- a/src/receive_imf/receive_imf_tests.rs +++ b/src/receive_imf/receive_imf_tests.rs @@ -1680,8 +1680,8 @@ async fn test_save_mime_headers_off() -> anyhow::Result<()> { let msg = bob.recv_msg(&alice.pop_sent_msg().await).await; assert_eq!(msg.get_text(), "hi!"); - let mime = message::get_mime_headers(&bob, msg.id).await?; - assert!(mime.is_empty()); + let html = msg.id.get_html(&bob).await?; + assert!(html.is_none()); Ok(()) }