pax_global_header00006660000000000000000000000064145540441010014510gustar00rootroot0000000000000052 comment=67011827de5fd93ea533d83f30a164569b2bd190 go-maildir-0.4.1/000077500000000000000000000000001455404410100135365ustar00rootroot00000000000000go-maildir-0.4.1/.build.yml000066400000000000000000000005741455404410100154440ustar00rootroot00000000000000image: alpine/latest packages: - go sources: - https://github.com/emersion/go-maildir artifacts: - coverage.html tasks: - build: | cd go-maildir go build -v ./... - test: | cd go-maildir go test -race -coverprofile=coverage.txt -covermode=atomic ./... - coverage: | cd go-maildir go tool cover -html=coverage.txt -o ~/coverage.html go-maildir-0.4.1/LICENSE000066400000000000000000000021241455404410100145420ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2014 Lukas Senger Copyright (c) 2019 Simon Ser Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. go-maildir-0.4.1/README.md000066400000000000000000000005671455404410100150250ustar00rootroot00000000000000# go-maildir [![GoDoc](https://godoc.org/github.com/emersion/go-maildir?status.svg)](https://godoc.org/github.com/emersion/go-maildir) [![builds.sr.ht status](https://builds.sr.ht/~emersion/go-maildir/commits/master.svg)](https://builds.sr.ht/~emersion/go-maildir/commits/master?) A Go library for [maildir]. # License MIT [maildir]: https://cr.yp.to/proto/maildir.html go-maildir-0.4.1/go.mod000066400000000000000000000000571455404410100146460ustar00rootroot00000000000000module github.com/emersion/go-maildir go 1.18 go-maildir-0.4.1/maildir.go000066400000000000000000000344701455404410100155160ustar00rootroot00000000000000// The maildir package provides an interface to mailboxes in the Maildir format. // // Maildir mailboxes are designed to be safe for concurrent delivery. This // means that at the same time, multiple processes can deliver to the same // mailbox. However only one process can receive and read messages stored in // the Maildir. package maildir import ( "crypto/rand" "errors" "fmt" "io" "os" "path/filepath" "sort" "strings" "sync/atomic" "time" ) // readdirChunk represents the number of files to load at once from the mailbox // when searching for a message var readdirChunk = 4096 var id int64 = 10000 // A KeyError occurs when a key matches more or less than one message. type KeyError struct { Key string // the (invalid) key N int // number of matches (!= 1) } func (e *KeyError) Error() string { return fmt.Sprintf("maildir: key %q matches %v files, expected exactly one", e.Key, e.N) } // A FlagError occurs when a non-standard info section is encountered. type FlagError struct { Info string // the encountered info section Experimental bool // info section starts with 1 } func (e *FlagError) Error() string { if e.Experimental { return "maildir: experimental info section encountered: " + e.Info[2:] } return "maildir: bad info section encountered: " + e.Info } // A MailfileError occurs when a mailfile has an invalid format type MailfileError struct { Name string // the name of the mailfile } func (e *MailfileError) Error() string { return "maildir: invalid mailfile format: " + e.Name } // A Dir represents a single directory in a Maildir mailbox. // // Dir is used by programs receiving and reading messages from a Maildir. Only // one process can perform these operations. Programs which only need to // deliver new messages to the Maildir should use Delivery. type Dir string // Unseen moves messages from new to cur and returns their keys. // This means the messages are now known to the application. To find out whether // a user has seen a message, use Flags(). func (d Dir) Unseen() ([]string, error) { f, err := os.Open(filepath.Join(string(d), "new")) if err != nil { return nil, err } defer f.Close() var keys []string for { names, err := f.Readdirnames(readdirChunk) if errors.Is(err, io.EOF) { break } else if err != nil { return keys, err } for _, n := range names { if n[0] == '.' { continue } // Messages in new shouldn't have an info field, but some programs // (e.g. offlineimap) do that anyways. Discard the info field in // that case. key, _, _ := strings.Cut(n, string(separator)) info := "2," err = os.Rename(filepath.Join(string(d), "new", n), filepath.Join(string(d), "cur", key+string(separator)+info)) if err != nil { return keys, err } keys = append(keys, key) } } return keys, nil } // UnseenCount returns the number of messages in new without looking at them. func (d Dir) UnseenCount() (int, error) { f, err := os.Open(filepath.Join(string(d), "new")) if err != nil { return 0, err } defer f.Close() c := 0 for { names, err := f.Readdirnames(readdirChunk) if errors.Is(err, io.EOF) { break } else if err != nil { return 0, err } for _, n := range names { if n[0] != '.' { c++ } } } return c, nil } // parseBasename splits a basename into its key and info fields. func parseBasename(basename string) (key, info string, err error) { split := strings.FieldsFunc(basename, func(r rune) bool { return r == separator }) if len(split) < 2 { return "", "", &MailfileError{basename} } return split[0], split[1], nil } func parseKey(basename string) (string, error) { key, _, err := parseBasename(basename) return key, err } // Key returns the key for the given file path. func (d Dir) Key(path string) (string, error) { if filepath.Dir(path) != string(d) { return "", fmt.Errorf("maildir: filepath %q belongs to a different Maildir", path) } filename := filepath.Base(path) return parseKey(filename) } // Walk calls fn for every message. func (d Dir) Walk(fn func(key string, flags []Flag) error) error { f, err := os.Open(filepath.Join(string(d), "cur")) if err != nil { return err } defer f.Close() for { names, err := f.Readdirnames(readdirChunk) if errors.Is(err, io.EOF) { break } else if err != nil { return err } for _, n := range names { if n[0] == '.' { continue } key, err := parseKey(n) if err != nil { return err } flags, err := parseFlags(n) if err != nil { return err } if err := fn(key, flags); err != nil { return err } } } return nil } // Keys returns a slice of valid keys to access messages by. func (d Dir) Keys() ([]string, error) { var keys []string err := d.Walk(func(key string, flags []Flag) error { keys = append(keys, key) return nil }) return keys, err } func (d Dir) filenameGuesses(key string) []string { basename := filepath.Join(string(d), "cur", key+string(separator)+"2,") return []string{ basename, basename + string(FlagPassed), basename + string(FlagReplied), basename + string(FlagSeen), basename + string(FlagDraft), basename + string(FlagFlagged), basename + string(FlagFlagged) + string(FlagPassed), basename + string(FlagFlagged) + string(FlagPassed) + string(FlagSeen), basename + string(FlagFlagged) + string(FlagReplied), basename + string(FlagFlagged) + string(FlagReplied) + string(FlagSeen), basename + string(FlagFlagged) + string(FlagSeen), basename + string(FlagPassed), basename + string(FlagPassed) + string(FlagSeen), basename + string(FlagReplied) + string(FlagSeen), } } // Filename returns the path to the file corresponding to the key. func (d Dir) Filename(key string) (string, error) { // before doing an expensive Glob, see if we can guess the path based on some // common flags for _, guess := range d.filenameGuesses(key) { if _, err := os.Stat(guess); err == nil { return guess, nil } } file, err := os.Open(filepath.Join(string(d), "cur")) if err != nil { return "", err } defer file.Close() // search for a valid candidate (in blocks of readdirChunk) for { names, err := file.Readdirnames(readdirChunk) if errors.Is(err, io.EOF) { // no match return "", &KeyError{key, 0} } else if err != nil { return "", err } for _, name := range names { if strings.HasPrefix(name, key+string(separator)) { return filepath.Join(file.Name(), name), nil } } } } // Open reads a message by key. func (d Dir) Open(key string) (io.ReadCloser, error) { filename, err := d.Filename(key) if err != nil { return nil, err } return os.Open(filename) } type Flag rune const ( // The user has resent/forwarded/bounced this message to someone else. FlagPassed Flag = 'P' // The user has replied to this message. FlagReplied Flag = 'R' // The user has viewed this message, though perhaps he didn't read all the // way through it. FlagSeen Flag = 'S' // The user has moved this message to the trash; the trash will be emptied // by a later user action. FlagTrashed Flag = 'T' // The user considers this message a draft; toggled at user discretion. FlagDraft Flag = 'D' // User-defined flag; toggled at user discretion. FlagFlagged Flag = 'F' ) type flagList []Flag func (s flagList) Len() int { return len(s) } func (s flagList) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s flagList) Less(i, j int) bool { return s[i] < s[j] } func parseFlags(basename string) ([]Flag, error) { _, info, err := parseBasename(basename) if err != nil { return nil, err } switch { case len(info) < 2, info[1] != ',': return nil, &FlagError{info, false} case info[0] == '1': return nil, &FlagError{info, true} case info[0] != '2': return nil, &FlagError{info, false} } fl := []Flag(info[2:]) sort.Sort(flagList(fl)) return []Flag(fl), nil } // Flags returns the flags for a message sorted in ascending order. // See the documentation of SetFlags for details. func (d Dir) Flags(key string) ([]Flag, error) { filename, err := d.Filename(key) if err != nil { return nil, err } return parseFlags(filepath.Base(filename)) } func formatInfo(flags []Flag) string { info := "2," sort.Sort(flagList(flags)) for _, f := range flags { if []rune(info)[len(info)-1] != rune(f) { info += string(f) } } return info } // SetFlags appends an info section to the filename according to the given flags. // This function removes duplicates and sorts the flags, but doesn't check // whether they conform with the Maildir specification. func (d Dir) SetFlags(key string, flags []Flag) error { return d.SetInfo(key, formatInfo(flags)) } // Set the info part of the filename. // Only use this if you plan on using a non-standard info part. func (d Dir) SetInfo(key, info string) error { filename, err := d.Filename(key) if err != nil { return err } err = os.Rename(filename, filepath.Join(string(d), "cur", key+ string(separator)+info)) return err } // newKey generates a new unique key as described in the Maildir specification. // For the third part of the key (delivery identifier) it uses an internal // counter, the process id and a cryptographical random number to ensure // uniqueness among messages delivered in the same second. func newKey() (string, error) { host, err := os.Hostname() if err != nil { return "", err } host = strings.Replace(host, "/", "\057", -1) host = strings.Replace(host, string(separator), "\072", -1) bs := make([]byte, 10) _, err = io.ReadFull(rand.Reader, bs) if err != nil { return "", err } key := fmt.Sprintf("%d.%d%d%x.%s", time.Now().Unix(), os.Getpid(), atomic.AddInt64(&id, 1), bs, host, ) return key, nil } // Init creates the directory structure for a Maildir. // // If the main directory already exists, it tries to create the subdirectories // in there. If an error occurs while creating one of the subdirectories, this // function may leave a partially created directory structure. func (d Dir) Init() error { dirnames := []string{ string(d), filepath.Join(string(d), "tmp"), filepath.Join(string(d), "new"), filepath.Join(string(d), "cur"), } for _, name := range dirnames { if err := os.Mkdir(name, 0700); err != nil && !os.IsExist(err) { return err } } return nil } // Move moves a message from this Maildir to another. func (d Dir) Move(target Dir, key string) error { path, err := d.Filename(key) if err != nil { return err } return os.Rename(path, filepath.Join(string(target), "cur", filepath.Base(path))) } // Copy copies the message with key from this Maildir to the target, preserving // its flags, returning the newly generated key for the target maildir or an // error. func (d Dir) Copy(target Dir, key string) (string, error) { flags, err := d.Flags(key) if err != nil { return "", err } targetKey, err := d.copyToTmp(target, key) if err != nil { return "", err } tmpfile := filepath.Join(string(target), "tmp", targetKey) curfile := filepath.Join(string(target), "cur", targetKey+string(separator)+formatInfo(flags)) if err = os.Rename(tmpfile, curfile); err != nil { return "", err } return targetKey, nil } // copyToTmp copies the message with key from d into a file in the target // maildir's tmp directory with a new key, returning the newly generated key or // an error. func (d Dir) copyToTmp(target Dir, key string) (string, error) { src, err := d.Open(key) if err != nil { return "", err } defer src.Close() targetKey, err := newKey() if err != nil { return "", err } tmpfile := filepath.Join(string(target), "tmp", targetKey) dst, err := os.OpenFile(tmpfile, os.O_CREATE|os.O_WRONLY|os.O_EXCL, 0666) if err != nil { return "", err } defer dst.Close() if _, err = io.Copy(dst, src); err != nil { return "", err } if err := dst.Close(); err != nil { return "", err } return targetKey, nil } // Create inserts a new message into the Maildir. func (d Dir) Create(flags []Flag) (key string, w io.WriteCloser, err error) { key, err = newKey() if err != nil { return "", nil, err } basename := key + string(separator) + formatInfo(flags) filename := filepath.Join(string(d), "cur", basename) w, err = os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_EXCL, 0666) if err != nil { return "", nil, err } return key, w, err } // Remove removes the actual file behind this message. func (d Dir) Remove(key string) error { f, err := d.Filename(key) if err != nil { return err } return os.Remove(f) } // Clean removes old files from tmp and should be run periodically. // This does not use access time but modification time for portability reasons. func (d Dir) Clean() error { f, err := os.Open(filepath.Join(string(d), "tmp")) if err != nil { return err } defer f.Close() now := time.Now() for { names, err := f.Readdirnames(readdirChunk) if errors.Is(err, io.EOF) { break } else if err != nil { return err } for _, n := range names { fi, err := os.Stat(filepath.Join(string(d), "tmp", n)) if err != nil { continue } if now.Sub(fi.ModTime()).Hours() > 36 { err = os.Remove(filepath.Join(string(d), "tmp", n)) if err != nil { return err } } } } return nil } // Delivery represents an ongoing message delivery to the mailbox. It // implements the io.WriteCloser interface. On Close the underlying file is // moved/relinked to new. // // Multiple processes can perform a delivery on the same Maildir concurrently. type Delivery struct { file *os.File d Dir key string } // NewDelivery creates a new Delivery. func NewDelivery(d string) (*Delivery, error) { key, err := newKey() if err != nil { return nil, err } del := &Delivery{} filename := filepath.Join(d, "tmp", key) file, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_EXCL, 0666) if err != nil { return nil, err } del.file = file del.d = Dir(d) del.key = key return del, nil } // Write implements io.Writer. func (d *Delivery) Write(p []byte) (int, error) { return d.file.Write(p) } // Close closes the underlying file and moves it to new. func (d *Delivery) Close() error { tmppath := d.file.Name() err := d.file.Close() if err != nil { return err } newfile := filepath.Join(string(d.d), "new", d.key) if err = os.Rename(tmppath, newfile); err != nil { return err } return nil } // Abort closes the underlying file and removes it completely. func (d *Delivery) Abort() error { tmppath := d.file.Name() err := d.file.Close() if err != nil { return err } return os.Remove(tmppath) } go-maildir-0.4.1/maildir_notwindows.go000066400000000000000000000003651455404410100200050ustar00rootroot00000000000000//go:build !windows package maildir // The separator separates a messages unique key from its flags in the filename. // This should only be changed on operating systems where the colon isn't // allowed in filenames. const separator rune = ':' go-maildir-0.4.1/maildir_test.go000066400000000000000000000216171455404410100165540ustar00rootroot00000000000000package maildir import ( "fmt" "io" "io/ioutil" "math/rand" "os" "path/filepath" "sync" "testing" ) // cleanup removes a Dir's directory structure func cleanup(tb testing.TB, d Dir) { err := os.RemoveAll(string(d)) if err != nil { tb.Error(err) } } // exists checks if the given path exists func exists(path string) bool { _, err := os.Stat(path) if err == nil { return true } if os.IsNotExist(err) { return false } panic(err) } // cat returns the content of a file as a string func cat(t *testing.T, path string) string { f, err := os.Open(path) if err != nil { t.Fatal(err) } defer f.Close() c, err := ioutil.ReadAll(f) if err != nil { t.Fatal(err) } return string(c) } // makeDelivery creates a new message func makeDelivery(tb testing.TB, d Dir, msg string) { del, err := NewDelivery(string(d)) if err != nil { tb.Fatal(err) } _, err = del.Write([]byte(msg)) if err != nil { tb.Fatal(err) } err = del.Close() if err != nil { tb.Fatal(err) } } func TestInit(t *testing.T) { t.Parallel() var d Dir = "test_init" err := d.Init() if err != nil { t.Fatal(err) } f, err := os.Open("test_init") if err != nil { t.Fatal(err) } fis, err := f.Readdir(0) subdirs := make(map[string]os.FileInfo) for _, fi := range fis { if !fi.IsDir() { t.Errorf("%s was not a directory", fi.Name()) continue } subdirs[fi.Name()] = fi } // Verify the directories have been created. if _, ok := subdirs["tmp"]; !ok { t.Error("'tmp' directory was not created") } if _, ok := subdirs["new"]; !ok { t.Error("'new' directory was not created") } if _, ok := subdirs["cur"]; !ok { t.Error("'cur' directory was not created") } // Make sure no error is returned if the directories already exist. err = d.Init() if err != nil { t.Fatal(err) } defer cleanup(t, d) } func TestDelivery(t *testing.T) { t.Parallel() var d Dir = "test_delivery" err := d.Init() if err != nil { t.Fatal(err) } defer cleanup(t, d) var msg = "this is a message" makeDelivery(t, d, msg) keys, err := d.Unseen() if err != nil { t.Fatal(err) } path, err := d.Filename(keys[0]) if err != nil { t.Fatal(err) } if !exists(path) { t.Fatal("File doesn't exist") } if cat(t, path) != msg { t.Fatal("Content doesn't match") } } func TestDir_Create(t *testing.T) { t.Parallel() var d Dir = "test_create" err := d.Init() if err != nil { t.Fatal(err) } defer cleanup(t, d) var msg = "this is a message" key, w, err := d.Create([]Flag{FlagFlagged}) if err != nil { t.Fatal(err) } defer w.Close() if _, err := io.WriteString(w, msg); err != nil { t.Fatal(err) } if err := w.Close(); err != nil { t.Fatal(err) } flags, err := d.Flags(key) if err != nil { t.Fatal(err) } else if len(flags) != 1 || flags[0] != FlagFlagged { t.Errorf("Dir.Flags() = %v, want {FlagFlagged}", flags) } path, err := d.Filename(key) if err != nil { t.Fatal(err) } if !exists(path) { t.Fatal("File doesn't exist") } if cat(t, path) != msg { t.Fatal("Content doesn't match") } } func TestPurge(t *testing.T) { t.Parallel() var d Dir = "test_purge" err := d.Init() if err != nil { t.Fatal(err) } defer cleanup(t, d) makeDelivery(t, d, "foo") keys, err := d.Unseen() if err != nil { t.Fatal(err) } path, err := d.Filename(keys[0]) if err != nil { t.Fatal(err) } err = d.Remove(keys[0]) if err != nil { t.Fatal(err) } if exists(path) { t.Fatal("File still exists") } } func TestMove(t *testing.T) { t.Parallel() var d1 Dir = "test_move1" err := d1.Init() if err != nil { t.Fatal(err) } defer cleanup(t, d1) var d2 Dir = "test_move2" err = d2.Init() if err != nil { t.Fatal(err) } defer cleanup(t, d2) const msg = "a moving message" makeDelivery(t, d1, msg) keys, err := d1.Unseen() if err != nil { t.Fatal(err) } err = d1.Move(d2, keys[0]) if err != nil { t.Fatal(err) } keys, err = d2.Keys() if err != nil { t.Fatal(err) } path, err := d2.Filename(keys[0]) if err != nil { t.Fatal(err) } if cat(t, path) != msg { t.Fatal("Content doesn't match") } } func TestCopy(t *testing.T) { t.Parallel() var d1 Dir = "test_copy1" err := d1.Init() if err != nil { t.Fatal(err) } defer cleanup(t, d1) var d2 Dir = "test_copy2" err = d2.Init() if err != nil { t.Fatal(err) } defer cleanup(t, d2) const msg = "a moving message" makeDelivery(t, d1, msg) keys, err := d1.Unseen() if err != nil { t.Fatal(err) } if err = d1.SetFlags(keys[0], []Flag{FlagSeen}); err != nil { t.Fatal(err) } key2, err := d1.Copy(d2, keys[0]) if err != nil { t.Fatal(err) } path, err := d1.Filename(keys[0]) if err != nil { t.Fatal(err) } if cat(t, path) != msg { t.Error("original content has changed") } path, err = d2.Filename(key2) if err != nil { t.Fatal(err) } if cat(t, path) != msg { t.Error("target content doesn't match source") } flags, err := d2.Flags(key2) if err != nil { t.Fatal(err) } if len(flags) != 1 { t.Fatal("no flags on target") } if flags[0] != FlagSeen { t.Error("seen flag not present on target") } } func TestIllegal(t *testing.T) { t.Parallel() var d1 Dir = "test_illegal" err := d1.Init() if err != nil { t.Fatal(err) } defer cleanup(t, d1) const msg = "an illegal message" makeDelivery(t, d1, msg) keys, err := d1.Unseen() if err != nil { t.Fatal(err) } if err = d1.SetFlags(keys[0], []Flag{FlagSeen}); err != nil { t.Fatal(err) } path, err := d1.Filename(keys[0]) if err != nil { t.Fatal(err) } os.Rename(path, "test_illegal/cur/"+keys[0]) err = d1.Walk(func(string, []Flag) error { return nil }) if _, ok := err.(*MailfileError); !ok { t.Fatal(err) } } func TestFolderWithSquareBrackets(t *testing.T) { t.Parallel() root := t.TempDir() name := "[Google Mail].All Mail" dir := Dir(filepath.Join(root, name)) if err := dir.Init(); err != nil { t.Fatal(err) } key := func() string { key, writer, err := dir.Create([]Flag{FlagPassed, FlagReplied}) if err != nil { t.Fatal(err) } defer writer.Close() _, err = writer.Write([]byte("this is a message")) if err != nil { t.Fatal(err) } return key }() filename, err := dir.Filename(key) if err != nil { t.Fatal(err) } if filename == "" { t.Error("filename should not be empty") } } func TestGeneratedKeysAreUnique(t *testing.T) { t.Parallel() totalThreads := 10 unique := sync.Map{} for thread := 0; thread < totalThreads; thread++ { t.Run("", func(t *testing.T) { t.Parallel() total := 5000 for i := 0; i < total; i++ { key, err := newKey() if err != nil { t.Fatalf("error generating key: %s", err) } if _, found := unique.Load(key); found { t.Fatalf("non unique key generated: %q", key) } unique.Store(key, true) } }) } } func TestDifferentSizesOfReaddirChunks(t *testing.T) { totalFiles := 3 // don't run this test in // as it modifies a package variable source := t.TempDir() dir := Dir(source) if err := dir.Init(); err != nil { t.Fatal(err) } for i := 0; i < totalFiles; i++ { makeDelivery(t, dir, fmt.Sprintf("here is message number %d", i)) } // grab keys keys, err := dir.Unseen() if err != nil { t.Fatal(err) } // avoid the filename guesser for _, key := range keys { err := dir.SetFlags(key, []Flag{FlagPassed, FlagReplied}) if err != nil { t.Fatal(err) } } previousReaddirChunk := readdirChunk // set it back to normal for the following tests defer func() { readdirChunk = previousReaddirChunk }() // try different sizes of chunks for chunkSize := 0; chunkSize <= totalFiles+1; chunkSize++ { readdirChunk = chunkSize for _, key := range keys { filename, err := dir.Filename(key) if err != nil { t.Fatal(err) } if filename == "" { t.Errorf("cannot find filename for key %q", key) } } } } func BenchmarkFilename(b *testing.B) { // set up test maildir d := Dir("benchmark_filename") if err := d.Init(); err != nil { b.Fatalf("could not set up benchmark: %v", err) } defer cleanup(b, d) // make 5000 deliveries for i := 0; i < 5000; i++ { makeDelivery(b, d, fmt.Sprintf("here is message number %d", i)) } // grab keys keys, err := d.Unseen() if err != nil { b.Fatal(err) } // shuffle keys rand.Shuffle(len(keys), func(i, j int) { keys[i], keys[j] = keys[j], keys[i] }) // set some flags for i, key := range keys { var err error switch i % 5 { case 0: // no flags fallthrough case 1: err = d.SetFlags(key, []Flag{FlagSeen}) case 2: err = d.SetFlags(key, []Flag{FlagSeen, FlagReplied}) case 3: err = d.SetFlags(key, []Flag{FlagReplied}) case 4: err = d.SetFlags(key, []Flag{FlagFlagged}) } if err != nil { b.Fatal(err) } } // run benchmark for the first N shuffled keys keyIdx := 0 b.ResetTimer() for i := 0; i < b.N; i++ { b.StartTimer() _, err := d.Filename(keys[keyIdx]) b.StopTimer() if err != nil { b.Errorf("could not get filename for key %s", keys[keyIdx]) } keyIdx++ if keyIdx >= len(keys) { keyIdx = 0 } } } go-maildir-0.4.1/maildir_windows.go000066400000000000000000000003641455404410100172630ustar00rootroot00000000000000//go:build windows package maildir // The separator separates a messages unique key from its flags in the filename. // This should only be changed on operating systems where the colon isn't // allowed in filenames. const separator rune = ';' go-maildir-0.4.1/maildirpp/000077500000000000000000000000001455404410100155175ustar00rootroot00000000000000go-maildir-0.4.1/maildirpp/maildirpp.go000066400000000000000000000010751455404410100200320ustar00rootroot00000000000000// Package maildirpp implements Maildir++. package maildirpp import ( "errors" "strings" ) const separator = '.' func Split(key string) ([]string, error) { if len(key) == 0 || key[0] != '.' { return nil, errors.New("maildirpp: invalid key") } return strings.Split(key, string(separator))[1:], nil } func Join(elems []string) (key string, err error) { for _, d := range elems { if strings.ContainsRune(d, separator) { return "", errors.New("maildirpp: directory name cannot contain a dot") } } return "." + strings.Join(elems, string(separator)), nil }