1 Return-Path: <awg@lagos.xvx.ca>
\r
2 X-Original-To: notmuch@notmuchmail.org
\r
3 Delivered-To: notmuch@notmuchmail.org
\r
4 Received: from localhost (localhost [127.0.0.1])
\r
5 by olra.theworths.org (Postfix) with ESMTP id CD3BF429E32
\r
6 for <notmuch@notmuchmail.org>; Mon, 16 Jan 2012 10:13:55 -0800 (PST)
\r
7 X-Virus-Scanned: Debian amavisd-new at olra.theworths.org
\r
11 X-Spam-Status: No, score=0 tagged_above=-999 required=5
\r
12 tests=[RCVD_IN_DNSWL_NONE=-0.0001] autolearn=disabled
\r
13 Received: from olra.theworths.org ([127.0.0.1])
\r
14 by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)
\r
15 with ESMTP id dN726q9zcsE3 for <notmuch@notmuchmail.org>;
\r
16 Mon, 16 Jan 2012 10:13:51 -0800 (PST)
\r
17 Received: from idcmail-mo1so.shaw.ca (idcmail-mo1so.shaw.ca [24.71.223.10])
\r
18 by olra.theworths.org (Postfix) with ESMTP id 71599431FAF
\r
19 for <notmuch@notmuchmail.org>; Mon, 16 Jan 2012 10:13:51 -0800 (PST)
\r
20 Received: from pd4ml3so-ssvc.prod.shaw.ca ([10.0.141.150])
\r
21 by pd4mo1so-svcs.prod.shaw.ca with ESMTP; 16 Jan 2012 11:13:51 -0700
\r
22 X-Cloudmark-SP-Filtered: true
\r
23 X-Cloudmark-SP-Result: v=1.1 cv=b/gStvK98Bs6YfhBwCpxRApoLO2qoBYaarCtclPE+68=
\r
25 a=lLI09cVVNRQA:10 a=BLceEmwcHowA:10 a=yQp6g8lIsgqumF79BAsFDg==:17
\r
26 a=z0yAGGARn2UZrTcHzMYA:9 a=kfhJ8YOzeKj4naP4xKsA:7
\r
27 a=HpAAvcLHHh0Zw7uRqdWCyQ==:117
\r
28 Received: from unknown (HELO lagos.xvx.ca) ([96.52.216.56])
\r
29 by pd4ml3so-dmz.prod.shaw.ca with ESMTP; 16 Jan 2012 11:13:51 -0700
\r
30 Received: by lagos.xvx.ca (Postfix, from userid 1000)
\r
31 id D22E18004208; Mon, 16 Jan 2012 11:13:50 -0700 (MST)
\r
32 From: Adam Wolfe Gordon <awg+notmuch@xvx.ca>
\r
33 To: notmuch@notmuchmail.org
\r
34 Subject: [PATCH v2 2/4] reply: Add a JSON reply format.
\r
35 Date: Mon, 16 Jan 2012 11:13:21 -0700
\r
36 Message-Id: <1326737603-21166-3-git-send-email-awg+notmuch@xvx.ca>
\r
37 X-Mailer: git-send-email 1.7.5.4
\r
38 In-Reply-To: <1326737603-21166-1-git-send-email-awg+notmuch@xvx.ca>
\r
39 References: <1326737603-21166-1-git-send-email-awg+notmuch@xvx.ca>
\r
40 X-BeenThere: notmuch@notmuchmail.org
\r
41 X-Mailman-Version: 2.1.13
\r
43 List-Id: "Use and development of the notmuch mail system."
\r
44 <notmuch.notmuchmail.org>
\r
45 List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,
\r
46 <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>
\r
47 List-Archive: <http://notmuchmail.org/pipermail/notmuch>
\r
48 List-Post: <mailto:notmuch@notmuchmail.org>
\r
49 List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>
\r
50 List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,
\r
51 <mailto:notmuch-request@notmuchmail.org?subject=subscribe>
\r
52 X-List-Received-Date: Mon, 16 Jan 2012 18:13:56 -0000
\r
54 This new JSON format for replies includes headers generated for a reply
\r
55 message as well as the headers and all text parts of the original message.
\r
56 Using this data, a client can intelligently create a reply. For example,
\r
57 the emacs client will be able to create replies with quoted HTML parts by
\r
58 parsing the HTML parts using w3m.
\r
60 notmuch-reply.c | 313 ++++++++++++++++++++++++++++++++++++++++++++-----------
\r
61 1 files changed, 253 insertions(+), 60 deletions(-)
\r
63 diff --git a/notmuch-reply.c b/notmuch-reply.c
\r
64 index da3acce..f5a5dcf 100644
\r
65 --- a/notmuch-reply.c
\r
66 +++ b/notmuch-reply.c
\r
67 @@ -30,6 +30,15 @@ reply_headers_message_part (GMimeMessage *message);
\r
69 reply_part_content (GMimeObject *part);
\r
72 +reply_part_start_json (GMimeObject *part, int *part_count);
\r
75 +reply_part_content_json (GMimeObject *part);
\r
78 +reply_part_end_json (GMimeObject *part);
\r
80 static const notmuch_show_format_t format_reply = {
\r
83 @@ -46,6 +55,22 @@ static const notmuch_show_format_t format_reply = {
\r
87 +static const notmuch_show_format_t format_json = {
\r
90 + "", NULL, NULL, "",
\r
92 + reply_part_start_json,
\r
95 + reply_part_content_json,
\r
96 + reply_part_end_json,
\r
104 show_reply_headers (GMimeMessage *message)
\r
106 @@ -54,14 +79,14 @@ show_reply_headers (GMimeMessage *message)
\r
107 stream_stdout = g_mime_stream_file_new (stdout);
\r
108 if (stream_stdout) {
\r
109 g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream_stdout), FALSE);
\r
110 - stream_filter = g_mime_stream_filter_new(stream_stdout);
\r
111 + stream_filter = g_mime_stream_filter_new (stream_stdout);
\r
112 if (stream_filter) {
\r
113 - g_mime_stream_filter_add(GMIME_STREAM_FILTER(stream_filter),
\r
114 - g_mime_filter_headers_new());
\r
115 - g_mime_object_write_to_stream(GMIME_OBJECT(message), stream_filter);
\r
116 - g_object_unref(stream_filter);
\r
117 + g_mime_stream_filter_add (GMIME_STREAM_FILTER(stream_filter),
\r
118 + g_mime_filter_headers_new());
\r
119 + g_mime_object_write_to_stream (GMIME_OBJECT(message), stream_filter);
\r
120 + g_object_unref (stream_filter);
\r
122 - g_object_unref(stream_stdout);
\r
123 + g_object_unref (stream_stdout);
\r
127 @@ -86,6 +111,17 @@ reply_headers_message_part (GMimeMessage *message)
\r
128 printf ("> Date: %s\n", g_mime_message_get_date_as_string (message));
\r
131 +static notmuch_bool_t
\r
132 +reply_check_part_type (GMimeObject *part, const char *type, const char *subtype,
\r
133 + const char *disposition)
\r
135 + GMimeContentType *content_type = g_mime_object_get_content_type (GMIME_OBJECT (part));
\r
136 + GMimeContentDisposition *part_disposition = g_mime_object_get_content_disposition (part);
\r
138 + return (g_mime_content_type_is_type (content_type, type, subtype) &&
\r
139 + (!part_disposition ||
\r
140 + strcmp (part_disposition->disposition, disposition) == 0));
\r
144 reply_part_content (GMimeObject *part)
\r
145 @@ -108,32 +144,29 @@ reply_part_content (GMimeObject *part)
\r
147 GMimeStream *stream_stdout = NULL, *stream_filter = NULL;
\r
148 GMimeDataWrapper *wrapper;
\r
149 - const char *charset;
\r
151 - charset = g_mime_object_get_content_type_parameter (part, "charset");
\r
152 stream_stdout = g_mime_stream_file_new (stdout);
\r
153 if (stream_stdout) {
\r
154 g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream_stdout), FALSE);
\r
155 - stream_filter = g_mime_stream_filter_new(stream_stdout);
\r
157 - g_mime_stream_filter_add(GMIME_STREAM_FILTER(stream_filter),
\r
158 - g_mime_filter_charset_new(charset, "UTF-8"));
\r
160 + stream_filter = g_mime_stream_filter_new (stream_stdout);
\r
162 + const char *charset = g_mime_object_get_content_type_parameter (part, "charset");
\r
164 + g_mime_stream_filter_add(GMIME_STREAM_FILTER (stream_filter),
\r
165 + g_mime_filter_charset_new (charset, "UTF-8"));
\r
167 - g_mime_stream_filter_add(GMIME_STREAM_FILTER(stream_filter),
\r
168 - g_mime_filter_reply_new(TRUE));
\r
169 + g_mime_stream_filter_add (GMIME_STREAM_FILTER (stream_filter),
\r
170 + g_mime_filter_reply_new (TRUE));
\r
171 wrapper = g_mime_part_get_content_object (GMIME_PART (part));
\r
172 if (wrapper && stream_filter)
\r
173 g_mime_data_wrapper_write_to_stream (wrapper, stream_filter);
\r
175 - g_object_unref(stream_filter);
\r
176 + g_object_unref (stream_filter);
\r
178 - g_object_unref(stream_stdout);
\r
179 + g_object_unref (stream_stdout);
\r
183 - if (disposition &&
\r
184 - strcmp (disposition->disposition, GMIME_DISPOSITION_ATTACHMENT) == 0)
\r
185 + if (disposition && strcmp (disposition->disposition, GMIME_DISPOSITION_ATTACHMENT) == 0)
\r
187 const char *filename = g_mime_part_get_filename (GMIME_PART (part));
\r
188 printf ("Attachment: %s (%s)\n", filename,
\r
189 @@ -147,6 +180,67 @@ reply_part_content (GMimeObject *part)
\r
194 +reply_part_start_json (GMimeObject *part, unused (int *part_count))
\r
196 + if (reply_check_part_type (part, "text", "*", GMIME_DISPOSITION_INLINE))
\r
201 +reply_part_end_json (GMimeObject *part)
\r
203 + if (reply_check_part_type (part, "text", "*", GMIME_DISPOSITION_INLINE))
\r
208 +reply_part_content_json (GMimeObject *part)
\r
210 + GMimeContentType *content_type = g_mime_object_get_content_type (GMIME_OBJECT (part));
\r
211 + void *ctx = talloc_new (NULL);
\r
213 + /* We only care about inline text parts for reply purposes */
\r
214 + if (reply_check_part_type (part, "text", "*", GMIME_DISPOSITION_INLINE))
\r
216 + GMimeDataWrapper *wrapper;
\r
217 + GByteArray *part_content;
\r
219 + printf ("\"content-type\": %s, \"content\": ",
\r
220 + json_quote_str (ctx, g_mime_content_type_to_string (content_type)));
\r
222 + wrapper = g_mime_part_get_content_object (GMIME_PART (part));
\r
225 + const char *charset = g_mime_object_get_content_type_parameter (part, "charset");
\r
226 + GMimeStream *stream_memory = g_mime_stream_mem_new ();
\r
227 + if (stream_memory) {
\r
228 + GMimeStream *stream_filter = NULL;
\r
229 + stream_filter = g_mime_stream_filter_new (stream_memory);
\r
231 + g_mime_stream_filter_add (GMIME_STREAM_FILTER (stream_filter),
\r
232 + g_mime_filter_charset_new (charset, "UTF-8"));
\r
235 + if (stream_filter)
\r
237 + g_mime_data_wrapper_write_to_stream (wrapper, stream_filter);
\r
238 + part_content = g_mime_stream_mem_get_byte_array (GMIME_STREAM_MEM (stream_memory));
\r
240 + printf ("%s", json_quote_chararray (ctx, (char *) part_content->data, part_content->len));
\r
242 + if (stream_filter)
\r
243 + g_object_unref (stream_filter);
\r
246 + if (stream_memory)
\r
247 + g_object_unref (stream_memory);
\r
251 + talloc_free (ctx);
\r
254 /* Is the given address configured as one of the user's "personal" or
\r
255 * "other" addresses. */
\r
257 @@ -505,6 +599,61 @@ guess_from_received_header (notmuch_config_t *config, notmuch_message_t *message
\r
261 +static GMimeMessage *
\r
262 +create_reply_message(void *ctx,
\r
263 + notmuch_config_t *config,
\r
264 + notmuch_message_t *message,
\r
265 + notmuch_bool_t reply_all)
\r
267 + const char *subject, *from_addr = NULL;
\r
268 + const char *in_reply_to, *orig_references, *references;
\r
270 + /* The 1 means we want headers in a "pretty" order. */
\r
271 + GMimeMessage *reply = g_mime_message_new (1);
\r
272 + if (reply == NULL) {
\r
273 + fprintf (stderr, "Out of memory\n");
\r
277 + subject = notmuch_message_get_header (message, "subject");
\r
279 + if (strncasecmp (subject, "Re:", 3))
\r
280 + subject = talloc_asprintf (ctx, "Re: %s", subject);
\r
281 + g_mime_message_set_subject (reply, subject);
\r
284 + from_addr = add_recipients_from_message (reply, config,
\r
285 + message, reply_all);
\r
287 + if (from_addr == NULL)
\r
288 + from_addr = guess_from_received_header (config, message);
\r
290 + if (from_addr == NULL)
\r
291 + from_addr = notmuch_config_get_user_primary_email (config);
\r
293 + from_addr = talloc_asprintf (ctx, "%s <%s>",
\r
294 + notmuch_config_get_user_name (config),
\r
296 + g_mime_object_set_header (GMIME_OBJECT (reply),
\r
297 + "From", from_addr);
\r
299 + in_reply_to = talloc_asprintf (ctx, "<%s>",
\r
300 + notmuch_message_get_message_id (message));
\r
302 + g_mime_object_set_header (GMIME_OBJECT (reply),
\r
303 + "In-Reply-To", in_reply_to);
\r
305 + orig_references = notmuch_message_get_header (message, "references");
\r
306 + references = talloc_asprintf (ctx, "%s%s%s",
\r
307 + orig_references ? orig_references : "",
\r
308 + orig_references ? " " : "",
\r
310 + g_mime_object_set_header (GMIME_OBJECT (reply),
\r
311 + "References", references);
\r
317 notmuch_reply_format_default(void *ctx,
\r
318 notmuch_config_t *config,
\r
319 @@ -515,8 +664,6 @@ notmuch_reply_format_default(void *ctx,
\r
320 GMimeMessage *reply;
\r
321 notmuch_messages_t *messages;
\r
322 notmuch_message_t *message;
\r
323 - const char *subject, *from_addr = NULL;
\r
324 - const char *in_reply_to, *orig_references, *references;
\r
325 const notmuch_show_format_t *format = &format_reply;
\r
327 for (messages = notmuch_query_search_messages (query);
\r
328 @@ -525,62 +672,104 @@ notmuch_reply_format_default(void *ctx,
\r
330 message = notmuch_messages_get (messages);
\r
332 - /* The 1 means we want headers in a "pretty" order. */
\r
333 - reply = g_mime_message_new (1);
\r
334 - if (reply == NULL) {
\r
335 - fprintf (stderr, "Out of memory\n");
\r
338 + reply = create_reply_message (ctx, config, message, reply_all);
\r
340 - subject = notmuch_message_get_header (message, "subject");
\r
342 - if (strncasecmp (subject, "Re:", 3))
\r
343 - subject = talloc_asprintf (ctx, "Re: %s", subject);
\r
344 - g_mime_message_set_subject (reply, subject);
\r
349 - from_addr = add_recipients_from_message (reply, config, message,
\r
351 + show_reply_headers (reply);
\r
353 - if (from_addr == NULL)
\r
354 - from_addr = guess_from_received_header (config, message);
\r
355 + g_object_unref (G_OBJECT (reply));
\r
358 - if (from_addr == NULL)
\r
359 - from_addr = notmuch_config_get_user_primary_email (config);
\r
360 + printf ("On %s, %s wrote:\n",
\r
361 + notmuch_message_get_header (message, "date"),
\r
362 + notmuch_message_get_header (message, "from"));
\r
364 - from_addr = talloc_asprintf (ctx, "%s <%s>",
\r
365 - notmuch_config_get_user_name (config),
\r
367 - g_mime_object_set_header (GMIME_OBJECT (reply),
\r
368 - "From", from_addr);
\r
369 + show_message_body (message, format, params);
\r
371 - in_reply_to = talloc_asprintf (ctx, "<%s>",
\r
372 - notmuch_message_get_message_id (message));
\r
373 + notmuch_message_destroy (message);
\r
378 - g_mime_object_set_header (GMIME_OBJECT (reply),
\r
379 - "In-Reply-To", in_reply_to);
\r
381 +notmuch_reply_format_json(void *ctx,
\r
382 + notmuch_config_t *config,
\r
383 + notmuch_query_t *query,
\r
384 + unused (notmuch_show_params_t *params),
\r
385 + notmuch_bool_t reply_all)
\r
387 + GMimeMessage *reply;
\r
388 + notmuch_messages_t *messages;
\r
389 + notmuch_message_t *message;
\r
390 + const notmuch_show_format_t *format = &format_json;
\r
392 - orig_references = notmuch_message_get_header (message, "references");
\r
393 - references = talloc_asprintf (ctx, "%s%s%s",
\r
394 - orig_references ? orig_references : "",
\r
395 - orig_references ? " " : "",
\r
397 - g_mime_object_set_header (GMIME_OBJECT (reply),
\r
398 - "References", references);
\r
399 + const char *reply_headers[] = {"from", "to", "subject", "in-reply-to", "references"};
\r
400 + const char *orig_headers[] = {"from", "to", "cc", "subject", "date", "in-reply-to", "references"};
\r
401 + unsigned int hidx;
\r
403 - show_reply_headers (reply);
\r
404 + /* Start array of reply objects */
\r
407 + for (messages = notmuch_query_search_messages (query);
\r
408 + notmuch_messages_valid (messages);
\r
409 + notmuch_messages_move_to_next (messages))
\r
411 + /* Start a reply object */
\r
412 + printf ("{ \"reply\": { \"headers\": { ");
\r
414 + message = notmuch_messages_get (messages);
\r
416 + reply = create_reply_message (ctx, config, message, reply_all);
\r
420 + for (hidx = 0; hidx < ARRAY_SIZE (reply_headers); hidx++)
\r
425 + printf ("%s: %s", json_quote_str (ctx, reply_headers[hidx]),
\r
426 + json_quote_str (ctx, g_mime_object_get_header (GMIME_OBJECT (reply), reply_headers[hidx])));
\r
429 g_object_unref (G_OBJECT (reply));
\r
432 - printf ("On %s, %s wrote:\n",
\r
433 - notmuch_message_get_header (message, "date"),
\r
434 - notmuch_message_get_header (message, "from"));
\r
435 + /* Done the headers for the reply, which has no body parts */
\r
438 + /* Start the original */
\r
439 + printf (", \"original\": { \"headers\": { ");
\r
441 + for (hidx = 0; hidx < ARRAY_SIZE (orig_headers); hidx++)
\r
446 + printf ("%s: %s", json_quote_str (ctx, orig_headers[hidx]),
\r
447 + json_quote_str (ctx, notmuch_message_get_header (message, orig_headers[hidx])));
\r
450 + /* End headers */
\r
451 + printf (" }, \"body\": [ ");
\r
453 + /* Show body parts */
\r
454 show_message_body (message, format, params);
\r
456 notmuch_message_destroy (message);
\r
458 + /* Done the original */
\r
459 + printf ("{} ] }");
\r
461 + /* End the reply object. */
\r
465 + /* End array of reply objects */
\r
466 + printf ("{} ]\n");
\r
471 @@ -646,6 +835,7 @@ notmuch_reply_format_headers_only(void *ctx,
\r
476 FORMAT_HEADERS_ONLY,
\r
479 @@ -666,6 +856,7 @@ notmuch_reply_command (void *ctx, int argc, char *argv[])
\r
480 notmuch_opt_desc_t options[] = {
\r
481 { NOTMUCH_OPT_KEYWORD, &format, "format", 'f',
\r
482 (notmuch_keyword_t []){ { "default", FORMAT_DEFAULT },
\r
483 + { "json", FORMAT_JSON },
\r
484 { "headers-only", FORMAT_HEADERS_ONLY },
\r
486 { NOTMUCH_OPT_KEYWORD, &reply_all, "reply-to", 'r',
\r
487 @@ -684,6 +875,8 @@ notmuch_reply_command (void *ctx, int argc, char *argv[])
\r
489 if (format == FORMAT_HEADERS_ONLY)
\r
490 reply_format_func = notmuch_reply_format_headers_only;
\r
491 + else if (format == FORMAT_JSON)
\r
492 + reply_format_func = notmuch_reply_format_json;
\r
494 reply_format_func = notmuch_reply_format_default;
\r