mirror of
https://github.com/chatmail/core.git
synced 2026-05-03 13:26:28 +03:00
feat: MsgId::get_html: Make only one db query
Merge `message::get_mime_headers()` into `MsgId::get_html()` for that, it's unlikely that it will be used elsewhere.
This commit is contained in:
67
src/html.rs
67
src/html.rs
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
|
||||||
use anyhow::{Context as _, Result};
|
use anyhow::{Context as _, Result, ensure};
|
||||||
use base64::Engine as _;
|
use base64::Engine as _;
|
||||||
use mailparse::ParsedContentType;
|
use mailparse::ParsedContentType;
|
||||||
use mime::Mime;
|
use mime::Mime;
|
||||||
@@ -17,10 +17,12 @@ use mime::Mime;
|
|||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::headerdef::{HeaderDef, HeaderDefMap};
|
use crate::headerdef::{HeaderDef, HeaderDefMap};
|
||||||
use crate::log::warn;
|
use crate::log::warn;
|
||||||
use crate::message::{self, Message, MsgId};
|
use crate::message::{Message, MsgId};
|
||||||
use crate::mimeparser::parse_message_id;
|
use crate::mimeparser::parse_message_id;
|
||||||
use crate::param::Param::SendHtml;
|
use crate::param::{Param::SendHtml, Params};
|
||||||
use crate::plaintext::PlainText;
|
use crate::plaintext::PlainText;
|
||||||
|
use crate::sql;
|
||||||
|
use crate::tools::{buf_compress, buf_decompress};
|
||||||
|
|
||||||
impl Message {
|
impl Message {
|
||||||
/// Check if the message can be retrieved as HTML.
|
/// Check if the message can be retrieved as HTML.
|
||||||
@@ -258,16 +260,24 @@ impl MsgId {
|
|||||||
/// NB: we do not save raw mime unconditionally in the database to save space.
|
/// NB: we do not save raw mime unconditionally in the database to save space.
|
||||||
/// The corresponding ffi-function is `dc_get_msg_html()`.
|
/// The corresponding ffi-function is `dc_get_msg_html()`.
|
||||||
pub async fn get_html(self, context: &Context) -> Result<Option<String>> {
|
pub async fn get_html(self, context: &Context) -> Result<Option<String>> {
|
||||||
// If there are many concurrent db readers, going to the queue earlier makes sense.
|
let (param, headers, compressed) = context
|
||||||
let (param, rawmime) = tokio::join!(
|
.sql
|
||||||
self.get_param(context),
|
.query_row(
|
||||||
message::get_mime_headers(context, self)
|
"SELECT param, mime_headers, mime_compressed FROM msgs WHERE id=?",
|
||||||
);
|
(self,),
|
||||||
if let Some(html) = param?.get(SendHtml) {
|
|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()));
|
return Ok(Some(html.to_string()));
|
||||||
}
|
}
|
||||||
|
let from_rawmime = |rawmime: Vec<u8>| {
|
||||||
let rawmime = rawmime?;
|
|
||||||
if !rawmime.is_empty() {
|
if !rawmime.is_empty() {
|
||||||
match HtmlMsgParser::from_bytes(context, &rawmime) {
|
match HtmlMsgParser::from_bytes(context, &rawmime) {
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
@@ -280,6 +290,41 @@ impl MsgId {
|
|||||||
warn!(context, "get_html: no mime for {}", self);
|
warn!(context, "get_html: no mime for {}", self);
|
||||||
Ok(None)
|
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(())
|
||||||
|
};
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,13 +30,12 @@ use crate::log::warn;
|
|||||||
use crate::mimeparser::{SystemMessage, parse_message_id};
|
use crate::mimeparser::{SystemMessage, parse_message_id};
|
||||||
use crate::param::{Param, Params};
|
use crate::param::{Param, Params};
|
||||||
use crate::reaction::get_msg_reactions;
|
use crate::reaction::get_msg_reactions;
|
||||||
use crate::sql;
|
|
||||||
use crate::summary::Summary;
|
use crate::summary::Summary;
|
||||||
use crate::sync::SyncData;
|
use crate::sync::SyncData;
|
||||||
use crate::tools::create_outgoing_rfc724_mid;
|
use crate::tools::create_outgoing_rfc724_mid;
|
||||||
use crate::tools::{
|
use crate::tools::{
|
||||||
buf_compress, buf_decompress, get_filebytes, get_filemeta, gm2local_offset, read_file,
|
get_filebytes, get_filemeta, gm2local_offset, read_file, sanitize_filename, time,
|
||||||
sanitize_filename, time, timestamp_to_str,
|
timestamp_to_str,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Message ID, including reserved IDs.
|
/// Message ID, including reserved IDs.
|
||||||
@@ -1595,62 +1594,6 @@ pub(crate) fn guess_msgtype_from_path_suffix(path: &Path) -> Option<(Viewtype, &
|
|||||||
Some(info)
|
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<Vec<u8>> {
|
|
||||||
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.
|
/// 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.
|
/// 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<()> {
|
pub(crate) async fn delete_msg_locally(context: &Context, msg: &Message) -> Result<()> {
|
||||||
|
|||||||
@@ -1684,8 +1684,8 @@ async fn test_save_mime_headers_off() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
let msg = bob.recv_msg(&alice.pop_sent_msg().await).await;
|
let msg = bob.recv_msg(&alice.pop_sent_msg().await).await;
|
||||||
assert_eq!(msg.get_text(), "hi!");
|
assert_eq!(msg.get_text(), "hi!");
|
||||||
let mime = message::get_mime_headers(&bob, msg.id).await?;
|
let html = msg.id.get_html(&bob).await?;
|
||||||
assert!(mime.is_empty());
|
assert!(html.is_none());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user