diff --git a/app/controllers/mailbox.go b/app/controllers/mailbox.go new file mode 100644 index 0000000..15a5ff1 --- /dev/null +++ b/app/controllers/mailbox.go @@ -0,0 +1,60 @@ +package controllers + +import ( + "fmt" + "github.com/jhillyerd/inbucket/app/inbucket" + "github.com/robfig/revel" +) + +type Mailbox struct { + *rev.Controller +} + +func (c Mailbox) Index(name string) rev.Result { + return c.Redirect("/mailbox/list/%v", name) +} + +func (c Mailbox) List(name string) rev.Result { + title := fmt.Sprintf("Mailbox for %v", name) + + ds := inbucket.NewDataStore() + mb, err := ds.MailboxFor(name) + if err != nil { + rev.ERROR.Printf(err.Error()) + c.Flash.Error(err.Error()) + return c.Redirect(Application.Index) + } + messages, err := mb.GetMessages() + if err != nil { + rev.ERROR.Printf(err.Error()) + c.Flash.Error(err.Error()) + return c.Redirect(Application.Index) + } + rev.INFO.Printf("Got %v messsages", len(messages)) + + return c.Render(title, name, messages) +} + +func (c Mailbox) Show(name string, id string) rev.Result { + ds := inbucket.NewDataStore() + mb, err := ds.MailboxFor(name) + if err != nil { + rev.ERROR.Printf(err.Error()) + c.Flash.Error(err.Error()) + return c.Redirect(Application.Index) + } + message, err := mb.GetMessage(id) + if err != nil { + rev.ERROR.Printf(err.Error()) + c.Flash.Error(err.Error()) + return c.Redirect(Application.Index) + } + _, body, err := message.ReadBody() + if err != nil { + rev.ERROR.Printf(err.Error()) + c.Flash.Error(err.Error()) + return c.Redirect(Application.Index) + } + + return c.Render(name, message, body) +} diff --git a/app/inbucket/datastore.go b/app/inbucket/datastore.go index 61f9b77..b8d24d8 100644 --- a/app/inbucket/datastore.go +++ b/app/inbucket/datastore.go @@ -76,21 +76,56 @@ func (mb *Mailbox) String() string { return mb.name + "[" + mb.dirName + "]" } +// GetMessages scans the mailbox directory for .gob files and decodes them into +// a slice of Message objects. func (mb *Mailbox) GetMessages() ([]*Message, error) { files, err := ioutil.ReadDir(mb.path) if err != nil { return nil, err } - // This is twice the size it needs to be, oh darn - messages := make([]*Message, len(files)) + rev.TRACE.Printf("Scanning %v files for %v", len(files), mb) + + messages := make([]*Message, 0, len(files)) for _, f := range files { if (!f.IsDir()) && strings.HasSuffix(strings.ToLower(f.Name()), ".gob") { - // TODO: implement + // We have a gob file + file, err := os.Open(filepath.Join(mb.path, f.Name())) + if err != nil { + return nil, err + } + dec := gob.NewDecoder(bufio.NewReader(file)) + msg := new(Message) + if err = dec.Decode(msg); err != nil { + return nil, err + } + file.Close() + msg.mailbox = mb + rev.TRACE.Printf("Found: %v", msg) + messages = append(messages, msg) } } return messages, nil } +// GetMessage decodes a single message by Id and returns a Message object +func (mb *Mailbox) GetMessage(id string) (*Message, error) { + file, err := os.Open(filepath.Join(mb.path, id+".gob")) + if err != nil { + return nil, err + } + + dec := gob.NewDecoder(bufio.NewReader(file)) + msg := new(Message) + if err = dec.Decode(msg); err != nil { + return nil, err + } + file.Close() + msg.mailbox = mb + rev.TRACE.Printf("Found: %v", msg) + + return msg, nil +} + // Message contains a little bit of data about a particular email message, and // methods to retrieve the rest of it from disk. type Message struct { @@ -122,6 +157,8 @@ func (m *Message) gobPath() string { } func (m *Message) rawPath() string { + rev.TRACE.Println(m.mailbox.path) + rev.TRACE.Println(m.Id) return filepath.Join(m.mailbox.path, m.Id+".raw") } @@ -137,6 +174,26 @@ func (m *Message) ReadHeader() (msg *mail.Message, err error) { return msg, err } +// ReadBody opens the .raw portion of a Message and returns a standard Go mail.Message object +func (m *Message) ReadBody() (msg *mail.Message, body *string, err error) { + file, err := os.Open(m.rawPath()) + defer file.Close() + if err != nil { + return nil, nil, err + } + reader := bufio.NewReader(file) + msg, err = mail.ReadMessage(reader) + if err != nil { + return nil, nil, err + } + bodyBytes, err := ioutil.ReadAll(reader) + if err != nil { + return nil, nil, err + } + bodyString := string(bodyBytes) + return msg, &bodyString, err +} + // Append data to a newly opened Message, this will fail on a pre-existing Message and // after Close() is called. func (m *Message) Append(data []byte) error { diff --git a/app/views/Mailbox/List.html b/app/views/Mailbox/List.html new file mode 100644 index 0000000..b5c59d2 --- /dev/null +++ b/app/views/Mailbox/List.html @@ -0,0 +1,22 @@ +{{template "header.html" .}} + +

Your Index Is Ready

+{{$name := .name}} +

{{.name}} inbox

+ +{{if .messages}} + +{{else}} +

No messages!

+{{end}} + +{{template "footer.html" .}} + diff --git a/app/views/Mailbox/Show.html b/app/views/Mailbox/Show.html new file mode 100644 index 0000000..d33e0e0 --- /dev/null +++ b/app/views/Mailbox/Show.html @@ -0,0 +1,18 @@ +{{template "header.html" .}} + +

{{.message.Subject}}

+ + + + + + + + + +
From:{{.message.From}}
Date:{{.message.Date}}
+ +
{{.body}}
+ +{{template "footer.html" .}} + diff --git a/conf/routes b/conf/routes index 2ce276a..d0d6f7e 100644 --- a/conf/routes +++ b/conf/routes @@ -3,6 +3,9 @@ # ~~~~ GET / Application.Index +GET /mailbox/{name} Mailbox.Index +GET /mailbox/list/{name} Mailbox.List +GET /mailbox/show/{name}/{id} Mailbox.Show # Ignore favicon requests GET /favicon.ico 404