aerc/worker/notmuch/lib/database.go

200 lines
4.1 KiB
Go

//+build notmuch
package lib
import (
"fmt"
"log"
notmuch "github.com/zenhack/go.notmuch"
)
type DB struct {
path string
excludedTags []string
ro *notmuch.DB
logger *log.Logger
}
func NewDB(path string, excludedTags []string,
logger *log.Logger) *DB {
db := &DB{
path: path,
excludedTags: excludedTags,
logger: logger,
}
return db
}
func (db *DB) Connect() error {
return db.connectRO()
}
// connectRW returns a writable notmuch DB, which needs to be closed to commit
// the changes and to release the DB lock
func (db *DB) connectRW() (*notmuch.DB, error) {
rw, err := notmuch.Open(db.path, notmuch.DBReadWrite)
if err != nil {
return nil, fmt.Errorf("could not connect to notmuch db: %v", err)
}
return rw, err
}
// connectRO connects a RO db to the worker
func (db *DB) connectRO() error {
if db.ro != nil {
if err := db.ro.Close(); err != nil {
db.logger.Printf("connectRO: could not close the old db: %v", err)
}
}
var err error
db.ro, err = notmuch.Open(db.path, notmuch.DBReadOnly)
if err != nil {
return fmt.Errorf("could not connect to notmuch db: %v", err)
}
return nil
}
// ListTags lists all known tags
func (db *DB) ListTags() ([]string, error) {
if db.ro == nil {
return nil, fmt.Errorf("not connected to the notmuch db")
}
tags, err := db.ro.Tags()
if err != nil {
return nil, err
}
var result []string
var tag *notmuch.Tag
for tags.Next(&tag) {
result = append(result, tag.Value)
}
return result, nil
}
//getQuery returns a query based on the provided query string.
//It also configures the query as specified on the worker
func (db *DB) newQuery(query string) (*notmuch.Query, error) {
if db.ro == nil {
return nil, fmt.Errorf("not connected to the notmuch db")
}
q := db.ro.NewQuery(query)
q.SetExcludeScheme(notmuch.EXCLUDE_TRUE)
q.SetSortScheme(notmuch.SORT_OLDEST_FIRST)
for _, t := range db.excludedTags {
err := q.AddTagExclude(t)
if err != nil && err != notmuch.ErrIgnored {
return nil, err
}
}
return q, nil
}
func (db *DB) MsgIDsFromQuery(q string) ([]string, error) {
if db.ro == nil {
return nil, fmt.Errorf("not connected to the notmuch db")
}
query, err := db.newQuery(q)
if err != nil {
return nil, err
}
msgs, err := query.Messages()
if err != nil {
return nil, err
}
var msg *notmuch.Message
var msgIDs []string
for msgs.Next(&msg) {
msgIDs = append(msgIDs, msg.ID())
}
return msgIDs, nil
}
type MessageCount struct {
Exists int
Unread int
}
func (db *DB) QueryCountMessages(q string) (MessageCount, error) {
query, err := db.newQuery(q)
if err != nil {
return MessageCount{}, err
}
exists := query.CountMessages()
query.Close()
uq, err := db.newQuery(fmt.Sprintf("(%v) and (tag:unread)", q))
if err != nil {
return MessageCount{}, err
}
defer uq.Close()
unread := uq.CountMessages()
return MessageCount{
Exists: exists,
Unread: unread,
}, nil
}
func (db *DB) MsgFilename(key string) (string, error) {
msg, err := db.ro.FindMessage(key)
if err != nil {
return "", err
}
defer msg.Close()
return msg.Filename(), nil
}
func (db *DB) MsgTags(key string) ([]string, error) {
msg, err := db.ro.FindMessage(key)
if err != nil {
return nil, err
}
defer msg.Close()
ts := msg.Tags()
var tags []string
var tag *notmuch.Tag
for ts.Next(&tag) {
tags = append(tags, tag.Value)
}
return tags, nil
}
func (db *DB) msgModify(key string,
cb func(*notmuch.Message) error) error {
defer db.connectRO()
db.ro.Close()
rw, err := db.connectRW()
if err != nil {
return err
}
defer rw.Close()
msg, err := rw.FindMessage(key)
if err != nil {
return err
}
defer msg.Close()
cb(msg)
err = msg.TagsToMaildirFlags()
if err != nil {
db.logger.Printf("could not sync maildir flags: %v", err)
}
return nil
}
func (db *DB) MsgModifyTags(key string, add, remove []string) error {
err := db.msgModify(key, func(msg *notmuch.Message) error {
ierr := msg.Atomic(func(msg *notmuch.Message) {
for _, t := range add {
msg.AddTag(t)
}
for _, t := range remove {
msg.RemoveTag(t)
}
})
return ierr
})
return err
}