a2c5233f71
Notmuch only allows a single write connection, all other clients trying to modify the db block. Hence we should only open one when we actually need it. Apparently we also need to refresh the RO DB connection upon modification, else we get stale message tag results
190 lines
3.8 KiB
Go
190 lines
3.8 KiB
Go
//+build notmuch
|
|
|
|
package notmuch
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
|
|
"git.sr.ht/~sircmpwn/aerc/models"
|
|
"git.sr.ht/~sircmpwn/aerc/worker/lib"
|
|
"github.com/emersion/go-message"
|
|
_ "github.com/emersion/go-message/charset"
|
|
notmuch "github.com/zenhack/go.notmuch"
|
|
)
|
|
|
|
type Message struct {
|
|
uid uint32
|
|
key string
|
|
msg *notmuch.Message
|
|
rwDB func() (*notmuch.DB, error) // used to open a db for writing
|
|
refresh func(*Message) error // called after msg modification
|
|
}
|
|
|
|
// NewReader reads a message into memory and returns an io.Reader for it.
|
|
func (m *Message) NewReader() (io.Reader, error) {
|
|
f, err := os.Open(m.msg.Filename())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer f.Close()
|
|
b, err := ioutil.ReadAll(f)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return bytes.NewReader(b), nil
|
|
}
|
|
|
|
// MessageInfo populates a models.MessageInfo struct for the message.
|
|
func (m *Message) MessageInfo() (*models.MessageInfo, error) {
|
|
return lib.MessageInfo(m)
|
|
}
|
|
|
|
// NewBodyPartReader creates a new io.Reader for the requested body part(s) of
|
|
// the message.
|
|
func (m *Message) NewBodyPartReader(requestedParts []int) (io.Reader, error) {
|
|
f, err := os.Open(m.msg.Filename())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer f.Close()
|
|
msg, err := message.Read(f)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not read message: %v", err)
|
|
}
|
|
return lib.FetchEntityPartReader(msg, requestedParts)
|
|
}
|
|
|
|
// MarkRead either adds or removes the maildir.FlagSeen flag from the message.
|
|
func (m *Message) MarkRead(seen bool) error {
|
|
haveUnread := false
|
|
for _, t := range m.tags() {
|
|
if t == "unread" {
|
|
haveUnread = true
|
|
break
|
|
}
|
|
}
|
|
if (haveUnread && !seen) || (!haveUnread && seen) {
|
|
// we already have the desired state
|
|
return nil
|
|
}
|
|
|
|
if haveUnread {
|
|
err := m.RemoveTag("unread")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
err := m.AddTag("unread")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// tags returns the notmuch tags of a message
|
|
func (m *Message) tags() []string {
|
|
ts := m.msg.Tags()
|
|
var tags []string
|
|
var tag *notmuch.Tag
|
|
for ts.Next(&tag) {
|
|
tags = append(tags, tag.Value)
|
|
}
|
|
return tags
|
|
}
|
|
|
|
func (m *Message) modify(cb func(*notmuch.Message) error) error {
|
|
db, err := m.rwDB()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer db.Close()
|
|
msg, err := db.FindMessage(m.key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = cb(msg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// we need to explicitly close here, else we don't commit
|
|
dcerr := db.Close()
|
|
if dcerr != nil && err == nil {
|
|
err = dcerr
|
|
}
|
|
// next we need to refresh the notmuch msg, else we serve stale tags
|
|
rerr := m.refresh(m)
|
|
if rerr != nil && err == nil {
|
|
err = rerr
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (m *Message) AddTag(tag string) error {
|
|
err := m.modify(func(msg *notmuch.Message) error {
|
|
return msg.AddTag(tag)
|
|
})
|
|
return err
|
|
}
|
|
|
|
func (m *Message) AddTags(tags []string) error {
|
|
err := m.modify(func(msg *notmuch.Message) error {
|
|
ierr := msg.Atomic(func(msg *notmuch.Message) {
|
|
for _, t := range tags {
|
|
msg.AddTag(t)
|
|
}
|
|
})
|
|
return ierr
|
|
})
|
|
return err
|
|
}
|
|
|
|
func (m *Message) RemoveTag(tag string) error {
|
|
err := m.modify(func(msg *notmuch.Message) error {
|
|
return msg.RemoveTag(tag)
|
|
})
|
|
return err
|
|
}
|
|
|
|
func (m *Message) RemoveTags(tags []string) error {
|
|
err := m.modify(func(msg *notmuch.Message) error {
|
|
ierr := msg.Atomic(func(msg *notmuch.Message) {
|
|
for _, t := range tags {
|
|
msg.RemoveTag(t)
|
|
}
|
|
})
|
|
return ierr
|
|
})
|
|
return err
|
|
}
|
|
|
|
func (m *Message) ModelFlags() ([]models.Flag, error) {
|
|
var flags []models.Flag
|
|
seen := true
|
|
|
|
for _, tag := range m.tags() {
|
|
switch tag {
|
|
case "replied":
|
|
flags = append(flags, models.AnsweredFlag)
|
|
case "flagged":
|
|
flags = append(flags, models.FlaggedFlag)
|
|
case "unread":
|
|
seen = false
|
|
default:
|
|
continue
|
|
}
|
|
}
|
|
if seen {
|
|
flags = append(flags, models.SeenFlag)
|
|
}
|
|
return flags, nil
|
|
}
|
|
|
|
func (m *Message) UID() uint32 {
|
|
return m.uid
|
|
}
|