Convert from BoltDB to Maildir storage for emails
This commit is contained in:
@@ -1,50 +1,108 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/mail"
|
||||
|
||||
"github.com/asdine/storm/q"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Message struct {
|
||||
Maildir Maildir // maildir.name
|
||||
|
||||
UID uint64
|
||||
Key string
|
||||
Data io.ReadCloser
|
||||
hdrCache mail.Header
|
||||
}
|
||||
|
||||
// TODO: the cache is probably fine
|
||||
func (m *Message) Header() (mail.Header, error) {
|
||||
if m.hdrCache != nil {
|
||||
return m.hdrCache, nil
|
||||
}
|
||||
|
||||
hdr, err := m.Maildir.Header(m.Key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m.hdrCache = hdr
|
||||
return hdr, nil
|
||||
}
|
||||
|
||||
func (m *Message) Len() uint64 {
|
||||
return 0 // FIXME
|
||||
}
|
||||
|
||||
func (m *Message) HeaderString() (string, error) {
|
||||
var sb strings.Builder
|
||||
|
||||
hdr, err := m.Header()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for k, vs := range hdr {
|
||||
for _, v := range vs {
|
||||
fmt.Fprintf(&sb, "%s: %s\r\n", k, v)
|
||||
}
|
||||
}
|
||||
return sb.String(), nil // FIXME
|
||||
}
|
||||
|
||||
type MessageInterface interface {
|
||||
CreateMessage(Message) error
|
||||
FindMessages(Mailbox) ([]Message, error)
|
||||
|
||||
// if end == 0, it means "from start to most recent"
|
||||
FindMessagesInRange(mailbox Mailbox, start, end uint64) ([]Message, error)
|
||||
}
|
||||
|
||||
type Message struct {
|
||||
// FIXME: this should be per-mailbox, IMAP "only" allows 32-bit message UIDs?
|
||||
ID uint64 `storm:"id,increment"`
|
||||
|
||||
Mailbox uint64 `storm:"index"` // FK mailbox.id
|
||||
|
||||
Header mail.Header // The parsed headers
|
||||
EML []byte // The full email, unaltered
|
||||
}
|
||||
|
||||
// DeliverMessage takes the given message and delivers it to the correct maildir
|
||||
// It does not call Close() on message.Data
|
||||
func (c *concrete) CreateMessage(message Message) error {
|
||||
return c.storm.Save(&message)
|
||||
}
|
||||
// TODO: recover from panics, assure Abort() is called for them
|
||||
maildir := message.Maildir
|
||||
|
||||
func (c *concrete) FindMessages(mailbox Mailbox) ([]Message, error) {
|
||||
var out []Message
|
||||
|
||||
return out, c.storm.Find("Mailbox", mailbox.ID, &out)
|
||||
}
|
||||
|
||||
func (c *concrete) FindMessagesInRange(mailbox Mailbox, start, end uint64) ([]Message, error) {
|
||||
var out []Message
|
||||
|
||||
matchers := []q.Matcher{
|
||||
q.Eq("Mailbox", mailbox.ID),
|
||||
q.Gte("ID", start),
|
||||
delivery, err := maildir.filesystem.NewDelivery()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if end > 0 {
|
||||
matchers = append(matchers, q.Lte("ID", end))
|
||||
if _, err := io.Copy(delivery, message.Data); err != nil {
|
||||
delivery.Abort()
|
||||
return err
|
||||
}
|
||||
|
||||
return out, c.storm.Select(matchers...).Find(&out)
|
||||
if err := delivery.Close(); err != nil {
|
||||
delivery.Abort()
|
||||
return err
|
||||
}
|
||||
|
||||
// FIXME: this moves files in the maildir before the dovecot-uidlist is
|
||||
// updated. Not ideal.
|
||||
// FIXME: we're not
|
||||
keys, err := maildir.filesystem.Unseen()
|
||||
if err != nil {
|
||||
log.Printf("Failed moving messages from new/ to cur/: %v", err)
|
||||
}
|
||||
|
||||
var filenames []string
|
||||
for _, key := range keys {
|
||||
filename, err := maildir.filesystem.Filename(key)
|
||||
if err != nil {
|
||||
log.Printf("Failed getting filename for unseen message %q: %v", key, err)
|
||||
continue
|
||||
}
|
||||
filenames = append(filenames, filepath.Base(filename))
|
||||
}
|
||||
|
||||
// Don't bubble the error up here as delivery has succeeded, IMAP just
|
||||
// doesn't know it has. We don't want the client to attempt redelivery.
|
||||
//
|
||||
// FIXME: this is IMAP stuff that needs moving
|
||||
if err := maildir.uids.Append(filenames...); err != nil {
|
||||
log.Printf("Allocating UIDs for unseen messages failed: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
Reference in New Issue
Block a user