diff --git a/internal/imap/mailbox.go b/internal/imap/mailbox.go new file mode 100644 index 0000000..e45788b --- /dev/null +++ b/internal/imap/mailbox.go @@ -0,0 +1,138 @@ +package imap + +import ( + "fmt" + "time" + + "github.com/emersion/go-imap" +) + +type Mailbox struct { + name string + session *Session +} + +// Name returns this mailbox name. +func (m *Mailbox) Name() string { + return m.name +} + +// Info returns this mailbox info. +func (m *Mailbox) Info() (*imap.MailboxInfo, error) { + return &imap.MailboxInfo{ + Attributes: []string{}, // FIXME: what do we need here? + Delimiter: "/", + Name: m.Name(), + }, nil +} + +// Status returns this mailbox status. The fields Name, Flags, PermanentFlags +// and UnseenSeqNum in the returned MailboxStatus must be always populated. +// 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) { + 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 +} + +// SetSubscribed adds or removes the mailbox to the server's set of "active" +// or "subscribed" mailboxes. +func (m *Mailbox) SetSubscribed(subscribed bool) error { + return ErrNotImplemented +} + +// Check requests a checkpoint of the currently selected mailbox. A checkpoint +// refers to any implementation-dependent housekeeping associated with the +// mailbox (e.g., resolving the server's in-memory state of the mailbox with +// the state on its disk). A checkpoint MAY take a non-instantaneous amount of +// real time to complete. If a server implementation has no such housekeeping +// considerations, CHECK is equivalent to NOOP. +func (m *Mailbox) Check() error { + return nil // FIXME: do we need to do anything here? +} + +// ListMessages returns a list of messages. seqset must be interpreted as UIDs +// if uid is set to true and as message sequence numbers otherwise. See RFC +// 3501 section 6.4.5 for a list of items that can be requested. +// +// Messages must be sent to ch. When the function returns, ch must be closed. +func (m *Mailbox) ListMessages(uid bool, seqset *imap.SeqSet, items []string, ch chan<- *imap.Message) error { + defer close(ch) + + // FIXME: TODO + + messages, err := m.session.store.FindMessages(m.session.Username(), m.name) + if err != nil { + return err + } + + for i, _ := range messages { + envelope := &imap.Envelope{} + + body := map[*imap.BodySectionName]imap.Literal{} + + imapMsg := &imap.Message{ + SeqNum: uint32(i + 1), + Envelope: envelope, + Uid: uint32(i + 1), + Body: body, + } + + fmt.Printf("Sending message %#v", imapMsg) + + ch <- imapMsg + } + + return nil +} + +// SearchMessages searches messages. The returned list must contain UIDs if +// uid is set to true, or sequence numbers otherwise. +func (m *Mailbox) SearchMessages(uid bool, criteria *imap.SearchCriteria) ([]uint32, error) { + return nil, ErrNotImplemented +} + +// CreateMessage appends a new message to this mailbox. The \Recent flag will +// be added no matter flags is empty or not. If date is nil, the current time +// will be used. +// +// If the Backend implements Updater, it must notify the client immediately +// via a mailbox update. +func (m *Mailbox) CreateMessage(flags []string, date time.Time, body imap.Literal) error { + return ErrNotImplemented +} + +// UpdateMessagesFlags alters flags for the specified message(s). +// +// If the Backend implements Updater, it must notify the client immediately +// via a message update. +func (m *Mailbox) UpdateMessagesFlags(uid bool, seqset *imap.SeqSet, operation imap.FlagsOp, flags []string) error { + return ErrNotImplemented +} + +// CopyMessages copies the specified message(s) to the end of the specified +// destination mailbox. The flags and internal date of the message(s) SHOULD +// be preserved, and the Recent flag SHOULD be set, in the copy. +// +// If the destination mailbox does not exist, a server SHOULD return an error. +// It SHOULD NOT automatically create the mailbox. +// +// If the Backend implements Updater, it must notify the client immediately +// via a mailbox update. +func (m *Mailbox) CopyMessages(uid bool, seqset *imap.SeqSet, dest string) error { + return ErrNotImplemented +} + +// Expunge permanently removes all messages that have the \Deleted flag set +// from the currently selected mailbox. +// +// If the Backend implements Updater, it must notify the client immediately +// via an expunge update. +func (m *Mailbox) Expunge() error { + return ErrNotImplemented +} diff --git a/internal/imap/server.go b/internal/imap/server.go index 463ea34..35ea655 100644 --- a/internal/imap/server.go +++ b/internal/imap/server.go @@ -82,6 +82,7 @@ func (c *concrete) Login(user, pass string) (imapbackend.User, error) { ID: atomic.AddUint64(&c.sid, uint64(1)), Account: account, ServiceName: c.name, + store: c.store, } log.Printf("Beginning %s session %d for %s", c.name, session.ID, user) diff --git a/internal/imap/session.go b/internal/imap/session.go index 271aa32..0451fd2 100644 --- a/internal/imap/session.go +++ b/internal/imap/session.go @@ -4,13 +4,14 @@ import ( "fmt" "log" + "github.com/emersion/go-imap" imapbackend "github.com/emersion/go-imap/backend" "ur.gs/crockery/internal/store" ) var ( - notImplemented = fmt.Errorf("Not yet implemented") + ErrNotImplemented = fmt.Errorf("Not yet implemented") ) // type Session implements the User interface for emersion/go-imap @@ -18,6 +19,8 @@ type Session struct { ID uint64 Account store.Account ServiceName string + + store store.Interface } func (s *Session) Username() string { @@ -25,23 +28,29 @@ func (s *Session) Username() string { } func (s *Session) ListMailboxes(subscribed bool) ([]imapbackend.Mailbox, error) { - return nil, notImplemented + return []imapbackend.Mailbox{ // FIXME: hardcoded! + &Mailbox{name: imap.InboxName, session: s}, + }, nil } func (s *Session) GetMailbox(name string) (imapbackend.Mailbox, error) { - return nil, notImplemented + if name == imap.InboxName { // FIXME: hardcoded! + return &Mailbox{name: name, session: s}, nil + } + + return nil, ErrNotImplemented } func (s *Session) CreateMailbox(name string) error { - return notImplemented + return ErrNotImplemented } func (s *Session) DeleteMailbox(name string) error { - return notImplemented + return ErrNotImplemented } func (s *Session) RenameMailbox(existingName, newName string) error { - return notImplemented + return ErrNotImplemented } func (s *Session) Logout() error { diff --git a/internal/store/message.go b/internal/store/message.go index e0671b4..7258efb 100644 --- a/internal/store/message.go +++ b/internal/store/message.go @@ -6,6 +6,7 @@ import ( type MessageInterface interface { CreateMessage(Message) error + FindMessages(string, string) ([]Message, error) } type Message struct { @@ -20,3 +21,22 @@ type Message struct { func (c *concrete) CreateMessage(message Message) error { return c.storm.Save(&message) } + +func (c *concrete) FindMessages(username string, mailbox string) ([]Message, error) { + var messages []Message + var out []Message + + err := c.storm.Find("Username", username, &messages) + if err != nil { + return nil, err + } + + // FIXME: I cannot storm, so I just filter out all non-mailbox messages + for _, message := range messages { + if message.Mailbox == mailbox { + out = append(out, message) + } + } + + return out, nil +}