package main import ( "context" "flag" "fmt" "log/slog" "os" "path/filepath" "strings" "archivmail/config" "archivmail/internal/index" "archivmail/internal/storage" "archivmail/pkg/mailparser" ) func main() { configPath := flag.String("config", "/etc/archivmail/config.yml", "path to config file") flag.Parse() args := flag.Args() if len(args) == 0 { fmt.Fprintln(os.Stderr, "usage: archivmail-import --config ") os.Exit(1) } target := args[0] logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo})) cfg, err := config.Load(*configPath) if err != nil { logger.Error("failed to load config", "err", err) os.Exit(1) } mailStore, err := storage.New(storage.Config{ Dir: cfg.Storage.StorePath, Keyfile: cfg.Storage.Keyfile, DSN: cfg.Database.DSN(), }) if err != nil { logger.Error("storage init failed", "err", err) os.Exit(1) } defer mailStore.Close() indexBackend := cfg.Index.Backend if indexBackend == "" { indexBackend = "xapian" } batchSize := cfg.Index.BatchSize if batchSize <= 0 { batchSize = 100 } idx, err := index.New(cfg.Index.Path, batchSize, indexBackend) if err != nil { logger.Error("index init failed", "err", err) os.Exit(1) } defer idx.Close() var emlFiles []string info, err := os.Stat(target) if err != nil { logger.Error("target not found", "path", target, "err", err) os.Exit(1) } if info.IsDir() { err = filepath.Walk(target, func(path string, fi os.FileInfo, err error) error { if err != nil { return err } if !fi.IsDir() && strings.HasSuffix(strings.ToLower(fi.Name()), ".eml") { emlFiles = append(emlFiles, path) } return nil }) if err != nil { logger.Error("walk failed", "err", err) os.Exit(1) } } else { emlFiles = []string{target} } logger.Info("found EML files", "count", len(emlFiles)) imported := 0 skipped := 0 errors := 0 for i, path := range emlFiles { raw, err := os.ReadFile(path) if err != nil { logger.Error("read file failed", "path", path, "err", err) errors++ continue } pm, err := mailparser.Parse(raw) if err != nil { logger.Error("parse failed", "path", path, "err", err) errors++ continue } id, err := mailStore.Save(context.Background(), raw, pm.Date, nil) if err != nil { logger.Error("save failed", "path", path, "err", err) errors++ continue } // Build attachment names list var attachNames []string for _, att := range pm.Attachments { attachNames = append(attachNames, att.Filename) } doc := index.MailDocument{ ID: id, From: pm.From, To: strings.Join(pm.To, " "), CC: strings.Join(pm.CC, " "), Subject: pm.Subject, Body: pm.TextBody + " " + pm.HTMLBody, AttachNames: strings.Join(attachNames, " "), HasAttachment: len(pm.Attachments) > 0, Date: pm.Date, Size: int64(len(raw)), } if err := idx.IndexSync(doc); err != nil { logger.Error("index failed", "id", id, "err", err) errors++ continue } imported++ if (i+1)%100 == 0 || i+1 == len(emlFiles) { fmt.Printf("Progress: %d/%d (imported: %d, skipped: %d, errors: %d)\n", i+1, len(emlFiles), imported, skipped, errors) } } logger.Info("import complete", "total", len(emlFiles), "imported", imported, "skipped", skipped, "errors", errors, ) }