mirror of
https://github.com/rustmailer/rustmailer.git
synced 2026-09-21 00:02:12 +00:00
209 lines
7.0 KiB
Rust
209 lines
7.0 KiB
Rust
// Copyright © 2025 rustmailer.com
|
|
// Licensed under RustMailer License Agreement v1.0
|
|
// Unauthorized copying, modification, or distribution is prohibited.
|
|
|
|
use std::time::Instant;
|
|
|
|
use native_db::*;
|
|
use native_model::{native_model, Model};
|
|
use serde::{Deserialize, Serialize};
|
|
use tracing::{error, info};
|
|
|
|
use crate::{
|
|
modules::{
|
|
cache::imap::{manager::EnvelopeFlagsManager, v2::EmailEnvelopeV3},
|
|
database::{
|
|
batch_delete_impl, batch_insert_impl, delete_impl, filter_by_secondary_key_impl,
|
|
manager::DB_MANAGER, update_impl,
|
|
},
|
|
error::{code::ErrorCode, RustMailerResult},
|
|
utils::envelope_hash,
|
|
},
|
|
raise_error,
|
|
};
|
|
|
|
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
|
|
#[native_model(id = 3, version = 1)]
|
|
#[native_db(primary_key(pk -> u64))]
|
|
pub struct MinimalEnvelope {
|
|
/// The ID of the account owning the email.
|
|
#[secondary_key]
|
|
pub account_id: u64,
|
|
/// The unique identifier of the mailbox where the email is stored (e.g., `MailBox::id`).
|
|
/// Used for indexing to avoid updating indexes when mailboxes are renamed.
|
|
#[secondary_key]
|
|
pub mailbox_id: u64,
|
|
/// The unique identifier (IMAP UID) of the email within the mailbox.
|
|
pub uid: u32,
|
|
/// A hash of the email's flags for efficient comparison or indexing.
|
|
pub flags_hash: u64,
|
|
}
|
|
|
|
impl MinimalEnvelope {
|
|
/// Generates a primary key to ensure ordered storage of email metadata by internal_date.
|
|
pub fn pk(&self) -> u64 {
|
|
envelope_hash(self.account_id, self.mailbox_id, self.uid)
|
|
}
|
|
|
|
pub async fn list_by_account(account_id: u64) -> RustMailerResult<Vec<MinimalEnvelope>> {
|
|
filter_by_secondary_key_impl(
|
|
DB_MANAGER.envelope_db(),
|
|
MinimalEnvelopeKey::account_id,
|
|
account_id,
|
|
)
|
|
.await
|
|
}
|
|
|
|
pub async fn batch_insert(envelopes: Vec<MinimalEnvelope>) -> RustMailerResult<()> {
|
|
for e in &envelopes {
|
|
EnvelopeFlagsManager::update_flag_change(
|
|
e.account_id,
|
|
e.mailbox_id,
|
|
e.uid,
|
|
e.flags_hash,
|
|
);
|
|
}
|
|
batch_insert_impl(DB_MANAGER.envelope_db(), envelopes).await?;
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn clean_mailbox_envelopes(account_id: u64, mailbox_id: u64) -> RustMailerResult<()> {
|
|
const BATCH_SIZE: usize = 200;
|
|
let mut total_deleted = 0usize;
|
|
let start_time = Instant::now();
|
|
loop {
|
|
let deleted = batch_delete_impl(DB_MANAGER.envelope_db(), move |rw| {
|
|
let to_delete: Vec<MinimalEnvelope> = rw
|
|
.scan()
|
|
.secondary(MinimalEnvelopeKey::mailbox_id)
|
|
.map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?
|
|
.start_with(mailbox_id)
|
|
.map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?
|
|
.take(BATCH_SIZE)
|
|
.filter_map(Result::ok) // filter only Ok values
|
|
.filter(|e: &MinimalEnvelope| e.account_id == account_id)
|
|
.collect();
|
|
Ok(to_delete)
|
|
})
|
|
.await?;
|
|
total_deleted += deleted;
|
|
// If this batch is empty, break the loop
|
|
if deleted == 0 {
|
|
break;
|
|
}
|
|
}
|
|
|
|
info!(
|
|
"Finished deleting envelopes for mailbox_id={} account_id={} total_deleted={} in {:?}",
|
|
mailbox_id,
|
|
account_id,
|
|
total_deleted,
|
|
start_time.elapsed()
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn clean_envelopes(
|
|
account_id: u64,
|
|
mailbox_id: u64,
|
|
to_delete_uid: &[u32],
|
|
) -> RustMailerResult<()> {
|
|
for uid in to_delete_uid {
|
|
let key = envelope_hash(account_id, mailbox_id, *uid);
|
|
delete_impl(DB_MANAGER.envelope_db(), move |rw| {
|
|
rw.get()
|
|
.primary::<MinimalEnvelope>(key)
|
|
.map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?
|
|
.ok_or_else(|| {
|
|
raise_error!("minimal envelope missing".into(), ErrorCode::InternalError)
|
|
})
|
|
})
|
|
.await?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn update_flags(
|
|
account_id: u64,
|
|
mailbox_id: u64,
|
|
uid: u32,
|
|
flags_hash: u64,
|
|
) -> RustMailerResult<()> {
|
|
update_impl(
|
|
DB_MANAGER.envelope_db(),
|
|
move |rw| {
|
|
rw.get()
|
|
.primary::<MinimalEnvelope>(envelope_hash(account_id, mailbox_id, uid))
|
|
.map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?
|
|
.ok_or_else(|| {
|
|
raise_error!(
|
|
"The MinimalEnvelope that you want to modify was not found."
|
|
.to_string(),
|
|
ErrorCode::ResourceNotFound
|
|
)
|
|
})
|
|
},
|
|
move |current| {
|
|
let mut updated = current.clone();
|
|
updated.flags_hash = flags_hash;
|
|
Ok(updated)
|
|
},
|
|
)
|
|
.await
|
|
.map_err(|e| {
|
|
error!(
|
|
"Failed to update flags: account_id={}, mailbox_id={}, uid={}, error={:?}",
|
|
account_id, mailbox_id, uid, e
|
|
);
|
|
e
|
|
})?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn clean_account(account_id: u64) -> RustMailerResult<()> {
|
|
const BATCH_SIZE: usize = 200;
|
|
let mut total_deleted = 0usize;
|
|
let start_time = Instant::now();
|
|
loop {
|
|
let deleted = batch_delete_impl(DB_MANAGER.envelope_db(), move |rw| {
|
|
let to_delete: Vec<MinimalEnvelope> = rw
|
|
.scan()
|
|
.secondary(MinimalEnvelopeKey::account_id)
|
|
.map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?
|
|
.start_with(account_id)
|
|
.map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?
|
|
.take(BATCH_SIZE)
|
|
.filter_map(Result::ok) // filter only Ok values
|
|
.collect();
|
|
Ok(to_delete)
|
|
})
|
|
.await?;
|
|
total_deleted += deleted;
|
|
// If this batch is empty, break the loop
|
|
if deleted == 0 {
|
|
break;
|
|
}
|
|
}
|
|
|
|
info!(
|
|
"Finished deleting envelopes for account_id={} total_deleted={} in {:?}",
|
|
account_id,
|
|
total_deleted,
|
|
start_time.elapsed()
|
|
);
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl From<&EmailEnvelopeV3> for MinimalEnvelope {
|
|
fn from(value: &EmailEnvelopeV3) -> Self {
|
|
Self {
|
|
account_id: value.account_id,
|
|
mailbox_id: value.mailbox_id,
|
|
uid: value.uid,
|
|
flags_hash: value.flags_hash,
|
|
}
|
|
}
|
|
}
|