New Upstream Release - golang-github-zenhack-go.notmuch

Ready changes

Summary

Merged new upstream version: 0.0~git20220918.0c91863 (was: 0.0~git20190821.5a19619).

Resulting package

Built on 2022-10-13T10:44 (took 3m13s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-releases golang-github-zenhack-go.notmuch-dev

Lintian Result

Diff

diff --git a/db.go b/db.go
index a999567..3b85d04 100644
--- a/db.go
+++ b/db.go
@@ -60,14 +60,65 @@ func Create(path string) (*DB, error) {
 // Open opens the database at the location path using mode. Caller is responsible
 // for closing the database when done.
 func Open(path string, mode DBMode) (*DB, error) {
-	cpath := C.CString(path)
-	defer C.free(unsafe.Pointer(cpath))
+	config := ""
+	return OpenWithConfig(&path, &config, nil, mode)
+}
+
+// OpenWithConfig opens the database at the location 'path' using 'mode' and
+// the configuration in 'config'.
+//
+// If 'path' is nil, use the location specified:
+//  - in the environment variable $NOTMUCH_DATABASE, if non-empty
+//  - in a configuration file, located as described in 'config'
+//  - by $XDG_DATA_HOME/notmuch/<profile>, if profile argument is set
+//
+// If 'path' is non-nil, but does not appear to be a Xapian database, check
+// for a directory '.notmuch/xapian' below 'path'.
+//
+// If 'config' is nil, it will look:
+//  - the environment variable $NOTMUCH_CONFIG, if non-empty
+//  - $XDG_CONFIG_HOME/notmuch
+//  - $HOME/.notmuch-config
+//
+// If 'config' is an empty string (""), then it will not open any configuration
+// file.
+//
+// If 'profile' is nil, it will use:
+//	 - the environment variable $NOTMUCH_PROFILE if defined
+//   - otherwise 'default' for directories, and '' for paths
+//
+// If 'profile' is non-nil, append to the directory / file path determined
+// for 'config' and 'path'.
+//
+// Caller is responsible for closing the database when done.
+func OpenWithConfig(path, config, profile *string, mode DBMode) (*DB, error) {
+	var cpath *C.char
+	if path != nil {
+		cpath = C.CString(*path)
+		defer C.free(unsafe.Pointer(cpath))
+	}
+
+	var cconfig *C.char
+	if config != nil {
+		cconfig = C.CString(*config)
+		defer C.free(unsafe.Pointer(cconfig))
+	}
+
+	var cprofile *C.char
+	if profile != nil {
+		cprofile = C.CString(*profile)
+		defer C.free(unsafe.Pointer(cprofile))
+	}
+
+	var errMsg string
+	cErrMsg := C.CString(errMsg)
+	defer C.free(unsafe.Pointer(cErrMsg))
 
 	cmode := C.notmuch_database_mode_t(mode)
 	var cdb *C.notmuch_database_t
 	cdbptr := (**C.notmuch_database_t)(&cdb)
-	err := statusErr(C.notmuch_database_open(cpath, cmode, cdbptr))
-	if err != nil {
+	err := statusErr(C.notmuch_database_open_with_config(cpath, cmode, cconfig, cprofile, cdbptr, &cErrMsg))
+	if err != nil || errMsg != "" {
 		return nil, err
 	}
 	db := &DB{cptr: unsafe.Pointer(cdb)}
@@ -149,7 +200,9 @@ func (db *DB) AddMessage(filename string) (*Message, error) {
 	defer C.free(unsafe.Pointer(cfilename))
 
 	var cmsg *C.notmuch_message_t
-	if err := statusErr(C.notmuch_database_index_file(db.toC(), cfilename, nil, &cmsg)); err != nil {
+	err := statusErr(C.notmuch_database_index_file(db.toC(), cfilename, nil, &cmsg))
+
+	if err != nil && err != ErrDuplicateMessageID {
 		return nil, err
 	}
 	msg := &Message{
@@ -157,7 +210,7 @@ func (db *DB) AddMessage(filename string) (*Message, error) {
 		parent: (*cStruct)(db),
 	}
 	setGcClose(msg)
-	return msg, nil
+	return msg, err
 }
 
 // RemoveMessage remove a message filename from the current database. If the
diff --git a/db_test.go b/db_test.go
index 247edfd..63290e5 100644
--- a/db_test.go
+++ b/db_test.go
@@ -308,3 +308,126 @@ func TestConfigListNext(t *testing.T) {
 		t.Errorf("iteration did not stop after the end of the options")
 	}
 }
+
+func TestOpenWithConfig(t *testing.T) {
+	// Temp dir for our configs and profiles
+	tmpDir, err := os.MkdirTemp("", "notmuch")
+	if err != nil {
+		t.Fatalf("MkdirTemp(): unexpected error: %s", err)
+	}
+	defer os.RemoveAll(tmpDir)
+
+	// "Default" profile
+	defaultProfileDir := path.Join(tmpDir, "notmuch", "default")
+	if err := os.MkdirAll(defaultProfileDir, 0700); err != nil {
+		t.Fatalf("MkdirAll(%s): unexpected error: %s", defaultProfileDir, err)
+	}
+
+	// "Test" profile
+	testProfile := "testProfile"
+	testProfileDir := path.Join(tmpDir, "notmuch", testProfile)
+	if err := os.MkdirAll(testProfileDir, 0700); err != nil {
+		t.Fatalf("MkdirAll(%s): unexpected error: %s", testProfileDir, err)
+	}
+
+	for _, dir := range []string{testProfileDir, defaultProfileDir, tmpDir} {
+		config, err := os.Create(path.Join(dir, "config"))
+		if err != nil {
+			t.Fatalf("CreateTemp(%s): unexpected error: %s", dir, err)
+		}
+		defer config.Close()
+		if _, err := config.WriteString(fmt.Sprintf("[database]\npath=%s\n", dbPath)); err != nil {
+			t.Fatalf("WriteString(%q): unexpected error: %s", config.Name(), err)
+		}
+	}
+
+	configPath := path.Join(tmpDir, "config")
+	badPath := "/nowherexyz73"
+	badProfile := "nowherexyz13"
+
+	type args struct {
+		path     *string
+		config   *string
+		profile  *string
+		xdg_home *string
+		mode     DBMode
+	}
+	tests := []struct {
+		name    string
+		args    args
+		wantErr bool
+	}{
+		{
+			name: "with default profile",
+			args: args{
+				mode:     DBReadOnly,
+				xdg_home: &tmpDir,
+			},
+		},
+		{
+			name: "with custom profile",
+			args: args{
+				mode:     DBReadOnly,
+				profile:  &testProfile,
+				xdg_home: &tmpDir,
+			},
+		},
+		{
+			name: "with config file",
+			args: args{
+				path:   nil,
+				config: &configPath,
+				mode:   DBReadOnly,
+			},
+		},
+		{
+			name: "with db path only",
+			args: args{
+				path: &dbPath,
+				mode: DBReadOnly,
+			},
+		},
+		{
+			name: "with non-existent config file",
+			args: args{
+				config: &badPath,
+			},
+			wantErr: true,
+		},
+		{
+			name: "with non-existent path",
+			args: args{
+				path: &badPath,
+			},
+			wantErr: true,
+		},
+		{
+			name: "with non-existent profile",
+			args: args{
+				profile:  &badProfile,
+				xdg_home: &tmpDir,
+			},
+			wantErr: true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if tt.args.xdg_home != nil {
+				os.Setenv("XDG_CONFIG_HOME", *tt.args.xdg_home)
+				defer os.Unsetenv("XDG_CONFIG_HOME")
+			}
+
+			db, err := OpenWithConfig(tt.args.path, tt.args.config, tt.args.profile, tt.args.mode)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("OpenWithConfig() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+
+			if !tt.wantErr {
+				if want, got := 1, db.Version(); got < want {
+					t.Errorf("db.Version(): want at least %d got %d", want, got)
+				}
+			}
+		})
+	}
+}
diff --git a/debian/changelog b/debian/changelog
index 7052137..a80b813 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,8 +1,9 @@
-golang-github-zenhack-go.notmuch (0.0~git20190821.5a19619-3) UNRELEASED; urgency=low
+golang-github-zenhack-go.notmuch (0.0~git20220918.0c91863-1) UNRELEASED; urgency=low
 
   * Set upstream metadata fields: Bug-Database, Bug-Submit.
+  * New upstream snapshot.
 
- -- Debian Janitor <janitor@jelmer.uk>  Sun, 30 Aug 2020 16:41:32 -0000
+ -- Debian Janitor <janitor@jelmer.uk>  Thu, 13 Oct 2022 10:42:09 -0000
 
 golang-github-zenhack-go.notmuch (0.0~git20190821.5a19619-2) unstable; urgency=medium
 
diff --git a/message.go b/message.go
index ee56c1f..a4bd535 100644
--- a/message.go
+++ b/message.go
@@ -117,6 +117,50 @@ func (m *Message) RemoveAllTags() error {
 	return statusErr(C.notmuch_message_remove_all_tags(m.toC()))
 }
 
+// Properties returns the properties for the current message, returning a
+// *MessageProperties which can be used to iterate over all properties using
+// `MessageProperties.Next(MessageProperty)`
+func (m *Message) Properties(key string, exact bool) *MessageProperties {
+	cexact := 0
+	if exact {
+		cexact = 1
+	}
+	ckey := C.CString(key)
+	defer C.free(unsafe.Pointer(ckey))
+	cprops := C.notmuch_message_get_properties(m.toC(), ckey, C.int(cexact))
+	props := &MessageProperties{
+		cptr:   unsafe.Pointer(cprops),
+		parent: (*cStruct)(m),
+	}
+	setGcClose(props)
+	return props
+}
+
+// AddProperty adds a property to the message.
+func (m *Message) AddProperty(key string, value string) error {
+	ckey := C.CString(key)
+	defer C.free(unsafe.Pointer(ckey))
+	cvalue := C.CString(value)
+	defer C.free(unsafe.Pointer(cvalue))
+	return statusErr(C.notmuch_message_add_property(m.toC(), ckey, cvalue))
+}
+
+// RemoveProperty removes a key/value pair from the message properties.
+func (m *Message) RemoveProperty(key string, value string) error {
+	ckey := C.CString(key)
+	defer C.free(unsafe.Pointer(ckey))
+	cvalue := C.CString(value)
+	defer C.free(unsafe.Pointer(cvalue))
+	return statusErr(C.notmuch_message_remove_property(m.toC(), ckey, cvalue))
+}
+
+// RemoveAllProperties removes all properties with key from the message.
+func (m *Message) RemoveAllProperties(key string) error {
+	ckey := C.CString(key)
+	defer C.free(unsafe.Pointer(ckey))
+	return statusErr(C.notmuch_message_remove_all_properties(m.toC(), ckey))
+}
+
 // Atomic allows a transactional change of tags to the message.
 func (m *Message) Atomic(callback func(*Message)) error {
 	if err := statusErr(C.notmuch_message_freeze(m.toC())); err != nil {
diff --git a/message_properties.go b/message_properties.go
new file mode 100644
index 0000000..3361877
--- /dev/null
+++ b/message_properties.go
@@ -0,0 +1,62 @@
+package notmuch
+
+// Copyright © 2015 The go.notmuch Authors. Authors can be found in the AUTHORS file.
+// Licensed under the GPLv3 or later.
+// See COPYING at the root of the repository for details.
+
+// #cgo LDFLAGS: -lnotmuch
+// #include <stdlib.h>
+// #include <notmuch.h>
+import "C"
+
+// MessageProperties represent a notmuch properties type.
+type MessageProperties cStruct
+
+func (props *MessageProperties) toC() *C.notmuch_message_properties_t {
+	return (*C.notmuch_message_properties_t)(props.cptr)
+}
+
+func (props *MessageProperties) Close() error {
+	return (*cStruct)(props).doClose(func() error {
+		C.notmuch_message_properties_destroy(props.toC())
+		return nil
+	})
+}
+
+// Next retrieves the next prop from the result set. Next returns true if a prop
+// was successfully retrieved.
+func (props *MessageProperties) Next(p **MessageProperty) bool {
+	if !props.valid() {
+		return false
+	}
+	*p = props.get()
+	C.notmuch_message_properties_move_to_next(props.toC())
+	return true
+}
+
+// Return a slice of strings containing each element of props.
+func (props *MessageProperties) slice() []string {
+	var prop *MessageProperty
+	ret := []string{}
+	for props.Next(&prop) {
+		ret = append(ret, prop.Value)
+	}
+	return ret
+}
+
+func (props *MessageProperties) get() *MessageProperty {
+	ckey := C.notmuch_message_properties_key(props.toC())
+	cvalue := C.notmuch_message_properties_value(props.toC())
+
+	prop := &MessageProperty{
+		Key:        C.GoString(ckey),
+		Value:      C.GoString(cvalue),
+		properties: props,
+	}
+	return prop
+}
+
+func (props *MessageProperties) valid() bool {
+	cbool := C.notmuch_message_properties_valid(props.toC())
+	return int(cbool) != 0
+}
diff --git a/message_properties_test.go b/message_properties_test.go
new file mode 100644
index 0000000..29e861e
--- /dev/null
+++ b/message_properties_test.go
@@ -0,0 +1,39 @@
+package notmuch
+
+import (
+	"testing"
+)
+
+func TestMessagesProperties(t *testing.T) {
+	db, err := Open(dbPath, DBReadWrite)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer db.Close()
+
+	qs := "subject:\"Introducing myself\""
+	messages, err := db.NewQuery(qs).Messages()
+	if err != nil {
+		t.Fatalf("error getting the messages: %s", err)
+	}
+
+	first := &Message{}
+	found := messages.Next(&first)
+	if !found {
+		t.Fatalf("couldn't get the first message: %s", err)
+	}
+
+	if err := first.AddProperty("go-notmuch-test", "success"); err != nil {
+		t.Fatalf("couldn't add property: %s", err)
+	}
+
+	properties := first.Properties("go-notmuch-test", true)
+	property := &MessageProperty{}
+	for properties.Next(&property) {
+		if property.Key == "go-notmuch-test" && property.Value == "success" {
+			return
+		}
+	}
+
+	t.Fatalf("couldn't find expected property")
+}
diff --git a/message_property.go b/message_property.go
new file mode 100644
index 0000000..bf313c5
--- /dev/null
+++ b/message_property.go
@@ -0,0 +1,21 @@
+package notmuch
+
+// Copyright © 2015 The go.notmuch Authors. Authors can be found in the AUTHORS file.
+// Licensed under the GPLv3 or later.
+// See COPYING at the root of the repository for details.
+
+// #cgo LDFLAGS: -lnotmuch
+// #include <stdlib.h>
+// #include <notmuch.h>
+import "C"
+
+// MessageProperty represents a property in the database.
+type MessageProperty struct {
+	Key        string
+	Value      string
+	properties *MessageProperties
+}
+
+func (p *MessageProperty) String() string {
+	return p.Key + "=" + p.Value
+}

Debdiff

[The following lists of changes regard files as different if they have different names, permissions or owners.]

Files in second set of .debs but not in first

-rw-r--r--  root/root   /usr/share/gocode/src/github.com/zenhack/go.notmuch/message_properties.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/zenhack/go.notmuch/message_properties_test.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/zenhack/go.notmuch/message_property.go

No differences were encountered in the control files

More details

Full run details