NewMailDir and parse MailFile from string
This commit is contained in:
parent
5a199eec6b
commit
b74764b71a
2 changed files with 192 additions and 8 deletions
153
fs/maildir.go
153
fs/maildir.go
|
@ -2,15 +2,24 @@ package fs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"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
|
type Flag string
|
||||||
|
|
||||||
func cat(f []Flag) string {
|
func cat(f []Flag) string {
|
||||||
|
sort.Slice(f, func(i, j int) bool { return f[i] < f[j] })
|
||||||
var list []string
|
var list []string
|
||||||
for _, el := range f {
|
for _, el := range f {
|
||||||
list = append(list, string(el))
|
list = append(list, string(el))
|
||||||
|
@ -19,13 +28,62 @@ func cat(f []Flag) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Seen Flag = "S"
|
FlagSeen Flag = "S"
|
||||||
Answered = "R"
|
FlagAnswered = "R"
|
||||||
Flagged = "F"
|
FlagFlagged = "F"
|
||||||
Deleted = "T"
|
FlagDeleted = "T"
|
||||||
Draft = "D"
|
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
|
// MailFile holds the information needed to format the name of the file
|
||||||
// containing a single email. The format is:
|
// containing a single email. The format is:
|
||||||
// `<%d_%d.%d.%s>,U=<%d>,FMD5=<%s>:2,<FLAGS>`
|
// `<%d_%d.%d.%s>,U=<%d>,FMD5=<%s>:2,<FLAGS>`
|
||||||
|
@ -79,6 +137,91 @@ func (m *MailFile) String() 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 {
|
type MailDir struct {
|
||||||
basePath string
|
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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"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) {
|
func TestMailFileString(t *testing.T) {
|
||||||
clock := mockClock{}
|
clock := mockClock{}
|
||||||
timestamp := clock.Now()
|
timestamp := clock.Now()
|
||||||
|
@ -16,7 +38,7 @@ func TestMailFileString(t *testing.T) {
|
||||||
name := "main_account" // known md5sum: f2cf513ad46d4d9b9684103e468803a0
|
name := "main_account" // known md5sum: f2cf513ad46d4d9b9684103e468803a0
|
||||||
uid := 33243
|
uid := 33243
|
||||||
mf := &MailFile{timestamp: timestamp, progressive: progressive, pid: pid, hostname: hostname}
|
mf := &MailFile{timestamp: timestamp, progressive: progressive, pid: pid, hostname: hostname}
|
||||||
mf.SetFlags([]Flag{Seen, Answered})
|
mf.SetFlags([]Flag{FlagSeen, FlagAnswered})
|
||||||
mf.SetMd5FromName(name)
|
mf.SetMd5FromName(name)
|
||||||
mf.SetUid(uid)
|
mf.SetUid(uid)
|
||||||
|
|
||||||
|
@ -34,3 +56,22 @@ func TestMailFileString(t *testing.T) {
|
||||||
t.Logf("diff: %s", cmp.Diff(expect, result))
|
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