|
@@ -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
|
|
|
}
|