904ffacb0e
Previously, Message.NewReader returned the wrapped buffered reader without a reference to the opened file, so the files descriptors were left unclosed after reading. Now, the file reader is returned directly and closed on the call site. Buffering is not needed here because it is an implementation detail of go-message. Fixes: https://todo.sr.ht/~rjarry/aerc/9
179 lines
4 KiB
Go
179 lines
4 KiB
Go
//go:build notmuch
|
|
// +build notmuch
|
|
|
|
package notmuch
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
|
|
"git.sr.ht/~rjarry/aerc/models"
|
|
"git.sr.ht/~rjarry/aerc/worker/lib"
|
|
notmuch "git.sr.ht/~rjarry/aerc/worker/notmuch/lib"
|
|
"github.com/emersion/go-message"
|
|
_ "github.com/emersion/go-message/charset"
|
|
)
|
|
|
|
type Message struct {
|
|
uid uint32
|
|
key string
|
|
db *notmuch.DB
|
|
}
|
|
|
|
// NewReader returns a reader for a message
|
|
func (m *Message) NewReader() (io.ReadCloser, error) {
|
|
name, err := m.Filename()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return os.Open(name)
|
|
}
|
|
|
|
// 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) {
|
|
name, err := m.Filename()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
f, err := os.Open(name)
|
|
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)
|
|
}
|
|
|
|
// SetFlag adds or removes a flag from the message.
|
|
// Notmuch doesn't support all the flags, and for those this errors.
|
|
func (m *Message) SetFlag(flag models.Flag, enable bool) error {
|
|
// Translate the flag into a notmuch tag, ignoring no-op flags.
|
|
var tag string
|
|
switch flag {
|
|
case models.SeenFlag:
|
|
// Note: Inverted properly later
|
|
tag = "unread"
|
|
case models.AnsweredFlag:
|
|
tag = "replied"
|
|
case models.FlaggedFlag:
|
|
tag = "flagged"
|
|
default:
|
|
return fmt.Errorf("Notmuch doesn't support flag %v", flag)
|
|
}
|
|
|
|
// Get the current state of the flag.
|
|
// Note that notmuch handles models.SeenFlag in an inverted sense.
|
|
oldState := false
|
|
tags, err := m.Tags()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, t := range tags {
|
|
if t == tag {
|
|
oldState = true
|
|
break
|
|
}
|
|
}
|
|
if flag == models.SeenFlag {
|
|
oldState = !oldState
|
|
}
|
|
|
|
// Skip if flag already in correct state.
|
|
if oldState == enable {
|
|
return nil
|
|
}
|
|
|
|
if !enable {
|
|
if flag == models.SeenFlag {
|
|
return m.AddTag("unread")
|
|
} else {
|
|
return m.RemoveTag(tag)
|
|
}
|
|
} else {
|
|
if flag == models.SeenFlag {
|
|
return m.RemoveTag("unread")
|
|
} else {
|
|
return m.AddTag(tag)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MarkAnswered either adds or removes the "replied" tag from the message.
|
|
func (m *Message) MarkAnswered(answered bool) error {
|
|
return m.SetFlag(models.AnsweredFlag, answered)
|
|
}
|
|
|
|
// MarkRead either adds or removes the maildir.FlagSeen flag from the message.
|
|
func (m *Message) MarkRead(seen bool) error {
|
|
return m.SetFlag(models.SeenFlag, seen)
|
|
}
|
|
|
|
// tags returns the notmuch tags of a message
|
|
func (m *Message) Tags() ([]string, error) {
|
|
return m.db.MsgTags(m.key)
|
|
}
|
|
|
|
func (m *Message) Labels() ([]string, error) {
|
|
return m.Tags()
|
|
}
|
|
|
|
func (m *Message) ModelFlags() ([]models.Flag, error) {
|
|
var flags []models.Flag
|
|
seen := true
|
|
tags, err := m.Tags()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, tag := range 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
|
|
}
|
|
|
|
func (m *Message) Filename() (string, error) {
|
|
return m.db.MsgFilename(m.key)
|
|
}
|
|
|
|
//AddTag adds a single tag.
|
|
//Consider using *Message.ModifyTags for multiple additions / removals
|
|
//instead of looping over a tag array
|
|
func (m *Message) AddTag(tag string) error {
|
|
return m.ModifyTags([]string{tag}, nil)
|
|
}
|
|
|
|
//RemoveTag removes a single tag.
|
|
//Consider using *Message.ModifyTags for multiple additions / removals
|
|
//instead of looping over a tag array
|
|
func (m *Message) RemoveTag(tag string) error {
|
|
return m.ModifyTags(nil, []string{tag})
|
|
}
|
|
|
|
func (m *Message) ModifyTags(add, remove []string) error {
|
|
return m.db.MsgModifyTags(m.key, add, remove)
|
|
}
|