feat: add comprehensive Rust implementation with feature parity
This commit completes the Rust implementation of mail2couch with full feature parity to the Go version, including: - Complete IMAP client with TLS support and retry logic - Advanced email parsing with MIME multipart support using mail-parser - Email attachment extraction and CouchDB storage - Sync mode implementation with deleted message handling - Enhanced error handling and retry mechanisms - Identical command-line interface with bash completion - Test configurations for both implementations The Rust implementation now provides: - Memory safety and type safety guarantees - Modern async/await patterns with tokio/async-std - Comprehensive error handling with anyhow/thiserror - Structured logging and progress reporting - Performance optimizations and retry logic Test configurations created: - rust/config-test-rust.json - Rust implementation test config - go/config-test-go.json - Go implementation test config - test-config-comparison.md - Detailed comparison documentation - test-both-implementations.sh - Automated testing script Both implementations can now be tested side-by-side with identical configurations to validate feature parity and performance. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
35c3c8657a
commit
7b98efe06b
8 changed files with 1086 additions and 100 deletions
|
|
@ -26,6 +26,7 @@ pub struct MailboxSyncResult {
|
|||
pub messages_processed: u32,
|
||||
pub messages_stored: u32,
|
||||
pub messages_skipped: u32,
|
||||
pub messages_deleted: u32,
|
||||
pub last_uid: Option<u32>,
|
||||
pub sync_time: DateTime<Utc>,
|
||||
}
|
||||
|
|
@ -148,13 +149,24 @@ impl SyncCoordinator {
|
|||
|
||||
match self.sync_mailbox(&mut imap_client, &db_name, mailbox, source).await {
|
||||
Ok(result) => {
|
||||
info!(
|
||||
" ✅ {}: {} processed, {} stored, {} skipped",
|
||||
result.mailbox,
|
||||
result.messages_processed,
|
||||
result.messages_stored,
|
||||
result.messages_skipped
|
||||
);
|
||||
if result.messages_deleted > 0 {
|
||||
info!(
|
||||
" ✅ {}: {} processed, {} stored, {} skipped, {} deleted",
|
||||
result.mailbox,
|
||||
result.messages_processed,
|
||||
result.messages_stored,
|
||||
result.messages_skipped,
|
||||
result.messages_deleted
|
||||
);
|
||||
} else {
|
||||
info!(
|
||||
" ✅ {}: {} processed, {} stored, {} skipped",
|
||||
result.mailbox,
|
||||
result.messages_processed,
|
||||
result.messages_stored,
|
||||
result.messages_skipped
|
||||
);
|
||||
}
|
||||
total_messages += result.messages_processed;
|
||||
mailbox_results.push(result);
|
||||
}
|
||||
|
|
@ -214,12 +226,27 @@ impl SyncCoordinator {
|
|||
let message_uids = imap_client.search_messages(since_date.as_ref()).await?;
|
||||
info!(" Found {} messages to process", message_uids.len());
|
||||
|
||||
// Handle sync mode - check for deleted messages
|
||||
let mut messages_deleted = 0;
|
||||
if source.mode == "sync" {
|
||||
messages_deleted = self.handle_deleted_messages(db_name, mailbox, &message_uids).await
|
||||
.unwrap_or_else(|e| {
|
||||
warn!(" Failed to handle deleted messages: {}", e);
|
||||
0
|
||||
});
|
||||
|
||||
if messages_deleted > 0 {
|
||||
info!(" 🗑️ Deleted {} messages that no longer exist on server", messages_deleted);
|
||||
}
|
||||
}
|
||||
|
||||
if message_uids.is_empty() {
|
||||
return Ok(MailboxSyncResult {
|
||||
mailbox: mailbox.to_string(),
|
||||
messages_processed: 0,
|
||||
messages_stored: 0,
|
||||
messages_skipped: 0,
|
||||
messages_deleted,
|
||||
last_uid: None,
|
||||
sync_time: start_time,
|
||||
});
|
||||
|
|
@ -238,7 +265,7 @@ impl SyncCoordinator {
|
|||
};
|
||||
|
||||
// Fetch and process messages
|
||||
let messages = imap_client.fetch_messages(uids_to_process, self.args.max_messages).await?;
|
||||
let messages = imap_client.fetch_messages(uids_to_process, self.args.max_messages, mailbox).await?;
|
||||
|
||||
let mut messages_stored = 0;
|
||||
let mut messages_skipped = 0;
|
||||
|
|
@ -289,11 +316,58 @@ impl SyncCoordinator {
|
|||
messages_processed: uids_to_process.len() as u32,
|
||||
messages_stored,
|
||||
messages_skipped,
|
||||
messages_deleted,
|
||||
last_uid,
|
||||
sync_time: start_time,
|
||||
})
|
||||
}
|
||||
|
||||
/// Handle deleted messages in sync mode
|
||||
/// Compares UIDs from IMAP server with stored messages in CouchDB
|
||||
/// and deletes messages that no longer exist on the server
|
||||
async fn handle_deleted_messages(
|
||||
&mut self,
|
||||
db_name: &str,
|
||||
mailbox: &str,
|
||||
current_server_uids: &[u32],
|
||||
) -> Result<u32> {
|
||||
// Get all stored message UIDs for this mailbox from CouchDB
|
||||
let stored_uids = self.get_stored_message_uids(db_name, mailbox).await?;
|
||||
|
||||
if stored_uids.is_empty() {
|
||||
return Ok(0); // No stored messages to delete
|
||||
}
|
||||
|
||||
// Find UIDs that exist in CouchDB but not on the server
|
||||
let server_uid_set: std::collections::HashSet<u32> = current_server_uids.iter().cloned().collect();
|
||||
let mut deleted_count = 0;
|
||||
|
||||
for stored_uid in stored_uids {
|
||||
if !server_uid_set.contains(&stored_uid) {
|
||||
// This message was deleted from the server, remove it from CouchDB
|
||||
let doc_id = format!("{}_{}", mailbox, stored_uid);
|
||||
|
||||
match self.couch_client.delete_document(db_name, &doc_id).await {
|
||||
Ok(_) => {
|
||||
debug!(" Deleted document: {}", doc_id);
|
||||
deleted_count += 1;
|
||||
}
|
||||
Err(e) => {
|
||||
warn!(" Failed to delete document {}: {}", doc_id, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(deleted_count)
|
||||
}
|
||||
|
||||
/// Get all stored message UIDs for a mailbox from CouchDB
|
||||
async fn get_stored_message_uids(&self, db_name: &str, mailbox: &str) -> Result<Vec<u32>> {
|
||||
// Use the CouchDB client method to get stored UIDs
|
||||
self.couch_client.get_mailbox_uids(db_name, mailbox).await
|
||||
}
|
||||
|
||||
/// Print summary of sync results
|
||||
pub fn print_sync_summary(&self, results: &[SourceSyncResult]) {
|
||||
info!("\n🎉 Synchronization completed!");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue