Browse Source

NewMailDir and parse MailFile from string

Blallo 3 years ago
parent
commit
b74764b71a
2 changed files with 191 additions and 7 deletions
  1. 149 6
      fs/maildir.go
  2. 42 1
      fs/maildir_test.go

+ 149 - 6
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 {
 	)
 }
 
+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
+	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
 }

+ 42 - 1
fs/maildir_test.go

@@ -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{})))
+	}
+}