From b4ec9f6e220ee6426bb279c7a1bfc9c14b98697f Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Wed, 9 May 2012 13:15:16 +0200 Subject: [PATCH] [PATCH 1/5] go: reorganize the go bindings --- 7f/463839d6941519512e7d025dce2cd64590ebc1 | 2882 +++++++++++++++++++++ 1 file changed, 2882 insertions(+) create mode 100644 7f/463839d6941519512e7d025dce2cd64590ebc1 diff --git a/7f/463839d6941519512e7d025dce2cd64590ebc1 b/7f/463839d6941519512e7d025dce2cd64590ebc1 new file mode 100644 index 000000000..e197f4236 --- /dev/null +++ b/7f/463839d6941519512e7d025dce2cd64590ebc1 @@ -0,0 +1,2882 @@ +Return-Path: +X-Original-To: notmuch@notmuchmail.org +Delivered-To: notmuch@notmuchmail.org +Received: from localhost (localhost [127.0.0.1]) + by olra.theworths.org (Postfix) with ESMTP id 53297431FC3 + for ; Wed, 9 May 2012 04:16:59 -0700 (PDT) +X-Virus-Scanned: Debian amavisd-new at olra.theworths.org +X-Spam-Flag: NO +X-Spam-Score: 0 +X-Spam-Level: +X-Spam-Status: No, score=0 tagged_above=-999 required=5 tests=[none] + autolearn=disabled +Received: from olra.theworths.org ([127.0.0.1]) + by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024) + with ESMTP id do-Gl7X3Cjep for ; + Wed, 9 May 2012 04:16:41 -0700 (PDT) +Received: from mail.cryptobitch.de (cryptobitch.de [88.198.7.68]) + (using TLSv1 with cipher ADH-AES256-SHA (256/256 bits)) + (No client certificate requested) + by olra.theworths.org (Postfix) with ESMTPS id B7AAA431FAE + for ; Wed, 9 May 2012 04:16:40 -0700 (PDT) +Received: from mail.jade-hamburg.de (mail.jade-hamburg.de [85.183.11.228]) + (using TLSv1 with cipher ADH-AES256-SHA (256/256 bits)) + (No client certificate requested) + by mail.cryptobitch.de (Postfix) with ESMTPSA id 39B25588C1D + for ; Wed, 9 May 2012 13:16:39 +0200 (CEST) +Received: by mail.jade-hamburg.de (Postfix, from userid 401) + id D6E8CDF2A3; Wed, 9 May 2012 13:16:23 +0200 (CEST) +Received: from thinkbox.jade-hamburg.de (mail.jade-hamburg.de [85.183.11.228]) + (using TLSv1 with cipher AES256-SHA (256/256 bits)) + (No client certificate requested) (Authenticated sender: teythoon) + by mail.jade-hamburg.de (Postfix) with ESMTPSA id C73E4DF2A1; + Wed, 9 May 2012 13:15:18 +0200 (CEST) +Received: from teythoon by thinkbox.jade-hamburg.de with local (Exim 4.77) + (envelope-from ) + id 1SS4rj-0003cW-1k; Wed, 09 May 2012 13:15:31 +0200 +From: Justus Winter <4winter@informatik.uni-hamburg.de> +To: notmuch@notmuchmail.org +Subject: [PATCH 1/5] go: reorganize the go bindings +Date: Wed, 9 May 2012 13:15:16 +0200 +Message-Id: + <1336562120-13837-2-git-send-email-4winter@informatik.uni-hamburg.de> +X-Mailer: git-send-email 1.7.10 +In-Reply-To: + <1336562120-13837-1-git-send-email-4winter@informatik.uni-hamburg.de> +References: <20120505111535.3687.9018@thinkbox.jade-hamburg.de> + <1336562120-13837-1-git-send-email-4winter@informatik.uni-hamburg.de> +X-BeenThere: notmuch@notmuchmail.org +X-Mailman-Version: 2.1.13 +Precedence: list +List-Id: "Use and development of the notmuch mail system." + +List-Unsubscribe: , + +List-Archive: +List-Post: +List-Help: +List-Subscribe: , + +X-List-Received-Date: Wed, 09 May 2012 11:16:59 -0000 + +go 1 introduced the "go" program that simplifies building of libraries +and programs. This patch reorganizes the go code so it can be compiled +using the new utility, it does not change any files. + +Signed-off-by: Justus Winter <4winter@informatik.uni-hamburg.de> +--- + bindings/go/cmds/notmuch-addrlookup.go | 263 ----- + bindings/go/pkg/notmuch.go | 1125 ---------------------- + bindings/go/src/notmuch-addrlookup/addrlookup.go | 263 +++++ + bindings/go/src/notmuch/notmuch.go | 1125 ++++++++++++++++++++++ + 4 files changed, 1388 insertions(+), 1388 deletions(-) + delete mode 100644 bindings/go/cmds/notmuch-addrlookup.go + delete mode 100644 bindings/go/pkg/notmuch.go + create mode 100644 bindings/go/src/notmuch-addrlookup/addrlookup.go + create mode 100644 bindings/go/src/notmuch/notmuch.go + +diff --git a/bindings/go/cmds/notmuch-addrlookup.go b/bindings/go/cmds/notmuch-addrlookup.go +deleted file mode 100644 +index 03699fb..0000000 +--- a/bindings/go/cmds/notmuch-addrlookup.go ++++ /dev/null +@@ -1,263 +0,0 @@ +-package main +- +-// stdlib imports +-import "os" +-import "path" +-import "log" +-import "fmt" +-import "regexp" +-import "strings" +-import "sort" +- +-// 3rd-party imports +-import "notmuch" +-import "github.com/kless/goconfig/config" +- +-type mail_addr_freq struct { +- addr string +- count [3]uint +-} +- +-type frequencies map[string]uint +- +-/* Used to sort the email addresses from most to least used */ +-func sort_by_freq(m1, m2 *mail_addr_freq) int { +- if (m1.count[0] == m2.count[0] && +- m1.count[1] == m2.count[1] && +- m1.count[2] == m2.count[2]) { +- return 0 +- } +- +- if (m1.count[0] > m2.count[0] || +- m1.count[0] == m2.count[0] && +- m1.count[1] > m2.count[1] || +- m1.count[0] == m2.count[0] && +- m1.count[1] == m2.count[1] && +- m1.count[2] > m2.count[2]) { +- return -1 +- } +- +- return 1 +-} +- +-type maddresses []*mail_addr_freq +- +-func (self *maddresses) Len() int { +- return len(*self) +-} +- +-func (self *maddresses) Less(i,j int) bool { +- m1 := (*self)[i] +- m2 := (*self)[j] +- v := sort_by_freq(m1, m2) +- if v<=0 { +- return true +- } +- return false +-} +- +-func (self *maddresses) Swap(i,j int) { +- (*self)[i], (*self)[j] = (*self)[j], (*self)[i] +-} +- +-// find most frequent real name for each mail address +-func frequent_fullname(freqs frequencies) string { +- var maxfreq uint = 0 +- fullname := "" +- freqs_sz := len(freqs) +- +- for mail,freq := range freqs { +- if (freq > maxfreq && mail != "") || freqs_sz == 1 { +- // only use the entry if it has a real name +- // or if this is the only entry +- maxfreq = freq +- fullname = mail +- } +- } +- return fullname +-} +- +-func addresses_by_frequency(msgs *notmuch.Messages, name string, pass uint, addr_to_realname *map[string]*frequencies) *frequencies { +- +- freqs := make(frequencies) +- +- pattern := `\s*(("(\.|[^"])*"|[^,])*?)` +- // pattern := "\\s*((\\\"(\\\\.|[^\\\\\"])*\\\"|[^,])*" + +- // "\\b\\w+([-+.]\\w+)*\\@\\w+[-\\.\\w]*\\.([-\\.\\w]+)*\\w\\b)>?)" +- pattern = `.*` + strings.ToLower(name) + `.*` +- var re *regexp.Regexp = nil +- var err os.Error = nil +- if re,err = regexp.Compile(pattern); err != nil { +- log.Printf("error: %v\n", err) +- return &freqs +- } +- +- headers := []string{"from"} +- if pass == 1 { +- headers = append(headers, "to", "cc", "bcc") +- } +- +- for ;msgs.Valid();msgs.MoveToNext() { +- msg := msgs.Get() +- //println("==> msg [", msg.GetMessageId(), "]") +- for _,header := range headers { +- froms := strings.ToLower(msg.GetHeader(header)) +- //println(" froms: ["+froms+"]") +- for _,from := range strings.Split(froms, ",", -1) { +- from = strings.Trim(from, " ") +- match := re.FindString(from) +- //println(" -> match: ["+match+"]") +- occ,ok := freqs[match] +- if !ok { +- freqs[match] = 0 +- occ = 0 +- } +- freqs[match] = occ+1 +- } +- } +- } +- return &freqs +-} +- +-func search_address_passes(queries [3]*notmuch.Query, name string) []string { +- var val []string +- addr_freq := make(map[string]*mail_addr_freq) +- addr_to_realname := make(map[string]*frequencies) +- +- var pass uint = 0 // 0-based +- for _,query := range queries { +- if query == nil { +- //println("**warning: idx [",idx,"] contains a nil query") +- continue +- } +- msgs := query.SearchMessages() +- ht := addresses_by_frequency(msgs, name, pass, &addr_to_realname) +- for addr, count := range *ht { +- freq,ok := addr_freq[addr] +- if !ok { +- freq = &mail_addr_freq{addr:addr, count:[3]uint{0,0,0}} +- } +- freq.count[pass] = count +- addr_freq[addr] = freq +- } +- msgs.Destroy() +- pass += 1 +- } +- +- addrs := make(maddresses, len(addr_freq)) +- { +- iaddr := 0 +- for _, freq := range addr_freq { +- addrs[iaddr] = freq +- iaddr += 1 +- } +- } +- sort.Sort(&addrs) +- +- for _,addr := range addrs { +- freqs,ok := addr_to_realname[addr.addr] +- if ok { +- val = append(val, frequent_fullname(*freqs)) +- } else { +- val = append(val, addr.addr) +- } +- } +- //println("val:",val) +- return val +-} +- +-type address_matcher struct { +- // the notmuch database +- db *notmuch.Database +- // full path of the notmuch database +- user_db_path string +- // user primary email +- user_primary_email string +- // user tag to mark from addresses as in the address book +- user_addrbook_tag string +-} +- +-func new_address_matcher() *address_matcher { +- var cfg *config.Config +- var err os.Error +- +- // honor NOTMUCH_CONFIG +- home := os.Getenv("NOTMUCH_CONFIG") +- if home == "" { +- home = os.Getenv("HOME") +- } +- +- if cfg,err = config.ReadDefault(path.Join(home, ".notmuch-config")); err != nil { +- log.Fatalf("error loading config file:",err) +- } +- +- db_path,_ := cfg.String("database", "path") +- primary_email,_ := cfg.String("user", "primary_email") +- addrbook_tag,err := cfg.String("user", "addrbook_tag") +- if err != nil { +- addrbook_tag = "addressbook" +- } +- +- self := &address_matcher{db:nil, +- user_db_path:db_path, +- user_primary_email:primary_email, +- user_addrbook_tag:addrbook_tag} +- return self +-} +- +-func (self *address_matcher) run(name string) { +- queries := [3]*notmuch.Query{} +- +- // open the database +- if db, status := notmuch.OpenDatabase(self.user_db_path, +- notmuch.DATABASE_MODE_READ_ONLY); status == notmuch.STATUS_SUCCESS { +- self.db = db +- } else { +- log.Fatalf("Failed to open the database: %v\n", status) +- } +- +- // pass 1: look at all from: addresses with the address book tag +- query := "tag:" + self.user_addrbook_tag +- if name != "" { +- query = query + " and from:" + name + "*" +- } +- queries[0] = self.db.CreateQuery(query) +- +- // pass 2: look at all to: addresses sent from our primary mail +- query = "" +- if name != "" { +- query = "to:"+name+"*" +- } +- if self.user_primary_email != "" { +- query = query + " from:" + self.user_primary_email +- } +- queries[1] = self.db.CreateQuery(query) +- +- // if that leads only to a few hits, we check every from too +- if queries[0].CountMessages() + queries[1].CountMessages() < 10 { +- query = "" +- if name != "" { +- query = "from:"+name+"*" +- } +- queries[2] = self.db.CreateQuery(query) +- } +- +- // actually retrieve and sort addresses +- results := search_address_passes(queries, name) +- for _,v := range results { +- if v != "" && v != "\n" { +- fmt.Println(v) +- } +- } +- return +-} +- +-func main() { +- //fmt.Println("args:",os.Args) +- app := new_address_matcher() +- name := "" +- if len(os.Args) > 1 { +- name = os.Args[1] +- } +- app.run(name) +-} +\ No newline at end of file +diff --git a/bindings/go/pkg/notmuch.go b/bindings/go/pkg/notmuch.go +deleted file mode 100644 +index 8faf3bb..0000000 +--- a/bindings/go/pkg/notmuch.go ++++ /dev/null +@@ -1,1125 +0,0 @@ +-// Wrapper around the notmuch library +- +-package notmuch +- +-/* +-#include +-#include +-#include +-#include "notmuch.h" +-*/ +-import "C" +-import "unsafe" +- +-// Status codes used for the return values of most functions +-type Status C.notmuch_status_t +-const ( +- STATUS_SUCCESS Status = iota +- STATUS_OUT_OF_MEMORY +- STATUS_READ_ONLY_DATABASE +- STATUS_XAPIAN_EXCEPTION +- STATUS_FILE_ERROR +- STATUS_FILE_NOT_EMAIL +- STATUS_DUPLICATE_MESSAGE_ID +- STATUS_NULL_POINTER +- STATUS_TAG_TOO_LONG +- STATUS_UNBALANCED_FREEZE_THAW +- STATUS_UNBALANCED_ATOMIC +- +- STATUS_LAST_STATUS +-) +- +-func (self Status) String() string { +- var p *C.char +- +- // p is read-only +- p = C.notmuch_status_to_string(C.notmuch_status_t(self)) +- if p != nil { +- s := C.GoString(p) +- return s +- } +- return "" +-} +- +-/* Various opaque data types. For each notmuch__t see the various +- * notmuch_ functions below. */ +- +-type Database struct { +- db *C.notmuch_database_t +-} +- +-type Query struct { +- query *C.notmuch_query_t +-} +- +-type Threads struct { +- threads *C.notmuch_threads_t +-} +- +-type Thread struct { +- thread *C.notmuch_thread_t +-} +- +-type Messages struct { +- messages *C.notmuch_messages_t +-} +- +-type Message struct { +- message *C.notmuch_message_t +-} +- +-type Tags struct { +- tags *C.notmuch_tags_t +-} +- +-type Directory struct { +- dir *C.notmuch_directory_t +-} +- +-type Filenames struct { +- fnames *C.notmuch_filenames_t +-} +- +-type DatabaseMode C.notmuch_database_mode_t +-const ( +- DATABASE_MODE_READ_ONLY DatabaseMode = 0 +- DATABASE_MODE_READ_WRITE +-) +- +-// Create a new, empty notmuch database located at 'path' +-func NewDatabase(path string) (*Database, Status) { +- +- var c_path *C.char = C.CString(path) +- defer C.free(unsafe.Pointer(c_path)) +- +- if c_path == nil { +- return nil, STATUS_OUT_OF_MEMORY +- } +- +- self := &Database{db:nil} +- st := Status(C.notmuch_database_create(c_path, &self.db)) +- if st != STATUS_SUCCESS { +- return nil, st +- } +- return self, st +-} +- +-/* Open an existing notmuch database located at 'path'. +- * +- * The database should have been created at some time in the past, +- * (not necessarily by this process), by calling +- * notmuch_database_create with 'path'. By default the database should be +- * opened for reading only. In order to write to the database you need to +- * pass the NOTMUCH_DATABASE_MODE_READ_WRITE mode. +- * +- * An existing notmuch database can be identified by the presence of a +- * directory named ".notmuch" below 'path'. +- * +- * The caller should call notmuch_database_destroy when finished with +- * this database. +- * +- * In case of any failure, this function returns NULL, (after printing +- * an error message on stderr). +- */ +-func OpenDatabase(path string, mode DatabaseMode) (*Database, Status) { +- +- var c_path *C.char = C.CString(path) +- defer C.free(unsafe.Pointer(c_path)) +- +- if c_path == nil { +- return nil, STATUS_OUT_OF_MEMORY +- } +- +- self := &Database{db:nil} +- st := Status(C.notmuch_database_open(c_path, C.notmuch_database_mode_t(mode), &self.db)) +- if st != STATUS_SUCCESS { +- return nil, st +- } +- return self, st +-} +- +-/* Close the given notmuch database, freeing all associated +- * resources. See notmuch_database_open. */ +-func (self *Database) Close() { +- C.notmuch_database_destroy(self.db) +-} +- +-/* Return the database path of the given database. +- */ +-func (self *Database) GetPath() string { +- +- /* The return value is a string owned by notmuch so should not be +- * modified nor freed by the caller. */ +- var p *C.char = C.notmuch_database_get_path(self.db) +- if p != nil { +- s := C.GoString(p) +- return s +- } +- return "" +-} +- +-/* Return the database format version of the given database. */ +-func (self *Database) GetVersion() uint { +- return uint(C.notmuch_database_get_version(self.db)) +-} +- +-/* Does this database need to be upgraded before writing to it? +- * +- * If this function returns TRUE then no functions that modify the +- * database (notmuch_database_add_message, notmuch_message_add_tag, +- * notmuch_directory_set_mtime, etc.) will work unless the function +- * notmuch_database_upgrade is called successfully first. */ +-func (self *Database) NeedsUpgrade() bool { +- do_upgrade := C.notmuch_database_needs_upgrade(self.db) +- if do_upgrade == 0 { +- return false +- } +- return true +-} +- +-// TODO: notmuch_database_upgrade +- +- +-/* Retrieve a directory object from the database for 'path'. +- * +- * Here, 'path' should be a path relative to the path of 'database' +- * (see notmuch_database_get_path), or else should be an absolute path +- * with initial components that match the path of 'database'. +- * +- * Can return NULL if a Xapian exception occurs. +- */ +-func (self *Database) GetDirectory(path string) *Directory { +- var c_path *C.char = C.CString(path) +- defer C.free(unsafe.Pointer(c_path)) +- +- if c_path == nil { +- return nil +- } +- +- c_dir := C.notmuch_database_get_directory(self.db, c_path) +- if c_dir == nil { +- return nil +- } +- return &Directory{dir:c_dir} +-} +- +-/* Add a new message to the given notmuch database. +- * +- * Here,'filename' should be a path relative to the path of +- * 'database' (see notmuch_database_get_path), or else should be an +- * absolute filename with initial components that match the path of +- * 'database'. +- * +- * The file should be a single mail message (not a multi-message mbox) +- * that is expected to remain at its current location, (since the +- * notmuch database will reference the filename, and will not copy the +- * entire contents of the file. +- * +- * If 'message' is not NULL, then, on successful return '*message' +- * will be initialized to a message object that can be used for things +- * such as adding tags to the just-added message. The user should call +- * notmuch_message_destroy when done with the message. On any failure +- * '*message' will be set to NULL. +- * +- * Return value: +- * +- * NOTMUCH_STATUS_SUCCESS: Message successfully added to database. +- * +- * NOTMUCH_STATUS_XAPIAN_EXCEPTION: A Xapian exception occurred, +- * message not added. +- * +- * NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID: Message has the same message +- * ID as another message already in the database. The new +- * filename was successfully added to the message in the database +- * (if not already present). +- * +- * NOTMUCH_STATUS_FILE_ERROR: an error occurred trying to open the +- * file, (such as permission denied, or file not found, +- * etc.). Nothing added to the database. +- * +- * NOTMUCH_STATUS_FILE_NOT_EMAIL: the contents of filename don't look +- * like an email message. Nothing added to the database. +- * +- * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only +- * mode so no message can be added. +- */ +-func +-(self *Database) AddMessage(fname string) (*Message, Status) { +- var c_fname *C.char = C.CString(fname) +- defer C.free(unsafe.Pointer(c_fname)) +- +- if c_fname == nil { +- return nil, STATUS_OUT_OF_MEMORY +- } +- +- var c_msg *C.notmuch_message_t = new(C.notmuch_message_t) +- st := Status(C.notmuch_database_add_message(self.db, c_fname, &c_msg)) +- +- return &Message{message:c_msg}, st +-} +- +-/* Remove a message from the given notmuch database. +- * +- * Note that only this particular filename association is removed from +- * the database. If the same message (as determined by the message ID) +- * is still available via other filenames, then the message will +- * persist in the database for those filenames. When the last filename +- * is removed for a particular message, the database content for that +- * message will be entirely removed. +- * +- * Return value: +- * +- * NOTMUCH_STATUS_SUCCESS: The last filename was removed and the +- * message was removed from the database. +- * +- * NOTMUCH_STATUS_XAPIAN_EXCEPTION: A Xapian exception occurred, +- * message not removed. +- * +- * NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID: This filename was removed but +- * the message persists in the database with at least one other +- * filename. +- * +- * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only +- * mode so no message can be removed. +- */ +-func (self *Database) RemoveMessage(fname string) Status { +- +- var c_fname *C.char = C.CString(fname) +- defer C.free(unsafe.Pointer(c_fname)) +- +- if c_fname == nil { +- return STATUS_OUT_OF_MEMORY +- } +- +- st := C.notmuch_database_remove_message(self.db, c_fname) +- return Status(st) +-} +- +-/* Find a message with the given message_id. +- * +- * If the database contains a message with the given message_id, then +- * a new notmuch_message_t object is returned. The caller should call +- * notmuch_message_destroy when done with the message. +- * +- * This function returns NULL in the following situations: +- * +- * * No message is found with the given message_id +- * * An out-of-memory situation occurs +- * * A Xapian exception occurs +- */ +-func (self *Database) FindMessage(message_id string) (*Message, Status) { +- +- var c_msg_id *C.char = C.CString(message_id) +- defer C.free(unsafe.Pointer(c_msg_id)) +- +- if c_msg_id == nil { +- return nil, STATUS_OUT_OF_MEMORY +- } +- +- msg := &Message{message:nil} +- st := Status(C.notmuch_database_find_message(self.db, c_msg_id, &msg.message)) +- if st != STATUS_SUCCESS { +- return nil, st +- } +- return msg, st +-} +- +-/* Return a list of all tags found in the database. +- * +- * This function creates a list of all tags found in the database. The +- * resulting list contains all tags from all messages found in the database. +- * +- * On error this function returns NULL. +- */ +-func (self *Database) GetAllTags() *Tags { +- tags := C.notmuch_database_get_all_tags(self.db) +- if tags == nil { +- return nil +- } +- return &Tags{tags:tags} +-} +- +-/* Create a new query for 'database'. +- * +- * Here, 'database' should be an open database, (see +- * notmuch_database_open and notmuch_database_create). +- * +- * For the query string, we'll document the syntax here more +- * completely in the future, but it's likely to be a specialized +- * version of the general Xapian query syntax: +- * +- * http://xapian.org/docs/queryparser.html +- * +- * As a special case, passing either a length-zero string, (that is ""), +- * or a string consisting of a single asterisk (that is "*"), will +- * result in a query that returns all messages in the database. +- * +- * See notmuch_query_set_sort for controlling the order of results. +- * See notmuch_query_search_messages and notmuch_query_search_threads +- * to actually execute the query. +- * +- * User should call notmuch_query_destroy when finished with this +- * query. +- * +- * Will return NULL if insufficient memory is available. +- */ +-func (self *Database) CreateQuery(query string) *Query { +- +- var c_query *C.char = C.CString(query) +- defer C.free(unsafe.Pointer(c_query)) +- +- if c_query == nil { +- return nil +- } +- +- q := C.notmuch_query_create(self.db, c_query) +- if q == nil { +- return nil +- } +- return &Query{query:q} +-} +- +-/* Sort values for notmuch_query_set_sort */ +-type Sort C.notmuch_sort_t +-const ( +- SORT_OLDEST_FIRST Sort = 0 +- SORT_NEWEST_FIRST +- SORT_MESSAGE_ID +- SORT_UNSORTED +-) +- +-/* Return the query_string of this query. See notmuch_query_create. */ +-func (self *Query) String() string { +- // FIXME: do we own 'q' or not ? +- q := C.notmuch_query_get_query_string(self.query) +- //defer C.free(unsafe.Pointer(q)) +- +- if q != nil { +- s := C.GoString(q) +- return s +- } +- +- return "" +-} +- +-/* Specify the sorting desired for this query. */ +-func (self *Query) SetSort(sort Sort) { +- C.notmuch_query_set_sort(self.query, C.notmuch_sort_t(sort)) +-} +- +-/* Return the sort specified for this query. See notmuch_query_set_sort. */ +-func (self *Query) GetSort() Sort { +- return Sort(C.notmuch_query_get_sort(self.query)) +-} +- +-/* Execute a query for threads, returning a notmuch_threads_t object +- * which can be used to iterate over the results. The returned threads +- * object is owned by the query and as such, will only be valid until +- * notmuch_query_destroy. +- * +- * Typical usage might be: +- * +- * notmuch_query_t *query; +- * notmuch_threads_t *threads; +- * notmuch_thread_t *thread; +- * +- * query = notmuch_query_create (database, query_string); +- * +- * for (threads = notmuch_query_search_threads (query); +- * notmuch_threads_valid (threads); +- * notmuch_threads_move_to_next (threads)) +- * { +- * thread = notmuch_threads_get (threads); +- * .... +- * notmuch_thread_destroy (thread); +- * } +- * +- * notmuch_query_destroy (query); +- * +- * Note: If you are finished with a thread before its containing +- * query, you can call notmuch_thread_destroy to clean up some memory +- * sooner (as in the above example). Otherwise, if your thread objects +- * are long-lived, then you don't need to call notmuch_thread_destroy +- * and all the memory will still be reclaimed when the query is +- * destroyed. +- * +- * Note that there's no explicit destructor needed for the +- * notmuch_threads_t object. (For consistency, we do provide a +- * notmuch_threads_destroy function, but there's no good reason +- * to call it if the query is about to be destroyed). +- * +- * If a Xapian exception occurs this function will return NULL. +- */ +-func (self *Query) SearchThreads() *Threads { +- threads := C.notmuch_query_search_threads(self.query) +- if threads == nil { +- return nil +- } +- return &Threads{threads:threads} +-} +- +-/* Execute a query for messages, returning a notmuch_messages_t object +- * which can be used to iterate over the results. The returned +- * messages object is owned by the query and as such, will only be +- * valid until notmuch_query_destroy. +- * +- * Typical usage might be: +- * +- * notmuch_query_t *query; +- * notmuch_messages_t *messages; +- * notmuch_message_t *message; +- * +- * query = notmuch_query_create (database, query_string); +- * +- * for (messages = notmuch_query_search_messages (query); +- * notmuch_messages_valid (messages); +- * notmuch_messages_move_to_next (messages)) +- * { +- * message = notmuch_messages_get (messages); +- * .... +- * notmuch_message_destroy (message); +- * } +- * +- * notmuch_query_destroy (query); +- * +- * Note: If you are finished with a message before its containing +- * query, you can call notmuch_message_destroy to clean up some memory +- * sooner (as in the above example). Otherwise, if your message +- * objects are long-lived, then you don't need to call +- * notmuch_message_destroy and all the memory will still be reclaimed +- * when the query is destroyed. +- * +- * Note that there's no explicit destructor needed for the +- * notmuch_messages_t object. (For consistency, we do provide a +- * notmuch_messages_destroy function, but there's no good +- * reason to call it if the query is about to be destroyed). +- * +- * If a Xapian exception occurs this function will return NULL. +- */ +-func (self *Query) SearchMessages() *Messages { +- msgs := C.notmuch_query_search_messages(self.query) +- if msgs == nil { +- return nil +- } +- return &Messages{messages:msgs} +-} +- +-/* Destroy a notmuch_query_t along with any associated resources. +- * +- * This will in turn destroy any notmuch_threads_t and +- * notmuch_messages_t objects generated by this query, (and in +- * turn any notmuch_thread_t and notmuch_message_t objects generated +- * from those results, etc.), if such objects haven't already been +- * destroyed. +- */ +-func (self *Query) Destroy() { +- if self.query != nil { +- C.notmuch_query_destroy(self.query) +- } +-} +- +-/* Return an estimate of the number of messages matching a search +- * +- * This function performs a search and returns Xapian's best +- * guess as to number of matching messages. +- * +- * If a Xapian exception occurs, this function may return 0 (after +- * printing a message). +- */ +-func (self *Query) CountMessages() uint { +- return uint(C.notmuch_query_count_messages(self.query)) +-} +- +-// TODO: wrap threads and thread +- +-/* Is the given 'threads' iterator pointing at a valid thread. +- * +- * When this function returns TRUE, notmuch_threads_get will return a +- * valid object. Whereas when this function returns FALSE, +- * notmuch_threads_get will return NULL. +- * +- * See the documentation of notmuch_query_search_threads for example +- * code showing how to iterate over a notmuch_threads_t object. +- */ +-func (self *Threads) Valid() bool { +- if self.threads == nil { +- return false +- } +- valid := C.notmuch_threads_valid(self.threads) +- if valid == 0 { +- return false +- } +- return true +-} +- +-/* Destroy a notmuch_threads_t object. +- * +- * It's not strictly necessary to call this function. All memory from +- * the notmuch_threads_t object will be reclaimed when the +- * containg query object is destroyed. +- */ +-func (self *Threads) Destroy() { +- if self.threads != nil { +- C.notmuch_threads_destroy(self.threads) +- } +-} +- +-/* Is the given 'messages' iterator pointing at a valid message. +- * +- * When this function returns TRUE, notmuch_messages_get will return a +- * valid object. Whereas when this function returns FALSE, +- * notmuch_messages_get will return NULL. +- * +- * See the documentation of notmuch_query_search_messages for example +- * code showing how to iterate over a notmuch_messages_t object. +- */ +-func (self *Messages) Valid() bool { +- if self.messages == nil { +- return false +- } +- valid := C.notmuch_messages_valid(self.messages) +- if valid == 0 { +- return false +- } +- return true +-} +- +-/* Get the current message from 'messages' as a notmuch_message_t. +- * +- * Note: The returned message belongs to 'messages' and has a lifetime +- * identical to it (and the query to which it belongs). +- * +- * See the documentation of notmuch_query_search_messages for example +- * code showing how to iterate over a notmuch_messages_t object. +- * +- * If an out-of-memory situation occurs, this function will return +- * NULL. +- */ +-func (self *Messages) Get() *Message { +- if self.messages == nil { +- return nil +- } +- msg := C.notmuch_messages_get(self.messages) +- if msg == nil { +- return nil +- } +- return &Message{message:msg} +-} +- +-/* Move the 'messages' iterator to the next message. +- * +- * If 'messages' is already pointing at the last message then the +- * iterator will be moved to a point just beyond that last message, +- * (where notmuch_messages_valid will return FALSE and +- * notmuch_messages_get will return NULL). +- * +- * See the documentation of notmuch_query_search_messages for example +- * code showing how to iterate over a notmuch_messages_t object. +- */ +-func (self *Messages) MoveToNext() { +- if self.messages == nil { +- return +- } +- C.notmuch_messages_move_to_next(self.messages) +-} +- +-/* Destroy a notmuch_messages_t object. +- * +- * It's not strictly necessary to call this function. All memory from +- * the notmuch_messages_t object will be reclaimed when the containing +- * query object is destroyed. +- */ +-func (self *Messages) Destroy() { +- if self.messages != nil { +- C.notmuch_messages_destroy(self.messages) +- } +-} +- +-/* Return a list of tags from all messages. +- * +- * The resulting list is guaranteed not to contain duplicated tags. +- * +- * WARNING: You can no longer iterate over messages after calling this +- * function, because the iterator will point at the end of the list. +- * We do not have a function to reset the iterator yet and the only +- * way how you can iterate over the list again is to recreate the +- * message list. +- * +- * The function returns NULL on error. +- */ +-func (self *Messages) CollectTags() *Tags { +- if self.messages == nil { +- return nil +- } +- tags := C.notmuch_messages_collect_tags(self.messages) +- if tags == nil { +- return nil +- } +- return &Tags{tags:tags} +-} +- +-/* Get the message ID of 'message'. +- * +- * The returned string belongs to 'message' and as such, should not be +- * modified by the caller and will only be valid for as long as the +- * message is valid, (which is until the query from which it derived +- * is destroyed). +- * +- * This function will not return NULL since Notmuch ensures that every +- * message has a unique message ID, (Notmuch will generate an ID for a +- * message if the original file does not contain one). +- */ +-func (self *Message) GetMessageId() string { +- +- if self.message == nil { +- return "" +- } +- id := C.notmuch_message_get_message_id(self.message) +- // we dont own id +- // defer C.free(unsafe.Pointer(id)) +- if id == nil { +- return "" +- } +- return C.GoString(id) +-} +- +-/* Get the thread ID of 'message'. +- * +- * The returned string belongs to 'message' and as such, should not be +- * modified by the caller and will only be valid for as long as the +- * message is valid, (for example, until the user calls +- * notmuch_message_destroy on 'message' or until a query from which it +- * derived is destroyed). +- * +- * This function will not return NULL since Notmuch ensures that every +- * message belongs to a single thread. +- */ +-func (self *Message) GetThreadId() string { +- +- if self.message == nil { +- return "" +- } +- id := C.notmuch_message_get_thread_id(self.message) +- // we dont own id +- // defer C.free(unsafe.Pointer(id)) +- +- if id == nil { +- return "" +- } +- +- return C.GoString(id) +-} +- +-/* Get a notmuch_messages_t iterator for all of the replies to +- * 'message'. +- * +- * Note: This call only makes sense if 'message' was ultimately +- * obtained from a notmuch_thread_t object, (such as by coming +- * directly from the result of calling notmuch_thread_get_ +- * toplevel_messages or by any number of subsequent +- * calls to notmuch_message_get_replies). +- * +- * If 'message' was obtained through some non-thread means, (such as +- * by a call to notmuch_query_search_messages), then this function +- * will return NULL. +- * +- * If there are no replies to 'message', this function will return +- * NULL. (Note that notmuch_messages_valid will accept that NULL +- * value as legitimate, and simply return FALSE for it.) +- */ +-func (self *Message) GetReplies() *Messages { +- if self.message == nil { +- return nil +- } +- msgs := C.notmuch_message_get_replies(self.message) +- if msgs == nil { +- return nil +- } +- return &Messages{messages:msgs} +-} +- +-/* Get a filename for the email corresponding to 'message'. +- * +- * The returned filename is an absolute filename, (the initial +- * component will match notmuch_database_get_path() ). +- * +- * The returned string belongs to the message so should not be +- * modified or freed by the caller (nor should it be referenced after +- * the message is destroyed). +- * +- * Note: If this message corresponds to multiple files in the mail +- * store, (that is, multiple files contain identical message IDs), +- * this function will arbitrarily return a single one of those +- * filenames. +- */ +-func (self *Message) GetFileName() string { +- if self.message == nil { +- return "" +- } +- fname := C.notmuch_message_get_filename(self.message) +- // we dont own fname +- // defer C.free(unsafe.Pointer(fname)) +- +- if fname == nil { +- return "" +- } +- +- return C.GoString(fname) +-} +- +-type Flag C.notmuch_message_flag_t +-const ( +- MESSAGE_FLAG_MATCH Flag = 0 +-) +- +-/* Get a value of a flag for the email corresponding to 'message'. */ +-func (self *Message) GetFlag(flag Flag) bool { +- if self.message == nil { +- return false +- } +- v := C.notmuch_message_get_flag(self.message, C.notmuch_message_flag_t(flag)) +- if v == 0 { +- return false +- } +- return true +-} +- +-/* Set a value of a flag for the email corresponding to 'message'. */ +-func (self *Message) SetFlag(flag Flag, value bool) { +- if self.message == nil { +- return +- } +- var v C.notmuch_bool_t = 0 +- if value { +- v = 1 +- } +- C.notmuch_message_set_flag(self.message, C.notmuch_message_flag_t(flag), v) +-} +- +-// TODO: wrap notmuch_message_get_date +- +-/* Get the value of the specified header from 'message'. +- * +- * The value will be read from the actual message file, not from the +- * notmuch database. The header name is case insensitive. +- * +- * The returned string belongs to the message so should not be +- * modified or freed by the caller (nor should it be referenced after +- * the message is destroyed). +- * +- * Returns an empty string ("") if the message does not contain a +- * header line matching 'header'. Returns NULL if any error occurs. +- */ +-func (self *Message) GetHeader(header string) string { +- if self.message == nil { +- return "" +- } +- +- var c_header *C.char = C.CString(header) +- defer C.free(unsafe.Pointer(c_header)) +- +- /* we dont own value */ +- value := C.notmuch_message_get_header(self.message, c_header) +- if value == nil { +- return "" +- } +- +- return C.GoString(value) +-} +- +-/* Get the tags for 'message', returning a notmuch_tags_t object which +- * can be used to iterate over all tags. +- * +- * The tags object is owned by the message and as such, will only be +- * valid for as long as the message is valid, (which is until the +- * query from which it derived is destroyed). +- * +- * Typical usage might be: +- * +- * notmuch_message_t *message; +- * notmuch_tags_t *tags; +- * const char *tag; +- * +- * message = notmuch_database_find_message (database, message_id); +- * +- * for (tags = notmuch_message_get_tags (message); +- * notmuch_tags_valid (tags); +- * notmuch_result_move_to_next (tags)) +- * { +- * tag = notmuch_tags_get (tags); +- * .... +- * } +- * +- * notmuch_message_destroy (message); +- * +- * Note that there's no explicit destructor needed for the +- * notmuch_tags_t object. (For consistency, we do provide a +- * notmuch_tags_destroy function, but there's no good reason to call +- * it if the message is about to be destroyed). +- */ +-func (self *Message) GetTags() *Tags { +- if self.message == nil { +- return nil +- } +- tags := C.notmuch_message_get_tags(self.message) +- if tags == nil { +- return nil +- } +- return &Tags{tags:tags} +-} +- +-/* The longest possible tag value. */ +-const TAG_MAX = 200 +- +-/* Add a tag to the given message. +- * +- * Return value: +- * +- * NOTMUCH_STATUS_SUCCESS: Tag successfully added to message +- * +- * NOTMUCH_STATUS_NULL_POINTER: The 'tag' argument is NULL +- * +- * NOTMUCH_STATUS_TAG_TOO_LONG: The length of 'tag' is too long +- * (exceeds NOTMUCH_TAG_MAX) +- * +- * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only +- * mode so message cannot be modified. +- */ +-func (self *Message) AddTag(tag string) Status { +- if self.message == nil { +- return STATUS_NULL_POINTER +- } +- c_tag := C.CString(tag) +- defer C.free(unsafe.Pointer(c_tag)) +- +- return Status(C.notmuch_message_add_tag(self.message, c_tag)) +-} +- +-/* Remove a tag from the given message. +- * +- * Return value: +- * +- * NOTMUCH_STATUS_SUCCESS: Tag successfully removed from message +- * +- * NOTMUCH_STATUS_NULL_POINTER: The 'tag' argument is NULL +- * +- * NOTMUCH_STATUS_TAG_TOO_LONG: The length of 'tag' is too long +- * (exceeds NOTMUCH_TAG_MAX) +- * +- * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only +- * mode so message cannot be modified. +- */ +-func (self *Message) RemoveTag(tag string) Status { +- if self.message == nil { +- return STATUS_NULL_POINTER +- } +- c_tag := C.CString(tag) +- defer C.free(unsafe.Pointer(c_tag)) +- +- return Status(C.notmuch_message_remove_tag(self.message, c_tag)) +-} +- +-/* Remove all tags from the given message. +- * +- * See notmuch_message_freeze for an example showing how to safely +- * replace tag values. +- * +- * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only +- * mode so message cannot be modified. +- */ +-func (self *Message) RemoveAllTags() Status { +- if self.message == nil { +- return STATUS_NULL_POINTER +- } +- return Status(C.notmuch_message_remove_all_tags(self.message)) +-} +- +-/* Freeze the current state of 'message' within the database. +- * +- * This means that changes to the message state, (via +- * notmuch_message_add_tag, notmuch_message_remove_tag, and +- * notmuch_message_remove_all_tags), will not be committed to the +- * database until the message is thawed with notmuch_message_thaw. +- * +- * Multiple calls to freeze/thaw are valid and these calls will +- * "stack". That is there must be as many calls to thaw as to freeze +- * before a message is actually thawed. +- * +- * The ability to do freeze/thaw allows for safe transactions to +- * change tag values. For example, explicitly setting a message to +- * have a given set of tags might look like this: +- * +- * notmuch_message_freeze (message); +- * +- * notmuch_message_remove_all_tags (message); +- * +- * for (i = 0; i < NUM_TAGS; i++) +- * notmuch_message_add_tag (message, tags[i]); +- * +- * notmuch_message_thaw (message); +- * +- * With freeze/thaw used like this, the message in the database is +- * guaranteed to have either the full set of original tag values, or +- * the full set of new tag values, but nothing in between. +- * +- * Imagine the example above without freeze/thaw and the operation +- * somehow getting interrupted. This could result in the message being +- * left with no tags if the interruption happened after +- * notmuch_message_remove_all_tags but before notmuch_message_add_tag. +- * +- * Return value: +- * +- * NOTMUCH_STATUS_SUCCESS: Message successfully frozen. +- * +- * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only +- * mode so message cannot be modified. +- */ +-func (self *Message) Freeze() Status { +- if self.message == nil { +- return STATUS_NULL_POINTER +- } +- return Status(C.notmuch_message_freeze(self.message)) +-} +- +-/* Thaw the current 'message', synchronizing any changes that may have +- * occurred while 'message' was frozen into the notmuch database. +- * +- * See notmuch_message_freeze for an example of how to use this +- * function to safely provide tag changes. +- * +- * Multiple calls to freeze/thaw are valid and these calls with +- * "stack". That is there must be as many calls to thaw as to freeze +- * before a message is actually thawed. +- * +- * Return value: +- * +- * NOTMUCH_STATUS_SUCCESS: Message successfully thawed, (or at least +- * its frozen count has successfully been reduced by 1). +- * +- * NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW: An attempt was made to thaw +- * an unfrozen message. That is, there have been an unbalanced +- * number of calls to notmuch_message_freeze and +- * notmuch_message_thaw. +- */ +-func (self *Message) Thaw() Status { +- if self.message == nil { +- return STATUS_NULL_POINTER +- } +- +- return Status(C.notmuch_message_thaw(self.message)) +-} +- +-/* Destroy a notmuch_message_t object. +- * +- * It can be useful to call this function in the case of a single +- * query object with many messages in the result, (such as iterating +- * over the entire database). Otherwise, it's fine to never call this +- * function and there will still be no memory leaks. (The memory from +- * the messages get reclaimed when the containing query is destroyed.) +- */ +-func (self *Message) Destroy() { +- if self.message == nil { +- return +- } +- C.notmuch_message_destroy(self.message) +-} +- +-/* Is the given 'tags' iterator pointing at a valid tag. +- * +- * When this function returns TRUE, notmuch_tags_get will return a +- * valid string. Whereas when this function returns FALSE, +- * notmuch_tags_get will return NULL. +- * +- * See the documentation of notmuch_message_get_tags for example code +- * showing how to iterate over a notmuch_tags_t object. +- */ +-func (self *Tags) Valid() bool { +- if self.tags == nil { +- return false +- } +- v := C.notmuch_tags_valid(self.tags) +- if v == 0 { +- return false +- } +- return true +-} +- +-/* Get the current tag from 'tags' as a string. +- * +- * Note: The returned string belongs to 'tags' and has a lifetime +- * identical to it (and the query to which it ultimately belongs). +- * +- * See the documentation of notmuch_message_get_tags for example code +- * showing how to iterate over a notmuch_tags_t object. +- */ +-func (self *Tags) Get() string { +- if self.tags == nil { +- return "" +- } +- s := C.notmuch_tags_get(self.tags) +- // we dont own 's' +- +- return C.GoString(s) +-} +-func (self *Tags) String() string { +- return self.Get() +-} +- +-/* Move the 'tags' iterator to the next tag. +- * +- * If 'tags' is already pointing at the last tag then the iterator +- * will be moved to a point just beyond that last tag, (where +- * notmuch_tags_valid will return FALSE and notmuch_tags_get will +- * return NULL). +- * +- * See the documentation of notmuch_message_get_tags for example code +- * showing how to iterate over a notmuch_tags_t object. +- */ +-func (self *Tags) MoveToNext() { +- if self.tags == nil { +- return +- } +- C.notmuch_tags_move_to_next(self.tags) +-} +- +-/* Destroy a notmuch_tags_t object. +- * +- * It's not strictly necessary to call this function. All memory from +- * the notmuch_tags_t object will be reclaimed when the containing +- * message or query objects are destroyed. +- */ +-func (self *Tags) Destroy() { +- if self.tags == nil { +- return +- } +- C.notmuch_tags_destroy(self.tags) +-} +- +-// TODO: wrap notmuch_directory_ +- +-/* Destroy a notmuch_directory_t object. */ +-func (self *Directory) Destroy() { +- if self.dir == nil { +- return +- } +- C.notmuch_directory_destroy(self.dir) +-} +- +-// TODO: wrap notmuch_filenames_ +- +-/* Destroy a notmuch_filenames_t object. +- * +- * It's not strictly necessary to call this function. All memory from +- * the notmuch_filenames_t object will be reclaimed when the +- * containing directory object is destroyed. +- * +- * It is acceptable to pass NULL for 'filenames', in which case this +- * function will do nothing. +- */ +-func (self *Filenames) Destroy() { +- if self.fnames == nil { +- return +- } +- C.notmuch_filenames_destroy(self.fnames) +-} +-/* EOF */ +diff --git a/bindings/go/src/notmuch-addrlookup/addrlookup.go b/bindings/go/src/notmuch-addrlookup/addrlookup.go +new file mode 100644 +index 0000000..03699fb +--- /dev/null ++++ b/bindings/go/src/notmuch-addrlookup/addrlookup.go +@@ -0,0 +1,263 @@ ++package main ++ ++// stdlib imports ++import "os" ++import "path" ++import "log" ++import "fmt" ++import "regexp" ++import "strings" ++import "sort" ++ ++// 3rd-party imports ++import "notmuch" ++import "github.com/kless/goconfig/config" ++ ++type mail_addr_freq struct { ++ addr string ++ count [3]uint ++} ++ ++type frequencies map[string]uint ++ ++/* Used to sort the email addresses from most to least used */ ++func sort_by_freq(m1, m2 *mail_addr_freq) int { ++ if (m1.count[0] == m2.count[0] && ++ m1.count[1] == m2.count[1] && ++ m1.count[2] == m2.count[2]) { ++ return 0 ++ } ++ ++ if (m1.count[0] > m2.count[0] || ++ m1.count[0] == m2.count[0] && ++ m1.count[1] > m2.count[1] || ++ m1.count[0] == m2.count[0] && ++ m1.count[1] == m2.count[1] && ++ m1.count[2] > m2.count[2]) { ++ return -1 ++ } ++ ++ return 1 ++} ++ ++type maddresses []*mail_addr_freq ++ ++func (self *maddresses) Len() int { ++ return len(*self) ++} ++ ++func (self *maddresses) Less(i,j int) bool { ++ m1 := (*self)[i] ++ m2 := (*self)[j] ++ v := sort_by_freq(m1, m2) ++ if v<=0 { ++ return true ++ } ++ return false ++} ++ ++func (self *maddresses) Swap(i,j int) { ++ (*self)[i], (*self)[j] = (*self)[j], (*self)[i] ++} ++ ++// find most frequent real name for each mail address ++func frequent_fullname(freqs frequencies) string { ++ var maxfreq uint = 0 ++ fullname := "" ++ freqs_sz := len(freqs) ++ ++ for mail,freq := range freqs { ++ if (freq > maxfreq && mail != "") || freqs_sz == 1 { ++ // only use the entry if it has a real name ++ // or if this is the only entry ++ maxfreq = freq ++ fullname = mail ++ } ++ } ++ return fullname ++} ++ ++func addresses_by_frequency(msgs *notmuch.Messages, name string, pass uint, addr_to_realname *map[string]*frequencies) *frequencies { ++ ++ freqs := make(frequencies) ++ ++ pattern := `\s*(("(\.|[^"])*"|[^,])*?)` ++ // pattern := "\\s*((\\\"(\\\\.|[^\\\\\"])*\\\"|[^,])*" + ++ // "\\b\\w+([-+.]\\w+)*\\@\\w+[-\\.\\w]*\\.([-\\.\\w]+)*\\w\\b)>?)" ++ pattern = `.*` + strings.ToLower(name) + `.*` ++ var re *regexp.Regexp = nil ++ var err os.Error = nil ++ if re,err = regexp.Compile(pattern); err != nil { ++ log.Printf("error: %v\n", err) ++ return &freqs ++ } ++ ++ headers := []string{"from"} ++ if pass == 1 { ++ headers = append(headers, "to", "cc", "bcc") ++ } ++ ++ for ;msgs.Valid();msgs.MoveToNext() { ++ msg := msgs.Get() ++ //println("==> msg [", msg.GetMessageId(), "]") ++ for _,header := range headers { ++ froms := strings.ToLower(msg.GetHeader(header)) ++ //println(" froms: ["+froms+"]") ++ for _,from := range strings.Split(froms, ",", -1) { ++ from = strings.Trim(from, " ") ++ match := re.FindString(from) ++ //println(" -> match: ["+match+"]") ++ occ,ok := freqs[match] ++ if !ok { ++ freqs[match] = 0 ++ occ = 0 ++ } ++ freqs[match] = occ+1 ++ } ++ } ++ } ++ return &freqs ++} ++ ++func search_address_passes(queries [3]*notmuch.Query, name string) []string { ++ var val []string ++ addr_freq := make(map[string]*mail_addr_freq) ++ addr_to_realname := make(map[string]*frequencies) ++ ++ var pass uint = 0 // 0-based ++ for _,query := range queries { ++ if query == nil { ++ //println("**warning: idx [",idx,"] contains a nil query") ++ continue ++ } ++ msgs := query.SearchMessages() ++ ht := addresses_by_frequency(msgs, name, pass, &addr_to_realname) ++ for addr, count := range *ht { ++ freq,ok := addr_freq[addr] ++ if !ok { ++ freq = &mail_addr_freq{addr:addr, count:[3]uint{0,0,0}} ++ } ++ freq.count[pass] = count ++ addr_freq[addr] = freq ++ } ++ msgs.Destroy() ++ pass += 1 ++ } ++ ++ addrs := make(maddresses, len(addr_freq)) ++ { ++ iaddr := 0 ++ for _, freq := range addr_freq { ++ addrs[iaddr] = freq ++ iaddr += 1 ++ } ++ } ++ sort.Sort(&addrs) ++ ++ for _,addr := range addrs { ++ freqs,ok := addr_to_realname[addr.addr] ++ if ok { ++ val = append(val, frequent_fullname(*freqs)) ++ } else { ++ val = append(val, addr.addr) ++ } ++ } ++ //println("val:",val) ++ return val ++} ++ ++type address_matcher struct { ++ // the notmuch database ++ db *notmuch.Database ++ // full path of the notmuch database ++ user_db_path string ++ // user primary email ++ user_primary_email string ++ // user tag to mark from addresses as in the address book ++ user_addrbook_tag string ++} ++ ++func new_address_matcher() *address_matcher { ++ var cfg *config.Config ++ var err os.Error ++ ++ // honor NOTMUCH_CONFIG ++ home := os.Getenv("NOTMUCH_CONFIG") ++ if home == "" { ++ home = os.Getenv("HOME") ++ } ++ ++ if cfg,err = config.ReadDefault(path.Join(home, ".notmuch-config")); err != nil { ++ log.Fatalf("error loading config file:",err) ++ } ++ ++ db_path,_ := cfg.String("database", "path") ++ primary_email,_ := cfg.String("user", "primary_email") ++ addrbook_tag,err := cfg.String("user", "addrbook_tag") ++ if err != nil { ++ addrbook_tag = "addressbook" ++ } ++ ++ self := &address_matcher{db:nil, ++ user_db_path:db_path, ++ user_primary_email:primary_email, ++ user_addrbook_tag:addrbook_tag} ++ return self ++} ++ ++func (self *address_matcher) run(name string) { ++ queries := [3]*notmuch.Query{} ++ ++ // open the database ++ if db, status := notmuch.OpenDatabase(self.user_db_path, ++ notmuch.DATABASE_MODE_READ_ONLY); status == notmuch.STATUS_SUCCESS { ++ self.db = db ++ } else { ++ log.Fatalf("Failed to open the database: %v\n", status) ++ } ++ ++ // pass 1: look at all from: addresses with the address book tag ++ query := "tag:" + self.user_addrbook_tag ++ if name != "" { ++ query = query + " and from:" + name + "*" ++ } ++ queries[0] = self.db.CreateQuery(query) ++ ++ // pass 2: look at all to: addresses sent from our primary mail ++ query = "" ++ if name != "" { ++ query = "to:"+name+"*" ++ } ++ if self.user_primary_email != "" { ++ query = query + " from:" + self.user_primary_email ++ } ++ queries[1] = self.db.CreateQuery(query) ++ ++ // if that leads only to a few hits, we check every from too ++ if queries[0].CountMessages() + queries[1].CountMessages() < 10 { ++ query = "" ++ if name != "" { ++ query = "from:"+name+"*" ++ } ++ queries[2] = self.db.CreateQuery(query) ++ } ++ ++ // actually retrieve and sort addresses ++ results := search_address_passes(queries, name) ++ for _,v := range results { ++ if v != "" && v != "\n" { ++ fmt.Println(v) ++ } ++ } ++ return ++} ++ ++func main() { ++ //fmt.Println("args:",os.Args) ++ app := new_address_matcher() ++ name := "" ++ if len(os.Args) > 1 { ++ name = os.Args[1] ++ } ++ app.run(name) ++} +\ No newline at end of file +diff --git a/bindings/go/src/notmuch/notmuch.go b/bindings/go/src/notmuch/notmuch.go +new file mode 100644 +index 0000000..8faf3bb +--- /dev/null ++++ b/bindings/go/src/notmuch/notmuch.go +@@ -0,0 +1,1125 @@ ++// Wrapper around the notmuch library ++ ++package notmuch ++ ++/* ++#include ++#include ++#include ++#include "notmuch.h" ++*/ ++import "C" ++import "unsafe" ++ ++// Status codes used for the return values of most functions ++type Status C.notmuch_status_t ++const ( ++ STATUS_SUCCESS Status = iota ++ STATUS_OUT_OF_MEMORY ++ STATUS_READ_ONLY_DATABASE ++ STATUS_XAPIAN_EXCEPTION ++ STATUS_FILE_ERROR ++ STATUS_FILE_NOT_EMAIL ++ STATUS_DUPLICATE_MESSAGE_ID ++ STATUS_NULL_POINTER ++ STATUS_TAG_TOO_LONG ++ STATUS_UNBALANCED_FREEZE_THAW ++ STATUS_UNBALANCED_ATOMIC ++ ++ STATUS_LAST_STATUS ++) ++ ++func (self Status) String() string { ++ var p *C.char ++ ++ // p is read-only ++ p = C.notmuch_status_to_string(C.notmuch_status_t(self)) ++ if p != nil { ++ s := C.GoString(p) ++ return s ++ } ++ return "" ++} ++ ++/* Various opaque data types. For each notmuch__t see the various ++ * notmuch_ functions below. */ ++ ++type Database struct { ++ db *C.notmuch_database_t ++} ++ ++type Query struct { ++ query *C.notmuch_query_t ++} ++ ++type Threads struct { ++ threads *C.notmuch_threads_t ++} ++ ++type Thread struct { ++ thread *C.notmuch_thread_t ++} ++ ++type Messages struct { ++ messages *C.notmuch_messages_t ++} ++ ++type Message struct { ++ message *C.notmuch_message_t ++} ++ ++type Tags struct { ++ tags *C.notmuch_tags_t ++} ++ ++type Directory struct { ++ dir *C.notmuch_directory_t ++} ++ ++type Filenames struct { ++ fnames *C.notmuch_filenames_t ++} ++ ++type DatabaseMode C.notmuch_database_mode_t ++const ( ++ DATABASE_MODE_READ_ONLY DatabaseMode = 0 ++ DATABASE_MODE_READ_WRITE ++) ++ ++// Create a new, empty notmuch database located at 'path' ++func NewDatabase(path string) (*Database, Status) { ++ ++ var c_path *C.char = C.CString(path) ++ defer C.free(unsafe.Pointer(c_path)) ++ ++ if c_path == nil { ++ return nil, STATUS_OUT_OF_MEMORY ++ } ++ ++ self := &Database{db:nil} ++ st := Status(C.notmuch_database_create(c_path, &self.db)) ++ if st != STATUS_SUCCESS { ++ return nil, st ++ } ++ return self, st ++} ++ ++/* Open an existing notmuch database located at 'path'. ++ * ++ * The database should have been created at some time in the past, ++ * (not necessarily by this process), by calling ++ * notmuch_database_create with 'path'. By default the database should be ++ * opened for reading only. In order to write to the database you need to ++ * pass the NOTMUCH_DATABASE_MODE_READ_WRITE mode. ++ * ++ * An existing notmuch database can be identified by the presence of a ++ * directory named ".notmuch" below 'path'. ++ * ++ * The caller should call notmuch_database_destroy when finished with ++ * this database. ++ * ++ * In case of any failure, this function returns NULL, (after printing ++ * an error message on stderr). ++ */ ++func OpenDatabase(path string, mode DatabaseMode) (*Database, Status) { ++ ++ var c_path *C.char = C.CString(path) ++ defer C.free(unsafe.Pointer(c_path)) ++ ++ if c_path == nil { ++ return nil, STATUS_OUT_OF_MEMORY ++ } ++ ++ self := &Database{db:nil} ++ st := Status(C.notmuch_database_open(c_path, C.notmuch_database_mode_t(mode), &self.db)) ++ if st != STATUS_SUCCESS { ++ return nil, st ++ } ++ return self, st ++} ++ ++/* Close the given notmuch database, freeing all associated ++ * resources. See notmuch_database_open. */ ++func (self *Database) Close() { ++ C.notmuch_database_destroy(self.db) ++} ++ ++/* Return the database path of the given database. ++ */ ++func (self *Database) GetPath() string { ++ ++ /* The return value is a string owned by notmuch so should not be ++ * modified nor freed by the caller. */ ++ var p *C.char = C.notmuch_database_get_path(self.db) ++ if p != nil { ++ s := C.GoString(p) ++ return s ++ } ++ return "" ++} ++ ++/* Return the database format version of the given database. */ ++func (self *Database) GetVersion() uint { ++ return uint(C.notmuch_database_get_version(self.db)) ++} ++ ++/* Does this database need to be upgraded before writing to it? ++ * ++ * If this function returns TRUE then no functions that modify the ++ * database (notmuch_database_add_message, notmuch_message_add_tag, ++ * notmuch_directory_set_mtime, etc.) will work unless the function ++ * notmuch_database_upgrade is called successfully first. */ ++func (self *Database) NeedsUpgrade() bool { ++ do_upgrade := C.notmuch_database_needs_upgrade(self.db) ++ if do_upgrade == 0 { ++ return false ++ } ++ return true ++} ++ ++// TODO: notmuch_database_upgrade ++ ++ ++/* Retrieve a directory object from the database for 'path'. ++ * ++ * Here, 'path' should be a path relative to the path of 'database' ++ * (see notmuch_database_get_path), or else should be an absolute path ++ * with initial components that match the path of 'database'. ++ * ++ * Can return NULL if a Xapian exception occurs. ++ */ ++func (self *Database) GetDirectory(path string) *Directory { ++ var c_path *C.char = C.CString(path) ++ defer C.free(unsafe.Pointer(c_path)) ++ ++ if c_path == nil { ++ return nil ++ } ++ ++ c_dir := C.notmuch_database_get_directory(self.db, c_path) ++ if c_dir == nil { ++ return nil ++ } ++ return &Directory{dir:c_dir} ++} ++ ++/* Add a new message to the given notmuch database. ++ * ++ * Here,'filename' should be a path relative to the path of ++ * 'database' (see notmuch_database_get_path), or else should be an ++ * absolute filename with initial components that match the path of ++ * 'database'. ++ * ++ * The file should be a single mail message (not a multi-message mbox) ++ * that is expected to remain at its current location, (since the ++ * notmuch database will reference the filename, and will not copy the ++ * entire contents of the file. ++ * ++ * If 'message' is not NULL, then, on successful return '*message' ++ * will be initialized to a message object that can be used for things ++ * such as adding tags to the just-added message. The user should call ++ * notmuch_message_destroy when done with the message. On any failure ++ * '*message' will be set to NULL. ++ * ++ * Return value: ++ * ++ * NOTMUCH_STATUS_SUCCESS: Message successfully added to database. ++ * ++ * NOTMUCH_STATUS_XAPIAN_EXCEPTION: A Xapian exception occurred, ++ * message not added. ++ * ++ * NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID: Message has the same message ++ * ID as another message already in the database. The new ++ * filename was successfully added to the message in the database ++ * (if not already present). ++ * ++ * NOTMUCH_STATUS_FILE_ERROR: an error occurred trying to open the ++ * file, (such as permission denied, or file not found, ++ * etc.). Nothing added to the database. ++ * ++ * NOTMUCH_STATUS_FILE_NOT_EMAIL: the contents of filename don't look ++ * like an email message. Nothing added to the database. ++ * ++ * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only ++ * mode so no message can be added. ++ */ ++func ++(self *Database) AddMessage(fname string) (*Message, Status) { ++ var c_fname *C.char = C.CString(fname) ++ defer C.free(unsafe.Pointer(c_fname)) ++ ++ if c_fname == nil { ++ return nil, STATUS_OUT_OF_MEMORY ++ } ++ ++ var c_msg *C.notmuch_message_t = new(C.notmuch_message_t) ++ st := Status(C.notmuch_database_add_message(self.db, c_fname, &c_msg)) ++ ++ return &Message{message:c_msg}, st ++} ++ ++/* Remove a message from the given notmuch database. ++ * ++ * Note that only this particular filename association is removed from ++ * the database. If the same message (as determined by the message ID) ++ * is still available via other filenames, then the message will ++ * persist in the database for those filenames. When the last filename ++ * is removed for a particular message, the database content for that ++ * message will be entirely removed. ++ * ++ * Return value: ++ * ++ * NOTMUCH_STATUS_SUCCESS: The last filename was removed and the ++ * message was removed from the database. ++ * ++ * NOTMUCH_STATUS_XAPIAN_EXCEPTION: A Xapian exception occurred, ++ * message not removed. ++ * ++ * NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID: This filename was removed but ++ * the message persists in the database with at least one other ++ * filename. ++ * ++ * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only ++ * mode so no message can be removed. ++ */ ++func (self *Database) RemoveMessage(fname string) Status { ++ ++ var c_fname *C.char = C.CString(fname) ++ defer C.free(unsafe.Pointer(c_fname)) ++ ++ if c_fname == nil { ++ return STATUS_OUT_OF_MEMORY ++ } ++ ++ st := C.notmuch_database_remove_message(self.db, c_fname) ++ return Status(st) ++} ++ ++/* Find a message with the given message_id. ++ * ++ * If the database contains a message with the given message_id, then ++ * a new notmuch_message_t object is returned. The caller should call ++ * notmuch_message_destroy when done with the message. ++ * ++ * This function returns NULL in the following situations: ++ * ++ * * No message is found with the given message_id ++ * * An out-of-memory situation occurs ++ * * A Xapian exception occurs ++ */ ++func (self *Database) FindMessage(message_id string) (*Message, Status) { ++ ++ var c_msg_id *C.char = C.CString(message_id) ++ defer C.free(unsafe.Pointer(c_msg_id)) ++ ++ if c_msg_id == nil { ++ return nil, STATUS_OUT_OF_MEMORY ++ } ++ ++ msg := &Message{message:nil} ++ st := Status(C.notmuch_database_find_message(self.db, c_msg_id, &msg.message)) ++ if st != STATUS_SUCCESS { ++ return nil, st ++ } ++ return msg, st ++} ++ ++/* Return a list of all tags found in the database. ++ * ++ * This function creates a list of all tags found in the database. The ++ * resulting list contains all tags from all messages found in the database. ++ * ++ * On error this function returns NULL. ++ */ ++func (self *Database) GetAllTags() *Tags { ++ tags := C.notmuch_database_get_all_tags(self.db) ++ if tags == nil { ++ return nil ++ } ++ return &Tags{tags:tags} ++} ++ ++/* Create a new query for 'database'. ++ * ++ * Here, 'database' should be an open database, (see ++ * notmuch_database_open and notmuch_database_create). ++ * ++ * For the query string, we'll document the syntax here more ++ * completely in the future, but it's likely to be a specialized ++ * version of the general Xapian query syntax: ++ * ++ * http://xapian.org/docs/queryparser.html ++ * ++ * As a special case, passing either a length-zero string, (that is ""), ++ * or a string consisting of a single asterisk (that is "*"), will ++ * result in a query that returns all messages in the database. ++ * ++ * See notmuch_query_set_sort for controlling the order of results. ++ * See notmuch_query_search_messages and notmuch_query_search_threads ++ * to actually execute the query. ++ * ++ * User should call notmuch_query_destroy when finished with this ++ * query. ++ * ++ * Will return NULL if insufficient memory is available. ++ */ ++func (self *Database) CreateQuery(query string) *Query { ++ ++ var c_query *C.char = C.CString(query) ++ defer C.free(unsafe.Pointer(c_query)) ++ ++ if c_query == nil { ++ return nil ++ } ++ ++ q := C.notmuch_query_create(self.db, c_query) ++ if q == nil { ++ return nil ++ } ++ return &Query{query:q} ++} ++ ++/* Sort values for notmuch_query_set_sort */ ++type Sort C.notmuch_sort_t ++const ( ++ SORT_OLDEST_FIRST Sort = 0 ++ SORT_NEWEST_FIRST ++ SORT_MESSAGE_ID ++ SORT_UNSORTED ++) ++ ++/* Return the query_string of this query. See notmuch_query_create. */ ++func (self *Query) String() string { ++ // FIXME: do we own 'q' or not ? ++ q := C.notmuch_query_get_query_string(self.query) ++ //defer C.free(unsafe.Pointer(q)) ++ ++ if q != nil { ++ s := C.GoString(q) ++ return s ++ } ++ ++ return "" ++} ++ ++/* Specify the sorting desired for this query. */ ++func (self *Query) SetSort(sort Sort) { ++ C.notmuch_query_set_sort(self.query, C.notmuch_sort_t(sort)) ++} ++ ++/* Return the sort specified for this query. See notmuch_query_set_sort. */ ++func (self *Query) GetSort() Sort { ++ return Sort(C.notmuch_query_get_sort(self.query)) ++} ++ ++/* Execute a query for threads, returning a notmuch_threads_t object ++ * which can be used to iterate over the results. The returned threads ++ * object is owned by the query and as such, will only be valid until ++ * notmuch_query_destroy. ++ * ++ * Typical usage might be: ++ * ++ * notmuch_query_t *query; ++ * notmuch_threads_t *threads; ++ * notmuch_thread_t *thread; ++ * ++ * query = notmuch_query_create (database, query_string); ++ * ++ * for (threads = notmuch_query_search_threads (query); ++ * notmuch_threads_valid (threads); ++ * notmuch_threads_move_to_next (threads)) ++ * { ++ * thread = notmuch_threads_get (threads); ++ * .... ++ * notmuch_thread_destroy (thread); ++ * } ++ * ++ * notmuch_query_destroy (query); ++ * ++ * Note: If you are finished with a thread before its containing ++ * query, you can call notmuch_thread_destroy to clean up some memory ++ * sooner (as in the above example). Otherwise, if your thread objects ++ * are long-lived, then you don't need to call notmuch_thread_destroy ++ * and all the memory will still be reclaimed when the query is ++ * destroyed. ++ * ++ * Note that there's no explicit destructor needed for the ++ * notmuch_threads_t object. (For consistency, we do provide a ++ * notmuch_threads_destroy function, but there's no good reason ++ * to call it if the query is about to be destroyed). ++ * ++ * If a Xapian exception occurs this function will return NULL. ++ */ ++func (self *Query) SearchThreads() *Threads { ++ threads := C.notmuch_query_search_threads(self.query) ++ if threads == nil { ++ return nil ++ } ++ return &Threads{threads:threads} ++} ++ ++/* Execute a query for messages, returning a notmuch_messages_t object ++ * which can be used to iterate over the results. The returned ++ * messages object is owned by the query and as such, will only be ++ * valid until notmuch_query_destroy. ++ * ++ * Typical usage might be: ++ * ++ * notmuch_query_t *query; ++ * notmuch_messages_t *messages; ++ * notmuch_message_t *message; ++ * ++ * query = notmuch_query_create (database, query_string); ++ * ++ * for (messages = notmuch_query_search_messages (query); ++ * notmuch_messages_valid (messages); ++ * notmuch_messages_move_to_next (messages)) ++ * { ++ * message = notmuch_messages_get (messages); ++ * .... ++ * notmuch_message_destroy (message); ++ * } ++ * ++ * notmuch_query_destroy (query); ++ * ++ * Note: If you are finished with a message before its containing ++ * query, you can call notmuch_message_destroy to clean up some memory ++ * sooner (as in the above example). Otherwise, if your message ++ * objects are long-lived, then you don't need to call ++ * notmuch_message_destroy and all the memory will still be reclaimed ++ * when the query is destroyed. ++ * ++ * Note that there's no explicit destructor needed for the ++ * notmuch_messages_t object. (For consistency, we do provide a ++ * notmuch_messages_destroy function, but there's no good ++ * reason to call it if the query is about to be destroyed). ++ * ++ * If a Xapian exception occurs this function will return NULL. ++ */ ++func (self *Query) SearchMessages() *Messages { ++ msgs := C.notmuch_query_search_messages(self.query) ++ if msgs == nil { ++ return nil ++ } ++ return &Messages{messages:msgs} ++} ++ ++/* Destroy a notmuch_query_t along with any associated resources. ++ * ++ * This will in turn destroy any notmuch_threads_t and ++ * notmuch_messages_t objects generated by this query, (and in ++ * turn any notmuch_thread_t and notmuch_message_t objects generated ++ * from those results, etc.), if such objects haven't already been ++ * destroyed. ++ */ ++func (self *Query) Destroy() { ++ if self.query != nil { ++ C.notmuch_query_destroy(self.query) ++ } ++} ++ ++/* Return an estimate of the number of messages matching a search ++ * ++ * This function performs a search and returns Xapian's best ++ * guess as to number of matching messages. ++ * ++ * If a Xapian exception occurs, this function may return 0 (after ++ * printing a message). ++ */ ++func (self *Query) CountMessages() uint { ++ return uint(C.notmuch_query_count_messages(self.query)) ++} ++ ++// TODO: wrap threads and thread ++ ++/* Is the given 'threads' iterator pointing at a valid thread. ++ * ++ * When this function returns TRUE, notmuch_threads_get will return a ++ * valid object. Whereas when this function returns FALSE, ++ * notmuch_threads_get will return NULL. ++ * ++ * See the documentation of notmuch_query_search_threads for example ++ * code showing how to iterate over a notmuch_threads_t object. ++ */ ++func (self *Threads) Valid() bool { ++ if self.threads == nil { ++ return false ++ } ++ valid := C.notmuch_threads_valid(self.threads) ++ if valid == 0 { ++ return false ++ } ++ return true ++} ++ ++/* Destroy a notmuch_threads_t object. ++ * ++ * It's not strictly necessary to call this function. All memory from ++ * the notmuch_threads_t object will be reclaimed when the ++ * containg query object is destroyed. ++ */ ++func (self *Threads) Destroy() { ++ if self.threads != nil { ++ C.notmuch_threads_destroy(self.threads) ++ } ++} ++ ++/* Is the given 'messages' iterator pointing at a valid message. ++ * ++ * When this function returns TRUE, notmuch_messages_get will return a ++ * valid object. Whereas when this function returns FALSE, ++ * notmuch_messages_get will return NULL. ++ * ++ * See the documentation of notmuch_query_search_messages for example ++ * code showing how to iterate over a notmuch_messages_t object. ++ */ ++func (self *Messages) Valid() bool { ++ if self.messages == nil { ++ return false ++ } ++ valid := C.notmuch_messages_valid(self.messages) ++ if valid == 0 { ++ return false ++ } ++ return true ++} ++ ++/* Get the current message from 'messages' as a notmuch_message_t. ++ * ++ * Note: The returned message belongs to 'messages' and has a lifetime ++ * identical to it (and the query to which it belongs). ++ * ++ * See the documentation of notmuch_query_search_messages for example ++ * code showing how to iterate over a notmuch_messages_t object. ++ * ++ * If an out-of-memory situation occurs, this function will return ++ * NULL. ++ */ ++func (self *Messages) Get() *Message { ++ if self.messages == nil { ++ return nil ++ } ++ msg := C.notmuch_messages_get(self.messages) ++ if msg == nil { ++ return nil ++ } ++ return &Message{message:msg} ++} ++ ++/* Move the 'messages' iterator to the next message. ++ * ++ * If 'messages' is already pointing at the last message then the ++ * iterator will be moved to a point just beyond that last message, ++ * (where notmuch_messages_valid will return FALSE and ++ * notmuch_messages_get will return NULL). ++ * ++ * See the documentation of notmuch_query_search_messages for example ++ * code showing how to iterate over a notmuch_messages_t object. ++ */ ++func (self *Messages) MoveToNext() { ++ if self.messages == nil { ++ return ++ } ++ C.notmuch_messages_move_to_next(self.messages) ++} ++ ++/* Destroy a notmuch_messages_t object. ++ * ++ * It's not strictly necessary to call this function. All memory from ++ * the notmuch_messages_t object will be reclaimed when the containing ++ * query object is destroyed. ++ */ ++func (self *Messages) Destroy() { ++ if self.messages != nil { ++ C.notmuch_messages_destroy(self.messages) ++ } ++} ++ ++/* Return a list of tags from all messages. ++ * ++ * The resulting list is guaranteed not to contain duplicated tags. ++ * ++ * WARNING: You can no longer iterate over messages after calling this ++ * function, because the iterator will point at the end of the list. ++ * We do not have a function to reset the iterator yet and the only ++ * way how you can iterate over the list again is to recreate the ++ * message list. ++ * ++ * The function returns NULL on error. ++ */ ++func (self *Messages) CollectTags() *Tags { ++ if self.messages == nil { ++ return nil ++ } ++ tags := C.notmuch_messages_collect_tags(self.messages) ++ if tags == nil { ++ return nil ++ } ++ return &Tags{tags:tags} ++} ++ ++/* Get the message ID of 'message'. ++ * ++ * The returned string belongs to 'message' and as such, should not be ++ * modified by the caller and will only be valid for as long as the ++ * message is valid, (which is until the query from which it derived ++ * is destroyed). ++ * ++ * This function will not return NULL since Notmuch ensures that every ++ * message has a unique message ID, (Notmuch will generate an ID for a ++ * message if the original file does not contain one). ++ */ ++func (self *Message) GetMessageId() string { ++ ++ if self.message == nil { ++ return "" ++ } ++ id := C.notmuch_message_get_message_id(self.message) ++ // we dont own id ++ // defer C.free(unsafe.Pointer(id)) ++ if id == nil { ++ return "" ++ } ++ return C.GoString(id) ++} ++ ++/* Get the thread ID of 'message'. ++ * ++ * The returned string belongs to 'message' and as such, should not be ++ * modified by the caller and will only be valid for as long as the ++ * message is valid, (for example, until the user calls ++ * notmuch_message_destroy on 'message' or until a query from which it ++ * derived is destroyed). ++ * ++ * This function will not return NULL since Notmuch ensures that every ++ * message belongs to a single thread. ++ */ ++func (self *Message) GetThreadId() string { ++ ++ if self.message == nil { ++ return "" ++ } ++ id := C.notmuch_message_get_thread_id(self.message) ++ // we dont own id ++ // defer C.free(unsafe.Pointer(id)) ++ ++ if id == nil { ++ return "" ++ } ++ ++ return C.GoString(id) ++} ++ ++/* Get a notmuch_messages_t iterator for all of the replies to ++ * 'message'. ++ * ++ * Note: This call only makes sense if 'message' was ultimately ++ * obtained from a notmuch_thread_t object, (such as by coming ++ * directly from the result of calling notmuch_thread_get_ ++ * toplevel_messages or by any number of subsequent ++ * calls to notmuch_message_get_replies). ++ * ++ * If 'message' was obtained through some non-thread means, (such as ++ * by a call to notmuch_query_search_messages), then this function ++ * will return NULL. ++ * ++ * If there are no replies to 'message', this function will return ++ * NULL. (Note that notmuch_messages_valid will accept that NULL ++ * value as legitimate, and simply return FALSE for it.) ++ */ ++func (self *Message) GetReplies() *Messages { ++ if self.message == nil { ++ return nil ++ } ++ msgs := C.notmuch_message_get_replies(self.message) ++ if msgs == nil { ++ return nil ++ } ++ return &Messages{messages:msgs} ++} ++ ++/* Get a filename for the email corresponding to 'message'. ++ * ++ * The returned filename is an absolute filename, (the initial ++ * component will match notmuch_database_get_path() ). ++ * ++ * The returned string belongs to the message so should not be ++ * modified or freed by the caller (nor should it be referenced after ++ * the message is destroyed). ++ * ++ * Note: If this message corresponds to multiple files in the mail ++ * store, (that is, multiple files contain identical message IDs), ++ * this function will arbitrarily return a single one of those ++ * filenames. ++ */ ++func (self *Message) GetFileName() string { ++ if self.message == nil { ++ return "" ++ } ++ fname := C.notmuch_message_get_filename(self.message) ++ // we dont own fname ++ // defer C.free(unsafe.Pointer(fname)) ++ ++ if fname == nil { ++ return "" ++ } ++ ++ return C.GoString(fname) ++} ++ ++type Flag C.notmuch_message_flag_t ++const ( ++ MESSAGE_FLAG_MATCH Flag = 0 ++) ++ ++/* Get a value of a flag for the email corresponding to 'message'. */ ++func (self *Message) GetFlag(flag Flag) bool { ++ if self.message == nil { ++ return false ++ } ++ v := C.notmuch_message_get_flag(self.message, C.notmuch_message_flag_t(flag)) ++ if v == 0 { ++ return false ++ } ++ return true ++} ++ ++/* Set a value of a flag for the email corresponding to 'message'. */ ++func (self *Message) SetFlag(flag Flag, value bool) { ++ if self.message == nil { ++ return ++ } ++ var v C.notmuch_bool_t = 0 ++ if value { ++ v = 1 ++ } ++ C.notmuch_message_set_flag(self.message, C.notmuch_message_flag_t(flag), v) ++} ++ ++// TODO: wrap notmuch_message_get_date ++ ++/* Get the value of the specified header from 'message'. ++ * ++ * The value will be read from the actual message file, not from the ++ * notmuch database. The header name is case insensitive. ++ * ++ * The returned string belongs to the message so should not be ++ * modified or freed by the caller (nor should it be referenced after ++ * the message is destroyed). ++ * ++ * Returns an empty string ("") if the message does not contain a ++ * header line matching 'header'. Returns NULL if any error occurs. ++ */ ++func (self *Message) GetHeader(header string) string { ++ if self.message == nil { ++ return "" ++ } ++ ++ var c_header *C.char = C.CString(header) ++ defer C.free(unsafe.Pointer(c_header)) ++ ++ /* we dont own value */ ++ value := C.notmuch_message_get_header(self.message, c_header) ++ if value == nil { ++ return "" ++ } ++ ++ return C.GoString(value) ++} ++ ++/* Get the tags for 'message', returning a notmuch_tags_t object which ++ * can be used to iterate over all tags. ++ * ++ * The tags object is owned by the message and as such, will only be ++ * valid for as long as the message is valid, (which is until the ++ * query from which it derived is destroyed). ++ * ++ * Typical usage might be: ++ * ++ * notmuch_message_t *message; ++ * notmuch_tags_t *tags; ++ * const char *tag; ++ * ++ * message = notmuch_database_find_message (database, message_id); ++ * ++ * for (tags = notmuch_message_get_tags (message); ++ * notmuch_tags_valid (tags); ++ * notmuch_result_move_to_next (tags)) ++ * { ++ * tag = notmuch_tags_get (tags); ++ * .... ++ * } ++ * ++ * notmuch_message_destroy (message); ++ * ++ * Note that there's no explicit destructor needed for the ++ * notmuch_tags_t object. (For consistency, we do provide a ++ * notmuch_tags_destroy function, but there's no good reason to call ++ * it if the message is about to be destroyed). ++ */ ++func (self *Message) GetTags() *Tags { ++ if self.message == nil { ++ return nil ++ } ++ tags := C.notmuch_message_get_tags(self.message) ++ if tags == nil { ++ return nil ++ } ++ return &Tags{tags:tags} ++} ++ ++/* The longest possible tag value. */ ++const TAG_MAX = 200 ++ ++/* Add a tag to the given message. ++ * ++ * Return value: ++ * ++ * NOTMUCH_STATUS_SUCCESS: Tag successfully added to message ++ * ++ * NOTMUCH_STATUS_NULL_POINTER: The 'tag' argument is NULL ++ * ++ * NOTMUCH_STATUS_TAG_TOO_LONG: The length of 'tag' is too long ++ * (exceeds NOTMUCH_TAG_MAX) ++ * ++ * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only ++ * mode so message cannot be modified. ++ */ ++func (self *Message) AddTag(tag string) Status { ++ if self.message == nil { ++ return STATUS_NULL_POINTER ++ } ++ c_tag := C.CString(tag) ++ defer C.free(unsafe.Pointer(c_tag)) ++ ++ return Status(C.notmuch_message_add_tag(self.message, c_tag)) ++} ++ ++/* Remove a tag from the given message. ++ * ++ * Return value: ++ * ++ * NOTMUCH_STATUS_SUCCESS: Tag successfully removed from message ++ * ++ * NOTMUCH_STATUS_NULL_POINTER: The 'tag' argument is NULL ++ * ++ * NOTMUCH_STATUS_TAG_TOO_LONG: The length of 'tag' is too long ++ * (exceeds NOTMUCH_TAG_MAX) ++ * ++ * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only ++ * mode so message cannot be modified. ++ */ ++func (self *Message) RemoveTag(tag string) Status { ++ if self.message == nil { ++ return STATUS_NULL_POINTER ++ } ++ c_tag := C.CString(tag) ++ defer C.free(unsafe.Pointer(c_tag)) ++ ++ return Status(C.notmuch_message_remove_tag(self.message, c_tag)) ++} ++ ++/* Remove all tags from the given message. ++ * ++ * See notmuch_message_freeze for an example showing how to safely ++ * replace tag values. ++ * ++ * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only ++ * mode so message cannot be modified. ++ */ ++func (self *Message) RemoveAllTags() Status { ++ if self.message == nil { ++ return STATUS_NULL_POINTER ++ } ++ return Status(C.notmuch_message_remove_all_tags(self.message)) ++} ++ ++/* Freeze the current state of 'message' within the database. ++ * ++ * This means that changes to the message state, (via ++ * notmuch_message_add_tag, notmuch_message_remove_tag, and ++ * notmuch_message_remove_all_tags), will not be committed to the ++ * database until the message is thawed with notmuch_message_thaw. ++ * ++ * Multiple calls to freeze/thaw are valid and these calls will ++ * "stack". That is there must be as many calls to thaw as to freeze ++ * before a message is actually thawed. ++ * ++ * The ability to do freeze/thaw allows for safe transactions to ++ * change tag values. For example, explicitly setting a message to ++ * have a given set of tags might look like this: ++ * ++ * notmuch_message_freeze (message); ++ * ++ * notmuch_message_remove_all_tags (message); ++ * ++ * for (i = 0; i < NUM_TAGS; i++) ++ * notmuch_message_add_tag (message, tags[i]); ++ * ++ * notmuch_message_thaw (message); ++ * ++ * With freeze/thaw used like this, the message in the database is ++ * guaranteed to have either the full set of original tag values, or ++ * the full set of new tag values, but nothing in between. ++ * ++ * Imagine the example above without freeze/thaw and the operation ++ * somehow getting interrupted. This could result in the message being ++ * left with no tags if the interruption happened after ++ * notmuch_message_remove_all_tags but before notmuch_message_add_tag. ++ * ++ * Return value: ++ * ++ * NOTMUCH_STATUS_SUCCESS: Message successfully frozen. ++ * ++ * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only ++ * mode so message cannot be modified. ++ */ ++func (self *Message) Freeze() Status { ++ if self.message == nil { ++ return STATUS_NULL_POINTER ++ } ++ return Status(C.notmuch_message_freeze(self.message)) ++} ++ ++/* Thaw the current 'message', synchronizing any changes that may have ++ * occurred while 'message' was frozen into the notmuch database. ++ * ++ * See notmuch_message_freeze for an example of how to use this ++ * function to safely provide tag changes. ++ * ++ * Multiple calls to freeze/thaw are valid and these calls with ++ * "stack". That is there must be as many calls to thaw as to freeze ++ * before a message is actually thawed. ++ * ++ * Return value: ++ * ++ * NOTMUCH_STATUS_SUCCESS: Message successfully thawed, (or at least ++ * its frozen count has successfully been reduced by 1). ++ * ++ * NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW: An attempt was made to thaw ++ * an unfrozen message. That is, there have been an unbalanced ++ * number of calls to notmuch_message_freeze and ++ * notmuch_message_thaw. ++ */ ++func (self *Message) Thaw() Status { ++ if self.message == nil { ++ return STATUS_NULL_POINTER ++ } ++ ++ return Status(C.notmuch_message_thaw(self.message)) ++} ++ ++/* Destroy a notmuch_message_t object. ++ * ++ * It can be useful to call this function in the case of a single ++ * query object with many messages in the result, (such as iterating ++ * over the entire database). Otherwise, it's fine to never call this ++ * function and there will still be no memory leaks. (The memory from ++ * the messages get reclaimed when the containing query is destroyed.) ++ */ ++func (self *Message) Destroy() { ++ if self.message == nil { ++ return ++ } ++ C.notmuch_message_destroy(self.message) ++} ++ ++/* Is the given 'tags' iterator pointing at a valid tag. ++ * ++ * When this function returns TRUE, notmuch_tags_get will return a ++ * valid string. Whereas when this function returns FALSE, ++ * notmuch_tags_get will return NULL. ++ * ++ * See the documentation of notmuch_message_get_tags for example code ++ * showing how to iterate over a notmuch_tags_t object. ++ */ ++func (self *Tags) Valid() bool { ++ if self.tags == nil { ++ return false ++ } ++ v := C.notmuch_tags_valid(self.tags) ++ if v == 0 { ++ return false ++ } ++ return true ++} ++ ++/* Get the current tag from 'tags' as a string. ++ * ++ * Note: The returned string belongs to 'tags' and has a lifetime ++ * identical to it (and the query to which it ultimately belongs). ++ * ++ * See the documentation of notmuch_message_get_tags for example code ++ * showing how to iterate over a notmuch_tags_t object. ++ */ ++func (self *Tags) Get() string { ++ if self.tags == nil { ++ return "" ++ } ++ s := C.notmuch_tags_get(self.tags) ++ // we dont own 's' ++ ++ return C.GoString(s) ++} ++func (self *Tags) String() string { ++ return self.Get() ++} ++ ++/* Move the 'tags' iterator to the next tag. ++ * ++ * If 'tags' is already pointing at the last tag then the iterator ++ * will be moved to a point just beyond that last tag, (where ++ * notmuch_tags_valid will return FALSE and notmuch_tags_get will ++ * return NULL). ++ * ++ * See the documentation of notmuch_message_get_tags for example code ++ * showing how to iterate over a notmuch_tags_t object. ++ */ ++func (self *Tags) MoveToNext() { ++ if self.tags == nil { ++ return ++ } ++ C.notmuch_tags_move_to_next(self.tags) ++} ++ ++/* Destroy a notmuch_tags_t object. ++ * ++ * It's not strictly necessary to call this function. All memory from ++ * the notmuch_tags_t object will be reclaimed when the containing ++ * message or query objects are destroyed. ++ */ ++func (self *Tags) Destroy() { ++ if self.tags == nil { ++ return ++ } ++ C.notmuch_tags_destroy(self.tags) ++} ++ ++// TODO: wrap notmuch_directory_ ++ ++/* Destroy a notmuch_directory_t object. */ ++func (self *Directory) Destroy() { ++ if self.dir == nil { ++ return ++ } ++ C.notmuch_directory_destroy(self.dir) ++} ++ ++// TODO: wrap notmuch_filenames_ ++ ++/* Destroy a notmuch_filenames_t object. ++ * ++ * It's not strictly necessary to call this function. All memory from ++ * the notmuch_filenames_t object will be reclaimed when the ++ * containing directory object is destroyed. ++ * ++ * It is acceptable to pass NULL for 'filenames', in which case this ++ * function will do nothing. ++ */ ++func (self *Filenames) Destroy() { ++ if self.fnames == nil { ++ return ++ } ++ C.notmuch_filenames_destroy(self.fnames) ++} ++/* EOF */ +-- +1.7.10 + -- 2.26.2