1package main23import (4 "flag"5 "fmt"6 "log"7 "os"8 "path/filepath"9 "strings"10 "sync"11)1213var (14 verbose = flag.Bool("v", false, "print out performed changes")15 dryrun = flag.Bool("d", false, "don't perform any changes, combine with -v")16)1718type MailWalkFn func(mail *Mail, db *MailDatabase, err error) error1920func indexOldMsgs(mail *Mail, db *MailDatabase, err error) error {21 if err != nil {22 panic(err)23 }2425 db.AddOldMessage(mail)26 return nil27}2829func indexNewMsgs(mail *Mail, db *MailDatabase, err error) error {30 if err != nil {31 panic(err)32 }3334 oldMail, err := db.GetOldMessage(mail)35 if err != nil {36 return err37 }3839 if oldMail == nil {40 db.AddNewMessage(nil, mail)41 } else if !oldMail.IsSame(mail) {42 db.AddNewMessage(oldMail, mail)43 }4445 return nil46}4748func walkMaildir(maildir string, db *MailDatabase, walkFn MailWalkFn) error {49 wrapFn := func(path string, info os.FileInfo, err error) error {50 handleError := func(err error) error { return walkFn(nil, nil, err) }51 if err != nil {52 return handleError(err)53 }5455 if info.IsDir() {56 if !isMaildirFn(info.Name()) {57 return handleError(fmt.Errorf("unexpected directory %q", info.Name()))58 } else {59 return nil60 }61 }6263 mail, err := NewMail(maildir, path)64 if err != nil {65 return handleError(err)66 }6768 return walkFn(mail, db, err)69 }7071 for _, dir := range []string{"cur", "new"} {72 err := filepath.Walk(filepath.Join(maildir, dir), wrapFn)73 if err != nil {74 return err75 }76 }7778 return nil79}8081// Returns mapping new maildir → old maildir.82func parseArgs(args []string) (map[string]string, error) {83 parsedArgs := make(map[string]string)84 for _, arg := range args {85 splitted := strings.Split(arg, "→")86 if len(splitted) != 2 {87 return nil, fmt.Errorf("invalid argument %q", arg)88 }8990 new := splitted[0]91 old := splitted[1]92 for _, dir := range []string{old, new} {93 if !isValidMaildir(dir) {94 return nil, fmt.Errorf("%q is not a valid maildir", dir)95 }96 }9798 if _, ok := parsedArgs[new]; ok {99 return nil, fmt.Errorf("duplicate maildir %q", arg)100 }101 parsedArgs[new] = old102 }103 return parsedArgs, nil104}105106func indexMsgs(args map[string]string) (*MailDatabase, error) {107 var wg sync.WaitGroup108 db := NewMailDatabase()109110 wfn := func(dir string, mfn MailWalkFn) {111 defer wg.Done()112 err := walkMaildir(dir, db, mfn)113 if err != nil {114 log.Fatal(err)115 }116 }117118 wg.Add(len(args))119 for _, old := range args {120 go wfn(old, indexOldMsgs)121 }122 wg.Wait()123124 wg.Add(len(args))125 for new, _ := range args {126 go wfn(new, indexNewMsgs)127 }128 wg.Wait()129130 return db, nil131}132133func archiveMsgs(args map[string]string, db *MailDatabase) error {134 for _, new := range db.newMsgs {135 if *verbose {136 fmt.Printf("new: %s\n", new)137 }138 if *dryrun {139 continue140 }141142 err := new.CopyTo(args[new.maildir])143 if err != nil {144 return err145 }146 }147 for _, pair := range db.modMsgs {148 if *verbose {149 fmt.Printf("move: %s → %s\n", pair.old, pair.new)150 }151 if *dryrun {152 continue153 }154155 destDir := args[pair.new.maildir]156 newFp := filepath.Join(destDir, pair.new.directory, pair.new.name)157 err := os.Rename(pair.old.Path(), newFp)158 if err != nil {159 return err160 }161 }162163 return nil164}165166func main() {167 log.SetFlags(log.Lshortfile)168169 flag.Usage = func() {170 fmt.Fprintf(flag.CommandLine.Output(),171 "Usage: %s [-v] [-d] MAILDIR_CURRENT→MAILDIR_ARCHIVE...\n\n", os.Args[0])172 flag.PrintDefaults()173 }174 flag.Parse()175176 if flag.NArg() < 1 {177 flag.Usage()178 os.Exit(1)179 }180 args, err := parseArgs(flag.Args())181 if err != nil {182 log.Fatal(err)183 }184185 db, err := indexMsgs(args)186 if err != nil {187 log.Fatal(err)188 }189 err = archiveMsgs(args, db)190 if err != nil {191 log.Fatal(err)192 }193}