//go:build notmuch // +build notmuch package notmuch /* #cgo LDFLAGS: -lnotmuch #include #include */ 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 }