2019-09-13 08:47:59 +02:00
|
|
|
//+build notmuch
|
|
|
|
|
|
|
|
package lib
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"log"
|
2020-02-15 09:29:54 +01:00
|
|
|
"time"
|
2019-09-13 08:47:59 +02:00
|
|
|
|
|
|
|
notmuch "github.com/zenhack/go.notmuch"
|
|
|
|
)
|
|
|
|
|
2020-02-15 09:29:54 +01:00
|
|
|
const MAX_DB_AGE time.Duration = 10 * time.Second
|
|
|
|
|
2019-09-13 08:47:59 +02:00
|
|
|
type DB struct {
|
|
|
|
path string
|
|
|
|
excludedTags []string
|
|
|
|
logger *log.Logger
|
2020-02-15 09:29:54 +01:00
|
|
|
lastOpenTime time.Time
|
|
|
|
db *notmuch.DB
|
2019-09-13 08:47:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
2020-02-15 09:29:54 +01:00
|
|
|
// used as sanity check upon initial connect
|
|
|
|
err := db.connect(false)
|
|
|
|
return err
|
2019-09-13 08:47:59 +02:00
|
|
|
}
|
|
|
|
|
2020-02-15 09:29:54 +01:00
|
|
|
func (db *DB) close() error {
|
|
|
|
if db.db == nil {
|
|
|
|
return nil
|
2019-09-13 08:47:59 +02:00
|
|
|
}
|
2020-02-15 09:29:54 +01:00
|
|
|
err := db.db.Close()
|
|
|
|
db.db = nil
|
|
|
|
return err
|
2019-09-13 08:47:59 +02:00
|
|
|
}
|
|
|
|
|
2020-02-15 09:29:54 +01:00
|
|
|
func (db *DB) connect(writable bool) error {
|
|
|
|
var mode notmuch.DBMode = notmuch.DBReadOnly
|
|
|
|
if writable {
|
|
|
|
mode = notmuch.DBReadWrite
|
2019-09-13 08:47:59 +02:00
|
|
|
}
|
|
|
|
var err error
|
2020-02-15 09:29:54 +01:00
|
|
|
db.db, err = notmuch.Open(db.path, mode)
|
2019-09-13 08:47:59 +02:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("could not connect to notmuch db: %v", err)
|
|
|
|
}
|
2020-02-15 09:29:54 +01:00
|
|
|
db.lastOpenTime = time.Now()
|
2019-09-13 08:47:59 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-02-15 09:29:54 +01:00
|
|
|
//withConnection calls callback on the DB object, cleaning up upon return.
|
|
|
|
//the error returned is from the connection attempt, if not successful,
|
|
|
|
//or from the callback otherwise.
|
|
|
|
func (db *DB) withConnection(writable bool, cb func(*notmuch.DB) error) error {
|
|
|
|
too_old := time.Now().After(db.lastOpenTime.Add(MAX_DB_AGE))
|
|
|
|
if db.db == nil || writable || too_old {
|
|
|
|
if cerr := db.close(); cerr != nil {
|
|
|
|
db.logger.Printf("failed to close the notmuch db: %v", cerr)
|
|
|
|
}
|
|
|
|
err := db.connect(writable)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-12-21 16:21:26 +01:00
|
|
|
}
|
2020-02-15 09:29:54 +01:00
|
|
|
err := cb(db.db)
|
|
|
|
if writable {
|
|
|
|
// we need to close to commit the changes, else we block others
|
|
|
|
if cerr := db.close(); cerr != nil {
|
|
|
|
db.logger.Printf("failed to close the notmuch db: %v", cerr)
|
|
|
|
}
|
2019-12-21 16:21:26 +01:00
|
|
|
}
|
2020-02-15 09:29:54 +01:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// ListTags lists all known tags
|
|
|
|
func (db *DB) ListTags() ([]string, error) {
|
2019-12-21 16:21:26 +01:00
|
|
|
var result []string
|
2020-02-15 09:29:54 +01:00
|
|
|
err := db.withConnection(false, func(ndb *notmuch.DB) error {
|
|
|
|
tags, err := ndb.Tags()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
var tag *notmuch.Tag
|
|
|
|
for tags.Next(&tag) {
|
|
|
|
result = append(result, tag.Value)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
return result, err
|
2019-12-21 16:21:26 +01:00
|
|
|
}
|
|
|
|
|
2019-09-13 08:47:59 +02:00
|
|
|
//getQuery returns a query based on the provided query string.
|
|
|
|
//It also configures the query as specified on the worker
|
2020-02-15 09:29:54 +01:00
|
|
|
func (db *DB) newQuery(ndb *notmuch.DB, query string) (*notmuch.Query, error) {
|
|
|
|
q := ndb.NewQuery(query)
|
2019-09-13 08:47:59 +02:00
|
|
|
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) {
|
|
|
|
var msgIDs []string
|
2020-02-15 09:29:54 +01:00
|
|
|
err := db.withConnection(false, func(ndb *notmuch.DB) error {
|
|
|
|
query, err := db.newQuery(ndb, q)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-08-07 09:49:47 +02:00
|
|
|
defer query.Close()
|
2020-02-15 09:29:54 +01:00
|
|
|
msgs, err := query.Messages()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-08-07 09:49:47 +02:00
|
|
|
defer msgs.Close()
|
2020-02-15 09:29:54 +01:00
|
|
|
var msg *notmuch.Message
|
|
|
|
for msgs.Next(&msg) {
|
|
|
|
msgIDs = append(msgIDs, msg.ID())
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
return msgIDs, err
|
2019-09-13 08:47:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
type MessageCount struct {
|
|
|
|
Exists int
|
|
|
|
Unread int
|
|
|
|
}
|
|
|
|
|
|
|
|
func (db *DB) QueryCountMessages(q string) (MessageCount, error) {
|
2020-02-15 09:29:54 +01:00
|
|
|
var (
|
|
|
|
exists int
|
|
|
|
unread int
|
|
|
|
)
|
|
|
|
err := db.withConnection(false, func(ndb *notmuch.DB) error {
|
|
|
|
query, err := db.newQuery(ndb, q)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
exists = query.CountMessages()
|
|
|
|
query.Close()
|
|
|
|
uq, err := db.newQuery(ndb, fmt.Sprintf("(%v) and (tag:unread)", q))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer uq.Close()
|
|
|
|
unread = uq.CountMessages()
|
|
|
|
return nil
|
|
|
|
})
|
2019-09-13 08:47:59 +02:00
|
|
|
return MessageCount{
|
|
|
|
Exists: exists,
|
|
|
|
Unread: unread,
|
2020-02-15 09:29:54 +01:00
|
|
|
}, err
|
2019-09-13 08:47:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (db *DB) MsgFilename(key string) (string, error) {
|
2020-02-15 09:29:54 +01:00
|
|
|
var filename string
|
|
|
|
err := db.withConnection(false, func(ndb *notmuch.DB) error {
|
|
|
|
msg, err := ndb.FindMessage(key)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer msg.Close()
|
|
|
|
filename = msg.Filename()
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
return filename, err
|
2019-09-13 08:47:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (db *DB) MsgTags(key string) ([]string, error) {
|
|
|
|
var tags []string
|
2020-02-15 09:29:54 +01:00
|
|
|
err := db.withConnection(false, func(ndb *notmuch.DB) error {
|
|
|
|
msg, err := ndb.FindMessage(key)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer msg.Close()
|
|
|
|
ts := msg.Tags()
|
2020-08-07 09:49:47 +02:00
|
|
|
defer ts.Close()
|
2020-02-15 09:29:54 +01:00
|
|
|
var tag *notmuch.Tag
|
|
|
|
for ts.Next(&tag) {
|
|
|
|
tags = append(tags, tag.Value)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
return tags, err
|
2019-09-13 08:47:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (db *DB) msgModify(key string,
|
|
|
|
cb func(*notmuch.Message) error) error {
|
2020-02-15 09:29:54 +01:00
|
|
|
err := db.withConnection(true, func(ndb *notmuch.DB) error {
|
|
|
|
msg, err := ndb.FindMessage(key)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer msg.Close()
|
2019-09-13 08:47:59 +02:00
|
|
|
|
2020-02-15 09:29:54 +01:00
|
|
|
cb(msg)
|
|
|
|
err = msg.TagsToMaildirFlags()
|
|
|
|
if err != nil {
|
|
|
|
db.logger.Printf("could not sync maildir flags: %v", err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
return err
|
2019-09-13 08:47:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|