aboutsummaryrefslogtreecommitdiffstats
path: root/lib/notmuch/database.go
diff options
context:
space:
mode:
Diffstat (limited to 'lib/notmuch/database.go')
-rw-r--r--lib/notmuch/database.go314
1 files changed, 314 insertions, 0 deletions
diff --git a/lib/notmuch/database.go b/lib/notmuch/database.go
new file mode 100644
index 00000000..046d5d18
--- /dev/null
+++ b/lib/notmuch/database.go
@@ -0,0 +1,314 @@
+//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
+}