]> git.notmuchmail.org Git - notmuch/blobdiff - bindings/go/src/notmuch-addrlookup/addrlookup.go
go: reorganize the go bindings
[notmuch] / bindings / go / src / notmuch-addrlookup / addrlookup.go
diff --git a/bindings/go/src/notmuch-addrlookup/addrlookup.go b/bindings/go/src/notmuch-addrlookup/addrlookup.go
new file mode 100644 (file)
index 0000000..03699fb
--- /dev/null
@@ -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