Return-Path: X-Original-To: notmuch@notmuchmail.org Delivered-To: notmuch@notmuchmail.org Received: from localhost (localhost [127.0.0.1]) by olra.theworths.org (Postfix) with ESMTP id 801CF40A3FF for ; Sat, 7 Jan 2012 23:53:12 -0800 (PST) X-Virus-Scanned: Debian amavisd-new at olra.theworths.org X-Spam-Flag: NO X-Spam-Score: 0 X-Spam-Level: X-Spam-Status: No, score=0 tagged_above=-999 required=5 tests=[RCVD_IN_DNSWL_NONE=-0.0001] autolearn=disabled Received: from olra.theworths.org ([127.0.0.1]) by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id 40bDacPm2bWK for ; Sat, 7 Jan 2012 23:53:10 -0800 (PST) Received: from idcmail-mo2no.shaw.ca (idcmail-mo2no.shaw.ca [64.59.134.9]) by olra.theworths.org (Postfix) with ESMTP id 385A340EF31 for ; Sat, 7 Jan 2012 23:53:09 -0800 (PST) Received: from lb7f8hsrpno-svcs.dcs.int.inet (HELO pd5ml3no-ssvc.prod.shaw.ca) ([10.0.144.222]) by pd7mo1no-svcs.prod.shaw.ca with ESMTP; 08 Jan 2012 00:53:08 -0700 X-Cloudmark-SP-Filtered: true X-Cloudmark-SP-Result: v=1.1 cv=TH029eDfZHqIRue7hhxWRjZ7JL4S9vo/jJY5vkv2d5E= c=1 sm=1 a=quntBF94qJUA:10 a=BLceEmwcHowA:10 a=yQp6g8lIsgqumF79BAsFDg==:17 a=P2v0uuFMy3zdClWoV1EA:9 a=7r3cU_-jv2UgmVONYvoA:7 a=HpAAvcLHHh0Zw7uRqdWCyQ==:117 Received: from unknown (HELO lagos.xvx.ca) ([96.52.216.56]) by pd5ml3no-dmz.prod.shaw.ca with ESMTP; 08 Jan 2012 00:53:08 -0700 Received: by lagos.xvx.ca (Postfix, from userid 1000) id 72F0B8004202; Sun, 8 Jan 2012 00:53:08 -0700 (MST) From: Adam Wolfe Gordon To: notmuch@notmuchmail.org, awg@xvx.ca Subject: [PATCH 2/4] reply: Add a JSON reply format. Date: Sun, 8 Jan 2012 00:52:40 -0700 Message-Id: <1326009162-19524-3-git-send-email-awg+notmuch@xvx.ca> X-Mailer: git-send-email 1.7.5.4 In-Reply-To: <1326009162-19524-1-git-send-email-awg+notmuch@xvx.ca> References: <1326009162-19524-1-git-send-email-awg+notmuch@xvx.ca> X-BeenThere: notmuch@notmuchmail.org X-Mailman-Version: 2.1.13 Precedence: list List-Id: "Use and development of the notmuch mail system." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Sun, 08 Jan 2012 07:53:13 -0000 From: Adam Wolfe Gordon This new JSON format for replies includes headers generated for a reply message as well as the headers and all text parts of the original message. Using this data, a client can intelligently create a reply. For example, the emacs client will be able to create replies with quoted HTML parts by parsing the HTML parts using w3m. --- notmuch-reply.c | 269 +++++++++++++++++++++++++++++++++++++++++++++++-------- 1 files changed, 230 insertions(+), 39 deletions(-) diff --git a/notmuch-reply.c b/notmuch-reply.c index f8d5f64..82df396 100644 --- a/notmuch-reply.c +++ b/notmuch-reply.c @@ -30,6 +30,15 @@ reply_headers_message_part (GMimeMessage *message); static void reply_part_content (GMimeObject *part); +static void +reply_part_start_json (GMimeObject *part, int *part_count); + +static void +reply_part_content_json (GMimeObject *part); + +static void +reply_part_end_json (GMimeObject *part); + static const notmuch_show_format_t format_reply = { "", "", NULL, @@ -46,6 +55,22 @@ static const notmuch_show_format_t format_reply = { "" }; +static const notmuch_show_format_t format_json = { + "", + "", NULL, + "", NULL, NULL, "", + "", + reply_part_start_json, + NULL, + NULL, + reply_part_content_json, + reply_part_end_json, + "", + "", + "", "", + "" +}; + static void show_reply_headers (GMimeMessage *message) { @@ -147,6 +172,78 @@ reply_part_content (GMimeObject *part) } } +static void +reply_part_start_json (GMimeObject *part, unused(int *part_count)) +{ + GMimeContentType *content_type = g_mime_object_get_content_type (GMIME_OBJECT (part)); + GMimeContentDisposition *disposition = g_mime_object_get_content_disposition (part); + + if (g_mime_content_type_is_type (content_type, "text", "*") && + (!disposition || + strcmp (disposition->disposition, GMIME_DISPOSITION_INLINE) == 0)) + { + printf("{ "); + } +} + +static void +reply_part_end_json (GMimeObject *part) +{ + GMimeContentType *content_type = g_mime_object_get_content_type (GMIME_OBJECT (part)); + GMimeContentDisposition *disposition = g_mime_object_get_content_disposition (part); + + if (g_mime_content_type_is_type (content_type, "text", "*") && + (!disposition || + strcmp (disposition->disposition, GMIME_DISPOSITION_INLINE) == 0)) + printf ("}, "); +} + +static void +reply_part_content_json (GMimeObject *part) +{ + GMimeContentType *content_type = g_mime_object_get_content_type (GMIME_OBJECT (part)); + GMimeContentDisposition *disposition = g_mime_object_get_content_disposition (part); + + void *ctx = talloc_new (NULL); + + /* We only care about inline text parts for reply purposes */ + if (g_mime_content_type_is_type (content_type, "text", "*") && + (!disposition || + strcmp (disposition->disposition, GMIME_DISPOSITION_INLINE) == 0)) + { + GMimeStream *stream_memory = NULL, *stream_filter = NULL; + GMimeDataWrapper *wrapper; + GByteArray *part_content; + const char *charset; + + printf("\"content-type\": %s, \"content\": ", + json_quote_str(ctx, g_mime_content_type_to_string(content_type))); + + charset = g_mime_object_get_content_type_parameter (part, "charset"); + stream_memory = g_mime_stream_mem_new (); + if (stream_memory) { + stream_filter = g_mime_stream_filter_new(stream_memory); + if (charset) { + g_mime_stream_filter_add(GMIME_STREAM_FILTER(stream_filter), + g_mime_filter_charset_new(charset, "UTF-8")); + } + } + wrapper = g_mime_part_get_content_object (GMIME_PART (part)); + if (wrapper && stream_filter) + g_mime_data_wrapper_write_to_stream (wrapper, stream_filter); + part_content = g_mime_stream_mem_get_byte_array (GMIME_STREAM_MEM (stream_memory)); + + printf("%s", json_quote_chararray(ctx, (char *) part_content->data, part_content->len)); + + if (stream_filter) + g_object_unref(stream_filter); + if (stream_memory) + g_object_unref(stream_memory); + } + + talloc_free (ctx); +} + /* Is the given address configured as one of the user's "personal" or * "other" addresses. */ static int @@ -476,6 +573,59 @@ guess_from_received_header (notmuch_config_t *config, notmuch_message_t *message return NULL; } +static GMimeMessage * +create_reply_message(void *ctx, + notmuch_config_t *config, + notmuch_message_t *message) +{ + const char *subject, *from_addr = NULL; + const char *in_reply_to, *orig_references, *references; + + /* The 1 means we want headers in a "pretty" order. */ + GMimeMessage *reply = g_mime_message_new (1); + if (reply == NULL) { + fprintf (stderr, "Out of memory\n"); + return NULL; + } + + subject = notmuch_message_get_header (message, "subject"); + if (subject) { + if (strncasecmp (subject, "Re:", 3)) + subject = talloc_asprintf (ctx, "Re: %s", subject); + g_mime_message_set_subject (reply, subject); + } + + from_addr = add_recipients_from_message (reply, config, message); + + if (from_addr == NULL) + from_addr = guess_from_received_header (config, message); + + if (from_addr == NULL) + from_addr = notmuch_config_get_user_primary_email (config); + + from_addr = talloc_asprintf (ctx, "%s <%s>", + notmuch_config_get_user_name (config), + from_addr); + g_mime_object_set_header (GMIME_OBJECT (reply), + "From", from_addr); + + in_reply_to = talloc_asprintf (ctx, "<%s>", + notmuch_message_get_message_id (message)); + + g_mime_object_set_header (GMIME_OBJECT (reply), + "In-Reply-To", in_reply_to); + + orig_references = notmuch_message_get_header (message, "references"); + references = talloc_asprintf (ctx, "%s%s%s", + orig_references ? orig_references : "", + orig_references ? " " : "", + in_reply_to); + g_mime_object_set_header (GMIME_OBJECT (reply), + "References", references); + + return reply; +} + static int notmuch_reply_format_default(void *ctx, notmuch_config_t *config, @@ -485,8 +635,6 @@ notmuch_reply_format_default(void *ctx, GMimeMessage *reply; notmuch_messages_t *messages; notmuch_message_t *message; - const char *subject, *from_addr = NULL; - const char *in_reply_to, *orig_references, *references; const notmuch_show_format_t *format = &format_reply; for (messages = notmuch_query_search_messages (query); @@ -495,61 +643,102 @@ notmuch_reply_format_default(void *ctx, { message = notmuch_messages_get (messages); - /* The 1 means we want headers in a "pretty" order. */ - reply = g_mime_message_new (1); - if (reply == NULL) { - fprintf (stderr, "Out of memory\n"); - return 1; - } + reply = create_reply_message(ctx, config, message); - subject = notmuch_message_get_header (message, "subject"); - if (subject) { - if (strncasecmp (subject, "Re:", 3)) - subject = talloc_asprintf (ctx, "Re: %s", subject); - g_mime_message_set_subject (reply, subject); - } + show_reply_headers (reply); - from_addr = add_recipients_from_message (reply, config, message); + g_object_unref (G_OBJECT (reply)); + reply = NULL; - if (from_addr == NULL) - from_addr = guess_from_received_header (config, message); + printf ("On %s, %s wrote:\n", + notmuch_message_get_header (message, "date"), + notmuch_message_get_header (message, "from")); - if (from_addr == NULL) - from_addr = notmuch_config_get_user_primary_email (config); + show_message_body (message, format, params); - from_addr = talloc_asprintf (ctx, "%s <%s>", - notmuch_config_get_user_name (config), - from_addr); - g_mime_object_set_header (GMIME_OBJECT (reply), - "From", from_addr); + notmuch_message_destroy (message); + } + return 0; +} - in_reply_to = talloc_asprintf (ctx, "<%s>", - notmuch_message_get_message_id (message)); +static int +notmuch_reply_format_json(void *ctx, + notmuch_config_t *config, + notmuch_query_t *query, + unused (notmuch_show_params_t *params)) +{ + GMimeMessage *reply; + notmuch_messages_t *messages; + notmuch_message_t *message; + const notmuch_show_format_t *format = &format_json; - g_mime_object_set_header (GMIME_OBJECT (reply), - "In-Reply-To", in_reply_to); + const char *reply_headers[] = {"from", "to", "subject", "in-reply-to", "references"}; + const int n_reply_headers = 5; + const char *orig_headers[] = {"from", "to", "cc", "subject", "date", "in-reply-to", "references"}; + const int n_orig_headers = 7; + int hidx; - orig_references = notmuch_message_get_header (message, "references"); - references = talloc_asprintf (ctx, "%s%s%s", - orig_references ? orig_references : "", - orig_references ? " " : "", - in_reply_to); - g_mime_object_set_header (GMIME_OBJECT (reply), - "References", references); + /* Start array of reply objects */ + printf ("["); - show_reply_headers (reply); + for (messages = notmuch_query_search_messages (query); + notmuch_messages_valid (messages); + notmuch_messages_move_to_next (messages)) + { + /* Start a reply object */ + printf ("{ \"reply\": { \"headers\": { "); + + message = notmuch_messages_get (messages); + + reply = create_reply_message(ctx, config, message); + + for (hidx = 0; hidx < n_reply_headers; hidx += 1) + { + if (hidx > 0) { + printf (", "); + } + + printf ("%s: %s", json_quote_str(ctx, reply_headers[hidx]), + json_quote_str (ctx, g_mime_object_get_header (GMIME_OBJECT (reply), reply_headers[hidx]))); + } g_object_unref (G_OBJECT (reply)); reply = NULL; - printf ("On %s, %s wrote:\n", - notmuch_message_get_header (message, "date"), - notmuch_message_get_header (message, "from")); + /* Done the headers for the reply, which has no body parts */ + printf ("} }"); + + /* Start the original */ + printf (", \"original\": { \"headers\": { "); + + for (hidx = 0; hidx < n_orig_headers; hidx += 1) + { + if (hidx > 0) { + printf (", "); + } + + printf ("%s: %s", json_quote_str(ctx, orig_headers[hidx]), + json_quote_str (ctx, notmuch_message_get_header (message, orig_headers[hidx]))); + } + + /* End headers */ + printf (" }, \"body\": [ "); + /* Show body parts */ show_message_body (message, format, params); notmuch_message_destroy (message); + + /* Done the original */ + printf ("{} ] }"); + + /* End the reply object. */ + printf (" }, "); } + + /* End array of reply objects */ + printf ("{} ]\n"); + return 0; } @@ -640,6 +829,8 @@ notmuch_reply_command (void *ctx, int argc, char *argv[]) reply_format_func = notmuch_reply_format_default; } else if (strcmp (opt, "headers-only") == 0) { reply_format_func = notmuch_reply_format_headers_only; + } else if (strcmp (opt, "json") == 0) { + reply_format_func = notmuch_reply_format_json; } else { fprintf (stderr, "Invalid value for --format: %s\n", opt); return 1; -- 1.7.5.4