diff --git a/internal/imap/mailbox.go b/internal/imap/mailbox.go index 0312094..ddf178a 100644 --- a/internal/imap/mailbox.go +++ b/internal/imap/mailbox.go @@ -45,15 +45,9 @@ func (m *Mailbox) Info() (*imap.MailboxInfo, error) { // This function does not affect the state of any messages in the mailbox. See // RFC 3501 section 6.3.10 for a list of items that can be requested. func (m *Mailbox) Status(items []string) (*imap.MailboxStatus, error) { - log.Printf("Mailbox(%v).Status(%#v)", m.stored.Rel(), items) - - return &imap.MailboxStatus{ - Name: m.Name(), - Flags: []string{}, // FIXME: what do we need here? - PermanentFlags: []string{}, // FIXME: what do we need here? - //UnseenSeqNum: 0, // FIXME: what do we need here? What even is a seqnum? - Messages: 1, // FIXME: hardcoded - }, nil + status, err := m.buildStatus(items...) + log.Printf("Mailbox(%v).Status(%#v): %#v %#v", m.stored.Rel(), items, status, err) + return status, err } // SetSubscribed adds or removes the mailbox to the server's set of "active" @@ -85,7 +79,7 @@ func (m *Mailbox) ListMessages(uid bool, seqset *imap.SeqSet, items []string, ch var messages []store.Message for _, set := range seqset.Set { - msgs, err := m.session.store.FindMessagesInRange( + msgs, err := m.session.store.FindMessagesInRangeBySeqNum( m.stored, uint64(set.Start), uint64(set.Stop), ) @@ -119,14 +113,13 @@ func (m *Mailbox) ListMessages(uid bool, seqset *imap.SeqSet, items []string, ch body := map[*imap.BodySectionName]imap.Literal{} - imapMsg := imap.NewMessage(uint32(message.UID), items) - if err := fillItems(imapMsg, message); err != nil { + imapMsg := imap.NewMessage(uint32(message.SeqNum), items) + if err := m.fillMessageItems(imapMsg, message); err != nil { return err } imapMsg.Envelope = envelope imapMsg.Body = body - imapMsg.Uid = uint32(message.UID) imapMsg.Size = uint32(message.Len()) log.Printf(" Sending message %#v", imapMsg) @@ -137,14 +130,50 @@ func (m *Mailbox) ListMessages(uid bool, seqset *imap.SeqSet, items []string, ch return nil } -func fillItems(imapMsg *imap.Message, msg store.Message) error { +func (m *Mailbox) buildStatus(items ...string) (*imap.MailboxStatus, error) { + out := &imap.MailboxStatus{ + Name: m.Name(), + } + + for _, item := range items { + switch item { + case "FLAGS": // TODO: ??? + out.Flags = []string{} + case "PERMANENTFLAGS": // TODO: ??? + out.PermanentFlags = []string{} + // The number of messages in the mailbox. + case "MESSAGES": + out.Messages = uint32(m.session.store.CountMessages(m.stored)) + // The number of messages with the \Recent flag set. TODO + case "RECENT": + // The next unique identifier value of the mailbox. + case "UIDNEXT": + out.UidNext = uint32(m.stored.NextUID()) + // The unique identifier validity value of the mailbox + case "UIDVALIDITY": + out.UidValidity = uint32(m.stored.UIDValidity()) + // The number of messages which do not have the \Seen flag set. TODO + case "UNSEEN": + default: + return nil, fmt.Errorf("Unknown status item: %v", item) + } + } + + return out, nil +} + +func (m *Mailbox) fillMessageItems(imapMsg *imap.Message, msg store.Message) error { + // FIXME: do I have to fill Items as well as the individual fields? Unclear for k, _ := range imapMsg.Items { switch k { case "UID": - imapMsg.Items[k] = msg.UID + imapMsg.Uid = uint32(msg.UID) + imapMsg.Items[k] = uint32(msg.UID) case "FLAGS": + imapMsg.Flags = []string{} imapMsg.Items[k] = []string{} case "RFC822.SIZE": + imapMsg.Size = uint32(msg.Len()) imapMsg.Items[k] = uint32(msg.Len()) case "RFC822.HEADER": str, err := msg.HeaderString() @@ -154,7 +183,7 @@ func fillItems(imapMsg *imap.Message, msg store.Message) error { imapMsg.Items[k] = str default: - return fmt.Errorf("Unknown item: %v", k) + return fmt.Errorf("Unknown message item: %v", k) } } diff --git a/internal/imap/uidlist/entry.go b/internal/imap/uidlist/entry.go index 682e005..502af78 100644 --- a/internal/imap/uidlist/entry.go +++ b/internal/imap/uidlist/entry.go @@ -7,8 +7,10 @@ import ( ) type Entry struct { - UID uint64 - Extra map[string][]string + UID uint64 + SeqNum uint64 + Extra map[string][]string + LastKnownFilename string } diff --git a/internal/imap/uidlist/uidlist.go b/internal/imap/uidlist/uidlist.go index cfce21c..d57a05a 100644 --- a/internal/imap/uidlist/uidlist.go +++ b/internal/imap/uidlist/uidlist.go @@ -83,6 +83,12 @@ func handleFile(filename string, flags int) (*List, error) { }, nil } +func (l *List) NextUID() uint64 { + l.mutex.Lock() + defer l.mutex.Unlock() + + return l.header.NextUID +} func (l *List) UIDValidity() uint64 { return l.header.UIDValidity } @@ -95,11 +101,35 @@ func (l *List) Entries() []Entry { l.mutex.Lock() defer l.mutex.Unlock() - // FIXME: use copy() ? return l.entries } -func (l *List) EntriesInRange(start, end uint64) []Entry { +func (l *List) EntriesInRangeBySeqNum(start, end uint64) []Entry { + l.mutex.Lock() + defer l.mutex.Unlock() + + // IMAP SeqNums are indexed from 1. So 0 should be invalid, right? + if start == 0 { + return nil + } + + start-- + if int(start) > len(l.entries) { + return nil + } + + if end == 0 { + return l.entries[start:] + } + + if int(end) > len(l.entries) { + end = uint64(len(l.entries)) + } + + return l.entries[start:end] +} + +func (l *List) EntriesInRangeByUID(start, end uint64) []Entry { l.mutex.Lock() defer l.mutex.Unlock() @@ -124,6 +154,13 @@ func (l *List) Entry(key string) *Entry { return nil } +func (l *List) Count() uint64 { + l.mutex.Lock() + defer l.mutex.Unlock() + + return uint64(len(l.entries)) +} + func (l *List) Append(filenames ...string) error { l.mutex.Lock() defer l.mutex.Unlock() diff --git a/internal/store/maildir.go b/internal/store/maildir.go index f1a3659..28b1948 100644 --- a/internal/store/maildir.go +++ b/internal/store/maildir.go @@ -16,7 +16,8 @@ type MaildirInterface interface { CreateMaildir(*Maildir) error FindMaildir(account Account, name string) (Maildir, error) FindMaildirs(account Account) ([]Maildir, error) - FindMessagesInRange(m Maildir, start, end uint64) ([]Message, error) + FindMessagesInRangeBySeqNum(m Maildir, start, end uint64) ([]Message, error) + CountMessages(m Maildir) uint64 } type Maildir struct { @@ -41,6 +42,14 @@ func (m *Maildir) Header(key string) (mail.Header, error) { return m.filesystem.Header(key) } +func (m *Maildir) UIDValidity() uint64 { + return m.uids.UIDValidity() +} + +func (m *Maildir) NextUID() uint64 { + return m.uids.NextUID() +} + func (c *concrete) CreateMaildir(m *Maildir) error { if m.directory == "" { m.directory = c.buildMaildirPath(m.Account, m.Name) @@ -130,13 +139,14 @@ func (c *concrete) buildMaildirPath(account Account, parts ...string) string { return filepath.Join(bits...) } -func (c *concrete) FindMessagesInRange(m Maildir, start, end uint64) ([]Message, error) { +func (c *concrete) FindMessagesInRangeBySeqNum(m Maildir, start, end uint64) ([]Message, error) { var out []Message - entries := m.uids.EntriesInRange(start, end) + entries := m.uids.EntriesInRangeBySeqNum(start, end) for _, entry := range entries { msg := Message{ UID: entry.UID, + SeqNum: entry.SeqNum, Maildir: m, Key: entry.Key(), } @@ -146,3 +156,7 @@ func (c *concrete) FindMessagesInRange(m Maildir, start, end uint64) ([]Message, return out, nil } + +func (c *concrete) CountMessages(m Maildir) uint64 { + return m.uids.Count() +} diff --git a/internal/store/message.go b/internal/store/message.go index 152083a..0530a0b 100644 --- a/internal/store/message.go +++ b/internal/store/message.go @@ -13,6 +13,7 @@ type Message struct { Maildir Maildir // maildir.name UID uint64 + SeqNum uint64 Key string Data io.ReadCloser hdrCache mail.Header