This commit is contained in:
rustmailer
2026-04-01 21:34:54 +08:00
parent 64a66b4f98
commit 8b4dc44c07
6 changed files with 59 additions and 71 deletions
+6 -6
View File
@@ -84,13 +84,13 @@ impl EnvelopeIndexManager {
Some(doc) => { Some(doc) => {
buffer.push(doc); buffer.push(doc);
if buffer.len() >= ENVELOPE_BATCH_SIZE { if buffer.len() >= ENVELOPE_BATCH_SIZE {
ENVELOPE_INDEX_MANAGER.drain_and_commit(&mut buffer).await; ENVELOPE_INDEX_MANAGER.flush(&mut buffer).await;
} }
} }
None => { None => {
if !buffer.is_empty() { if !buffer.is_empty() {
tracing::info!("Channel closed, flushing remaining {} items", buffer.len()); tracing::info!("Channel closed, flushing remaining {} items", buffer.len());
ENVELOPE_INDEX_MANAGER.drain_and_commit(&mut buffer).await; ENVELOPE_INDEX_MANAGER.flush(&mut buffer).await;
} }
break; break;
}, },
@@ -98,11 +98,11 @@ impl EnvelopeIndexManager {
} }
_ = interval.tick() => { _ = interval.tick() => {
if !buffer.is_empty() { if !buffer.is_empty() {
ENVELOPE_INDEX_MANAGER.drain_and_commit(&mut buffer).await; ENVELOPE_INDEX_MANAGER.flush(&mut buffer).await;
} }
} }
_ = shutdown.recv() => { _ = shutdown.recv() => {
ENVELOPE_INDEX_MANAGER.drain_and_commit(&mut buffer).await; ENVELOPE_INDEX_MANAGER.flush(&mut buffer).await;
break; break;
} }
} }
@@ -114,11 +114,11 @@ impl EnvelopeIndexManager {
} }
} }
pub async fn add_document(&self, doc: (Envelope, Vec<AttachmentInfo>)) { pub async fn queue(&self, doc: (Envelope, Vec<AttachmentInfo>)) {
let _ = self.sender.send(doc).await; let _ = self.sender.send(doc).await;
} }
async fn drain_and_commit(&self, buffer: &mut Vec<(Envelope, Vec<AttachmentInfo>)>) { async fn flush(&self, buffer: &mut Vec<(Envelope, Vec<AttachmentInfo>)>) {
if buffer.is_empty() { if buffer.is_empty() {
return; return;
} }
+45 -28
View File
@@ -37,6 +37,45 @@ impl BlobManager {
} }
} }
fn process_detached_email(
eml: DetachedEmail,
store: &Database,
email_ks: &Keyspace,
attach_ks: &Keyspace,
) {
let (email_hash, email_data) = eml.email;
let mut batch = store.batch();
let mut needs_commit = false;
match email_ks.contains_key(&email_hash) {
Ok(false) => {
batch.insert(email_ks, email_hash.as_bytes(), email_data);
needs_commit = true;
}
Err(e) => tracing::error!("Fjall email_ks error: {:?}", e),
_ => {}
}
if let Some(attachments) = eml.attachments {
for (a_hash, a_data) in attachments {
match attach_ks.contains_key(&a_hash) {
Ok(false) => {
batch.insert(attach_ks, a_hash.as_bytes(), a_data);
needs_commit = true;
}
Err(e) => tracing::error!("Fjall attach_ks error: {:?}", e),
_ => {}
}
}
}
if needs_commit {
if let Err(e) = batch.commit() {
tracing::error!("Fjall Batch Commit Error: {:?}", e);
}
}
}
pub fn new() -> Self { pub fn new() -> Self {
let db = Database::builder(&DATA_DIR_MANAGER.eml_dir) let db = Database::builder(&DATA_DIR_MANAGER.eml_dir)
.open() .open()
@@ -74,27 +113,16 @@ impl BlobManager {
let (sender, mut receiver) = mpsc::channel::<DetachedEmail>(100); let (sender, mut receiver) = mpsc::channel::<DetachedEmail>(100);
let store = db.clone(); let store = db.clone();
let email_keyspace_clone = email_keyspace.clone(); let email_ks = email_keyspace.clone();
let attachments_keyspace_clone = attachments_keyspace.clone(); let attach_ks = attachments_keyspace.clone();
let handler = task::spawn(async move { let handler = task::spawn(async move {
let mut shutdown = SIGNAL_MANAGER.subscribe(); let mut shutdown = SIGNAL_MANAGER.subscribe();
loop { loop {
tokio::select! { tokio::select! {
res = receiver.recv() => { res = receiver.recv() => {
match res { match res {
Some(email) => { Some(eml) => {
let mut batch = store.batch(); Self::process_detached_email(eml, &store, &email_ks, &attach_ks);
batch.insert(&email_keyspace_clone, email.email.0, email.email.1);
if let Some(attachments) = email.attachments {
for a in attachments {
batch.insert(&attachments_keyspace_clone,a.0, a.1);
}
}
if let Err(e) = batch.commit() {
tracing::error!("Fjall Put Error {:?}", e);
} else {
tracing::info!("Fjall Put Success");
}
} }
None => { None => {
tracing::info!("BlobManager: All senders dropped, closing storage."); tracing::info!("BlobManager: All senders dropped, closing storage.");
@@ -110,19 +138,8 @@ impl BlobManager {
remaining remaining
); );
while let Some(email) = receiver.recv().await { while let Some(eml) = receiver.recv().await {
let mut batch = store.batch(); Self::process_detached_email(eml, &store, &email_ks, &attach_ks);
batch.insert(&email_keyspace_clone, email.email.0, email.email.1);
if let Some(attachments) = email.attachments {
for a in attachments {
batch.insert(&attachments_keyspace_clone,a.0, a.1);
}
}
if let Err(e) = batch.commit() {
tracing::error!("Fjall Put Error {:?}", e);
} else {
tracing::info!("Fjall Put Success");
}
} }
tracing::info!("BlobManager: All remaining tasks processed. Closing Fjall."); tracing::info!("BlobManager: All remaining tasks processed. Closing Fjall.");
+3 -2
View File
@@ -83,8 +83,9 @@ impl DashboardStats {
stat.storage_usage_bytes = get_total_size(&DATA_DIR_MANAGER.eml_dir) stat.storage_usage_bytes = get_total_size(&DATA_DIR_MANAGER.eml_dir)
.map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?; .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?;
stat.index_usage_bytes = get_total_size(&DATA_DIR_MANAGER.envelope_dir) stat.index_usage_bytes =
.map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?; get_total_size(&DATA_DIR_MANAGER.envelope_dir.join("envelopes.db"))
.map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?;
} else { } else {
stat.storage_usage_bytes = 0; stat.storage_usage_bytes = 0;
stat.index_usage_bytes = 0; stat.index_usage_bytes = 0;
+3 -24
View File
@@ -1487,38 +1487,17 @@ impl DuckDBManager {
} }
if let Some(to) = filter.to { if let Some(to) = filter.to {
base_sql.push_str( base_sql.push_str(" AND array_to_string(e.recipients, ',') ILIKE ?");
"
AND EXISTS (
SELECT 1 FROM UNNEST(e.recipients) r
WHERE r::VARCHAR ILIKE ?
)
",
);
args.push(format!("%{}%", to).into()); args.push(format!("%{}%", to).into());
} }
if let Some(cc) = filter.cc { if let Some(cc) = filter.cc {
base_sql.push_str( base_sql.push_str(" AND array_to_string(e.cc, ',') ILIKE ?");
"
AND EXISTS (
SELECT 1 FROM UNNEST(e.cc) r
WHERE r::VARCHAR ILIKE ?
)
",
);
args.push(format!("%{}%", cc).into()); args.push(format!("%{}%", cc).into());
} }
if let Some(bcc) = filter.bcc { if let Some(bcc) = filter.bcc {
base_sql.push_str( base_sql.push_str(" AND array_to_string(e.bcc, ',') ILIKE ?");
"
AND EXISTS (
SELECT 1 FROM UNNEST(e.bcc) r
WHERE r::VARCHAR ILIKE ?
)
",
);
args.push(format!("%{}%", bcc).into()); args.push(format!("%{}%", bcc).into());
} }
+2 -4
View File
@@ -153,7 +153,7 @@ async fn extract_envelope_core(
mailbox_id, mailbox_id,
uid, uid,
subject, subject,
text, text: String::new(),//for test
from, from,
to, to,
cc, cc,
@@ -169,9 +169,7 @@ async fn extract_envelope_core(
mailbox_name: None, mailbox_name: None,
content_hash: email_content_hash, content_hash: email_content_hash,
}; };
ENVELOPE_INDEX_MANAGER ENVELOPE_INDEX_MANAGER.queue((envelope, attachments)).await;
.add_document((envelope, attachments))
.await;
Ok(()) Ok(())
} }
-7
View File
@@ -196,13 +196,6 @@ export default function MailArchiveDashboard() {
<FixedHeader /> <FixedHeader />
<Main higher> <Main higher>
<div className="flex-1 space-y-6 p-6 md:p-8"> <div className="flex-1 space-y-6 p-6 md:p-8">
<div className="flex items-center justify-between">
<div>
<h2 className="text-3xl font-bold tracking-tight">{t('dashboard.title')}</h2>
</div>
</div>
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-6"> <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-6">
<Card> <Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2"> <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">