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)) } return strings.Join(list, "") } const ( 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,` // (logical groups being enclosed by angle brackets) where: // - `<%d_%d.%d.%s>` is the concatenation of: // * system unix timestamp of arrival time of the message // * a progressive number to distinguish messages arrived at once // * pid of the current process // * hostname of the local machine // - `,U=<%d>` holds the UID of the message as decided by the server // - `,FMD5=<%s>:2` holds the md5sum of the name of the current mailbox // - `,` carries the flags active on the message type MailFile struct { timestamp time.Time progressive int pid int hostname string uid int md5 string flags []Flag } func (m *MailFile) SetUid(uid int) { m.uid = uid } func (m *MailFile) SetMd5(md5sum string) { m.md5 = md5sum } func (m *MailFile) SetMd5FromName(name string) { md5sum := md5.Sum([]byte(name)) m.SetMd5(fmt.Sprintf("%x", md5sum)) } func (m *MailFile) SetFlags(flags []Flag) { sort.Slice(flags, func(i, j int) bool { return flags[i] < flags[j] }) m.flags = flags } func (m *MailFile) String() string { return fmt.Sprintf( "%d_%d.%d.%s,U=%d,FMD5=%s:2,%s", m.timestamp.Unix(), m.progressive, m.pid, m.hostname, m.uid, m.md5, cat(m.flags), ) } 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 }