Add basic search

Closes: https://todo.sr.ht/~sircmpwn/koushin/23
This commit is contained in:
Simon Ser 2019-12-16 14:36:43 +01:00
parent 1a658fc22c
commit 1c5e17472d
No known key found for this signature in database
GPG key ID: 0FDE7BE0E88F5E48
5 changed files with 74 additions and 9 deletions

View file

@ -23,6 +23,7 @@ type MailboxRenderData struct {
Mailboxes []*imap.MailboxInfo Mailboxes []*imap.MailboxInfo
Messages []imapMessage Messages []imapMessage
PrevPage, NextPage int PrevPage, NextPage int
Query string
} }
func handleGetMailbox(ectx echo.Context) error { func handleGetMailbox(ectx echo.Context) error {
@ -41,6 +42,8 @@ func handleGetMailbox(ectx echo.Context) error {
} }
} }
query := ctx.FormValue("query")
var mailboxes []*imap.MailboxInfo var mailboxes []*imap.MailboxInfo
var msgs []imapMessage var msgs []imapMessage
var mbox *imap.MailboxStatus var mbox *imap.MailboxStatus
@ -49,7 +52,12 @@ func handleGetMailbox(ectx echo.Context) error {
if mailboxes, err = listMailboxes(c); err != nil { if mailboxes, err = listMailboxes(c); err != nil {
return err return err
} }
if msgs, err = listMessages(c, mboxName, page); err != nil { if query != "" {
msgs, err = searchMessages(c, mboxName, query)
} else {
msgs, err = listMessages(c, mboxName, page)
}
if err != nil {
return err return err
} }
mbox = c.Mailbox() mbox = c.Mailbox()
@ -60,12 +68,15 @@ func handleGetMailbox(ectx echo.Context) error {
} }
prevPage, nextPage := -1, -1 prevPage, nextPage := -1, -1
if query == "" {
// TODO: paging for search
if page > 0 { if page > 0 {
prevPage = page - 1 prevPage = page - 1
} }
if (page+1)*messagesPerPage < int(mbox.Messages) { if (page+1)*messagesPerPage < int(mbox.Messages) {
nextPage = page + 1 nextPage = page + 1
} }
}
return ctx.Render(http.StatusOK, "mailbox.html", &MailboxRenderData{ return ctx.Render(http.StatusOK, "mailbox.html", &MailboxRenderData{
RenderData: *koushin.NewRenderData(ctx), RenderData: *koushin.NewRenderData(ctx),
@ -74,6 +85,7 @@ func handleGetMailbox(ectx echo.Context) error {
Messages: msgs, Messages: msgs,
PrevPage: prevPage, PrevPage: prevPage,
NextPage: nextPage, NextPage: nextPage,
Query: query,
}) })
} }

View file

@ -189,7 +189,7 @@ func listMessages(conn *imapclient.Client, mboxName string, page int) ([]imapMes
return nil, nil return nil, nil
} }
seqSet := new(imap.SeqSet) var seqSet imap.SeqSet
seqSet.AddRange(uint32(from), uint32(to)) seqSet.AddRange(uint32(from), uint32(to))
fetch := []imap.FetchItem{imap.FetchEnvelope, imap.FetchUid, imap.FetchBodyStructure} fetch := []imap.FetchItem{imap.FetchEnvelope, imap.FetchUid, imap.FetchBodyStructure}
@ -197,7 +197,7 @@ func listMessages(conn *imapclient.Client, mboxName string, page int) ([]imapMes
ch := make(chan *imap.Message, 10) ch := make(chan *imap.Message, 10)
done := make(chan error, 1) done := make(chan error, 1)
go func() { go func() {
done <- conn.Fetch(seqSet, fetch, ch) done <- conn.Fetch(&seqSet, fetch, ch)
}() }()
msgs := make([]imapMessage, 0, to-from) msgs := make([]imapMessage, 0, to-from)
@ -218,6 +218,53 @@ func listMessages(conn *imapclient.Client, mboxName string, page int) ([]imapMes
return msgs, nil return msgs, nil
} }
func searchMessages(conn *imapclient.Client, mboxName, query string) ([]imapMessage, error) {
if err := ensureMailboxSelected(conn, mboxName); err != nil {
return nil, err
}
criteria := imap.SearchCriteria{Text: []string{query}}
nums, err := conn.Search(&criteria)
if err != nil {
return nil, fmt.Errorf("UID SEARCH failed: %v", err)
}
if len(nums) == 0 {
return nil, nil
}
indexes := make(map[uint32]int)
for i, num := range nums {
indexes[num] = i
}
// TODO: paging
var seqSet imap.SeqSet
seqSet.AddNum(nums...)
fetch := []imap.FetchItem{imap.FetchEnvelope, imap.FetchUid, imap.FetchBodyStructure}
ch := make(chan *imap.Message, 10)
done := make(chan error, 1)
go func() {
done <- conn.Fetch(&seqSet, fetch, ch)
}()
msgs := make([]imapMessage, len(nums))
for msg := range ch {
i, ok := indexes[msg.SeqNum]
if !ok {
continue
}
msgs[i] = imapMessage{msg}
}
if err := <-done; err != nil {
return nil, fmt.Errorf("failed to fetch message list: %v", err)
}
return msgs, nil
}
func getMessagePart(conn *imapclient.Client, mboxName string, uid uint32, partPath []int) (*imapMessage, *message.Entity, error) { func getMessagePart(conn *imapclient.Client, mboxName string, uid uint32, partPath []int) (*imapMessage, *message.Entity, error) {
if err := ensureMailboxSelected(conn, mboxName); err != nil { if err := ensureMailboxSelected(conn, mboxName); err != nil {
return nil, nil, err return nil, nil, err

View file

@ -23,6 +23,7 @@ func init() {
}) })
p.GET("/mailbox/:mbox", handleGetMailbox) p.GET("/mailbox/:mbox", handleGetMailbox)
p.POST("/mailbox/:mbox", handleGetMailbox)
p.GET("/message/:mbox/:uid", func(ectx echo.Context) error { p.GET("/message/:mbox/:uid", func(ectx echo.Context) error {
ctx := ectx.(*koushin.Context) ctx := ectx.(*koushin.Context)

View file

@ -2,7 +2,7 @@
<h1>koushin</h1> <h1>koushin</h1>
<form method="post" action="/login"> <form method="post" action="">
<label for="username">Username:</label> <label for="username">Username:</label>
<input type="text" name="username" id="username"/> <input type="text" name="username" id="username"/>
<br><br> <br><br>

View file

@ -8,6 +8,11 @@
<h2>{{.Mailbox.Name}}</h2> <h2>{{.Mailbox.Name}}</h2>
<form method="post" action="">
<input type="search" name="query" value="{{.Query}}">
<input type="submit" value="Search">
</form>
<p>Mailboxes:</p> <p>Mailboxes:</p>
<ul> <ul>
{{range .Mailboxes}} {{range .Mailboxes}}