def push (self, args, setup = False): if not setup: self.setup (args, args.dry_run, True) self.force = args.force self.limit = args.limit self.remote.get_labels () # loading local changes with notmuch.Database () as db: (rev, uuid) = db.get_revision () if rev == self.local.state.lastmod: print ("push: everything is up-to-date.") return qry = "path:%s/** and lastmod:%d..%d" % (self.local.nm_relative, self.local.state.lastmod, rev) # print ("collecting changes..: %s" % qry) query = notmuch.Query (db, qry) total = query.count_messages () # might be destructive here as well query = notmuch.Query (db, qry) messages = list(query.search_messages ()) if self.limit is not None and len(messages) > self.limit: messages = messages[:self.limit] # push changes bar = tqdm (leave = True, total = len(messages), desc = 'pushing, 0 changed') changed = 0 for m in messages: r = self.remote.update (m, self.local.state.last_historyId, self.force) if r: changed += 1 bar.set_description ('pushing, %d changed' % changed) bar.update (1) bar.close () if not self.remote.all_updated: # will not set last_mod, this forces messages to be pushed again at next push print ("push: not all changes could be pushed, will re-try at next push.") else: # TODO: Once I get more confident we might set the last history Id here to # avoid pulling back in the changes we just pushed. Currently there's a race # if something is modified remotely (new email, changed tags), so this might # not really be possible. pass if not self.dry_run and self.remote.all_updated: self.local.state.set_lastmod (rev) print ("remote historyId: %d" % self.remote.get_current_history_id (self.local.state.last_historyId))
def get(self, thread_id): threads = notmuch.Query( get_db(), "thread:{}".format(thread_id) ).search_threads() thread = next(threads) # there can be only 1 messages = thread.get_messages() return messages_to_json(messages)
def fetch_message(message_id): msgs = notmuch.Query(get_db(), "mid:{}".format(message_id)).search_messages() try: msg = next(msgs) # there can be only 1 except StopIteration: return 'Not found', 404 return message_to_json(msg)
def commit(self, dry_run=True): dirty_messages = set() dirty_messages.update(self._flush_tags) dirty_messages.update(self._add_tags.keys()) dirty_messages.update(self._remove_tags.keys()) if not dirty_messages: return if dry_run: self.log.info('I would commit changes to %i messages' % len(dirty_messages)) else: self.log.info('Committing changes to %i messages' % len(dirty_messages)) db = self.database.open(rw=True) for message_id in dirty_messages: messages = notmuch.Query(db, 'id:"%s"' % message_id).search_messages() for message in messages: if message_id in self._flush_tags: message.remove_all_tags() for tag in self._add_tags.get(message_id, []): message.add_tag(tag) for tag in self._remove_tags.get(message_id, []): message.remove_tag(tag) self.flush_changes()
def move(self, maildir, rules): ''' Move mails in folder maildir according to the given rules. ''' # identify and move messages logging.info("checking mails in '{}'".format(maildir)) to_delete_fnames = [] for query in rules.keys(): destination = '{}/{}/cur/'.format(self.db_path, rules[query]) main_query = self.query.format(folder=maildir, subquery=query) logging.debug("query: {}".format(main_query)) messages = notmuch.Query(self.db, main_query).search_messages() to_delete_fnames += self.__move(messages, maildir, destination) # remove mail from source locations only after all copies are finished for fname in set(to_delete_fnames): os.remove(fname) # update notmuch database logging.info("updating database") if not self.dry_run: self.__update_db(maildir) else: logging.info("Would update database")
def __init__(self): db = notmuch.Database() self.query = notmuch.Query(db, config.get('query')) if config.get('sort') == "oldest": self.query.set_sort(notmuch.Query.SORT.OLDEST_FIRST) else: self.query.set_sort(notmuch.Query.SORT.NEWEST_FIRST)
def populate_nm_patch_status(db,conn,project_name,all_my_tags): for t in all_my_tags: qstr = 'tag:pw-{} and tag:pw-{}-{}'.format(project_name,project_name,t) q = notmuch.Query(db, qstr) msgs = q.search_messages() for m in msgs: insert_nm_patch_status(conn,m.get_message_id(),project_name,t) conn.commit()
def thread(query): query = notmuch.Query(get_db(), query) query.set_sort(notmuch.Query.SORT.OLDEST_FIRST) thread = query.search_messages() messages = [message_to_json(m) for m in thread] if not messages: return 'Not found', 404 return jsonify(messages)
def download_message(message_id): msgs = notmuch.Query(get_db(), "mid:{}".format(message_id)).search_messages() try: msg = next(msgs) # there can be only 1 except StopIteration: return 'Not found', 404 # not message/rfc822: people might want to read it in browser return send_file(msg.get_filename(), mimetype="text/plain")
def download_message(message_id): msgs = notmuch.Query(get_db(), "mid:{}".format(message_id)).search_messages() msg = next(msgs) # there can be only 1 return send_file(msg.get_filename(), mimetype="message/rfc822", as_attachment=True, attachment_filename=message_id + ".eml")
def move(self, maildir, rules): ''' Move mails in folder maildir according to the given rules. ''' # identify and move messages logging.info("checking mails in '{}'".format(maildir)) to_delete_fnames = [] moved = False for query in rules.keys(): destination = '{}/{}/cur/'.format(self.db_path, rules[query]) main_query = self.query.format(folder=maildir.replace( "\"", "\\\""), subquery=query) logging.debug("query: {}".format(main_query)) messages = notmuch.Query(self.db, main_query).search_messages() for message in messages: # a single message (identified by Message-ID) can be in several # places; only touch the one(s) that exists in this maildir all_message_fnames = message.get_filenames() to_move_fnames = [ name for name in all_message_fnames if maildir in name ] if not to_move_fnames: continue moved = True self.__log_move_action(message, maildir, rules[query], self.dry_run) for fname in to_move_fnames: if self.dry_run: continue try: shutil.copy2(fname, self.get_new_name(fname, destination)) to_delete_fnames.append(fname) except shutil.SameFileError: logging.warn( "trying to move '{}' onto itself".format(fname)) continue except shutil.Error as e: # this is ugly, but shutil does not provide more # finely individuated errors if str(e).endswith("already exists"): continue else: raise # remove mail from source locations only after all copies are finished for fname in set(to_delete_fnames): os.remove(fname) # update notmuch database if not self.dry_run: if moved: logging.info("updating database") self.__update_db(maildir) else: logging.info("Would update database")
def smaSearch(q): #with notmuch.Database(path = MAILDIR) as db: db = notmuch.Database(path=MAILDIR) query = notmuch.Query(db, q) query.set_sort(notmuch.Query.SORT.NEWEST_FIRST) msgs = query.search_messages() msglist = list(msgs)[:100] # db.close() return msglist
def tag_search(db, search, *tags): q = notmuch.Query(db, search) count = 0 for msg in q.search_messages(): count += 1 tag_message(msg, *tags) if count > 0: logging.debug('Tagging %d messages with (%s)' % (count, ' '.join(tags)))
def remove_tag(message_id, tag): msgs = notmuch.Query(get_db(), "mid:{}".format(message_id)).search_messages() try: msg = next(msgs) # there can be only 1 except StopIteration: return 'Not found', 404 msg.remove_tag(tag, sync_maildir_flags=True) return jsonify(list(msg.get_tags()))
def state_color(self): # Check for query hits in the list. for query_tuple in self.color_query_tuples: query = self.unread_query + ' and (' + query_tuple[0] + ')' count = notmuch.Query(self.database, query).count_messages() # Stop here of any hit and return the queries color. if count > 0: return query_tuple[1] return ''
def do_query(self, query): """ Executes a notmuch query. :param query: the query to execute :type query: str :returns: the query result :rtype: :class:`notmuch.Query` """ logging.debug('Executing query %r' % query) return notmuch.Query(self.open(), query)
def download_attachment(message_id, num): msgs = notmuch.Query(get_db(), "mid:{}".format(message_id)).search_messages() msg = next(msgs) # there can be only 1 d = message_attachment(msg, num) if not d: return None if isinstance(d["content"], str): f = io.StringIO(d["content"]) else: f = io.BytesIO(d["content"]) return send_file(f, mimetype=d["content_type"], as_attachment=True, attachment_filename=d["filename"])
def differing_address_count(self): messages = notmuch.Query(self.database, self.unread_query).search_messages() address_list = dict() # Count uniquely addresses by add them as keys to a mapping. for message in messages: name, address = parseaddr(message.get_header('To')) address_list[address] = None # The number of keys are the amount of individual addresses. return len(address_list)
def get_oldest_nm_message(db, project_list): pw_list = 'to:{}'.format(project_list) qstr = pw_list #qstr = qstr + ' and not (tag:{}'.format(all_my_tags[0]) #for t in all_my_tags[1:]: # qstr = qstr + ' or tag:{}'.format(t) #qstr = qstr + ')' q = notmuch.Query(db, qstr) #q.exclude_tag('pwsync') q.set_sort(notmuch.Query.SORT.OLDEST_FIRST) #q.set_sort(notmuch.Query.SORT.NEWEST_FIRST) msgs = q.search_messages() since = datetime.datetime.fromtimestamp(next(msgs).get_date()) return since
def download_attachment(message_id, num, filename): # filename is unused, only added to url to name saved files appropriately msgs = notmuch.Query(get_db(), "mid:{}".format(message_id)).search_messages() try: msg = next(msgs) # there can be only 1 except StopIteration: return 'Not found', 404 d = message_attachment(msg, num) if not d: return None if isinstance(d["content"], str): f = io.BytesIO(d["content"].encode()) else: f = io.BytesIO(d["content"]) f.seek(0) return send_file(f, mimetype=d["content_type"])
def add_tags(notmuch_db, add_gmail=False): """loop through all the messages and tags from X-Keywords header to notmuch. This obviously might take a while @param notmuch_db: notmuch db instance @param add_gmail: wether to add tags like Sent, Important, etc""" all_messages = notmuch.Query(notmuch_db, '').search_messages() for message in all_messages: message_tags = message.get_header('X-Keywords') if message_tags: for tag in message_tags.split(','): if tag.startswith('\\'): if add_gmail: message.add_tag(tag) else: logging.debug('Adding tag %s to msg <%s>', tag, message.get_message_id()) message.add_tag(tag)
def iter_email_paths(args: typing.List[str]) -> typing.Iterator[str]: """Iterate over paths of matching emails.""" if len(args) > 0: yield from args return if notmuch is None: print( f"E: No files specified on command-line and could not import notmuch: {_nm_exc}", file=sys.stderr, ) sys.exit(1) database = notmuch.Database() query = notmuch.Query( database, "from:mintos subject:tägliche subject:Zusammenfassung") query.set_sort(notmuch.Query.SORT.OLDEST_FIRST) # pylint: disable=no-member msgs = query.search_messages() for msg in msgs: yield msg.get_filename()
def filtered_messages(self) -> List[Message]: """Filter unread messages which have not been notified already. Queries Notmuch for unread messages and compare their timestamp against the last time notifying messages by the caches state. Messages which arrived before this timestamp are considered as already notified and therefore part of the returned list. """ unfiltered_messages = notmuch.Query( self.database, self.notmuch_query).search_messages() messages = [] for message in unfiltered_messages: if self._get_message_date(message) > self.cache.last_update: messages.append(message) return messages
def dump_notmuch_query(patches, args): import notmuch sub_series = find_subseries(patches, args) if not sub_series: return def fn(series): return 'id:"%s"' % series['messages'][0]['message-id'] query_str = ' or '.join(map(fn, sub_series)) db = notmuch.Database(config.get_notmuch_dir()) q = notmuch.Query(db, query_str) tids = [] for thread in q.search_threads(): tids.append('thread:%s' % thread.get_thread_id()) print(' or '.join(tids))
def _main(): logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s") # If called without any argument, this is supposed to be a first run: index everything if len(sys.argv) == 1: if os.path.exists(os.path.expanduser(_DBPATH)): print("%s already exists.\n" "Please delete it if you want to recreate the database." % _DBPATH) sys.exit(1) book = AddrBook(True) nmdb = notmuch.Database(mode=notmuch.Database.MODE.READ_ONLY) query = notmuch.Query(nmdb, "tag:sent or tag:replied") book.index(query.search_messages()) # If called with arguments, search for that. else: needle = " ".join(sys.argv[1:]) AddrBook().find(needle)
def run(db): query = notmuch.Query(db, "") cnt = 0 for msg in query.search_messages(): fns = [fn for fn in msg.get_filenames()] if len(fns) < 2: continue dirnames = [os.path.dirname(os.path.dirname(fn)) for fn in fns] same_dir = True for dirname in dirnames: if dirname != dirnames[0]: same_dir = False break if not same_dir: continue fns.sort(key=os.path.getmtime) for fn in fns[1:]: cnt += 1 os.unlink(fn) print cnt
def push(self, args, setup=False): if not setup: self.setup(args, args.dry_run, True) self.force = args.force self.limit = args.limit self.remote.get_labels() # loading local changes with notmuch.Database() as db: (rev, uuid) = db.get_revision() if rev == self.local.state.lastmod: self.vprint("push: everything is up-to-date.") return qry = "path:%s/** and lastmod:%d..%d" % ( self.local.nm_relative, self.local.state.lastmod, rev) query = notmuch.Query(db, qry) total = query.count_messages() # probably destructive here as well query = notmuch.Query(db, qry) messages = list(query.search_messages()) if self.limit is not None and len(messages) > self.limit: messages = messages[:self.limit] # get gids and filter out messages outside this repository messages, gids = self.local.messages_to_gids(messages) # get meta-data on changed messages from remote remote_messages = [] self.bar_create(leave=True, total=len(gids), desc='receiving metadata') def _got_msgs(ms): for m in ms: self.bar_update(1) remote_messages.append(m) self.remote.get_messages(gids, _got_msgs, 'minimal') self.bar_close() # resolve changes self.bar_create(leave=True, total=len(gids), desc='resolving changes') actions = [] for rm, nm in zip(remote_messages, messages): actions.append( self.remote.update(rm, nm, self.local.state.last_historyId, self.force)) self.bar_update(1) self.bar_close() # remove no-ops actions = [a for a in actions if a] # limit if self.limit is not None and len(actions) >= self.limit: actions = actions[:self.limit] # push changes if len(actions) > 0: self.bar_create(leave=True, total=len(actions), desc='pushing, 0 changed') changed = 0 def cb(resp): nonlocal changed self.bar_update(1) changed += 1 if not self.args.quiet and self.bar: self.bar.set_description('pushing, %d changed' % changed) self.remote.push_changes(actions, cb) self.bar_close() else: self.vprint('push: nothing to push') if not self.remote.all_updated: # will not set last_mod, this forces messages to be pushed again at next push print( "push: not all changes could be pushed, will re-try at next push." ) else: # TODO: Once we get more confident we might set the last history Id here to # avoid pulling back in the changes we just pushed. Currently there's a race # if something is modified remotely (new email, changed tags), so this might # not really be possible. pass if not self.dry_run and self.remote.all_updated: self.local.state.set_lastmod(rev) self.vprint("remote historyId: %d" % self.remote.get_current_history_id( self.local.state.last_historyId))
markup_cnt = '[<span foreground="#93e0e3">{0}</span>]' markup_from = '<span foreground="#ffeece">{0}</span>' markup_subj = '<b>{0}</b>' markup_tags = '<i>(<span foreground="#9fc59f">{0}</span>)</i>' MAX_FROM_LEN = 35 MAX_SUBJ_LEN = 70 MAX_THREADS = 15 config = configparser.ConfigParser() with open(os.path.expanduser("~/.notmuch-config")) as ini: config.read_file(ini) exc_tags = config["search"].get("exclude_tags", "").split(";") db = notmuch.Database(mode=notmuch.Database.MODE.READ_ONLY) q = notmuch.Query(db, "tag:unread or tag:todo") q.set_sort(notmuch.Query.SORT.NEWEST_FIRST) for tag in exc_tags: q.exclude_tag(tag.strip()) # Read data about the new messages tab = [] msgs, threads_total, threads_unread = 0, 0, 0 mlc, mlf, mls = 0, 0, 0 for thr in q.search_threads(): threads_total += 1 tags = list(thr.get_tags()) if "unread" not in tags: continue threads_unread += 1
def get(self, query_string): threads = notmuch.Query(get_db(), query_string).search_threads() return threads_to_json(threads, number=None)
def _get_all_messages(self): notmuch_db = notmuch.Database(self.db_path) query = notmuch.Query(notmuch_db, self.query) return query.search_messages()