diff --git a/go/config/config.go b/go/config/config.go index 713e73d..ddb3689 100644 --- a/go/config/config.go +++ b/go/config/config.go @@ -86,7 +86,6 @@ func (ms *MailSource) IsArchiveMode() bool { type CommandLineArgs struct { ConfigPath string MaxMessages int - DryRun bool } // ParseCommandLine parses command line arguments using GNU-style options @@ -96,7 +95,6 @@ func ParseCommandLine() *CommandLineArgs { // Define long options with -- and short options with - pflag.StringVarP(&args.ConfigPath, "config", "c", "", "Path to configuration file") pflag.IntVarP(&args.MaxMessages, "max-messages", "m", 0, "Maximum number of messages to process per mailbox per run (0 = no limit)") - pflag.BoolVarP(&args.DryRun, "dry-run", "n", false, "Show what would be done without making any changes") // Add utility options pflag.BoolP("help", "h", false, "Show help message") @@ -148,7 +146,7 @@ _%s_completions() { if [[ $cur == -* ]]; then # Complete with available options - local opts="-c --config -m --max-messages -n --dry-run -h --help --generate-bash-completion" + local opts="-c --config -m --max-messages -h --help --generate-bash-completion" COMPREPLY=($(compgen -W "$opts" -- "$cur")) return fi @@ -219,8 +217,5 @@ func LoadConfigWithDiscovery(args *CommandLineArgs) (*Config, error) { } else { fmt.Printf("Maximum messages per mailbox: unlimited\n") } - if args.DryRun { - fmt.Printf("DRY-RUN MODE: No changes will be made to CouchDB\n") - } return LoadConfig(configPath) } diff --git a/go/main.go b/go/main.go index 1523b09..399d67c 100644 --- a/go/main.go +++ b/go/main.go @@ -19,14 +19,10 @@ func main() { log.Fatalf("Failed to load configuration: %v", err) } - // Initialize CouchDB client (skip in dry-run mode) - var couchClient *couch.Client - if !args.DryRun { - var err error - couchClient, err = couch.NewClient(&cfg.CouchDb) - if err != nil { - log.Fatalf("Failed to create CouchDB client: %v", err) - } + // Initialize CouchDB client + couchClient, err := couch.NewClient(&cfg.CouchDb) + if err != nil { + log.Fatalf("Failed to create CouchDB client: %v", err) } fmt.Printf("Found %d mail source(s) to process.\n", len(cfg.MailSources)) @@ -38,25 +34,21 @@ func main() { // Generate per-account database name dbName := couch.GenerateAccountDBName(source.Name, source.User) - // Ensure the account-specific database exists (skip in dry-run mode) - if !args.DryRun { - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - err = couchClient.EnsureDB(ctx, dbName) - cancel() + // Ensure the account-specific database exists + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + err = couchClient.EnsureDB(ctx, dbName) + cancel() - if err != nil { - log.Printf("Could not ensure CouchDB database '%s' exists (is it running?): %v", dbName, err) - continue - } else { - fmt.Printf("CouchDB database '%s' is ready for account: %s\n", dbName, source.Name) - } + if err != nil { + log.Printf("Could not ensure CouchDB database '%s' exists (is it running?): %v", dbName, err) + continue } else { - fmt.Printf("DRY-RUN: Would ensure CouchDB database '%s' exists for account: %s\n", dbName, source.Name) + fmt.Printf("CouchDB database '%s' is ready for account: %s\n", dbName, source.Name) } fmt.Printf(" - Processing source: %s\n", source.Name) if source.Protocol == "imap" { - err := processImapSource(&source, couchClient, dbName, args.MaxMessages, args.DryRun) + err := processImapSource(&source, couchClient, dbName, args.MaxMessages) if err != nil { log.Printf(" ERROR: Failed to process IMAP source %s: %v", source.Name, err) } @@ -64,7 +56,7 @@ func main() { } } -func processImapSource(source *config.MailSource, couchClient *couch.Client, dbName string, maxMessages int, dryRun bool) error { +func processImapSource(source *config.MailSource, couchClient *couch.Client, dbName string, maxMessages int) error { fmt.Printf(" Connecting to IMAP server: %s:%d\n", source.Host, source.Port) imapClient, err := mail.NewImapClient(source) if err != nil { @@ -100,17 +92,13 @@ func processImapSource(source *config.MailSource, couchClient *couch.Client, dbN for _, mailbox := range mailboxes { fmt.Printf(" Processing mailbox: %s (mode: %s)\n", mailbox, source.Mode) - // Get sync metadata to determine incremental sync date (skip in dry-run mode) - var syncMetadata *couch.SyncMetadata - if !dryRun { - syncCtx, syncCancel := context.WithTimeout(context.Background(), 10*time.Second) - var err error - syncMetadata, err = couchClient.GetSyncMetadata(syncCtx, dbName, mailbox) - syncCancel() - if err != nil { - log.Printf(" ERROR: Failed to get sync metadata for %s: %v", mailbox, err) - continue - } + // Get sync metadata to determine incremental sync date + syncCtx, syncCancel := context.WithTimeout(context.Background(), 10*time.Second) + syncMetadata, err := couchClient.GetSyncMetadata(syncCtx, dbName, mailbox) + syncCancel() + if err != nil { + log.Printf(" ERROR: Failed to get sync metadata for %s: %v", mailbox, err) + continue } // Determine the since date for incremental sync @@ -126,11 +114,7 @@ func processImapSource(source *config.MailSource, couchClient *couch.Client, dbN if sinceDate != nil { fmt.Printf(" First sync since: %s (from config)\n", sinceDate.Format("2006-01-02")) } else { - if dryRun { - fmt.Printf(" DRY-RUN: Would perform first full sync (no date filter)\n") - } else { - fmt.Printf(" First full sync (no date filter)\n") - } + fmt.Printf(" First full sync (no date filter)\n") } } @@ -141,18 +125,13 @@ func processImapSource(source *config.MailSource, couchClient *couch.Client, dbN continue } - // Perform sync/archive logic (skip in dry-run mode) - if !dryRun { - mailboxSyncCtx, mailboxSyncCancel := context.WithTimeout(context.Background(), 30*time.Second) - err = couchClient.SyncMailbox(mailboxSyncCtx, dbName, mailbox, currentUIDs, source.IsSyncMode()) - mailboxSyncCancel() - if err != nil { - log.Printf(" ERROR: Failed to sync mailbox %s: %v", mailbox, err) - continue - } - } else { - fmt.Printf(" DRY-RUN: Would sync mailbox %s with %d current UIDs (mode: %s)\n", - mailbox, len(currentUIDs), source.Mode) + // Perform sync/archive logic + mailboxSyncCtx, mailboxSyncCancel := context.WithTimeout(context.Background(), 30*time.Second) + err = couchClient.SyncMailbox(mailboxSyncCtx, dbName, mailbox, currentUIDs, source.IsSyncMode()) + mailboxSyncCancel() + if err != nil { + log.Printf(" ERROR: Failed to sync mailbox %s: %v", mailbox, err) + continue } if len(messages) == 0 { @@ -170,32 +149,23 @@ func processImapSource(source *config.MailSource, couchClient *couch.Client, dbN docs = append(docs, doc) } - // Store messages in CouchDB with attachments (skip in dry-run mode) + // Store messages in CouchDB with attachments + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) stored := 0 - if !dryRun { - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - for i, doc := range docs { - err := couchClient.StoreMessage(ctx, dbName, doc, messages[i]) - if err != nil { - log.Printf(" ERROR: Failed to store message %s: %v", doc.ID, err) - } else { - stored++ - } - } - cancel() - fmt.Printf(" Stored %d/%d messages from %s\n", stored, len(messages), mailbox) - } else { - stored = len(messages) // In dry-run, assume all would be stored - fmt.Printf(" DRY-RUN: Would store %d messages from %s\n", len(messages), mailbox) - // Show sample of what would be stored - if len(docs) > 0 { - fmt.Printf(" DRY-RUN: Sample message ID: %s (Subject: %s)\n", - docs[0].ID, docs[0].Subject) + for i, doc := range docs { + err := couchClient.StoreMessage(ctx, dbName, doc, messages[i]) + if err != nil { + log.Printf(" ERROR: Failed to store message %s: %v", doc.ID, err) + } else { + stored++ } } + cancel() + + fmt.Printf(" Stored %d/%d messages from %s\n", stored, len(messages), mailbox) totalStored += stored - // Update sync metadata after successful processing (skip in dry-run mode) + // Update sync metadata after successful processing if len(messages) > 0 { // Find the highest UID processed var maxUID uint32 @@ -205,34 +175,26 @@ func processImapSource(source *config.MailSource, couchClient *couch.Client, dbN } } - if !dryRun { - // Create/update sync metadata - newMetadata := &couch.SyncMetadata{ - Mailbox: mailbox, - LastSyncTime: time.Now(), - LastMessageUID: maxUID, - MessageCount: stored, - } + // Create/update sync metadata + newMetadata := &couch.SyncMetadata{ + Mailbox: mailbox, + LastSyncTime: time.Now(), + LastMessageUID: maxUID, + MessageCount: stored, + } - // Store sync metadata - metadataCtx, metadataCancel := context.WithTimeout(context.Background(), 10*time.Second) - err = couchClient.StoreSyncMetadata(metadataCtx, dbName, newMetadata) - metadataCancel() - if err != nil { - log.Printf(" WARNING: Failed to store sync metadata for %s: %v", mailbox, err) - } else { - fmt.Printf(" Updated sync metadata (last UID: %d)\n", maxUID) - } + // Store sync metadata + metadataCtx, metadataCancel := context.WithTimeout(context.Background(), 10*time.Second) + err = couchClient.StoreSyncMetadata(metadataCtx, dbName, newMetadata) + metadataCancel() + if err != nil { + log.Printf(" WARNING: Failed to store sync metadata for %s: %v", mailbox, err) } else { - fmt.Printf(" DRY-RUN: Would update sync metadata (last UID: %d, %d messages)\n", maxUID, stored) + fmt.Printf(" Updated sync metadata (last UID: %d)\n", maxUID) } } } - if dryRun { - fmt.Printf(" DRY-RUN Summary: Found %d messages, would store %d messages\n", totalMessages, totalStored) - } else { - fmt.Printf(" Summary: Processed %d messages, stored %d new messages\n", totalMessages, totalStored) - } + fmt.Printf(" Summary: Processed %d messages, stored %d new messages\n", totalMessages, totalStored) return nil } diff --git a/rust/src/cli.rs b/rust/src/cli.rs index abb9159..cdb8a4e 100644 --- a/rust/src/cli.rs +++ b/rust/src/cli.rs @@ -28,11 +28,6 @@ pub fn parse_command_line() -> CommandLineArgs { .help("Maximum number of messages to process per mailbox per run (0 = no limit)") .value_parser(clap::value_parser!(u32)) .action(ArgAction::Set)) - .arg(Arg::new("dry-run") - .short('n') - .long("dry-run") - .help("Show what would be done without making any changes") - .action(ArgAction::SetTrue)) .arg(Arg::new("generate-bash-completion") .long("generate-bash-completion") .help("Generate bash completion script and exit") @@ -49,7 +44,6 @@ pub fn parse_command_line() -> CommandLineArgs { CommandLineArgs { config_path: matches.get_one::("config").map(|s| s.clone()), max_messages: matches.get_one::("max-messages").copied(), - dry_run: matches.get_flag("dry-run"), generate_bash_completion: matches.get_flag("generate-bash-completion"), help: false, // Using clap's built-in help } @@ -89,7 +83,7 @@ _{}_completions() {{ if [[ $cur == -* ]]; then # Complete with available options - local opts="-c --config -m --max-messages -n --dry-run -h --help --generate-bash-completion" + local opts="-c --config -m --max-messages -h --help --generate-bash-completion" COMPREPLY=($(compgen -W "$opts" -- "$cur")) return fi diff --git a/rust/src/config.rs b/rust/src/config.rs index 38a6f38..209a120 100644 --- a/rust/src/config.rs +++ b/rust/src/config.rs @@ -107,7 +107,6 @@ impl MailSource { pub struct CommandLineArgs { pub config_path: Option, pub max_messages: Option, - pub dry_run: bool, pub generate_bash_completion: bool, pub help: bool, } @@ -284,7 +283,6 @@ mod tests { let args = CommandLineArgs { config_path: None, max_messages: None, - dry_run: false, generate_bash_completion: false, help: false, }; diff --git a/rust/src/main.rs b/rust/src/main.rs index ed2c83a..2f15cbe 100644 --- a/rust/src/main.rs +++ b/rust/src/main.rs @@ -36,10 +36,6 @@ async fn run(args: mail2couch::config::CommandLineArgs) -> Result<()> { info!("Maximum messages per mailbox: unlimited"); } - if args.dry_run { - info!("šŸ” DRY-RUN MODE: No changes will be made to CouchDB"); - } - // Display configuration summary print_config_summary(&config); diff --git a/rust/src/sync.rs b/rust/src/sync.rs index 29e9e95..8fdec27 100644 --- a/rust/src/sync.rs +++ b/rust/src/sync.rs @@ -119,12 +119,8 @@ impl SyncCoordinator { let db_name = generate_database_name(&source.name, &source.user); info!("Using database: {}", db_name); - // Create database if it doesn't exist (skip in dry-run mode) - if !self.args.dry_run { - self.couch_client.create_database(&db_name).await?; - } else { - info!("šŸ” DRY-RUN: Would create database {}", db_name); - } + // Create database if it doesn't exist + self.couch_client.create_database(&db_name).await?; // Connect to IMAP server let mut imap_client = ImapClient::connect(source.clone()).await?; @@ -216,33 +212,22 @@ impl SyncCoordinator { let mailbox_info = imap_client.select_mailbox(mailbox).await?; debug!("Selected mailbox {}: {} messages", mailbox, mailbox_info.exists); - // Get last sync metadata (skip in dry-run mode) - let since_date = if !self.args.dry_run { - match self.couch_client.get_sync_metadata(db_name, mailbox).await { - Ok(metadata) => { - info!(" Found sync metadata, last sync: {}", metadata.last_sync_time); - Some(metadata.last_sync_time) - } - Err(_) => { - info!(" No sync metadata found, performing full sync"); - // Parse since date from message filter if provided - source.message_filter.since.as_ref() - .and_then(|since_str| { - DateTime::parse_from_str(&format!("{} 00:00:00 +0000", since_str), "%Y-%m-%d %H:%M:%S %z") - .map(|dt| dt.with_timezone(&Utc)) - .ok() - }) - } + // Get last sync metadata + let since_date = match self.couch_client.get_sync_metadata(db_name, mailbox).await { + Ok(metadata) => { + info!(" Found sync metadata, last sync: {}", metadata.last_sync_time); + Some(metadata.last_sync_time) + } + Err(_) => { + info!(" No sync metadata found, performing full sync"); + // Parse since date from message filter if provided + source.message_filter.since.as_ref() + .and_then(|since_str| { + DateTime::parse_from_str(&format!("{} 00:00:00 +0000", since_str), "%Y-%m-%d %H:%M:%S %z") + .map(|dt| dt.with_timezone(&Utc)) + .ok() + }) } - } else { - info!(" šŸ” DRY-RUN: Would check for sync metadata"); - // In dry-run mode, use config since date if available - source.message_filter.since.as_ref() - .and_then(|since_str| { - DateTime::parse_from_str(&format!("{} 00:00:00 +0000", since_str), "%Y-%m-%d %H:%M:%S %z") - .map(|dt| dt.with_timezone(&Utc)) - .ok() - }) }; // Search for messages using server-side IMAP SEARCH with keyword filtering when possible @@ -272,21 +257,17 @@ impl SyncCoordinator { }; info!(" Found {} messages matching search criteria", message_uids.len()); - // Handle sync mode - check for deleted messages (skip in dry-run mode) + // Handle sync mode - check for deleted messages let mut messages_deleted = 0; if source.mode == "sync" { - if !self.args.dry_run { - 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); - } - } else { - info!(" šŸ” DRY-RUN: Would check for deleted messages in sync mode"); + 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); } } @@ -331,74 +312,54 @@ impl SyncCoordinator { // Extract UID before moving the document let uid_str = mail_doc.source_uid.clone(); - // Store the message document first (skip in dry-run mode) - if !self.args.dry_run { - match self.couch_client.store_mail_document(db_name, mail_doc).await { - Ok(doc_id) => { - messages_stored += 1; - - // Store attachments if any exist - if !attachments.is_empty() { - for (filename, content_type, data) in attachments { - match self.couch_client.store_attachment( - db_name, - &doc_id, - &filename, - &content_type, - &data, - ).await { - Ok(_) => { - debug!(" Stored attachment: {}", filename); - } - Err(e) => { - warn!(" Failed to store attachment {}: {}", filename, e); - } + // Store the message document first + match self.couch_client.store_mail_document(db_name, mail_doc).await { + Ok(doc_id) => { + messages_stored += 1; + + // Store attachments if any exist + if !attachments.is_empty() { + for (filename, content_type, data) in attachments { + match self.couch_client.store_attachment( + db_name, + &doc_id, + &filename, + &content_type, + &data, + ).await { + Ok(_) => { + debug!(" Stored attachment: {}", filename); + } + Err(e) => { + warn!(" Failed to store attachment {}: {}", filename, e); } } } - - // Parse UID from source_uid - if let Ok(uid) = uid_str.parse::() { - last_uid = Some(last_uid.map_or(uid, |prev: u32| prev.max(uid))); - } } - Err(e) => { - warn!(" Failed to store message {}: {}", uid_str, e); - messages_skipped += 1; + + // Parse UID from source_uid + if let Ok(uid) = uid_str.parse::() { + last_uid = Some(last_uid.map_or(uid, |prev: u32| prev.max(uid))); } } - } else { - // In dry-run mode, simulate successful storage - messages_stored += 1; - debug!(" šŸ” DRY-RUN: Would store message {} (Subject: {})", - uid_str, mail_doc.subject); - - if !attachments.is_empty() { - debug!(" šŸ” DRY-RUN: Would store {} attachments", attachments.len()); - } - - // Parse UID from source_uid - if let Ok(uid) = uid_str.parse::() { - last_uid = Some(last_uid.map_or(uid, |prev: u32| prev.max(uid))); + Err(e) => { + warn!(" Failed to store message {}: {}", uid_str, e); + messages_skipped += 1; } } } - // Update sync metadata (skip in dry-run mode) + // Update sync metadata if let Some(uid) = last_uid { - if !self.args.dry_run { - let sync_metadata = SyncMetadata::new( - mailbox.to_string(), - start_time, - uid, - messages_stored, - ); + let sync_metadata = SyncMetadata::new( + mailbox.to_string(), + start_time, + uid, + messages_stored, + ); - if let Err(e) = self.couch_client.store_sync_metadata(db_name, &sync_metadata).await { - warn!(" Failed to store sync metadata: {}", e); - } - } else { - info!(" šŸ” DRY-RUN: Would update sync metadata (last UID: {}, {} messages)", uid, messages_stored); + if let Err(e) = self.couch_client.store_sync_metadata(db_name, &sync_metadata).await { + warn!(" Failed to store sync metadata: {}", e); } } @@ -461,11 +422,7 @@ impl SyncCoordinator { /// Print summary of sync results pub fn print_sync_summary(&self, results: &[SourceSyncResult]) { - if self.args.dry_run { - info!("\nšŸ” DRY-RUN completed!"); - } else { - info!("\nšŸŽ‰ Synchronization completed!"); - } + info!("\nšŸŽ‰ Synchronization completed!"); info!("{}", "=".repeat(50)); let mut total_sources = 0; @@ -477,45 +434,24 @@ impl SyncCoordinator { total_mailboxes += result.mailboxes_processed; total_messages += result.total_messages; - if self.args.dry_run { - info!( - "šŸ“§ {}: {} mailboxes, {} messages found (database: {})", - result.source_name, - result.mailboxes_processed, - result.total_messages, - result.database - ); - } else { - info!( - "šŸ“§ {}: {} mailboxes, {} messages (database: {})", - result.source_name, - result.mailboxes_processed, - result.total_messages, - result.database - ); - } + info!( + "šŸ“§ {}: {} mailboxes, {} messages (database: {})", + result.source_name, + result.mailboxes_processed, + result.total_messages, + result.database + ); } info!("{}", "=".repeat(50)); - if self.args.dry_run { - info!( - "šŸ“Š DRY-RUN Total: {} sources, {} mailboxes, {} messages found", - total_sources, total_mailboxes, total_messages - ); - } else { - info!( - "šŸ“Š Total: {} sources, {} mailboxes, {} messages", - total_sources, total_mailboxes, total_messages - ); - } + info!( + "šŸ“Š Total: {} sources, {} mailboxes, {} messages", + total_sources, total_mailboxes, total_messages + ); if let Some(max) = self.args.max_messages { info!("āš ļø Message limit was applied: {} per mailbox", max); } - - if self.args.dry_run { - info!("šŸ” No changes were made to CouchDB"); - } } } @@ -557,7 +493,6 @@ mod tests { let args = CommandLineArgs { config_path: None, max_messages: Some(10), - dry_run: false, generate_bash_completion: false, help: false, };