mirror of
https://github.com/jhillyerd/inbucket.git
synced 2025-12-17 09:37:02 +00:00
storage: Add Seen flag, tests for #58
This commit is contained in:
@@ -16,10 +16,14 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
page.
|
||||
- Debian `.deb` package generation to release process.
|
||||
- RedHat `.rpm` package generation to release process.
|
||||
- Message seen flag in REST and Web UI so you can see which messages have
|
||||
already been read.
|
||||
|
||||
### Changed
|
||||
- Massive refactor of back-end code. Inbucket should now be both easier and
|
||||
more enjoyable to work on.
|
||||
- Changes to file storage format, will require pre-2.0 mail store directories to
|
||||
be deleted.
|
||||
- Renamed `themes` directory to `ui` and eliminated the intermediate `bootstrap`
|
||||
directory.
|
||||
- Docker build:
|
||||
|
||||
@@ -21,6 +21,7 @@ type Metadata struct {
|
||||
Date time.Time
|
||||
Subject string
|
||||
Size int64
|
||||
Seen bool
|
||||
}
|
||||
|
||||
// Message holds both the metadata and content of a message.
|
||||
@@ -109,3 +110,8 @@ func (d *Delivery) Size() int64 {
|
||||
func (d *Delivery) Source() (io.ReadCloser, error) {
|
||||
return ioutil.NopCloser(d.Reader), nil
|
||||
}
|
||||
|
||||
// Seen getter.
|
||||
func (d *Delivery) Seen() bool {
|
||||
return d.Meta.Seen
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ type Message struct {
|
||||
Fto []*mail.Address
|
||||
Fsubject string
|
||||
Fsize int64
|
||||
Fseen bool
|
||||
}
|
||||
|
||||
// newMessage creates a new FileMessage object and sets the Date and ID fields.
|
||||
@@ -96,3 +97,8 @@ func (m *Message) Source() (reader io.ReadCloser, err error) {
|
||||
}
|
||||
return file, nil
|
||||
}
|
||||
|
||||
// Seen returns the seen flag value.
|
||||
func (m *Message) Seen() bool {
|
||||
return m.Fseen
|
||||
}
|
||||
|
||||
@@ -147,6 +147,32 @@ func (fs *Store) GetMessages(mailbox string) ([]storage.Message, error) {
|
||||
return mb.getMessages()
|
||||
}
|
||||
|
||||
// MarkSeen flags the message as having been read.
|
||||
func (fs *Store) MarkSeen(mailbox, id string) error {
|
||||
mb, err := fs.mbox(mailbox)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mb.Lock()
|
||||
defer mb.Unlock()
|
||||
if !mb.indexLoaded {
|
||||
if err := mb.readIndex(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, m := range mb.messages {
|
||||
if m.Fid == id {
|
||||
if m.Fseen {
|
||||
// Already marked seen.
|
||||
return nil
|
||||
}
|
||||
m.Fseen = true
|
||||
break
|
||||
}
|
||||
}
|
||||
return mb.writeIndex()
|
||||
}
|
||||
|
||||
// RemoveMessage deletes a message by ID from the specified mailbox.
|
||||
func (fs *Store) RemoveMessage(mailbox, id string) error {
|
||||
mb, err := fs.mbox(mailbox)
|
||||
|
||||
@@ -21,6 +21,7 @@ type Message struct {
|
||||
date time.Time
|
||||
subject string
|
||||
source []byte
|
||||
seen bool
|
||||
el *list.Element // This message in Store.messages
|
||||
}
|
||||
|
||||
@@ -51,3 +52,6 @@ func (m *Message) Source() (io.ReadCloser, error) {
|
||||
|
||||
// Size returns the message size in bytes.
|
||||
func (m *Message) Size() int64 { return int64(len(m.source)) }
|
||||
|
||||
// Seen returns the message seen flag.
|
||||
func (m *Message) Seen() bool { return m.seen }
|
||||
|
||||
@@ -112,6 +112,17 @@ func (s *Store) GetMessages(mailbox string) (ms []storage.Message, err error) {
|
||||
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
|
||||
|
||||
@@ -28,6 +28,7 @@ type Store interface {
|
||||
AddMessage(message Message) (id string, err error)
|
||||
GetMessage(mailbox, id string) (Message, error)
|
||||
GetMessages(mailbox string) ([]Message, error)
|
||||
MarkSeen(mailbox, id string) error
|
||||
PurgeMessages(mailbox string) error
|
||||
RemoveMessage(mailbox, id string) error
|
||||
VisitMailboxes(f func([]Message) (cont bool)) error
|
||||
@@ -43,6 +44,7 @@ type Message interface {
|
||||
Subject() string
|
||||
Source() (io.ReadCloser, error)
|
||||
Size() int64
|
||||
Seen() bool
|
||||
}
|
||||
|
||||
// FromConfig creates an instance of the Store based on the provided configuration.
|
||||
|
||||
@@ -28,6 +28,7 @@ func StoreSuite(t *testing.T, factory StoreFactory) {
|
||||
{"content", testContent, config.Storage{}},
|
||||
{"delivery order", testDeliveryOrder, config.Storage{}},
|
||||
{"size", testSize, config.Storage{}},
|
||||
{"seen", testSeen, config.Storage{}},
|
||||
{"delete", testDelete, config.Storage{}},
|
||||
{"purge", testPurge, config.Storage{}},
|
||||
{"cap=10", testMsgCap, config.Storage{MailboxMsgCap: 10}},
|
||||
@@ -65,6 +66,7 @@ func testMetadata(t *testing.T, store storage.Store) {
|
||||
To: to,
|
||||
Date: date,
|
||||
Subject: subject,
|
||||
Seen: false,
|
||||
},
|
||||
Reader: strings.NewReader(content),
|
||||
}
|
||||
@@ -107,6 +109,9 @@ func testMetadata(t *testing.T, store storage.Store) {
|
||||
if sm.Size() != int64(len(content)) {
|
||||
t.Errorf("got size %v, want: %v", sm.Size(), len(content))
|
||||
}
|
||||
if sm.Seen() {
|
||||
t.Errorf("got seen %v, want: false", sm.Seen())
|
||||
}
|
||||
}
|
||||
|
||||
// testContent generates some binary content and makes sure it is correctly retrieved.
|
||||
@@ -210,6 +215,42 @@ func testSize(t *testing.T, store storage.Store) {
|
||||
}
|
||||
}
|
||||
|
||||
// testSeen verifies a message can be marked as seen.
|
||||
func testSeen(t *testing.T, store storage.Store) {
|
||||
mailbox := "lisa"
|
||||
id1, _ := DeliverToStore(t, store, mailbox, "whatever", time.Now())
|
||||
id2, _ := DeliverToStore(t, store, mailbox, "hello?", time.Now())
|
||||
// Confirm unseen.
|
||||
msg, err := store.GetMessage(mailbox, id1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if msg.Seen() {
|
||||
t.Errorf("got seen %v, want: false", msg.Seen())
|
||||
}
|
||||
// Mark id1 seen.
|
||||
err = store.MarkSeen(mailbox, id1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Verify id1 seen.
|
||||
msg, err = store.GetMessage(mailbox, id1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !msg.Seen() {
|
||||
t.Errorf("id1 got seen %v, want: true", msg.Seen())
|
||||
}
|
||||
// Verify id2 still unseen.
|
||||
msg, err = store.GetMessage(mailbox, id2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if msg.Seen() {
|
||||
t.Errorf("id2 got seen %v, want: false", msg.Seen())
|
||||
}
|
||||
}
|
||||
|
||||
// testDelete creates and deletes some messages.
|
||||
func testDelete(t *testing.T, store storage.Store) {
|
||||
mailbox := "fred"
|
||||
|
||||
Reference in New Issue
Block a user