nmhive.py: Add --host and --port options
[nmhive.git] / nmhive.py
1 #!/usr/bin/env python
2
3 """Serve a JSON API for getting/setting notmuch tags with nmbug commits."""
4
5 import json
6 import mailbox
7 import os
8 import tempfile
9 import urllib.request
10
11 import flask
12 import flask_cors
13 import nmbug
14 import notmuch
15
16
17 app = flask.Flask(__name__)
18 app.config['CORS_HEADERS'] = 'Content-Type'
19 flask_cors.CORS(app)
20
21 TAG_PREFIX = os.getenv('NMBPREFIX', 'notmuch::')
22 NOTMUCH_PATH = None
23
24
25 @app.route('/tags', methods=['GET'])
26 def tags():
27     tags = set()
28     database = notmuch.Database(path=NOTMUCH_PATH)
29     try:
30         for t in database.get_all_tags():
31             if t.startswith(TAG_PREFIX):
32                 tags.add(t[len(TAG_PREFIX):])
33     finally:
34         database.close()
35     return flask.Response(
36         response=json.dumps(sorted(tags)),
37         mimetype='application/json')
38
39
40 def _message_tags(message):
41     return sorted(
42         tag[len(TAG_PREFIX):] for tag in message.get_tags()
43         if tag.startswith(TAG_PREFIX))
44
45
46 @app.route('/mid/<message_id>', methods=['GET', 'POST'])
47 def message_id_tags(message_id):
48     if flask.request.method == 'POST':
49         changes = flask.request.get_json()
50         database = notmuch.Database(
51             path=NOTMUCH_PATH,
52             mode=notmuch.Database.MODE.READ_WRITE)
53         try:
54             message = database.find_message(message_id)
55             if not(message):
56                 return flask.Response(status=404)
57             database.begin_atomic()
58             message.freeze()
59             for change in changes:
60                 if change.startswith('+'):
61                     message.add_tag(TAG_PREFIX + change[1:])
62                 elif change.startswith('-'):
63                     message.remove_tag(TAG_PREFIX + change[1:])
64                 else:
65                     return flask.Response(status=400)
66             message.thaw()
67             database.end_atomic()
68             tags = _message_tags(message=message)
69         finally:
70             database.close()
71         nmbug.commit(message='nmhive: {} {}'.format(
72             message_id, ' '.join(changes)))
73     elif flask.request.method == 'GET':
74         database = notmuch.Database(path=NOTMUCH_PATH)
75         try:
76             message = database.find_message(message_id)
77             if not(message):
78                 return flask.Response(status=404)
79             tags = _message_tags(message=message)
80         finally:
81             database.close()
82     return flask.Response(
83         response=json.dumps(tags),
84         mimetype='application/json')
85
86
87 @app.route('/gmane/<group>/<int:article>', methods=['GET'])
88 def gmane_message_id(group, article):
89     url = 'http://download.gmane.org/{}/{}/{}'.format(
90         group, article, article + 1)
91     response = urllib.request.urlopen(url=url, timeout=3)
92     mbox_bytes = response.read()
93     with tempfile.NamedTemporaryFile(prefix='nmbug-', suffix='.mbox') as f:
94         f.write(mbox_bytes)
95         mbox = mailbox.mbox(path=f.name)
96         _, message = mbox.popitem()
97         message_id = message['message-id']
98     return flask.Response(
99         response=message_id.lstrip('<').rstrip('>'),
100         mimetype='text/plain')
101
102
103 if __name__ == '__main__':
104     import argparse
105
106     parser = argparse.ArgumentParser(description=__doc__)
107     parser.add_argument(
108         '-H', '--host', default='127.0.0.1',
109         help='The hostname to listen on.')
110     parser.add_argument(
111         '-p', '--port', type=int, default=5000,
112         help='The port to listen on.')
113
114     args = parser.parse_args()
115
116     app.run(host=args.host, port=args.port)