Ejemplo n.º 1
0
  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))
Ejemplo n.º 2
0
 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)
Ejemplo n.º 3
0
 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)
Ejemplo n.º 4
0
    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()
Ejemplo n.º 5
0
    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")
Ejemplo n.º 6
0
 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)
Ejemplo n.º 7
0
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()
Ejemplo n.º 8
0
 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)
Ejemplo n.º 9
0
 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")
Ejemplo n.º 10
0
 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")
Ejemplo n.º 11
0
    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")
Ejemplo n.º 12
0
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
Ejemplo n.º 13
0
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)))
Ejemplo n.º 14
0
    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()))
Ejemplo n.º 15
0
    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 ''
Ejemplo n.º 16
0
    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)
Ejemplo n.º 17
0
 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"])
Ejemplo n.º 18
0
    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)
Ejemplo n.º 19
0
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
Ejemplo n.º 20
0
 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"])
Ejemplo n.º 21
0
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)
Ejemplo n.º 22
0
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()
Ejemplo n.º 23
0
    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
Ejemplo n.º 24
0
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))
Ejemplo n.º 25
0
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)
Ejemplo n.º 26
0
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
Ejemplo n.º 27
0
    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))
Ejemplo n.º 28
0
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
Ejemplo n.º 29
0
 def get(self, query_string):
     threads = notmuch.Query(get_db(), query_string).search_threads()
     return threads_to_json(threads, number=None)
Ejemplo n.º 30
0
 def _get_all_messages(self):
     notmuch_db = notmuch.Database(self.db_path)
     query = notmuch.Query(notmuch_db, self.query)
     return query.search_messages()