NewMailDir and parse MailFile from string
This commit is contained in:
parent
5a199eec6b
commit
b74764b71a
2 changed files with 192 additions and 8 deletions
157
fs/maildir.go
157
fs/maildir.go
|
@ -2,15 +2,24 @@ package fs
|
|||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var ErrPathIsNotDirectory = errors.New("the path is not a directory")
|
||||
var ErrMalformedName = errors.New("file name is not recognised")
|
||||
var ErrUnknownFlag = errors.New("flag not known")
|
||||
|
||||
type Flag string
|
||||
|
||||
func cat(f []Flag) string {
|
||||
sort.Slice(f, func(i, j int) bool { return f[i] < f[j] })
|
||||
var list []string
|
||||
for _, el := range f {
|
||||
list = append(list, string(el))
|
||||
|
@ -19,13 +28,62 @@ func cat(f []Flag) string {
|
|||
}
|
||||
|
||||
const (
|
||||
Seen Flag = "S"
|
||||
Answered = "R"
|
||||
Flagged = "F"
|
||||
Deleted = "T"
|
||||
Draft = "D"
|
||||
FlagSeen Flag = "S"
|
||||
FlagAnswered = "R"
|
||||
FlagFlagged = "F"
|
||||
FlagDeleted = "T"
|
||||
FlagDraft = "D"
|
||||
FlagUnknown = ""
|
||||
)
|
||||
|
||||
func flagFromChar(char rune) (Flag, error) {
|
||||
switch string(char) {
|
||||
case "S":
|
||||
return FlagSeen, nil
|
||||
case "R":
|
||||
return FlagAnswered, nil
|
||||
case "F":
|
||||
return FlagFlagged, nil
|
||||
case "T":
|
||||
return FlagDeleted, nil
|
||||
case "D":
|
||||
return FlagDraft, nil
|
||||
default:
|
||||
return FlagUnknown, ErrUnknownFlag
|
||||
}
|
||||
}
|
||||
|
||||
func flagsFromString(flags string) ([]Flag, error) {
|
||||
result := []Flag{}
|
||||
knownFlags := map[Flag]bool{
|
||||
FlagSeen: true,
|
||||
FlagAnswered: true,
|
||||
FlagFlagged: true,
|
||||
FlagDeleted: true,
|
||||
FlagDraft: true,
|
||||
}
|
||||
|
||||
if flags == "" {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
for _, f := range flags {
|
||||
if flag, err := flagFromChar(f); err != nil {
|
||||
return []Flag{}, err
|
||||
} else {
|
||||
if knownFlags[flag] {
|
||||
result = append(result, flag)
|
||||
knownFlags[flag] = false
|
||||
} else {
|
||||
return []Flag{}, ErrMalformedName
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Slice(result, func(i, j int) bool { return result[i] < result[j] })
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// MailFile holds the information needed to format the name of the file
|
||||
// containing a single email. The format is:
|
||||
// `<%d_%d.%d.%s>,U=<%d>,FMD5=<%s>:2,<FLAGS>`
|
||||
|
@ -79,6 +137,91 @@ func (m *MailFile) String() string {
|
|||
)
|
||||
}
|
||||
|
||||
type MailDir struct {
|
||||
basePath string
|
||||
func NewMailFile(name string) (*MailFile, error) {
|
||||
mailFile := &MailFile{}
|
||||
|
||||
parts := strings.Split(name, ",")
|
||||
if len(parts) != 4 {
|
||||
return mailFile, ErrMalformedName
|
||||
}
|
||||
|
||||
sub1 := strings.Split(parts[0], ".")
|
||||
if len(sub1) != 3 {
|
||||
return mailFile, ErrMalformedName
|
||||
}
|
||||
|
||||
sub2 := strings.Split(sub1[0], "_")
|
||||
if len(sub2) != 2 {
|
||||
return mailFile, ErrMalformedName
|
||||
}
|
||||
|
||||
unix, err := strconv.ParseInt(sub2[0], 10, 64)
|
||||
if err != nil {
|
||||
return mailFile, err
|
||||
}
|
||||
mailFile.timestamp = time.Unix(unix, 0)
|
||||
|
||||
progressive, err := strconv.ParseInt(sub2[1], 10, 32)
|
||||
if err != nil {
|
||||
return mailFile, err
|
||||
}
|
||||
mailFile.progressive = int(progressive)
|
||||
|
||||
pid, err := strconv.ParseInt(sub1[1], 10, 32)
|
||||
if err != nil {
|
||||
return mailFile, err
|
||||
}
|
||||
mailFile.pid = int(pid)
|
||||
|
||||
mailFile.hostname = sub1[2]
|
||||
|
||||
sub3 := strings.Split(parts[1], "=")
|
||||
if len(sub3) != 2 || sub3[0] != "U" {
|
||||
return mailFile, ErrMalformedName
|
||||
}
|
||||
|
||||
uid, err := strconv.ParseInt(sub3[1], 10, 32)
|
||||
if err != nil {
|
||||
return mailFile, err
|
||||
}
|
||||
mailFile.uid = int(uid)
|
||||
|
||||
sub4 := strings.Split(parts[2], "=")
|
||||
if len(sub4) != 2 || sub4[0] != "FMD5" {
|
||||
return mailFile, ErrMalformedName
|
||||
}
|
||||
sub5 := strings.Split(sub4[1], ":")
|
||||
if len(sub5) != 2 || sub5[1] != "2" {
|
||||
return mailFile, ErrMalformedName
|
||||
}
|
||||
mailFile.md5 = sub5[0]
|
||||
|
||||
flags, err := flagsFromString(parts[3])
|
||||
if err != nil {
|
||||
return mailFile, err
|
||||
}
|
||||
mailFile.flags = flags
|
||||
|
||||
return mailFile, nil
|
||||
}
|
||||
|
||||
type MailDir struct {
|
||||
basePath string
|
||||
name string
|
||||
MailFiles []MailFile
|
||||
}
|
||||
|
||||
func NewMailDir(name, basePath string) (*MailDir, error) {
|
||||
mailDirPath := path.Join(basePath, name)
|
||||
mailDir := &MailDir{basePath: basePath, name: name, MailFiles: []MailFile{}}
|
||||
info, err := os.Stat(mailDirPath)
|
||||
if err != nil {
|
||||
if err == os.ErrNotExist {
|
||||
err = os.Mkdir(mailDirPath, 0750)
|
||||
}
|
||||
}
|
||||
if !info.IsDir() {
|
||||
return mailDir, ErrPathIsNotDirectory
|
||||
}
|
||||
return mailDir, err
|
||||
}
|
||||
|
|
|
@ -3,10 +3,32 @@ package fs
|
|||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
)
|
||||
|
||||
func TestFlagsFromString(t *testing.T) {
|
||||
flags, err := flagsFromString("RTDSF")
|
||||
if err != nil {
|
||||
t.Errorf("Flag parsing errored unexpectedly: %s\n", err)
|
||||
}
|
||||
expect := []Flag{FlagDraft, FlagFlagged, FlagAnswered, FlagSeen, FlagDeleted}
|
||||
if !cmp.Equal(flags, expect) {
|
||||
t.Errorf("Flags mismatch: %s", cmp.Diff(expect, flags))
|
||||
}
|
||||
|
||||
_, err = flagsFromString("RDX")
|
||||
if err != ErrUnknownFlag {
|
||||
t.Error("Flag parsing should have errored")
|
||||
}
|
||||
|
||||
_, err = flagsFromString("DDF")
|
||||
if err != ErrMalformedName {
|
||||
t.Error("Flag parsing should have errored")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMailFileString(t *testing.T) {
|
||||
clock := mockClock{}
|
||||
timestamp := clock.Now()
|
||||
|
@ -16,7 +38,7 @@ func TestMailFileString(t *testing.T) {
|
|||
name := "main_account" // known md5sum: f2cf513ad46d4d9b9684103e468803a0
|
||||
uid := 33243
|
||||
mf := &MailFile{timestamp: timestamp, progressive: progressive, pid: pid, hostname: hostname}
|
||||
mf.SetFlags([]Flag{Seen, Answered})
|
||||
mf.SetFlags([]Flag{FlagSeen, FlagAnswered})
|
||||
mf.SetMd5FromName(name)
|
||||
mf.SetUid(uid)
|
||||
|
||||
|
@ -34,3 +56,22 @@ func TestMailFileString(t *testing.T) {
|
|||
t.Logf("diff: %s", cmp.Diff(expect, result))
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewMailFile(t *testing.T) {
|
||||
mf, err := NewMailFile("12345_0.1312.myplace,U=666,FMD5=f2cf513ad46d4d9b9684103e468803a0:2,")
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %s\n", err)
|
||||
}
|
||||
expect := &MailFile{
|
||||
timestamp: time.Unix(12345, 0),
|
||||
progressive: 0,
|
||||
pid: 1312,
|
||||
hostname: "myplace",
|
||||
uid: 666,
|
||||
md5: "f2cf513ad46d4d9b9684103e468803a0",
|
||||
flags: []Flag{},
|
||||
}
|
||||
if !cmp.Equal(mf, expect, cmp.AllowUnexported(MailFile{})) {
|
||||
t.Errorf("MailFile mismatch: %s", cmp.Diff(expect, mf, cmp.AllowUnexported(MailFile{})))
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue