def collect_tags(self, querystring): """returns tags of messages that match `querystring`""" db = Database(path=self.path, mode=Database.MODE.READ_ONLY) tagset = notmuch2._tags.ImmutableTagSet( db.messages(querystring, exclude_tags=self.exclude_tags), '_iter_p', notmuch2.capi.lib.notmuch_messages_collect_tags) return [t for t in tagset]
def GET(self, terms): redir = False if web.input(terms=None).terms: redir = True terms = web.input().terms terms = unquote_plus(terms) if web.input(afters=None).afters: afters = web.input(afters=None).afters[:-3] else: afters = '0' if web.input(befores=None).befores: befores = web.input(befores=None).befores else: befores = '4294967296' # 2^32 try: if int(afters) > 0 or int(befores) < 4294967296: redir = True terms += ' date:@%s..@%s' % (int(afters), int(befores)) except ValueError: pass if redir: raise web.seeother('/search/%s' % quote_plus(terms.encode('utf8'))) web.header('Content-type', 'text/html') db = Database() ts = db.threads(query=terms, sort=Database.SORT.NEWEST_FIRST) template = env.get_template('search.html') return template.generate(terms=terms, ts=ts, title=terms, prefix=prefix, sprefix=webprefix)
def GET(self, mid): web.header('Content-type', 'text/html') db = Database() try: m = db.find(mid) except: raise web.notfound("No such message id.") template = env.get_template('show.html') # FIXME add reply-all link with email.urils.getaddresses # FIXME add forward link using mailto with body parameter? return template.render(m=m, mid=mid, title=m.header('Subject'), prefix=prefix, sprefix=webprefix)
def get_all_tags(self): """ returns all tagsstrings used in the database :rtype: list of str """ db = Database(path=self.path, mode=Database.MODE.READ_ONLY) return [t for t in db.tags]
def _with_notmuch_thread(self, tid): """returns :class:`notmuch2.Thread` with given id""" with Database(path=self.path, mode=Database.MODE.READ_ONLY) as db: try: yield next(db.threads('thread:' + tid)) except NotmuchError: errmsg = 'no thread with id %s exists!' % tid raise NonexistantObjectError(errmsg)
def get_named_queries(self): """ returns the named queries stored in the database. :rtype: dict (str -> str) mapping alias to full query string """ db = Database(path=self.path, mode=Database.MODE.READ_ONLY) return {k[6:]: db.config[k] for k in db.config if k.startswith('query.')}
def _with_notmuch_message(self, mid): """returns :class:`notmuch2.Message` with given id""" mode = Database.MODE.READ_ONLY with Database(path=self.path, mode=mode) as db: try: yield db.find_message(mid) except: errmsg = 'no message with id %s exists!' % mid raise NonexistantObjectError(errmsg)
def GET(self): web.header('Content-type', 'text/html') base = env.get_template('base.html') template = env.get_template('index.html') db = Database() tags = db.tags return template.render(tags=tags, title="Notmuch webmail", prefix=prefix, sprefix=webprefix)
def get_threads(self, querystring, sort='newest_first'): """ asynchronously look up thread ids matching `querystring`. :param querystring: The query string to use for the lookup :type querystring: str. :param sort: Sort order. one of ['oldest_first', 'newest_first', 'message_id', 'unsorted'] :type query: str :returns: a pipe together with the process that asynchronously writes to it. :rtype: (:class:`multiprocessing.Pipe`, :class:`multiprocessing.Process`) """ assert sort in self._sort_orders db = Database(path=self.path, mode=Database.MODE.READ_ONLY) for t in db.threads(querystring, sort=self._sort_orders[sort], exclude_tags=self.exclude_tags): yield t.threadid
def _with_notmuch_thread(self, tid, querystring=None): """returns :class:`notmuch2.Thread` with given id""" if querystring: query = 'thread:' + tid + ' AND (' + querystring + ')' else: query = 'thread:' + tid with Database(path=self.path, mode=Database.MODE.READ_ONLY) as db: try: yield next(db.threads(query)) except NotmuchError: errmsg = 'no thread with id %s exists!' % tid raise NonexistantObjectError(errmsg)
def thread_nav(m): if not show_thread_nav: return db = Database() thread = next(db.threads('thread:' + m.threadid)) prv = None found = False nxt = None for msg in thread: if m == msg: found = True elif not found: prv = msg else: # found message, but not on this loop nxt = msg break yield "<hr><ul>" if prv: yield "<li>Previous message (by thread): %s</li>" % link_msg(prv) if nxt: yield "<li>Next message (by thread): %s</li>" % link_msg(nxt) yield "</ul><h3>Thread:</h3>" # FIXME show now takes three queries instead of 1; # can we yield the message body while computing the thread shape? thread = next(db.threads('thread:' + m.threadid)) yield show_msgs(thread.toplevel()) return
def setUpClass(cls): # create temporary notmuch config with tempfile.NamedTemporaryFile(mode='w+', delete=False) as f: f.write(textwrap.dedent("""\ [maildir] synchronize_flags = true """)) cls.notmuch_config_path = f.name cls.addClassCleanup(os.unlink, f.name) # define an empty notmuch database in a temporary directory cls.dbpath = tempfile.mkdtemp() cls.db = Database.create(path=cls.dbpath) cls.db.close() cls.manager = DBManager(cls.dbpath) # clean up temporary database cls.addClassCleanup(shutil.rmtree, cls.dbpath) # let global settings manager read our temporary notmuch config settings.read_notmuch_config(cls.notmuch_config_path)
def flush(self): """ write out all queued write-commands in order, each one in a separate :meth:`atomic <notmuch2.Database.atomic>` transaction. If this fails the current action is rolled back, stays in the write queue and an exception is raised. You are responsible to retry flushing at a later time if you want to ensure that the cached changes are applied to the database. :exception: :exc:`~errors.DatabaseROError` if db is opened read-only :exception: :exc:`~errors.DatabaseLockedError` if db is locked """ if self.ro: raise DatabaseROError() if self.writequeue: # read notmuch's config regarding imap flag synchronization sync = settings.get_notmuch_setting('maildir', 'synchronize_flags') # go through writequeue entries while self.writequeue: current_item = self.writequeue.popleft() logging.debug('write-out item: %s', str(current_item)) # watch out for notmuch errors to re-insert current_item # to the queue on errors try: # the first two coordinants are cnmdname and post-callback cmd, afterwards = current_item[:2] logging.debug('cmd created') # acquire a writeable db handler try: mode = Database.MODE.READ_WRITE db = Database(path=self.path, mode=mode) except NotmuchError: raise DatabaseLockedError() logging.debug('got write lock') # make this a transaction with db.atomic(): logging.debug('got atomic') if cmd == 'add': logging.debug('add') path, tags = current_item[2:] msg, _ = db.add(path, sync_flags=sync) logging.debug('added msg') with msg.frozen(): logging.debug('freeze') for tag in tags: msg.tags.add(tag) if sync: msg.tags.to_maildir_flags() logging.debug('added tags ') logging.debug('thaw') elif cmd == 'remove': path = current_item[2] db.remove(path) elif cmd == 'setconfig': key = current_item[2] value = current_item[3] db.config[key] = value else: # tag/set/untag querystring, tags = current_item[2:] if cmd == 'toggle': presenttags = self.collect_tags(querystring) to_remove = [] to_add = [] for tag in tags: if tag in presenttags: to_remove.append(tag) else: to_add.append(tag) for msg in db.messages(querystring): with msg.frozen(): if cmd == 'toggle': for tag in to_remove: msg.tags.discard(tag) for tag in to_add: msg.tags.add(tag) else: if cmd == 'set': msg.tags.clear() for tag in tags: if cmd == 'tag' or cmd == 'set': msg.tags.add(tag) elif cmd == 'untag': msg.tags.discard(tag) if sync: msg.tags.to_maildir_flags() logging.debug('ended atomic') # close db db.close() logging.debug('closed db') # call post-callback if callable(afterwards): logging.debug(str(afterwards)) afterwards() logging.debug('called callback') # re-insert item to the queue upon Xapian/NotmuchErrors except (XapianError, NotmuchError) as e: logging.exception(e) self.writequeue.appendleft(current_item) raise DatabaseError(str(e)) except DatabaseLockedError as e: logging.debug('index temporarily locked') self.writequeue.appendleft(current_item) raise e logging.debug('flush finished')
def count_threads(self, querystring): """returns number of threads that match `querystring`""" db = Database(path=self.path, mode=Database.MODE.READ_ONLY) return db.count_threads(querystring, exclude_tags=self.exclude_tags)
def count_messages(self, querystring): """returns number of messages that match `querystring`""" db = Database(path=self.path, mode=Database.MODE.READ_ONLY) return db.count_messages(querystring, exclude_tags=settings.get('exclude_tags'))