package main import ( "context" "fmt" "log" "time" "mail2couch/config" "mail2couch/couch" "mail2couch/mail" ) func main() { args := config.ParseCommandLine() cfg, err := config.LoadConfigWithDiscovery(args) if err != nil { log.Fatalf("Failed to load configuration: %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)) for _, source := range cfg.MailSources { if !source.Enabled { continue } // Generate per-account database name dbName := couch.GenerateAccountDBName(source.Name, source.User) // 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) } fmt.Printf(" - Processing source: %s\n", source.Name) if source.Protocol == "imap" { err := processImapSource(&source, couchClient, dbName, args.MaxMessages) if err != nil { log.Printf(" ERROR: Failed to process IMAP source %s: %v", source.Name, err) } } } } 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 { return fmt.Errorf("failed to connect to IMAP server: %w", err) } defer imapClient.Logout() fmt.Println(" IMAP connection successful.") mailboxes, err := imapClient.ListMailboxes() if err != nil { return fmt.Errorf("failed to list mailboxes: %w", err) } fmt.Printf(" Found %d mailboxes.\n", len(mailboxes)) // Parse the since date if provided var sinceDate *time.Time if source.MessageFilter.Since != "" { parsed, err := time.Parse("2006-01-02", source.MessageFilter.Since) if err != nil { log.Printf(" WARNING: Invalid since date format '%s', ignoring filter", source.MessageFilter.Since) } else { sinceDate = &parsed } } totalMessages := 0 totalStored := 0 // Process each mailbox for _, mailbox := range mailboxes { // Check if this mailbox should be processed based on filters if !imapClient.ShouldProcessMailbox(mailbox, &source.FolderFilter) { fmt.Printf(" Skipping mailbox: %s (filtered)\n", mailbox) continue } fmt.Printf(" Processing mailbox: %s (mode: %s)\n", mailbox, source.Mode) // Retrieve messages from the mailbox messages, currentUIDs, err := imapClient.GetMessages(mailbox, sinceDate, maxMessages, &source.MessageFilter) if err != nil { log.Printf(" ERROR: Failed to get messages from %s: %v", mailbox, err) continue } // Perform sync/archive logic syncCtx, syncCancel := context.WithTimeout(context.Background(), 30*time.Second) err = couchClient.SyncMailbox(syncCtx, dbName, mailbox, currentUIDs, source.IsSyncMode()) syncCancel() if err != nil { log.Printf(" ERROR: Failed to sync mailbox %s: %v", mailbox, err) continue } if len(messages) == 0 { fmt.Printf(" No new messages found in %s\n", mailbox) continue } fmt.Printf(" Found %d messages in %s\n", len(messages), mailbox) totalMessages += len(messages) // Convert messages to CouchDB documents var docs []*couch.MailDocument for _, msg := range messages { doc := couch.ConvertMessage(msg, mailbox) docs = append(docs, doc) } // Store messages in CouchDB with attachments ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) stored := 0 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 } fmt.Printf(" Summary: Processed %d messages, stored %d new messages\n", totalMessages, totalStored) return nil }