aboutsummaryrefslogtreecommitdiff
path: root/bindings/go/src/notmuch-addrlookup/addrlookup.go
diff options
context:
space:
mode:
authorJustus Winter <4winter@informatik.uni-hamburg.de>2012-05-09 13:15:16 +0200
committerDavid Bremner <bremner@debian.org>2012-05-11 08:32:44 -0300
commit97565b77cdb4c7c2db82f0baa462eeabb17294dc (patch)
tree74dfc12deab8fc3d43b3b9fa840812b375c2674a /bindings/go/src/notmuch-addrlookup/addrlookup.go
parentf83a5c6241db308393d9295aedbcfbeff43a53fd (diff)
go: reorganize the go bindings
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>
Diffstat (limited to 'bindings/go/src/notmuch-addrlookup/addrlookup.go')
-rw-r--r--bindings/go/src/notmuch-addrlookup/addrlookup.go263
1 files changed, 263 insertions, 0 deletions
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*(("(\.|[^"])*"|[^,])*<?(?mail\b\w+([-+.]\w+)*\@\w+[-\.\w]*\.([-\.\w]+)*\w\b)>?)`
+ // pattern := "\\s*((\\\"(\\\\.|[^\\\\\"])*\\\"|[^,])*" +
+ // "<?(?P<mail>\\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