mirror of
https://github.com/jhillyerd/inbucket.git
synced 2025-12-17 09:37:02 +00:00
storage: Message refactoring for #69
- Message interface renamed to StoreMessage - Message.Delete becomes Store.RemoveMessage - Added deleted message tracking to Store stub for #80
This commit is contained in:
@@ -34,7 +34,7 @@ type Message struct {
|
||||
|
||||
// newMessage creates a new FileMessage object and sets the Date and ID fields.
|
||||
// It will also delete messages over messageCap if configured.
|
||||
func (mb *mbox) newMessage() (storage.Message, error) {
|
||||
func (mb *mbox) newMessage() (storage.StoreMessage, error) {
|
||||
// Load index
|
||||
if !mb.indexLoaded {
|
||||
if err := mb.readIndex(); err != nil {
|
||||
@@ -45,7 +45,7 @@ func (mb *mbox) newMessage() (storage.Message, error) {
|
||||
if mb.store.messageCap > 0 {
|
||||
for len(mb.messages) >= mb.store.messageCap {
|
||||
log.Infof("Mailbox %q over configured message cap", mb.name)
|
||||
if err := mb.messages[0].Delete(); err != nil {
|
||||
if err := mb.removeMessage(mb.messages[0].ID()); err != nil {
|
||||
log.Errorf("Error deleting message: %s", err)
|
||||
}
|
||||
}
|
||||
@@ -55,6 +55,11 @@ func (mb *mbox) newMessage() (storage.Message, error) {
|
||||
return &Message{mailbox: mb, Fid: id, Fdate: date, writable: true}, nil
|
||||
}
|
||||
|
||||
// Mailbox returns the name of the mailbox this message resides in.
|
||||
func (m *Message) Mailbox() string {
|
||||
return m.mailbox.name
|
||||
}
|
||||
|
||||
// ID gets the ID of the Message
|
||||
func (m *Message) ID() string {
|
||||
return m.Fid
|
||||
@@ -240,29 +245,3 @@ func (m *Message) Close() error {
|
||||
m.mailbox.messages = append(m.mailbox.messages, m)
|
||||
return m.mailbox.writeIndex()
|
||||
}
|
||||
|
||||
// Delete this Message from disk by removing it from the index and deleting the
|
||||
// raw files.
|
||||
func (m *Message) Delete() error {
|
||||
messages := m.mailbox.messages
|
||||
for i, mm := range messages {
|
||||
if m == mm {
|
||||
// Slice around message we are deleting
|
||||
m.mailbox.messages = append(messages[:i], messages[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
if err := m.mailbox.writeIndex(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(m.mailbox.messages) == 0 {
|
||||
// This was the last message, thus writeIndex() has removed the entire
|
||||
// directory; we don't need to delete the raw file.
|
||||
return nil
|
||||
}
|
||||
|
||||
// There are still messages in the index
|
||||
log.Tracef("Deleting %v", m.rawPath())
|
||||
return os.Remove(m.rawPath())
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ func New(cfg config.DataStoreConfig) storage.Store {
|
||||
}
|
||||
|
||||
// GetMessage returns the messages in the named mailbox, or an error.
|
||||
func (fs *Store) GetMessage(mailbox, id string) (storage.Message, error) {
|
||||
func (fs *Store) GetMessage(mailbox, id string) (storage.StoreMessage, error) {
|
||||
mb, err := fs.mbox(mailbox)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -84,7 +84,7 @@ func (fs *Store) GetMessage(mailbox, id string) (storage.Message, error) {
|
||||
}
|
||||
|
||||
// GetMessages returns the messages in the named mailbox, or an error.
|
||||
func (fs *Store) GetMessages(mailbox string) ([]storage.Message, error) {
|
||||
func (fs *Store) GetMessages(mailbox string) ([]storage.StoreMessage, error) {
|
||||
mb, err := fs.mbox(mailbox)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -92,6 +92,15 @@ func (fs *Store) GetMessages(mailbox string) ([]storage.Message, error) {
|
||||
return mb.getMessages()
|
||||
}
|
||||
|
||||
// RemoveMessage deletes a message by ID from the specified mailbox.
|
||||
func (fs *Store) RemoveMessage(mailbox, id string) error {
|
||||
mb, err := fs.mbox(mailbox)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return mb.removeMessage(id)
|
||||
}
|
||||
|
||||
// PurgeMessages deletes all messages in the named mailbox, or returns an error.
|
||||
func (fs *Store) PurgeMessages(mailbox string) error {
|
||||
mb, err := fs.mbox(mailbox)
|
||||
@@ -103,7 +112,7 @@ func (fs *Store) PurgeMessages(mailbox string) error {
|
||||
|
||||
// VisitMailboxes accepts a function that will be called with the messages in each mailbox while it
|
||||
// continues to return true.
|
||||
func (fs *Store) VisitMailboxes(f func([]storage.Message) (cont bool)) error {
|
||||
func (fs *Store) VisitMailboxes(f func([]storage.StoreMessage) (cont bool)) error {
|
||||
infos1, err := ioutil.ReadDir(fs.mailPath)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -159,7 +168,7 @@ func (fs *Store) LockFor(emailAddress string) (*sync.RWMutex, error) {
|
||||
}
|
||||
|
||||
// NewMessage is temproary until #69 MessageData refactor
|
||||
func (fs *Store) NewMessage(mailbox string) (storage.Message, error) {
|
||||
func (fs *Store) NewMessage(mailbox string) (storage.StoreMessage, error) {
|
||||
mb, err := fs.mbox(mailbox)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -196,13 +205,13 @@ type mbox struct {
|
||||
|
||||
// getMessages scans the mailbox directory for .gob files and decodes them into
|
||||
// a slice of Message objects.
|
||||
func (mb *mbox) getMessages() ([]storage.Message, error) {
|
||||
func (mb *mbox) getMessages() ([]storage.StoreMessage, error) {
|
||||
if !mb.indexLoaded {
|
||||
if err := mb.readIndex(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
messages := make([]storage.Message, len(mb.messages))
|
||||
messages := make([]storage.StoreMessage, len(mb.messages))
|
||||
for i, m := range mb.messages {
|
||||
messages[i] = m
|
||||
}
|
||||
@@ -210,7 +219,7 @@ func (mb *mbox) getMessages() ([]storage.Message, error) {
|
||||
}
|
||||
|
||||
// getMessage decodes a single message by ID and returns a Message object.
|
||||
func (mb *mbox) getMessage(id string) (storage.Message, error) {
|
||||
func (mb *mbox) getMessage(id string) (storage.StoreMessage, error) {
|
||||
if !mb.indexLoaded {
|
||||
if err := mb.readIndex(); err != nil {
|
||||
return nil, err
|
||||
@@ -227,6 +236,38 @@ func (mb *mbox) getMessage(id string) (storage.Message, error) {
|
||||
return nil, storage.ErrNotExist
|
||||
}
|
||||
|
||||
// removeMessage deletes the message off disk and removes it from the index.
|
||||
func (mb *mbox) removeMessage(id string) error {
|
||||
if !mb.indexLoaded {
|
||||
if err := mb.readIndex(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
var msg *Message
|
||||
for i, m := range mb.messages {
|
||||
if id == m.ID() {
|
||||
msg = m
|
||||
// Slice around message we are deleting
|
||||
mb.messages = append(mb.messages[:i], mb.messages[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
if msg == nil {
|
||||
return storage.ErrNotExist
|
||||
}
|
||||
if err := mb.writeIndex(); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(mb.messages) == 0 {
|
||||
// This was the last message, thus writeIndex() has removed the entire
|
||||
// directory; we don't need to delete the raw file.
|
||||
return nil
|
||||
}
|
||||
// There are still messages in the index
|
||||
log.Tracef("Deleting %v", msg.rawPath())
|
||||
return os.Remove(msg.rawPath())
|
||||
}
|
||||
|
||||
// purge deletes all messages in this mailbox.
|
||||
func (mb *mbox) purge() error {
|
||||
mb.messages = mb.messages[:0]
|
||||
@@ -256,14 +297,18 @@ func (mb *mbox) readIndex() error {
|
||||
log.Errorf("Failed to close %q: %v", mb.indexPath, err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Decode gob data
|
||||
dec := gob.NewDecoder(bufio.NewReader(file))
|
||||
name := ""
|
||||
if err = dec.Decode(&name); err != nil {
|
||||
return fmt.Errorf("Corrupt mailbox %q: %v", mb.indexPath, err)
|
||||
}
|
||||
mb.name = name
|
||||
for {
|
||||
msg := new(Message)
|
||||
// Load messages until EOF
|
||||
msg := &Message{}
|
||||
if err = dec.Decode(msg); err != nil {
|
||||
if err == io.EOF {
|
||||
// It's OK to get an EOF here
|
||||
break
|
||||
}
|
||||
return fmt.Errorf("Corrupt mailbox %q: %v", mb.indexPath, err)
|
||||
@@ -271,7 +316,6 @@ func (mb *mbox) readIndex() error {
|
||||
msg.mailbox = mb
|
||||
mb.messages = append(mb.messages, msg)
|
||||
}
|
||||
|
||||
mb.indexLoaded = true
|
||||
return nil
|
||||
}
|
||||
@@ -294,9 +338,12 @@ func (mb *mbox) writeIndex() error {
|
||||
writer := bufio.NewWriter(file)
|
||||
// Write each message and then flush
|
||||
enc := gob.NewEncoder(writer)
|
||||
if err = enc.Encode(mb.name); err != nil {
|
||||
_ = file.Close()
|
||||
return err
|
||||
}
|
||||
for _, m := range mb.messages {
|
||||
err = enc.Encode(m)
|
||||
if err != nil {
|
||||
if err = enc.Encode(m); err != nil {
|
||||
_ = file.Close()
|
||||
return err
|
||||
}
|
||||
@@ -314,7 +361,6 @@ func (mb *mbox) writeIndex() error {
|
||||
log.Tracef("Removing mailbox %v", mb.path)
|
||||
return mb.removeDir()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -63,9 +63,7 @@ func TestFSDirStructure(t *testing.T) {
|
||||
assert.True(t, isFile(expect), "Expected %q to be a file", expect)
|
||||
|
||||
// Delete message
|
||||
msg, err := ds.GetMessage(mbName, id1)
|
||||
assert.Nil(t, err)
|
||||
err = msg.Delete()
|
||||
err := ds.RemoveMessage(mbName, id1)
|
||||
assert.Nil(t, err)
|
||||
|
||||
// Message should be removed
|
||||
@@ -75,9 +73,7 @@ func TestFSDirStructure(t *testing.T) {
|
||||
assert.True(t, isFile(expect), "Expected %q to be a file", expect)
|
||||
|
||||
// Delete message
|
||||
msg, err = ds.GetMessage(mbName, id2)
|
||||
assert.Nil(t, err)
|
||||
err = msg.Delete()
|
||||
err = ds.RemoveMessage(mbName, id2)
|
||||
assert.Nil(t, err)
|
||||
|
||||
// Message should be removed
|
||||
@@ -114,7 +110,7 @@ func TestFSVisitMailboxes(t *testing.T) {
|
||||
}
|
||||
|
||||
seen := 0
|
||||
err := ds.VisitMailboxes(func(messages []storage.Message) bool {
|
||||
err := ds.VisitMailboxes(func(messages []storage.StoreMessage) bool {
|
||||
seen++
|
||||
count := len(messages)
|
||||
if count != 2 {
|
||||
@@ -196,8 +192,14 @@ func TestFSDelete(t *testing.T) {
|
||||
len(subjects), len(msgs))
|
||||
|
||||
// Delete a couple messages
|
||||
_ = msgs[1].Delete()
|
||||
_ = msgs[3].Delete()
|
||||
err = ds.RemoveMessage(mbName, msgs[1].ID())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = ds.RemoveMessage(mbName, msgs[3].ID())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Confirm deletion
|
||||
msgs, err = ds.GetMessages(mbName)
|
||||
|
||||
Reference in New Issue
Block a user