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())