mirror of
https://github.com/jhillyerd/inbucket.git
synced 2025-12-18 10:07:02 +00:00
Finishing up index.gob work
- Wrote more unit tests to make sure filestore behaves as expected - Renamed NewFileDataStore to DefaultFileDataStore, implemented a new NewFileDataStore for unit tests. - filestore now removes entire mailbox dir when last message is deleted
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
"Resources": {
|
||||
"Include": "README*,LICENSE*,bin,etc,themes"
|
||||
},
|
||||
"PackageVersion": "20131010",
|
||||
"PackageVersion": "20131013",
|
||||
"PrereleaseInfo": "snapshot",
|
||||
"FormatVersion": "0.8"
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ type Server struct {
|
||||
// Init a new Server object
|
||||
func New() *Server {
|
||||
// TODO is two filestores better/worse than sharing w/ smtpd?
|
||||
ds := smtpd.NewFileDataStore()
|
||||
ds := smtpd.DefaultFileDataStore()
|
||||
cfg := config.GetPop3Config()
|
||||
return &Server{domain: cfg.Domain, dataStore: ds, maxIdleSeconds: cfg.MaxIdleSeconds,
|
||||
waitgroup: new(sync.WaitGroup)}
|
||||
|
||||
@@ -48,9 +48,19 @@ type FileDataStore struct {
|
||||
mailPath string
|
||||
}
|
||||
|
||||
// NewDataStore creates a new DataStore object. It uses the inbucket.Config object to
|
||||
// NewFileDataStore creates a new DataStore object using the specified path
|
||||
func NewFileDataStore(path string) DataStore {
|
||||
mailPath := filepath.Join(path, "mail")
|
||||
if _, err := os.Stat(mailPath); err != nil {
|
||||
// Mail datastore does not yet exist
|
||||
os.MkdirAll(mailPath, 0770)
|
||||
}
|
||||
return &FileDataStore{path: path, mailPath: mailPath}
|
||||
}
|
||||
|
||||
// DefaultFileDataStore creates a new DataStore object. It uses the inbucket.Config object to
|
||||
// construct it's path.
|
||||
func NewFileDataStore() DataStore {
|
||||
func DefaultFileDataStore() DataStore {
|
||||
path, err := config.Config.String("datastore", "path")
|
||||
if err != nil {
|
||||
log.LogError("Error getting datastore path: %v", err)
|
||||
@@ -60,12 +70,7 @@ func NewFileDataStore() DataStore {
|
||||
log.LogError("No value configured for datastore path")
|
||||
return nil
|
||||
}
|
||||
mailPath := filepath.Join(path, "mail")
|
||||
if _, err := os.Stat(mailPath); err != nil {
|
||||
// Mail datastore does not yet exist
|
||||
os.MkdirAll(mailPath, 0770)
|
||||
}
|
||||
return &FileDataStore{path: path, mailPath: mailPath}
|
||||
return NewFileDataStore(path)
|
||||
}
|
||||
|
||||
// Retrieves the Mailbox object for a specified email address, if the mailbox
|
||||
@@ -230,6 +235,7 @@ func (mb *FileMailbox) writeIndex() error {
|
||||
// Lock for writing
|
||||
indexLock.Lock()
|
||||
defer indexLock.Unlock()
|
||||
if len(mb.messages) > 0 {
|
||||
// Ensure mailbox directory exists
|
||||
if err := mb.createDir(); err != nil {
|
||||
return err
|
||||
@@ -251,6 +257,11 @@ func (mb *FileMailbox) writeIndex() error {
|
||||
}
|
||||
}
|
||||
writer.Flush()
|
||||
} else {
|
||||
// No messages, delete index+maildir
|
||||
log.LogTrace("Removing mailbox %v", mb.path)
|
||||
return os.RemoveAll(mb.path)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -444,6 +455,13 @@ func (m *FileMessage) Delete() error {
|
||||
}
|
||||
m.mailbox.writeIndex()
|
||||
|
||||
if len(m.mailbox.messages) == 0 {
|
||||
// This was the last message, writeIndex() has removed the entire
|
||||
// directory
|
||||
return nil
|
||||
}
|
||||
|
||||
// There are still messages in the index
|
||||
log.LogTrace("Deleting %v", m.rawPath())
|
||||
return os.Remove(m.rawPath())
|
||||
}
|
||||
|
||||
@@ -10,6 +10,84 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Test directory structure created by filestore
|
||||
func TestFSDirStructure(t *testing.T) {
|
||||
ds := setupDataStore()
|
||||
defer teardownDataStore(ds)
|
||||
root := ds.path
|
||||
|
||||
// james hashes to 474ba67bdb289c6263b36dfd8a7bed6c85b04943
|
||||
mbName := "james"
|
||||
|
||||
// Check filestore root exists
|
||||
assert.True(t, isDir(root), "Expected %q to be a directory", root)
|
||||
|
||||
// Check mail dir exists
|
||||
expect := filepath.Join(root, "mail")
|
||||
assert.True(t, isDir(expect), "Expected %q to be a directory", expect)
|
||||
|
||||
// Check first hash section does not exist
|
||||
expect = filepath.Join(root, "mail", "474")
|
||||
assert.False(t, isDir(expect), "Expected %q to not exist", expect)
|
||||
|
||||
// Deliver test message
|
||||
id1, _ := deliverMessage(ds, mbName, "test", time.Now())
|
||||
|
||||
// Check path to message exists
|
||||
assert.True(t, isDir(expect), "Expected %q to be a directory", expect)
|
||||
expect = filepath.Join(expect, "474ba6")
|
||||
assert.True(t, isDir(expect), "Expected %q to be a directory", expect)
|
||||
expect = filepath.Join(expect, "474ba67bdb289c6263b36dfd8a7bed6c85b04943")
|
||||
assert.True(t, isDir(expect), "Expected %q to be a directory", expect)
|
||||
|
||||
// Check files
|
||||
mbPath := expect
|
||||
expect = filepath.Join(mbPath, "index.gob")
|
||||
assert.True(t, isFile(expect), "Expected %q to be a file", expect)
|
||||
expect = filepath.Join(mbPath, id1 + ".raw")
|
||||
assert.True(t, isFile(expect), "Expected %q to be a file", expect)
|
||||
|
||||
// Deliver second test message
|
||||
id2, _ := deliverMessage(ds, mbName, "test 2", time.Now())
|
||||
|
||||
// Check files
|
||||
expect = filepath.Join(mbPath, "index.gob")
|
||||
assert.True(t, isFile(expect), "Expected %q to be a file", expect)
|
||||
expect = filepath.Join(mbPath, id2 + ".raw")
|
||||
assert.True(t, isFile(expect), "Expected %q to be a file", expect)
|
||||
|
||||
// Delete message
|
||||
mb, err := ds.MailboxFor(mbName)
|
||||
assert.Nil(t, err)
|
||||
msg, err := mb.GetMessage(id1)
|
||||
assert.Nil(t, err)
|
||||
err = msg.Delete()
|
||||
assert.Nil(t, err)
|
||||
|
||||
// Message should be removed
|
||||
expect = filepath.Join(mbPath, id1 + ".raw")
|
||||
assert.False(t, isPresent(expect), "Did not expect %q to exist", expect)
|
||||
expect = filepath.Join(mbPath, "index.gob")
|
||||
assert.True(t, isFile(expect), "Expected %q to be a file", expect)
|
||||
|
||||
// Delete message
|
||||
msg, err = mb.GetMessage(id2)
|
||||
assert.Nil(t, err)
|
||||
err = msg.Delete()
|
||||
assert.Nil(t, err)
|
||||
|
||||
// Message should be removed
|
||||
expect = filepath.Join(mbPath, id2 + ".raw")
|
||||
assert.False(t, isPresent(expect), "Did not expect %q to exist", expect)
|
||||
|
||||
// No messages, index & maildir should be removed
|
||||
expect = filepath.Join(mbPath, "index.gob")
|
||||
assert.False(t, isPresent(expect), "Did not expect %q to exist", expect)
|
||||
expect = mbPath
|
||||
assert.False(t, isPresent(expect), "Did not expect %q to exist", expect)
|
||||
}
|
||||
|
||||
|
||||
// Test FileDataStore.AllMailboxes()
|
||||
func TestFSAllMailboxes(t *testing.T) {
|
||||
ds := setupDataStore()
|
||||
@@ -147,13 +225,12 @@ func setupDataStore() *FileDataStore {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
mailPath := filepath.Join(path, "mail")
|
||||
return &FileDataStore{path: path, mailPath: mailPath}
|
||||
return NewFileDataStore(path).(*FileDataStore)
|
||||
}
|
||||
|
||||
// deliverMessage creates and delivers a message to the specific mailbox, returning
|
||||
// the size of the generated message.
|
||||
func deliverMessage(ds *FileDataStore, mbName string, subject string, date time.Time) int {
|
||||
func deliverMessage(ds *FileDataStore, mbName string, subject string, date time.Time) (id string, size int) {
|
||||
// Build fake SMTP message for delivery
|
||||
testMsg := make([]byte, 0, 300)
|
||||
testMsg = append(testMsg, []byte("To: somebody@host\r\n")...)
|
||||
@@ -166,19 +243,20 @@ func deliverMessage(ds *FileDataStore, mbName string, subject string, date time.
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// Create day old message
|
||||
// Create message object
|
||||
id = generateId(date)
|
||||
msg := &FileMessage{
|
||||
mailbox: mb.(*FileMailbox),
|
||||
writable: true,
|
||||
Fdate: date,
|
||||
Fid: generateId(date),
|
||||
Fid: id,
|
||||
}
|
||||
msg.Append(testMsg)
|
||||
if err = msg.Close(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return len(testMsg)
|
||||
return id, len(testMsg)
|
||||
}
|
||||
|
||||
func teardownDataStore(ds *FileDataStore) {
|
||||
@@ -186,3 +264,23 @@ func teardownDataStore(ds *FileDataStore) {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func isPresent(path string) bool {
|
||||
_, err := os.Lstat(path)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func isFile(path string) bool {
|
||||
if fi, err := os.Lstat(path); err == nil {
|
||||
return !fi.IsDir()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isDir(path string) bool {
|
||||
if fi, err := os.Lstat(path); err == nil {
|
||||
return fi.IsDir()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ var expWarnsHist = new(expvar.String)
|
||||
|
||||
// Init a new Server object
|
||||
func New() *Server {
|
||||
ds := NewFileDataStore()
|
||||
ds := DefaultFileDataStore()
|
||||
cfg := config.GetSmtpConfig()
|
||||
return &Server{dataStore: ds, domain: cfg.Domain, maxRecips: cfg.MaxRecipients,
|
||||
maxIdleSeconds: cfg.MaxIdleSeconds, maxMessageBytes: cfg.MaxMessageBytes,
|
||||
|
||||
@@ -39,7 +39,7 @@ func headerMatch(req *http.Request, name string, value string) bool {
|
||||
func NewContext(req *http.Request) (*Context, error) {
|
||||
vars := mux.Vars(req)
|
||||
sess, err := sessionStore.Get(req, "inbucket")
|
||||
ds := smtpd.NewFileDataStore()
|
||||
ds := smtpd.DefaultFileDataStore()
|
||||
ctx := &Context{
|
||||
Vars: vars,
|
||||
Session: sess,
|
||||
|
||||
Reference in New Issue
Block a user