feat: use fjall to store detached emails and attachments

This commit is contained in:
rustmailer
2026-04-01 04:49:18 +08:00
parent c123ecb24a
commit bd10e15c65
36 changed files with 569 additions and 1542 deletions
+7 -35
View File
@@ -1,19 +1,17 @@
use std::collections::HashSet;
use std::{collections::HashSet, io::Cursor};
use crate::{
modules::{
envelope::extractor::reattach_eml_content,
error::{code::ErrorCode, BichonResult},
settings::dir::DATA_DIR_MANAGER,
utils::compute_content_hash,
},
raise_error,
};
use bytes::Bytes;
use mail_parser::MessageParser;
use poem_openapi::Object;
use serde::{Deserialize, Serialize};
use tokio::fs::File;
use tokio::io::AsyncWriteExt;
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)]
pub struct AttachmentMetadata {
@@ -34,8 +32,8 @@ pub async fn retrieve_attachment_content(
account_id: u64,
envelope_id: String,
content_hash: &str,
) -> BichonResult<File> {
let (envelope, eml) = reattach_eml_content(account_id, envelope_id).await?;
) -> BichonResult<Cursor<Bytes>> {
let (_, eml) = reattach_eml_content(account_id, envelope_id).await?;
let message = MessageParser::default().parse(&eml).ok_or_else(|| {
raise_error!(
"Failed to parse parent EML".into(),
@@ -53,20 +51,7 @@ pub async fn retrieve_attachment_content(
ErrorCode::ResourceNotFound
)
})?;
let mut path = DATA_DIR_MANAGER.temp_dir.clone();
path.push(format!("{}.eml", envelope.content_hash));
{
let mut file = File::create(&path)
.await
.map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?;
file.write_all(attachment_content)
.await
.map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?;
}
let file = File::open(&path)
.await
.map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?;
Ok(file)
Ok(Cursor::new(Bytes::copy_from_slice(attachment_content)))
}
pub async fn retrieve_nested_attachment_content(
@@ -74,7 +59,7 @@ pub async fn retrieve_nested_attachment_content(
envelope_id: String,
content_hash: &str,
nested_content_hash: &str,
) -> BichonResult<File> {
) -> BichonResult<Cursor<Bytes>> {
let (_, eml) = reattach_eml_content(account_id, envelope_id).await?;
let parent_message = MessageParser::default().parse(&eml).ok_or_else(|| {
raise_error!(
@@ -114,18 +99,5 @@ pub async fn retrieve_nested_attachment_content(
)
})?;
let mut path = DATA_DIR_MANAGER.temp_dir.clone();
path.push(format!("{}.eml", nested_content_hash));
{
let mut file = File::create(&path)
.await
.map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?;
file.write_all(attachment_content)
.await
.map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?;
}
let file = File::open(&path)
.await
.map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?;
Ok(file)
Ok(Cursor::new(Bytes::copy_from_slice(attachment_content)))
}
+1 -1
View File
@@ -22,7 +22,7 @@ use crate::modules::envelope::extractor::{
extract_envelope_from_nested_message, reattach_eml_content,
};
use crate::modules::error::code::ErrorCode;
use crate::modules::indexer::envelope::Envelope;
use crate::modules::blob::envelope::Envelope;
use crate::modules::utils::compute_content_hash;
use crate::{modules::error::BichonResult, raise_error};
use mail_parser::{MessageParser, MimeHeaders};
+3 -5
View File
@@ -16,10 +16,9 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use crate::modules::blob::manager::ENVELOPE_INDEX_MANAGER;
use crate::modules::blob::storage::BLOB_MANAGER;
use crate::modules::error::BichonResult;
use crate::modules::indexer::attachment::ATTACHMENT_INDEX_MANAGER;
use crate::modules::indexer::eml::EML_INDEX_MANAGER;
use crate::modules::indexer::manager::ENVELOPE_INDEX_MANAGER;
use std::collections::HashMap;
pub async fn delete_messages_impl(request: HashMap<u64, Vec<String>>) -> BichonResult<()> {
@@ -27,8 +26,7 @@ pub async fn delete_messages_impl(request: HashMap<u64, Vec<String>>) -> BichonR
.get_orphan_hashes_in_memory(request.clone())
.await?;
if !content_hashes.is_empty() {
EML_INDEX_MANAGER.delete(&content_hashes).await?;
ATTACHMENT_INDEX_MANAGER.delete(&content_hashes).await?;
BLOB_MANAGER.delete(&content_hashes, &content_hashes)?;
}
ENVELOPE_INDEX_MANAGER
.delete_envelopes_multi_account(request)
+1 -1
View File
@@ -20,7 +20,7 @@ use crate::{
modules::{
account::migration::AccountModel,
error::{code::ErrorCode, BichonResult},
indexer::{envelope::Envelope, manager::ENVELOPE_INDEX_MANAGER},
blob::{envelope::Envelope, manager::ENVELOPE_INDEX_MANAGER},
rest::response::DataPage,
},
raise_error,
+1 -1
View File
@@ -25,7 +25,7 @@ use crate::{
modules::{
duckdb::init::duckdb,
error::{code::ErrorCode, BichonResult},
indexer::{envelope::Envelope, manager::ENVELOPE_INDEX_MANAGER},
blob::{envelope::Envelope, manager::ENVELOPE_INDEX_MANAGER},
rest::response::DataPage,
},
raise_error,