diff --git a/cmd/mailhash/mailhash.go b/cmd/mailhash/mailhash.go index e94106c..3a78c1e 100644 --- a/cmd/mailhash/mailhash.go +++ b/cmd/mailhash/mailhash.go @@ -13,20 +13,28 @@ import ( ) var ( + dryrun = flag.Bool("dryrun", true, "don't actually rename files, just log actions.") saveFile = flag.String("save", "", "filename to store unread status.") loadFile = flag.String("load", "", "filename to load unread status.") maildir = flag.String("maildir", "", "Maildir root") ) type Status struct { - Hash string `json:"hash"` - Path string `json:"path,omitempthashy"` - Unread bool `json:"unread,omitempty"` + Hash string `json:"hash"` + Path string `json:"path,omitempthashy"` + Read bool `json:"read,omitempty"` } type Messages []Status +var headers = []string{"to", "from", "cc", "date", "subject"} + func (m *Messages) hashMail(path string, info os.FileInfo, err error) error { + glog.Infoln("Processing", path) + if err != nil { + return err + } + if info.IsDir() { return nil } @@ -34,23 +42,32 @@ func (m *Messages) hashMail(path string, info os.FileInfo, err error) error { if err != nil { glog.Fatal(err) } - headers := []string{"to", "from", "cc", "date", "subject"} h, err := email.Hash(r, headers) if err != nil { - glog.Errorln(path, err) + glog.Errorf("%s %q", path, err.Error()) return nil } md := email.NewInfo(path) - //fmt.Printf("%x %s\n", h.Sum(nil), path) *m = append(*m, Status{ - Path: path, - Hash: fmt.Sprintf("%x", h.Sum(nil)), - Unread: !md.Seen, + Path: path, + Hash: fmt.Sprintf("%x", h.Sum(nil)), + Read: md.Seen, }) return nil } -func (m *Messages) SaveStatus(maildir, saveFile string) error { +func (m *Messages) LoadStatus(statusFile string) error { + glog.Infof("Loading file %q", statusFile) + r, err := os.Open(statusFile) + if err != nil { + return nil + } + defer r.Close() + dec := json.NewDecoder(r) + return dec.Decode(m) +} + +func (m *Messages) SaveStatus(maildir, statusFile string) error { if err := filepath.Walk(maildir, m.hashMail); err != nil { return err } @@ -60,7 +77,8 @@ func (m *Messages) SaveStatus(maildir, saveFile string) error { return err } - f, err := os.Create(saveFile) + glog.Infof("Saving file %q", statusFile) + f, err := os.Create(statusFile) if err != nil { return err } @@ -77,18 +95,91 @@ func (m *Messages) SaveStatus(maildir, saveFile string) error { return nil } +func (m Messages) Reconcile(maildir string) error { + hashMap := make(map[string]*Status, len(m)) + for i, msg := range m { + hashMap[msg.Hash] = &m[i] + } + + return filepath.Walk(maildir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + if info.IsDir() { + return nil + } + + r, err := os.Open(path) + if err != nil { + glog.Fatal(err) + } + + h, err := email.Hash(r, headers) + if err != nil { + glog.Errorf("%s %q", path, err.Error()) + return nil + } + + chksum := fmt.Sprintf("%x", h.Sum(nil)) + md := email.NewInfo(path) + s := hashMap[chksum] + glog.V(2).Infof("Comparing flags of %q to %q", path, s.Path) + if md.Seen != s.Read { + md.Seen = s.Read + newPath := md.String() + glog.Infof("%s => %s", path, newPath) + if !*dryrun { + os.Rename(path, newPath) + } + } + return nil + }) +} + +func (m Messages) PrintStats() { + r, u, t := 0, 0, 0 + for _, msg := range m { + t++ + if msg.Read { + r++ + } else { + u++ + } + } + fmt.Println(r, "read") + fmt.Println(u, "unread") + fmt.Println(t, "total") +} + func main() { defer glog.Flush() flag.Parse() m := Messages{} if *maildir == "" { - glog.Fatal("Must specify Maildir with -maildir") + fmt.Println("Must specify Maildir with -maildir") + os.Exit(1) + } + + if *saveFile == "" && *loadFile == "" { + fmt.Println("Must specify one of -save or -load") + os.Exit(1) } if *saveFile != "" { if err := m.SaveStatus(*maildir, *saveFile); err != nil { glog.Fatal(err) } + m.PrintStats() + } + + if *loadFile != "" { + if err := m.LoadStatus(*loadFile); err != nil { + glog.Fatal(err) + } + m.PrintStats() + if err := m.Reconcile(*maildir); err != nil { + } } }