On May 03, 2011 at 08:47 AM +0200, Sebastian Tramp wrote:
Sounds interesting. do you think, notmuch is faster than mairix (not that
I have a problem with mairix speed, just for our information)

It's been a while since I've used mairix. I forget exactly how long it took to index things initially. I do remember however that I would only reindex to look for new mail once or twice a day because it took a fair amount of time and cpu power.

I'm currently indexing about 260,000 messages across many maildirs. For unimportant reasons, I just reindexed them all from scratch last night. It took 1 hour and 10 minutes. I did this while I was video chatting a friend. This was an 'initial' index. Subsequent indexing, looking for newly delivered messages, takes between 20-40 seconds, so I can run it every time I check for mail. Which I do - I have it in my getmail script. You can run it any time you want with the command `notmuch new`.

The other thing that I liked about notmuch infinitely more than mairix, and a fair bit more than mu, is that the search syntax just feels more natural to me. Your mileage may vary.

Can you share your config parts regarding notmuch / mutt integration?
(or do you use it via emacs?)

I don't use it via emacs. The mutt integration is very similar to what you'd do with mairix or mu. A couple of bindings that just run the command line 'notmuch' program with your search terms following. You do need to do a bit of command line piping to turn the filenames that notmuch outputs as search results into symbolic links. Then you jump to a maildir with the results.

Though you can run it directly like this, I wrote a little python script that I call instead which gives me readline search history and cleans out the search results automatically when I make a new search. I've attached the script 'notmuch-mutt.py'. I started building in support for notmuch's tagging features but kind of lost interest. You could also write an option if you want to *not* clear out the previous search results; then you could build up results incrementally if you desired.

I have a couple bindings defined. I'll try to list them below; hopefully they don't get too jumbled up since they are kind of long (I don't break up my bindings definitions into separate lines in my muttrc - hopefully the \'s I put in break things appropriately).

This one just changes to the search results folder:
    macro index,pager .r "<change-folder-readonly> \
~/.notmuchmutt/search <enter>"
This on runs a search:
    macro index,pager .s "<enter-command>unset wait_key<enter> \
      <shell-escape>~/bin/notmuch-mutt.py -p<enter> \
      <change-folder-readonly>~/.notmuchmutt/search<enter> \
      <enter-command>set wait_key<enter>"

This on runs a search and includes the full thread in the results. You can also run it on a search result to reconstruct the thread from the message of interest.
    macro index,pager .t "<enter-command>unset wait_key<enter> \
      <pipe-message>~/bin/notmuch-mutt.py - --thread<enter> \
      <change-folder-readonly>~/.notmuchmutt/search<enter> \
      <enter-command>set wait_key<enter>"

This runs muttjump on the current message (see below). I don't do all the screen stuff that muttjump can do.
    macro generic .o "<enter-command>push <pipe-message>muttjump \
      <enter><enter>" "jump to original message"

notmuch also has python bindings, so you can access the library directly. For some reason on OS X, they don't work right for me, so my python script just calls the command line utility directly.

I also use the awesome 'muttjump' script [1]. It takes you to the parent mailbox of a message in the search results.

Hopefully I didn't forget any important bits. The actual notmuch config file is very simple as there aren't a lot of relevant options. You just set the directory to your maildir, and define your user name and address (I think this is used only if you are running it in emacs or vim as a full mail client). Lastly, you can set which tags get set for new messages and whether or not you want imap flags (read, flagged, etc.) that are set in notmuch synchronized back to the maildir files. Again, the last option is mostly for the full notmuch client.

[1]: https://github.com/weisslj/muttjump
#!/usr/bin/env python

__author__ = "Tim Gray"
__version__ = "1.0"

import sys
import os
import optparse
import subprocess as sb
import shlex
import email
import readline

cfgdir = '~/.notmuchmutt'
cfgdir = os.path.expanduser(cfgdir)
searchdir = os.path.join(cfgdir, "search/")
searchhist = os.path.join(cfgdir, 'search-history')
taghist = os.path.join(cfgdir, 'tag-history')
if os.path.isdir(cfgdir):
        cfgFlag = True
else:
        print 'must run with --config first to set up directory'
        sys.exit(10)

class notmuch():
        def __init__(self):
                self.nm = '/usr/local/bin/notmuch'

        def runCmd(self, query):
                """Runs the given command."""
                cmd = " ".join([self.nm, self.cmd, self.output, query])
                args = shlex.split(cmd)
#               print args
                t = sb.Popen(args, stdout = sb.PIPE) 
                out = t.stdout.readlines()
                return out

        def search(self, query, op = 'files'):
                self.cmd = "search"
                self.output = '--output=' + op
                o = self.runCmd(query)
                o = ''.join(o)
                o = o.split('\n')
                return o

        def tags(self, tags, query, prefix = ''):
                tags = [prefix + tag for tag in tags]
                self.cmd = 'tag ' + ' '.join(tags)
                self.output = ''
                o = self.runCmd(query)
                return o
                
def makeLinks(results, outdir):
        oldshit = os.listdir(outdir)
        for o in oldshit:               
                os.remove(os.path.join(outdir,o))
        if results == ['']:
                return
        for r in results:
                ofn = os.path.basename(r)
                os.symlink(r, os.path.join(outdir, ofn))
        
def openAnything(source):
        if source == "-":    
                import sys
                return sys.stdin
        else:
                return open(source, 'r')

def findThread(mid):
        nm = notmuch()
        r = nm.search(mid, 'threads')
        o = nm.search(r[0])
        return o

def findSearch(args):
        query = " ".join(args)
        nm = notmuch()
        o = nm.search(query)
        return o

def tags(ts, mid):      
        nm = notmuch()
        r = nm.search(mid, 'threads')
        o = nm.tags(ts, r[0])

def tags2(prefix, ts, mid):

        nm = notmuch()
        r = nm.search(mid, 'threads')
        o = nm.tags(ts, r[0], prefix)


def getMsg(input):
        fn = openAnything(input)

        lines = fn.readlines()
        fn.close()
        lines = ''.join(lines)
        msg = email.message_from_string(lines)
        
        mid = msg['Message-ID']
        mm = 'id:'+mid.translate(None, "<>")
        return msg, mm

def loadHist(histfile):
        try:
                readline.read_history_file(histfile)
        except IOError:
                pass

def saveHist(histfile):
        try:
                readline.write_history_file(histfile)
                readline.set_history_length(200)
        except IOError:
                pass

def main(argv=None):
        
        if argv is None:
                argv = sys.argv
        programName = os.path.basename(argv[0])

        version = "%prog " +  "%s" % (__version__)
        usage = "%prog [options] query"
        
        parser = optparse.OptionParser(version = version, usage = usage)
        
        parser.add_option('-s', "--search",  help="search for term", 
dest="search", action="store_true", default=False)
        
        parser.add_option("--thread",  help="find thread of message", 
dest="thread", action="store_true", default=False)

        parser.add_option('-t', "--tags",  help="add/delete tags to message.  
Interactive.", dest="tags", action="store_true", default=False)
        
        parser.add_option('-a', "--add-tag",  help="add tag to message. 
separate by commas, no spaces.", dest="addtag", action="store", default=None)

        parser.add_option('-d', "--delete-tag",  help="delete tags to message. 
separate by commas, no spaces.", dest="removetag", action="store", 
default=None) 
        
        parser.add_option('-p', "--prompt",  help="prompt input", 
dest="prompt", action="store_true", default=False)

        parser.add_option("--config",  help="runs config - makes a settings 
director '.notmuchmutt'", dest="config", action="store_true", default=False)

        opts, args = parser.parse_args()

        outdir = os.path.join(searchdir, 'cur')
                
        if opts.config:
                try:
                        if not os.path.exists(cfgdir):
                                os.makedirs(cfgdir)
                        if not os.path.exists(outdir):
                                os.makedirs(outdir)
                        if not os.path.exists(os.path.join(searchdir, 'new/')):
                                os.makedirs(os.path.join(searchdir, 'new/'))
                        if not os.path.exists(os.path.join(searchdir, 'tmp/')):
                                os.makedirs(os.path.join(searchdir, 'tmp/'))
                except:
                        print 'unable to make settings directory'
                        return 9
                return 0

        if opts.addtag:
                t = opts.addtag.split(',')
                msg, mid = getMsg(args[0])
                tags2('+', t, mid)
                return 0

        if opts.removetag:
                t = opts.removetag.split(',')
                msg, mid = getMsg(args[0])
                tags2('-', t, mid)
                return 0

        if opts.tags:
                loadHist(taghist)       
                t = raw_input("(+/-) add/remove tags: ")
                saveHist(taghist)
                msg, mid = getMsg(args[0])
                tags(t, mid)
                return 0
                
        if opts.thread:
                msg, mid = getMsg(args[0])
                o = findThread(mid)
                makeLinks(o, outdir)
                return 0

        if opts.prompt:
                loadHist(searchhist)
                args = raw_input("search for: ")
                saveHist(searchhist)
                args = args.split(' ')

        o = findSearch(args)
        makeLinks(o, outdir)

        return 0


if __name__ == "__main__":
    sys.exit(main())

Reply via email to