Beispiel #1
0
def attachment(querystr, n):
    db = Database()
    query = Query(db, querystr)
    if query.count_messages() != 1:
        redirect('/!/%s/' % querystr)
    else:
        message = next(iter(query.search_messages()))
        parts = message.get_message_parts()
        i = n - 1
        if i >= len(parts):
            redirect('/!/%s/' % querystr)
        else:
            part = parts[i]
            content_type = part.get_content_type()
            response.content_type = content_type
         #  response.charset = part.get_content_charset()

            fn = part.get_filename()
            if fn != None:
                response.headers['content-disposition'] = 'filename="%s";' % unidecode(fn).replace('"', '')

            payload = message.get_part(n)
            if 'html' in content_type.lower():
                return clean_html(payload)
            else:
                return payload
Beispiel #2
0
def part():
    db = Database()
    query_string = ''
    part_num = 0
    first_search_term = 0
    for (num, arg) in enumerate(sys.argv[1:]):
        if arg.startswith('--part='):
            part_num_str = arg.split("=")[1]
            try:
                part_num = int(part_num_str)
            except ValueError:
                # just emulating behavior
                exit(1)
        elif not arg.startswith('--'):
            # save the position of the first sys.argv
            # that is a search term
            first_search_term = num + 1
    if first_search_term:
        # mangle arguments wrapping terms with spaces in quotes
        querystr = quote_query_line(sys.argv[first_search_term:])
    qry = Query(db, querystr)
    msgs = [msg for msg in qry.search_messages()]

    if not msgs:
        sys.exit(1)
    elif len(msgs) > 1:
        raise Exception("search term did not match precisely one message")
    else:
        msg = msgs[0]
        print msg.get_part(part_num)
Beispiel #3
0
def thread(thread_id):
    query = Query(db, 'thread:%s' % thread_id)
    if not query.count_threads() == 1:
        return 'Thread not found', 404
    messages = [('/messages/' + m.get_message_id(), m.get_header('subject')) \
                for m in query.search_messages()]
    return {'heading': _get_thread(thread_id).get_subject(), 'list': messages}
Beispiel #4
0
def part():
    db = Database()
    query_string = ''
    part_num = 0
    first_search_term = 0
    for (num, arg) in enumerate(sys.argv[1:]):
        if arg.startswith('--part='):
            part_num_str = arg.split("=")[1]
            try:
                part_num = int(part_num_str)
            except ValueError:
                # just emulating behavior
                exit(1)
        elif not arg.startswith('--'):
            # save the position of the first sys.argv
            # that is a search term
            first_search_term = num + 1
    if first_search_term:
        # mangle arguments wrapping terms with spaces in quotes
        querystr = quote_query_line(sys.argv[first_search_term:])
    qry = Query(db,querystr)
    msgs = [msg for msg in qry.search_messages()]

    if not msgs:
        sys.exit(1)
    elif len(msgs) > 1:
        raise Exception("search term did not match precisely one message")
    else:
        msg = msgs[0]
        print msg.get_part(part_num)
Beispiel #5
0
def get_training_data(progress=False):
    training_data = []
    training_labels = []
    db = Database()
    # query that returns all the messages
    q = Query(db, '')
    if progress:
        count = q.count_messages()
        n = 0
        pbar = ProgressBar(widgets=[Percentage(), Bar(),
                                    ETA()], maxval=count).start()

    data = []
    for m in q.search_messages():
        if progress:
            n += 1
            pbar.update(n)

        data.append(m.get_header('To'))
        data.append(m.get_header('From'))
        data.append(m.get_header('Subject'))
        data.append(m.get_part(1).decode("utf8", errors="ignore"))
        try:
            training_data.append('\n'.join(data))
        except UnicodeDecodeError:
            print map(lambda x: type(x), data)
            sys.exit(1)
        training_labels.append(erase_irrelevant_tags(list(m.get_tags())))
        data = []

    if progress:
        pbar.finish()
    return training_data, training_labels
Beispiel #6
0
def message(message_id):
    query = Query(db, 'id:%s' % message_id)
    if not query.count_messages() == 1:
        return 'Message not found', 404
    m = next(query.search_messages())
    return {
        'heading': m.get_header('subject'),
        'body': m.get_part(1),
    }
Beispiel #7
0
def message(message_id):
    query = Query(db, 'id:%s' % message_id)
    if not query.count_messages() == 1:
        return 'Message not found', 404
    m = next(query.search_messages())
    return {
        'heading': m.get_header('subject'),
        'body': m.get_part(1),
    }
Beispiel #8
0
 def notmuch_status(self):
     with Database() as db:
         inbox = Query(db, 'not tag:spam and tag:inbox').search_messages()
         unread = Query(db, 'not tag:spam and tag:unread').search_messages()
         inbox_count = len(list(inbox))
         unread_count = len(list(unread))
     return {
         'full_text': 'M: {}({})'.format(inbox_count, unread_count),
         'cached_until': self.py3.time_in(5)
     }
Beispiel #9
0
def thread(thread_id):
    query = Query(db, 'thread:%s' % thread_id)
    if not query.count_threads() == 1:
        return 'Thread not found', 404
    messages = [('/messages/' + m.get_message_id(), m.get_header('subject')) \
                for m in query.search_messages()]
    return {
        'heading': _get_thread(thread_id).get_subject(),
        'list': messages
    }
Beispiel #10
0
def recent():
    query = Query(db, '')
    messages = []
    for i, m in enumerate(query.search_messages()):
        if i >= 30:
            break
        messages.append(('/messages/' + m.get_message_id(), m.get_header('subject')))
    return {
        'heading': 'Recent messages',
        'list': messages,
    }
Beispiel #11
0
def recent():
    query = Query(db, '')
    messages = []
    for i, m in enumerate(query.search_messages()):
        if i >= 30:
            break
        messages.append(
            ('/messages/' + m.get_message_id(), m.get_header('subject')))
    return {
        'heading': 'Recent messages',
        'list': messages,
    }
Beispiel #12
0
def search():
    db = Database()
    query_string = ''
    sort_order = "newest-first"
    first_search_term = 0
    for (num, arg) in enumerate(sys.argv[1:]):
        if arg.startswith('--sort='):
            sort_order = arg.split("=")[1]
            if not sort_order in ("oldest-first", "newest-first"):
                raise Exception("unknown sort order")
        elif not arg.startswith('--'):
            # save the position of the first sys.argv that is a search term
            first_search_term = num + 1

    if first_search_term:
        # mangle arguments wrapping terms with spaces in quotes
        querystr = quote_query_line(sys.argv[first_search_term:])

    qry = Query(db, querystr)
    if sort_order == "oldest-first":
        qry.set_sort(Query.SORT.OLDEST_FIRST)
    else:
        qry.set_sort(Query.SORT.NEWEST_FIRST)
        threads = qry.search_threads()

    for thread in threads:
        print thread
Beispiel #13
0
def datesearch():
    if request.method == 'POST':
        tTo = datetime.datetime.strptime(request.form.get('to'), "%d %B, %Y").timetuple()
        tFrom = datetime.datetime.strptime(request.form.get('from'), "%d %B, %Y").timetuple()
        #to=int(time.mktime(tTo)),from=int(time.mktime(tFrom))
        msgs = list(Query(db,'%i..%i'%(int(time.mktime(tFrom)),int(time.mktime(tTo))) ).search_messages())
        return render_template('dateSearch.html',msgs=msgs)
        #msgs = list(Query(db, request.form.get('search')).search_messages())

    return render_template('dateSearch.html')
Beispiel #14
0
def get_new_mails():
    db = Database()
    query = Query(db, 'tag:new')
    data = []
    ids = []
    m_data = []
    for m in query.search_messages():
        m_data.append(m.get_header('To'))
        m_data.append(m.get_header('From'))
        m_data.append(m.get_header('Subject'))
        m_data.append(m.get_part(1).decode("utf8", errors="ignore"))
        try:
            data.append('\n'.join(m_data))
            ids.append(m.get_message_id())
        except UnicodeDecodeError:
            print map(lambda x: type(x), m_data)
            sys.exit(1)
        m_data = []
    return data, ids
Beispiel #15
0
def search():
    db = Database()
    query_string = ''
    sort_order = "newest-first"
    first_search_term = 0
    for (num, arg) in enumerate(sys.argv[1:]):
        if arg.startswith('--sort='):
            sort_order=arg.split("=")[1]
            if not sort_order in ("oldest-first", "newest-first"):
                raise Exception("unknown sort order")
        elif not arg.startswith('--'):
            # save the position of the first sys.argv that is a search term
            first_search_term = num + 1

    if first_search_term:
        # mangle arguments wrapping terms with spaces in quotes
        querystr = quote_query_line(sys.argv[first_search_term:])

    qry = Query(db, querystr)
    if sort_order == "oldest-first":
        qry.set_sort(Query.SORT.OLDEST_FIRST)
    else:
        qry.set_sort(Query.SORT.NEWEST_FIRST)
        threads = qry.search_threads()

    for thread in threads:
        print thread
Beispiel #16
0
def update(sessionmaker):
    '''
    Update the notmuch database.

    In order that updates are fast, this is how state is resumed.

    1. Get the date of the most recent email that has been imported.
    2. Get the message identifiers of all emails that have been imported.
    3. Search for emails that are no more than a week older than the
       most recent import. This buffer of a week should deal with issues
       of time zones and unsynchronized clocks.
    4. Process emails ascending chronological order.
    5. Skip an email if the message ID for the email has already been
       processed.
    '''
    if offlineimap_is_running():
        raise EnvironmentError('In case offlineimap runs "notmuch new", you should stop offlineimap while importing data from notmuch.')

    session = sessionmaker()
    most_recent = session.query(func.max(NotmuchMessage.datetime)).scalar()

    sql_query = session.query(NotmuchMessage.message_id)
    past_messages = set(row[0] for row in sql_query.distinct())

    start_date = (most_recent.date() - datetime.timedelta(weeks = 1))
    q = Query(Database(), 'date:%s..' % start_date)
    q.set_sort(Query.SORT.OLDEST_FIRST)
    for m in q.search_messages():
        message_id = m.get_message_id()
        if message_id in past_messages:
            logger.debug('Already imported %s' % message_id)
            continue
        past_messages.add(message_id)

        session.add(message(m))
        session.flush() # for foreign key constraints
        session.add_all(attachments(m))

        session.commit()
        logger.info('Added message "id:%s"' % m.get_message_id())
Beispiel #17
0
def search(querystr):
    db = Database()
    query = Query(db, querystr)
    if query.count_messages() == 1:
        message = next(iter(query.search_messages()))
        title = message.get_header('subject')
        try:
            parts = [(i + 1, part.get_filename('No description')) \
                     for i, part in enumerate(message.get_message_parts())]
            body = message.get_part(1)
        except UnicodeDecodeError:
            parts = []
            body = 'There was an encoding problem with this message.'
    else:
        title = 'Results for "%s"' % querystr
        parts = []
        body = None

    return {
        'title': title,
        'parts': parts,
        'body': body,
        'threads': list(hierarchy(query)),
    }
Beispiel #18
0
def show():
    entire_thread = False
    db = Database()
    out_format = "text"
    querystr = ''
    first_search_term = None

    # ugly homegrown option parsing
    # TODO: use OptionParser
    for (num, arg) in enumerate(sys.argv[1:]):
        if arg == '--entire-thread':
            entire_thread = True
        elif arg.startswith("--format="):
            out_format = arg.split("=")[1]
            if out_format == 'json':
                # for compatibility use --entire-thread for json
                entire_thread = True
            if not out_format in ("json", "text"):
                raise Exception("unknown format")
        elif not arg.startswith('--'):
            # save the position of the first sys.argv that is a search term
            first_search_term = num + 1

    if first_search_term:
        # mangle arguments wrapping terms with spaces in quotes
        querystr = quote_query_line(sys.argv[first_search_term:])

    threads = Query(db, querystr).search_threads()
    first_toplevel = True
    if out_format == "json":
        sys.stdout.write("[")
    for thread in threads:
        msgs = thread.get_toplevel_messages()
        if not first_toplevel:
            if out_format == "json":
                sys.stdout.write(", ")
        first_toplevel = False
        msgs.print_messages(out_format, 0, entire_thread)

    if out_format == "json":
        sys.stdout.write("]")
    sys.stdout.write("\n")
Beispiel #19
0
def main():
    parser = argparse.ArgumentParser(
            description="Sync message 'X-Keywords' header with notmuch tags.")
    parser.add_argument("-V", "--version", action="version",
            version="%(prog)s " + "v%s (%s)" % (__version__, __date__))
    parser.add_argument("-q", "--query", dest="query", required=True,
            help="notmuch database query string")
    parser.add_argument("-p", "--db-path", dest="dbpath",
            help="notmuch database path (default to try user configuration)")
    parser.add_argument("-n", "--dry-run", dest="dryrun",
            action="store_true", help="dry run")
    parser.add_argument("-v", "--verbose", dest="verbose",
            action="store_true", help="show verbose information")
    # Exclusive argument group for sync mode
    exgroup1 = parser.add_mutually_exclusive_group(required=True)
    exgroup1.add_argument("-m", "--merge-keywords-tags",
            dest="direction_merge", action="store_true",
            help="merge 'X-Keywords' and tags and update both")
    exgroup1.add_argument("-k", "--keywords-to-tags",
            dest="direction_keywords2tags", action="store_true",
            help="sync 'X-Keywords' to notmuch tags")
    exgroup1.add_argument("-t", "--tags-to-keywords",
            dest="direction_tags2keywords", action="store_true",
            help="sync notmuch tags to 'X-Keywords'")
    # Exclusive argument group for tag operation mode
    exgroup2 = parser.add_mutually_exclusive_group(required=False)
    exgroup2.add_argument("-a", "--add-only", dest="mode_addonly",
            action="store_true", help="only add notmuch tags")
    exgroup2.add_argument("-r", "--remove-only", dest="mode_removeonly",
            action="store_true", help="only remove notmuch tags")
    # Parse
    args = parser.parse_args()
    # Sync direction
    if args.direction_merge:
        sync_direction = SyncDirection.MERGE_KEYWORDS_TAGS
    elif args.direction_keywords2tags:
        sync_direction = SyncDirection.KEYWORDS_TO_TAGS
    elif args.direction_tags2keywords:
        sync_direction = SyncDirection.TAGS_TO_KEYWORDS
    else:
        raise ValueError("Invalid synchronization direction")
    # Sync mode
    if args.mode_addonly:
        sync_mode = SyncMode.ADD_ONLY
    elif args.mode_removeonly:
        sync_mode = SyncMode.REMOVE_ONLY
    else:
        sync_mode = SyncMode.ADD_REMOVE
    #
    if args.dbpath:
        dbpath = os.path.abspath(os.path.expanduser(args.dbpath))
    else:
        dbpath = None
    #
    db = Database(path=dbpath, create=False, mode=Database.MODE.READ_WRITE)
    dbinfo = get_notmuch_revision(dbpath=dbpath)
    q = Query(db, args.query)
    total_msgs = q.count_messages()
    msgs = q.search_messages()
    #
    if args.verbose:
        print("# Notmuch database path: %s" % dbpath)
        print("# Database revision: %d (uuid: %s)" %
                (dbinfo['revision'], dbinfo['uuid']))
        print("# Query: %s" % args.query)
        print("# Sync direction: %s" % sync_direction.name)
        print("# Sync mode: %s" % sync_mode.name)
        print("# Total messages to check: %d" % total_msgs)
        print("# Dryn run: %s" % args.dryrun)
    #
    for msg in msgs:
        kwmsg = KwMessage(msg)
        kwmsg.sync(direction=sync_direction, mode=sync_mode,
                   dryrun=args.dryrun, verbose=args.verbose)
    #
    db.close()
Beispiel #20
0
def get_messages(query_string):
    for msg in Query(db(), query_string).search_messages():
        yield MessageProxy(msg)
Beispiel #21
0
def index():
    db = Database()
    msgs = Query(db, 'inbox').search_messages()
    return render_template('index.html',msgs=list(msgs)[:100])
Beispiel #22
0
def search():
    if request.method == 'POST':
        msgs = list(Query(db, request.form.get('search')).search_messages())
    else:
        msgs = list(Query(db, request.args.get('q')).search_messages())
    return render_template('index.html',msgs=list(msgs)[:100])
Beispiel #23
0
def _get_thread(thread_id):
    querystr = 'thread:' + thread_id
    thread = next(iter(Query(db, querystr).search_threads()))
    return thread.get_subject()
Beispiel #24
0
def search(querystr):
    query = Query(db, querystr).search_threads()
    threads = [('/threads/' + t.get_thread_id(), t.get_subject()) \
               for t in query]
    return {'heading': 'Results for "%s"' % querystr, 'list': threads}
Beispiel #25
0
def viewmessage(messageid):
    msgs = list(Query(db, 'id:%s'%messageid).search_messages())
    msgParts = list(msgs[0].get_message_parts())
    mail = msgParts[0].as_string()
    return render_template('index.html',msgs=list(msgs)[:100],view=True,mail=mail)
Beispiel #26
0
def main():
    # Handle command line options
    #------------------------------------
    # No option given, print USAGE and exit
    if len(sys.argv) == 1:
        Notmuch().cmd_usage()
    #------------------------------------
    elif sys.argv[1] == 'setup':
       """Interactively setup notmuch for first use."""
       exit("Not implemented.")
    #-------------------------------------
    elif sys.argv[1] == 'new':
        """Check for new and removed messages."""
        Notmuch().cmd_new()
    #-------------------------------------
    elif sys.argv[1] == 'help':
        """Print the help text"""
        Notmuch().cmd_help(sys.argv[1:])
    #-------------------------------------
    elif sys.argv[1] == 'part':
        part()
    #-------------------------------------
    elif sys.argv[1] == 'search':
        search()
    #-------------------------------------
    elif sys.argv[1] == 'show':
        show()
    #-------------------------------------
    elif sys.argv[1] == 'reply':
        db = Database()
        if len(sys.argv) == 2:
            # no search term. abort
            exit("Error: notmuch reply requires at least one search term.")
        # mangle arguments wrapping terms with spaces in quotes
        querystr = quote_query_line(sys.argv[2:])
        msgs = Query(db, querystr).search_messages()
        print Notmuch().format_reply(msgs)
    #-------------------------------------
    elif sys.argv[1] == 'count':
        if len(sys.argv) == 2:
            # no further search term, count all
            querystr = ''
        else:
            # mangle arguments wrapping terms with spaces in quotes
            querystr = quote_query_line(sys.argv[2:])
	print Database().create_query(querystr).count_messages()
    #-------------------------------------
    elif sys.argv[1] == 'tag':
        # build lists of tags to be added and removed
        add = []
        remove = []
        while not sys.argv[2] == '--' and \
                (sys.argv[2].startswith('+') or sys.argv[2].startswith('-')):
                    if sys.argv[2].startswith('+'):
                        # append to add list without initial +
                        add.append(sys.argv.pop(2)[1:])
                    else:
                        # append to remove list without initial -
                        remove.append(sys.argv.pop(2)[1:])
        # skip eventual '--'
        if sys.argv[2] == '--': sys.argv.pop(2)
        # the rest is search terms
        querystr = quote_query_line(sys.argv[2:])
        db = Database(mode=Database.MODE.READ_WRITE)
        msgs  = Query(db, querystr).search_messages()
        for msg in msgs:
            # actually add and remove all tags
            map(msg.add_tag, add)
            map(msg.remove_tag, remove)
    #-------------------------------------
    elif sys.argv[1] == 'search-tags':
        if len(sys.argv) == 2:
            # no further search term
            print "\n".join(Database().get_all_tags())
        else:
            # mangle arguments wrapping terms with spaces in quotes
            querystr = quote_query_line(sys.argv[2:])
            db = Database()
            msgs  = Query(db, querystr).search_messages()
            print "\n".join([t for t in msgs.collect_tags()])
    #-------------------------------------
    elif sys.argv[1] == 'dump':
        # TODO: implement "dump <filename>"
        if len(sys.argv) == 2:
            f = sys.stdout
        else:
            f = open(sys.argv[2], "w")
        db = Database()
        query = Query(db, '')
        query.set_sort(Query.SORT.MESSAGE_ID)
        msgs = query.search_messages()
        for msg in msgs:
            f.write("%s (%s)\n" % (msg.get_message_id(), msg.get_tags()))
    #-------------------------------------
    elif sys.argv[1] == 'restore':
        if len(sys.argv) == 2:
            print("No filename given. Reading dump from stdin.")
            f = sys.stdin
        else:
            f = open(sys.argv[2], "r")

        # split the msg id and the tags
        MSGID_TAGS = re.compile("(\S+)\s\((.*)\)$")
        db = Database(mode=Database.MODE.READ_WRITE)

        #read each line of the dump file
        for line in f:
            msgs = MSGID_TAGS.match(line)
            if not msgs:
                sys.stderr.write("Warning: Ignoring invalid input line: %s" %
                        line)
                continue
            # split line in components and fetch message
            msg_id = msgs.group(1)
            new_tags = set(msgs.group(2).split())
            msg = db.find_message(msg_id)

            if msg == None:
                sys.stderr.write(
                        "Warning: Cannot apply tags to missing message: %s\n" % msg_id)
                continue

            # do nothing if the old set of tags is the same as the new one
            old_tags = set(msg.get_tags())
            if old_tags == new_tags: continue

            # set the new tags
            msg.freeze()
            # only remove tags if the new ones are not a superset anyway
            if not (new_tags > old_tags): msg.remove_all_tags()
            for tag in new_tags: msg.add_tag(tag)
            msg.thaw()
    #-------------------------------------
    else:
        # unknown command
        exit("Error: Unknown command '%s' (see \"notmuch help\")" % sys.argv[1])
Beispiel #27
0
def count_messages(query_string):
    return Query(db(), query_string).count_messages()
Beispiel #28
0
def main():
    # Handle command line options
    #------------------------------------
    # No option given, print USAGE and exit
    if len(sys.argv) == 1:
        Notmuch().cmd_usage()
    #------------------------------------
    elif sys.argv[1] == 'setup':
        """Interactively setup notmuch for first use."""
        exit("Not implemented.")
    #-------------------------------------
    elif sys.argv[1] == 'new':
        """Check for new and removed messages."""
        Notmuch().cmd_new()
    #-------------------------------------
    elif sys.argv[1] == 'help':
        """Print the help text"""
        Notmuch().cmd_help(sys.argv[1:])
    #-------------------------------------
    elif sys.argv[1] == 'part':
        part()
    #-------------------------------------
    elif sys.argv[1] == 'search':
        search()
    #-------------------------------------
    elif sys.argv[1] == 'show':
        show()
    #-------------------------------------
    elif sys.argv[1] == 'reply':
        db = Database()
        if len(sys.argv) == 2:
            # no search term. abort
            exit("Error: notmuch reply requires at least one search term.")
        # mangle arguments wrapping terms with spaces in quotes
        querystr = quote_query_line(sys.argv[2:])
        msgs = Query(db, querystr).search_messages()
        print Notmuch().format_reply(msgs)
    #-------------------------------------
    elif sys.argv[1] == 'count':
        if len(sys.argv) == 2:
            # no further search term, count all
            querystr = ''
        else:
            # mangle arguments wrapping terms with spaces in quotes
            querystr = quote_query_line(sys.argv[2:])
        print Database().create_query(querystr).count_messages()
    #-------------------------------------
    elif sys.argv[1] == 'tag':
        # build lists of tags to be added and removed
        add = []
        remove = []
        while not sys.argv[2] == '--' and \
                (sys.argv[2].startswith('+') or sys.argv[2].startswith('-')):
            if sys.argv[2].startswith('+'):
                # append to add list without initial +
                add.append(sys.argv.pop(2)[1:])
            else:
                # append to remove list without initial -
                remove.append(sys.argv.pop(2)[1:])
        # skip eventual '--'
        if sys.argv[2] == '--': sys.argv.pop(2)
        # the rest is search terms
        querystr = quote_query_line(sys.argv[2:])
        db = Database(mode=Database.MODE.READ_WRITE)
        msgs = Query(db, querystr).search_messages()
        for msg in msgs:
            # actually add and remove all tags
            map(msg.add_tag, add)
            map(msg.remove_tag, remove)
    #-------------------------------------
    elif sys.argv[1] == 'search-tags':
        if len(sys.argv) == 2:
            # no further search term
            print "\n".join(Database().get_all_tags())
        else:
            # mangle arguments wrapping terms with spaces in quotes
            querystr = quote_query_line(sys.argv[2:])
            db = Database()
            msgs = Query(db, querystr).search_messages()
            print "\n".join([t for t in msgs.collect_tags()])
    #-------------------------------------
    elif sys.argv[1] == 'dump':
        if len(sys.argv) == 2:
            f = sys.stdout
        else:
            f = open(sys.argv[2], "w")
        db = Database()
        query = Query(db, '')
        query.set_sort(Query.SORT.MESSAGE_ID)
        msgs = query.search_messages()
        for msg in msgs:
            f.write("%s (%s)\n" % (msg.get_message_id(), msg.get_tags()))
    #-------------------------------------
    elif sys.argv[1] == 'restore':
        if len(sys.argv) == 2:
            print("No filename given. Reading dump from stdin.")
            f = sys.stdin
        else:
            f = open(sys.argv[2], "r")

        # split the msg id and the tags
        MSGID_TAGS = re.compile("(\S+)\s\((.*)\)$")
        db = Database(mode=Database.MODE.READ_WRITE)

        #read each line of the dump file
        for line in f:
            msgs = MSGID_TAGS.match(line)
            if not msgs:
                sys.stderr.write("Warning: Ignoring invalid input line: %s" %
                                 line)
                continue
            # split line in components and fetch message
            msg_id = msgs.group(1)
            new_tags = set(msgs.group(2).split())
            msg = db.find_message(msg_id)

            if msg == None:
                sys.stderr.write(
                    "Warning: Cannot apply tags to missing message: %s\n" %
                    msg_id)
                continue

            # do nothing if the old set of tags is the same as the new one
            old_tags = set(msg.get_tags())
            if old_tags == new_tags: continue

            # set the new tags
            msg.freeze()
            # only remove tags if the new ones are not a superset anyway
            if not (new_tags > old_tags): msg.remove_all_tags()
            for tag in new_tags:
                msg.add_tag(tag)
            msg.thaw()
    #-------------------------------------
    else:
        # unknown command
        exit("Error: Unknown command '%s' (see \"notmuch help\")" %
             sys.argv[1])