package mem import ( "fmt" "io/ioutil" "sort" "strconv" "sync" "github.com/jhillyerd/inbucket/pkg/config" "github.com/jhillyerd/inbucket/pkg/storage" ) // Store implements an in-memory message store. type Store struct { sync.Mutex boxes map[string]*mbox cap int // Per-mailbox message cap. incoming chan *msgDone // New messages for size enforcer. remove chan *msgDone // Remove deleted messages from size enforcer. } type mbox struct { sync.RWMutex name string last int first int messages map[string]*Message } var _ storage.Store = &Store{} // New returns an emtpy memory store. func New(cfg config.Storage) (storage.Store, error) { s := &Store{ boxes: make(map[string]*mbox), cap: cfg.MailboxMsgCap, } if str, ok := cfg.Params["maxkb"]; ok { maxKB, err := strconv.ParseInt(str, 10, 64) if err != nil { return nil, fmt.Errorf("failed to parse maxkb: %v", err) } if maxKB > 0 { // Setup enforcer. s.incoming = make(chan *msgDone) s.remove = make(chan *msgDone) go s.maxSizeEnforcer(maxKB * 1024) } } return s, nil } // AddMessage stores the message, message ID and Size will be ignored. func (s *Store) AddMessage(message storage.Message) (id string, err error) { r, ierr := message.Source() if ierr != nil { err = ierr return } source, ierr := ioutil.ReadAll(r) if ierr != nil { err = ierr return } m := &Message{ mailbox: message.Mailbox(), from: message.From(), to: message.To(), date: message.Date(), subject: message.Subject(), } s.withMailbox(message.Mailbox(), true, func(mb *mbox) { // Generate message ID. mb.last++ m.index = mb.last id = strconv.Itoa(mb.last) m.id = id m.source = source mb.messages[id] = m if s.cap > 0 { // Enforce cap. for len(mb.messages) > s.cap { delete(mb.messages, strconv.Itoa(mb.first)) mb.first++ } } }) s.enforcerDeliver(m) return id, err } // GetMessage gets a mesage. func (s *Store) GetMessage(mailbox, id string) (m storage.Message, err error) { s.withMailbox(mailbox, false, func(mb *mbox) { m = mb.messages[id] }) return m, err } // GetMessages gets a list of messages. func (s *Store) GetMessages(mailbox string) (ms []storage.Message, err error) { s.withMailbox(mailbox, false, func(mb *mbox) { ms = make([]storage.Message, 0, len(mb.messages)) for _, v := range mb.messages { ms = append(ms, v) } sort.Slice(ms, func(i, j int) bool { return ms[i].(*Message).index < ms[j].(*Message).index }) }) return ms, err } // MarkSeen marks a message as having been read. func (s *Store) MarkSeen(mailbox, id string) error { s.withMailbox(mailbox, true, func(mb *mbox) { m := mb.messages[id] if m != nil { m.seen = true } }) return nil } // PurgeMessages deletes the contents of a mailbox. func (s *Store) PurgeMessages(mailbox string) error { var messages map[string]*Message s.withMailbox(mailbox, true, func(mb *mbox) { messages = mb.messages mb.messages = make(map[string]*Message) }) if len(messages) > 0 && s.remove != nil { for _, m := range messages { s.enforcerRemove(m) } } return nil } // removeMessage deletes a single message without notifying the size enforcer. Returns the message // that was removed. func (s *Store) removeMessage(mailbox, id string) *Message { var m *Message s.withMailbox(mailbox, true, func(mb *mbox) { m = mb.messages[id] if m != nil { delete(mb.messages, id) } }) return m } // RemoveMessage deletes a single message. func (s *Store) RemoveMessage(mailbox, id string) error { m := s.removeMessage(mailbox, id) if m != nil { s.enforcerRemove(m) } return nil } // VisitMailboxes visits each mailbox in the store. func (s *Store) VisitMailboxes(f func([]storage.Message) (cont bool)) error { // Lock store, get names of all mailboxes. s.Lock() boxNames := make([]string, 0, len(s.boxes)) for k := range s.boxes { boxNames = append(boxNames, k) } s.Unlock() // Process mailboxes. for _, mailbox := range boxNames { ms, _ := s.GetMessages(mailbox) if !f(ms) { break } } return nil } // withMailbox gets or creates a mailbox, locks it, then calls f. func (s *Store) withMailbox(mailbox string, writeLock bool, f func(mb *mbox)) { s.Lock() mb, ok := s.boxes[mailbox] if !ok { // Create mailbox mb = &mbox{ name: mailbox, messages: make(map[string]*Message), } s.boxes[mailbox] = mb } s.Unlock() if writeLock { mb.Lock() } else { mb.RLock() } defer func() { if writeLock { mb.Unlock() } else { mb.RUnlock() } }() f(mb) }