mirror of https://git.sr.ht/~rjarry/aerc
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
315 lines
8.0 KiB
Go
315 lines
8.0 KiB
Go
//go:build notmuch
|
|
// +build notmuch
|
|
|
|
package notmuch
|
|
|
|
/*
|
|
#cgo LDFLAGS: -lnotmuch
|
|
|
|
#include <stdlib.h>
|
|
#include <notmuch.h>
|
|
|
|
*/
|
|
import "C"
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"unsafe"
|
|
)
|
|
|
|
type Mode int
|
|
|
|
const (
|
|
MODE_READ_ONLY Mode = C.NOTMUCH_DATABASE_MODE_READ_ONLY
|
|
MODE_READ_WRITE Mode = C.NOTMUCH_DATABASE_MODE_READ_WRITE
|
|
)
|
|
|
|
type Database struct {
|
|
// The path to the notmuch database. If Path is the empty string, the
|
|
// location will be found in the following order:
|
|
//
|
|
// 1. The value of the environment variable NOTMUCH_DATABASE
|
|
// 2. From the config file specified by Config
|
|
// 3. From the Profile specified by profile, given by
|
|
// $XDG_DATA_HOME/notmuch/$PROFILE
|
|
Path string
|
|
|
|
// The path to the notmuch configuration file to use.
|
|
Config string
|
|
|
|
// If FindConfig is true, libnotmuch will attempt to locate a suitable
|
|
// configuration file in the following order:
|
|
//
|
|
// 1. The value of the environment variable NOTMUCH_CONFIG
|
|
// 2. $XDG_CONFIG_HOME/notmuch/
|
|
// 3. $HOME/.notmuch-config
|
|
//
|
|
// If not configuration file is found, a STATUS_NO_CONFIG error will be
|
|
// returned
|
|
FindConfig bool
|
|
|
|
// The profile to use. If Profile is non-empty, the value will be
|
|
// appended to the paths determined for Config and Path. If Profile is
|
|
// the empty string, the profile will be determined in the following
|
|
// order:
|
|
//
|
|
// 1. The value of the environment variable NOTMUCH_PROFILE
|
|
// 2. "default" if Config and/or Path are a directory, "" if they are a
|
|
// filepath
|
|
Profile string
|
|
|
|
db *C.notmuch_database_t
|
|
open bool
|
|
}
|
|
|
|
// Create creates a notmuch database at the Path
|
|
func (db *Database) Create() error {
|
|
var cdb *C.notmuch_database_t
|
|
var cPath *C.char
|
|
defer C.free(unsafe.Pointer(cPath))
|
|
if db.Path != "" {
|
|
cPath = C.CString(db.Path)
|
|
}
|
|
err := errorWrap(C.notmuch_database_create(cPath, &cdb)) //nolint:gocritic // see note in notmuch.go
|
|
if err != nil {
|
|
return err
|
|
}
|
|
db.db = cdb
|
|
return nil
|
|
}
|
|
|
|
// Open opens the database with the given mode. Caller must call Close when done
|
|
// to commit changes and free resources
|
|
func (db *Database) Open(mode Mode) error {
|
|
var (
|
|
cPath *C.char
|
|
cConfig *C.char
|
|
cProfile *C.char
|
|
cErr *C.char
|
|
)
|
|
defer C.free(unsafe.Pointer(cPath))
|
|
defer C.free(unsafe.Pointer(cConfig))
|
|
defer C.free(unsafe.Pointer(cProfile))
|
|
defer C.free(unsafe.Pointer(cErr))
|
|
|
|
if db.Path != "" {
|
|
cPath = C.CString(db.Path)
|
|
}
|
|
|
|
if !db.FindConfig {
|
|
cConfig = C.CString(db.Config)
|
|
}
|
|
|
|
if db.Profile != "" {
|
|
cProfile = C.CString(db.Profile)
|
|
}
|
|
cmode := C.notmuch_database_mode_t(mode)
|
|
|
|
var cdb *C.notmuch_database_t
|
|
|
|
// gocritic:dupSubExpr throws an issue here no matter how we call this
|
|
// function
|
|
err := errorWrap(
|
|
C.notmuch_database_open_with_config(
|
|
cPath, cmode, cConfig, cProfile, &cdb, &cErr, //nolint:gocritic // see above
|
|
),
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
db.db = cdb
|
|
db.open = true
|
|
return nil
|
|
}
|
|
|
|
// Reopen an open notmuch database, usually with a different mode
|
|
func (db *Database) Reopen(mode Mode) error {
|
|
cmode := C.notmuch_database_mode_t(mode)
|
|
return errorWrap(C.notmuch_database_reopen(db.db, cmode))
|
|
}
|
|
|
|
// Close commits changes and closes the database, freeing any resources
|
|
// associated with it
|
|
func (db *Database) Close() error {
|
|
if !db.open {
|
|
return nil
|
|
}
|
|
err := errorWrap(C.notmuch_database_close(db.db))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = errorWrap(C.notmuch_database_destroy(db.db))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
db.open = false
|
|
return nil
|
|
}
|
|
|
|
// LastStatus returns the last status string for the database
|
|
func (db *Database) LastStatus() string {
|
|
cStatus := C.notmuch_database_status_string(db.db)
|
|
defer C.free(unsafe.Pointer(cStatus))
|
|
return C.GoString(cStatus)
|
|
}
|
|
|
|
func (db *Database) Compact(backupPath string) error {
|
|
if backupPath == "" {
|
|
return fmt.Errorf("must have backup path before compacting")
|
|
}
|
|
var cBackupPath *C.char
|
|
defer C.free(unsafe.Pointer(cBackupPath))
|
|
return errorWrap(C.notmuch_database_compact_db(db.db, cBackupPath, nil, nil))
|
|
}
|
|
|
|
// Return the resolved path to the notmuch database
|
|
func (db *Database) ResolvedPath() string {
|
|
cPath := C.notmuch_database_get_path(db.db)
|
|
return C.GoString(cPath)
|
|
}
|
|
|
|
// NeedsUpgrade reports if the database must be upgraded before a write
|
|
// operation can be safely performed
|
|
func (db *Database) NeedsUpgrade() bool {
|
|
return C.notmuch_database_needs_upgrade(db.db) == 1
|
|
}
|
|
|
|
// Indicate the beginning of an atomic operation
|
|
func (db *Database) BeginAtomic() error {
|
|
return errorWrap(C.notmuch_database_begin_atomic(db.db))
|
|
}
|
|
|
|
// Indicate the end of an atomic operation
|
|
func (db *Database) EndAtomic() error {
|
|
return errorWrap(C.notmuch_database_end_atomic(db.db))
|
|
}
|
|
|
|
// Returns the UUID and LastMod of the notmuch database
|
|
func (db *Database) Revision() (string, uint64) {
|
|
var uuid *C.char
|
|
defer C.free(unsafe.Pointer(uuid))
|
|
lastmod := uint64(C.notmuch_database_get_revision(db.db, &uuid)) //nolint:gocritic // see note in notmuch.go
|
|
return C.GoString(uuid), lastmod
|
|
}
|
|
|
|
// Returns a Directory object relative to the path of the Database
|
|
func (db *Database) Directory(relativePath string) (Directory, error) {
|
|
var result Directory
|
|
|
|
if relativePath == "" {
|
|
return result, fmt.Errorf("path can't be empty")
|
|
}
|
|
var (
|
|
dir *C.notmuch_directory_t
|
|
cPath *C.char
|
|
)
|
|
cPath = C.CString(relativePath)
|
|
defer C.free(unsafe.Pointer(cPath))
|
|
err := errorWrap(C.notmuch_database_get_directory(db.db, cPath, &dir)) //nolint:gocritic // see note in notmuch.go
|
|
if err != nil {
|
|
return result, err
|
|
}
|
|
result.dir = dir
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// IndexFile indexes a file with path relative to the database path, or an
|
|
// absolute path which share a common ancestor as the database path
|
|
func (db *Database) IndexFile(path string) (Message, error) {
|
|
var (
|
|
cPath *C.char
|
|
msg *C.notmuch_message_t
|
|
)
|
|
cPath = C.CString(path)
|
|
defer C.free(unsafe.Pointer(cPath))
|
|
|
|
err := errorWrap(C.notmuch_database_index_file(db.db, cPath, nil, &msg)) //nolint:gocritic // see note in notmuch.go
|
|
switch {
|
|
case errors.Is(err, STATUS_DUPLICATE_MESSAGE_ID):
|
|
break
|
|
case err != nil:
|
|
return Message{}, err
|
|
}
|
|
message := Message{
|
|
message: msg,
|
|
}
|
|
return message, nil
|
|
}
|
|
|
|
// Remove a file from the database. If this is the last file associated with a
|
|
// message, the message will be removed from the database.
|
|
func (db *Database) RemoveFile(path string) error {
|
|
cPath := C.CString(path)
|
|
defer C.free(unsafe.Pointer(cPath))
|
|
return errorWrap(C.notmuch_database_remove_message(db.db, cPath))
|
|
}
|
|
|
|
// FindMessageByID finds a message by the Message-ID header field value
|
|
func (db *Database) FindMessageByID(id string) (Message, error) {
|
|
var (
|
|
cID *C.char
|
|
msg *C.notmuch_message_t
|
|
)
|
|
cID = C.CString(id)
|
|
defer C.free(unsafe.Pointer(cID))
|
|
err := errorWrap(C.notmuch_database_find_message(db.db, cID, &msg)) //nolint:gocritic // see note in notmuch.go
|
|
if err != nil {
|
|
return Message{}, err
|
|
}
|
|
message := Message{
|
|
message: msg,
|
|
}
|
|
return message, nil
|
|
}
|
|
|
|
// FindMessageByFilename finds a message by filename
|
|
func (db *Database) FindMessageByFilename(filename string) (Message, error) {
|
|
var (
|
|
cFilename *C.char
|
|
msg *C.notmuch_message_t
|
|
)
|
|
cFilename = C.CString(filename)
|
|
defer C.free(unsafe.Pointer(cFilename))
|
|
err := errorWrap(C.notmuch_database_find_message_by_filename(db.db, cFilename, &msg)) //nolint:gocritic // see note in notmuch.go
|
|
if err != nil {
|
|
return Message{}, err
|
|
}
|
|
if msg == nil {
|
|
return Message{}, fmt.Errorf("couldn't find message by filename: %s", filename)
|
|
}
|
|
message := Message{
|
|
message: msg,
|
|
}
|
|
return message, nil
|
|
}
|
|
|
|
// Tags returns a slice of all tags in the database
|
|
func (db *Database) Tags() []string {
|
|
cTags := C.notmuch_database_get_all_tags(db.db)
|
|
defer C.notmuch_tags_destroy(cTags)
|
|
|
|
tags := []string{}
|
|
for C.notmuch_tags_valid(cTags) > 0 {
|
|
tag := C.notmuch_tags_get(cTags)
|
|
tags = append(tags, C.GoString(tag))
|
|
C.notmuch_tags_move_to_next(cTags)
|
|
}
|
|
return tags
|
|
}
|
|
|
|
// Create a new Query
|
|
func (db *Database) Query(query string) (Query, error) {
|
|
cQuery := C.CString(query)
|
|
defer C.free(unsafe.Pointer(cQuery))
|
|
nmQuery := C.notmuch_query_create(db.db, cQuery)
|
|
if nmQuery == nil {
|
|
return Query{}, STATUS_OUT_OF_MEMORY
|
|
}
|
|
q := Query{
|
|
query: nmQuery,
|
|
}
|
|
return q, nil
|
|
}
|