Move maildir related functionality to own package.
This commit is contained in:
parent
774d1e3603
commit
2b0f02ae03
@ -7,17 +7,19 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"xinu.tv/email"
|
"xinu.tv/email"
|
||||||
|
"xinu.tv/email/maildir"
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
dryrun = flag.Bool("dryrun", true, "don't actually rename files, just log actions.")
|
dryrun = flag.Bool("dryrun", true, "don't actually rename files, just log actions.")
|
||||||
saveFile = flag.String("save", "", "filename to store unread status.")
|
saveFile = flag.String("save", "", "filename to store unread status.")
|
||||||
loadFile = flag.String("load", "", "filename to load unread status.")
|
loadFile = flag.String("load", "", "filename to load unread status.")
|
||||||
maildir = flag.String("maildir", "", "Maildir root")
|
maildirPath = flag.String("maildir", "", "Maildir root")
|
||||||
skipFiles = flag.String("skip", "maildirfolder,log,msgid.cache,razor-agent.log",
|
skipFiles = flag.String("skip", "maildirfolder,log,msgid.cache,razor-agent.log",
|
||||||
"comma separated files to skip")
|
"comma separated files to skip")
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -62,7 +64,7 @@ func (m *Messages) hashMail(path string, info os.FileInfo, err error) error {
|
|||||||
glog.Infof("%q", err.Error())
|
glog.Infof("%q", err.Error())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
md := email.NewInfo(path)
|
md := maildir.NewInfo(path)
|
||||||
m.Statuses = append(m.Statuses, Status{
|
m.Statuses = append(m.Statuses, Status{
|
||||||
Path: path,
|
Path: path,
|
||||||
Hash: h,
|
Hash: h,
|
||||||
@ -82,8 +84,8 @@ func (m *Messages) LoadStatus(statusFile string) error {
|
|||||||
return dec.Decode(&m.Statuses)
|
return dec.Decode(&m.Statuses)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Messages) SaveStatus(maildir, statusFile string) error {
|
func (m *Messages) SaveStatus(maildirPath, statusFile string) error {
|
||||||
if err := filepath.Walk(maildir, m.hashMail); err != nil {
|
if err := filepath.Walk(maildirPath, m.hashMail); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,13 +112,13 @@ func (m *Messages) SaveStatus(maildir, statusFile string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Messages) Reconcile(maildir string) error {
|
func (m Messages) Reconcile(maildirPath string) error {
|
||||||
hashMap := make(map[string]*Status, len(m.Statuses))
|
hashMap := make(map[string]*Status, len(m.Statuses))
|
||||||
for i, msg := range m.Statuses {
|
for i, msg := range m.Statuses {
|
||||||
hashMap[msg.Hash] = &m.Statuses[i]
|
hashMap[msg.Hash] = &m.Statuses[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
return filepath.Walk(maildir, func(path string, info os.FileInfo, err error) error {
|
return filepath.Walk(maildirPath, func(path string, info os.FileInfo, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -144,7 +146,7 @@ func (m Messages) Reconcile(maildir string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
md := email.NewInfo(path)
|
md := maildir.NewInfo(path)
|
||||||
s, ok := hashMap[chksum]
|
s, ok := hashMap[chksum]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
@ -192,7 +194,7 @@ func main() {
|
|||||||
Skip: skip,
|
Skip: skip,
|
||||||
}
|
}
|
||||||
|
|
||||||
if *maildir == "" {
|
if *maildirPath == "" {
|
||||||
fmt.Println("Must specify Maildir with -maildir")
|
fmt.Println("Must specify Maildir with -maildir")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
@ -203,7 +205,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if *saveFile != "" {
|
if *saveFile != "" {
|
||||||
if err := m.SaveStatus(*maildir, *saveFile); err != nil {
|
if err := m.SaveStatus(*maildirPath, *saveFile); err != nil {
|
||||||
glog.Fatal(err)
|
glog.Fatal(err)
|
||||||
}
|
}
|
||||||
m.PrintStats()
|
m.PrintStats()
|
||||||
@ -214,7 +216,7 @@ func main() {
|
|||||||
glog.Fatal(err)
|
glog.Fatal(err)
|
||||||
}
|
}
|
||||||
m.PrintStats()
|
m.PrintStats()
|
||||||
if err := m.Reconcile(*maildir); err != nil {
|
if err := m.Reconcile(*maildirPath); err != nil {
|
||||||
glog.Fatal(err)
|
glog.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
80
maildir.go
80
maildir.go
@ -1,80 +0,0 @@
|
|||||||
// Package email helps manage emails stored in Maildir format. A good
|
|
||||||
// write-up on the format is available at http://cr.yp.to/proto/maildir.html
|
|
||||||
package email
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Info represents the info bits encoded as the suffix of a Maildir file. info
|
|
||||||
// starting with "2,": Each character after the comma is an independent flag.
|
|
||||||
// Flag "P" (passed): the user has resent/forwarded/bounced this message to someone else.
|
|
||||||
// Flag "R" (replied): the user has replied to this message.
|
|
||||||
// Flag "S" (seen): the user has viewed this message, though perhaps he didn't read all the way through it.
|
|
||||||
// Flag "T" (trashed): the user has moved this message to the trash; the trash will be emptied by a later user action.
|
|
||||||
// Flag "D" (draft): the user considers this message a draft; toggled at user discretion.
|
|
||||||
// Flag "F" (flagged): user-defined flag; toggled at user discretion.
|
|
||||||
// New flags may be defined later. Flags must be stored in ASCII order: e.g., "2,FRS".
|
|
||||||
type Info struct {
|
|
||||||
Base string // The portion of the filename before the ':'.
|
|
||||||
Passed bool
|
|
||||||
Replied bool
|
|
||||||
Seen bool
|
|
||||||
Trashed bool
|
|
||||||
Draft bool
|
|
||||||
Flagged bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewInfo returns an Info containing the version 2 Maildir flags parsed from
|
|
||||||
// fn's suffix.
|
|
||||||
func NewInfo(fn string) (info Info) {
|
|
||||||
info.Base = fn
|
|
||||||
i := strings.LastIndex(fn, ":")
|
|
||||||
if i != -1 {
|
|
||||||
info.Base = fn[:i]
|
|
||||||
infoStr := fn[i:]
|
|
||||||
i = strings.Index(infoStr, ",")
|
|
||||||
if i == -1 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
flags := infoStr[i:]
|
|
||||||
info.Passed = strings.Contains(flags, "P")
|
|
||||||
info.Replied = strings.Contains(flags, "R")
|
|
||||||
info.Seen = strings.Contains(flags, "S")
|
|
||||||
info.Trashed = strings.Contains(flags, "T")
|
|
||||||
info.Draft = strings.Contains(flags, "D")
|
|
||||||
info.Flagged = strings.Contains(flags, "F")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i Info) String() string {
|
|
||||||
if i.Passed || i.Replied || i.Seen || i.Trashed || i.Draft || i.Flagged {
|
|
||||||
b := new(bytes.Buffer)
|
|
||||||
fmt.Fprintf(b, "%s:2,", i.Base)
|
|
||||||
if i.Passed {
|
|
||||||
fmt.Fprint(b, "P")
|
|
||||||
}
|
|
||||||
if i.Replied {
|
|
||||||
fmt.Fprint(b, "R")
|
|
||||||
}
|
|
||||||
if i.Seen {
|
|
||||||
fmt.Fprint(b, "S")
|
|
||||||
}
|
|
||||||
if i.Trashed {
|
|
||||||
fmt.Fprint(b, "T")
|
|
||||||
}
|
|
||||||
if i.Draft {
|
|
||||||
fmt.Fprint(b, "D")
|
|
||||||
}
|
|
||||||
if i.Flagged {
|
|
||||||
fmt.Fprint(b, "F")
|
|
||||||
}
|
|
||||||
return b.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
return i.Base
|
|
||||||
}
|
|
||||||
@ -1,91 +0,0 @@
|
|||||||
package email
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestInfo(t *testing.T) {
|
|
||||||
datum := []struct {
|
|
||||||
input string
|
|
||||||
want Info
|
|
||||||
clean string // Expected result from .String(), empty defaults to input.
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
input: "mail:2,PRSTDF",
|
|
||||||
want: Info{
|
|
||||||
Base: "mail",
|
|
||||||
Passed: true,
|
|
||||||
Replied: true,
|
|
||||||
Seen: true,
|
|
||||||
Trashed: true,
|
|
||||||
Draft: true,
|
|
||||||
Flagged: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "mail:2,PRTDF",
|
|
||||||
want: Info{
|
|
||||||
Base: "mail",
|
|
||||||
Passed: true,
|
|
||||||
Replied: true,
|
|
||||||
Seen: false,
|
|
||||||
Trashed: true,
|
|
||||||
Draft: true,
|
|
||||||
Flagged: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "mail:2,PRSTD",
|
|
||||||
want: Info{
|
|
||||||
Base: "mail",
|
|
||||||
Passed: true,
|
|
||||||
Replied: true,
|
|
||||||
Seen: true,
|
|
||||||
Trashed: true,
|
|
||||||
Draft: true,
|
|
||||||
Flagged: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "mail:2,RSTDF",
|
|
||||||
want: Info{
|
|
||||||
Base: "mail",
|
|
||||||
Passed: false,
|
|
||||||
Replied: true,
|
|
||||||
Seen: true,
|
|
||||||
Trashed: true,
|
|
||||||
Draft: true,
|
|
||||||
Flagged: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "mail",
|
|
||||||
want: Info{
|
|
||||||
Base: "mail",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "mail:2,",
|
|
||||||
want: Info{
|
|
||||||
Base: "mail",
|
|
||||||
},
|
|
||||||
clean: "mail",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, d := range datum {
|
|
||||||
got := NewInfo(d.input)
|
|
||||||
if !reflect.DeepEqual(d.want, got) {
|
|
||||||
t.Errorf("%d. Got %#v want %#v", i, got, d.want)
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := d.input
|
|
||||||
if d.clean != "" {
|
|
||||||
expected = d.clean
|
|
||||||
}
|
|
||||||
if gotStr := got.String(); gotStr != expected {
|
|
||||||
t.Errorf("%d. Got %q want %q", i, gotStr, expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user