From fa73ffc614b9966bede3250fdfe6a3755d684f5a Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Fri, 13 Jan 2012 22:08:23 -0500 Subject: Fix build warning: "/*" within comment --- notmuch-show.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'notmuch-show.c') diff --git a/notmuch-show.c b/notmuch-show.c index 87a1c90..d14dac9 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -677,7 +677,7 @@ format_part_content_json (GMimeObject *part) if (g_mime_content_type_is_type (content_type, "text", "*")) { - /* For non-HTML text/* parts, we include the content in the + /* For non-HTML text parts, we include the content in the * JSON. Since JSON must be Unicode, we handle charset * decoding here and do not report a charset to the caller. * For text/html parts, we do not include the content. If a -- cgit v1.2.3 From 18947b95cd1668d1b98f7ea4196e97b050599f7d Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Thu, 19 Jan 2012 17:29:18 -0500 Subject: show: Handle read and write errors For showing a message in raw format, rather than silently succeeding when a read or a write fails (or, probably, looping if a read fails), try to print an error message and exit with a non-zero status. This silences one of the buildbot warnings about unused results. While my libc lacks the declarations that trigger these warnings, this can be tested by adding the following to notmuch.h: __attribute__((warn_unused_result)) size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); --- notmuch-show.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) (limited to 'notmuch-show.c') diff --git a/notmuch-show.c b/notmuch-show.c index d14dac9..c674e25 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -883,7 +883,17 @@ do_show_single (void *ctx, while (!feof (file)) { size = fread (buf, 1, sizeof (buf), file); - (void) fwrite (buf, size, 1, stdout); + if (ferror (file)) { + fprintf (stderr, "Error: Read failed from %s\n", filename); + fclose (file); + return 1; + } + + if (fwrite (buf, size, 1, stdout) != 1) { + fprintf (stderr, "Error: Write failed\n"); + fclose (file); + return 1; + } } fclose (file); -- cgit v1.2.3 From 00b5623d1a21d886b564d031e30749e5d02e4ae6 Mon Sep 17 00:00:00 2001 From: Thomas Jost Date: Fri, 20 Jan 2012 10:39:24 +0100 Subject: Add compatibility with gmime 2.6 There are lots of API changes in gmime 2.6 crypto handling. By adding preprocessor directives, it is however possible to add gmime 2.6 compatibility while preserving compatibility with gmime 2.4 too. This is mostly based on id:"8762i8hrb9.fsf@bookbinder.fernseed.info". This was tested against both gmime 2.6.4 and 2.4.31. With gmime 2.4.31, the crypto tests all work fine (as expected). With gmime 2.6.4, one crypto test is currently broken (signature verification with signer key unavailable), most likely because of a bug in gmime which will hopefully be fixed in a future version. --- mime-node.c | 57 ++++++++++++++++++++++++++++++++-- notmuch-client.h | 30 ++++++++++++++++-- notmuch-reply.c | 7 +++++ notmuch-show.c | 95 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ show-message.c | 4 +++ test/crypto | 2 ++ 6 files changed, 191 insertions(+), 4 deletions(-) (limited to 'notmuch-show.c') diff --git a/mime-node.c b/mime-node.c index d26bb44..27077f7 100644 --- a/mime-node.c +++ b/mime-node.c @@ -33,7 +33,11 @@ typedef struct mime_node_context { GMimeMessage *mime_message; /* Context provided by the caller. */ +#ifdef GMIME_ATLEAST_26 + GMimeCryptoContext *cryptoctx; +#else GMimeCipherContext *cryptoctx; +#endif notmuch_bool_t decrypt; } mime_node_context_t; @@ -57,8 +61,12 @@ _mime_node_context_free (mime_node_context_t *res) notmuch_status_t mime_node_open (const void *ctx, notmuch_message_t *message, - GMimeCipherContext *cryptoctx, notmuch_bool_t decrypt, - mime_node_t **root_out) +#ifdef GMIME_ATLEAST_26 + GMimeCryptoContext *cryptoctx, +#else + GMimeCipherContext *cryptoctx, +#endif + notmuch_bool_t decrypt, mime_node_t **root_out) { const char *filename = notmuch_message_get_filename (message); mime_node_context_t *mctx; @@ -112,12 +120,21 @@ DONE: return status; } +#ifdef GMIME_ATLEAST_26 +static int +_signature_list_free (GMimeSignatureList **proxy) +{ + g_object_unref (*proxy); + return 0; +} +#else static int _signature_validity_free (GMimeSignatureValidity **proxy) { g_mime_signature_validity_free (*proxy); return 0; } +#endif static mime_node_t * _mime_node_create (const mime_node_t *parent, GMimeObject *part) @@ -165,11 +182,25 @@ _mime_node_create (const mime_node_t *parent, GMimeObject *part) GMimeMultipartEncrypted *encrypteddata = GMIME_MULTIPART_ENCRYPTED (part); node->decrypt_attempted = TRUE; +#ifdef GMIME_ATLEAST_26 + GMimeDecryptResult *decrypt_result = NULL; + node->decrypted_child = g_mime_multipart_encrypted_decrypt + (encrypteddata, node->ctx->cryptoctx, &decrypt_result, &err); +#else node->decrypted_child = g_mime_multipart_encrypted_decrypt (encrypteddata, node->ctx->cryptoctx, &err); +#endif if (node->decrypted_child) { node->decrypt_success = node->verify_attempted = TRUE; +#ifdef GMIME_ATLEAST_26 + /* This may be NULL if the part is not signed. */ + node->sig_list = g_mime_decrypt_result_get_signatures (decrypt_result); + if (node->sig_list) + g_object_ref (node->sig_list); + g_object_unref (decrypt_result); +#else node->sig_validity = g_mime_multipart_encrypted_get_signature_validity (encrypteddata); +#endif } else { fprintf (stderr, "Failed to decrypt part: %s\n", (err ? err->message : "no error explanation given")); @@ -182,6 +213,15 @@ _mime_node_create (const mime_node_t *parent, GMimeObject *part) "(must be exactly 2)\n", node->nchildren); } else { +#ifdef GMIME_ATLEAST_26 + node->sig_list = g_mime_multipart_signed_verify + (GMIME_MULTIPART_SIGNED (part), node->ctx->cryptoctx, &err); + node->verify_attempted = TRUE; + + if (!node->sig_list) + fprintf (stderr, "Failed to verify signed part: %s\n", + (err ? err->message : "no error explanation given")); +#else /* For some reason the GMimeSignatureValidity returned * here is not a const (inconsistent with that * returned by @@ -200,12 +240,25 @@ _mime_node_create (const mime_node_t *parent, GMimeObject *part) *proxy = sig_validity; talloc_set_destructor (proxy, _signature_validity_free); } +#endif } } +#ifdef GMIME_ATLEAST_26 + /* sig_list may be created in both above cases, so we need to + * cleanly handle it here. */ + if (node->sig_list) { + GMimeSignatureList **proxy = talloc (node, GMimeSignatureList *); + *proxy = node->sig_list; + talloc_set_destructor (proxy, _signature_list_free); + } +#endif + +#ifndef GMIME_ATLEAST_26 if (node->verify_attempted && !node->sig_validity) fprintf (stderr, "Failed to verify signed part: %s\n", (err ? err->message : "no error explanation given")); +#endif if (err) g_error_free (err); diff --git a/notmuch-client.h b/notmuch-client.h index 62ede28..9c1d383 100644 --- a/notmuch-client.h +++ b/notmuch-client.h @@ -30,6 +30,14 @@ #include +/* GMIME_CHECK_VERSION in gmime 2.4 is not usable from the + * preprocessor (it calls a runtime function). But since + * GMIME_MAJOR_VERSION and friends were added in gmime 2.6, we can use + * these to check the version number. */ +#ifdef GMIME_MAJOR_VERSION +#define GMIME_ATLEAST_26 +#endif + #include "notmuch.h" /* This is separate from notmuch-private.h because we're trying to @@ -69,7 +77,11 @@ typedef struct notmuch_show_format { void (*part_start) (GMimeObject *part, int *part_count); void (*part_encstatus) (int status); +#ifdef GMIME_ATLEAST_26 + void (*part_sigstatus) (GMimeSignatureList* siglist); +#else void (*part_sigstatus) (const GMimeSignatureValidity* validity); +#endif void (*part_content) (GMimeObject *part); void (*part_end) (GMimeObject *part); const char *part_sep; @@ -83,7 +95,11 @@ typedef struct notmuch_show_params { int entire_thread; int raw; int part; +#ifdef GMIME_ATLEAST_26 + GMimeCryptoContext* cryptoctx; +#else GMimeCipherContext* cryptoctx; +#endif int decrypt; } notmuch_show_params_t; @@ -290,11 +306,17 @@ typedef struct mime_node { /* True if signature verification on this part was attempted. */ notmuch_bool_t verify_attempted; +#ifdef GMIME_ATLEAST_26 + /* The list of signatures for signed or encrypted containers. If + * there are no signatures, this will be NULL. */ + GMimeSignatureList* sig_list; +#else /* For signed or encrypted containers, the validity of the * signature. May be NULL if signature verification failed. If * there are simply no signatures, this will be non-NULL with an * empty signers list. */ const GMimeSignatureValidity *sig_validity; +#endif /* Internal: Context inherited from the root iterator. */ struct mime_node_context *ctx; @@ -319,8 +341,12 @@ typedef struct mime_node { */ notmuch_status_t mime_node_open (const void *ctx, notmuch_message_t *message, - GMimeCipherContext *cryptoctx, notmuch_bool_t decrypt, - mime_node_t **node_out); +#ifdef GMIME_ATLEAST_26 + GMimeCryptoContext *cryptoctx, +#else + GMimeCipherContext *cryptoctx, +#endif + notmuch_bool_t decrypt, mime_node_t **node_out); /* Return a new MIME node for the requested child part of parent. * parent will be used as the talloc context for the returned child diff --git a/notmuch-reply.c b/notmuch-reply.c index 0f682db..bf67960 100644 --- a/notmuch-reply.c +++ b/notmuch-reply.c @@ -688,15 +688,22 @@ notmuch_reply_command (void *ctx, int argc, char *argv[]) reply_format_func = notmuch_reply_format_default; if (decrypt) { +#ifdef GMIME_ATLEAST_26 + /* TODO: GMimePasswordRequestFunc */ + params.cryptoctx = g_mime_gpg_context_new (NULL, "gpg"); +#else GMimeSession* session = g_object_new (g_mime_session_get_type(), NULL); params.cryptoctx = g_mime_gpg_context_new (session, "gpg"); +#endif if (params.cryptoctx) { g_mime_gpg_context_set_always_trust ((GMimeGpgContext*) params.cryptoctx, FALSE); params.decrypt = TRUE; } else { fprintf (stderr, "Failed to construct gpg context.\n"); } +#ifndef GMIME_ATLEAST_26 g_object_unref (session); +#endif } config = notmuch_config_open (ctx, NULL, NULL); diff --git a/notmuch-show.c b/notmuch-show.c index c674e25..43ee211 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -76,7 +76,11 @@ static void format_part_encstatus_json (int status); static void +#ifdef GMIME_ATLEAST_26 +format_part_sigstatus_json (GMimeSignatureList* siglist); +#else format_part_sigstatus_json (const GMimeSignatureValidity* validity); +#endif static void format_part_content_json (GMimeObject *part); @@ -486,6 +490,21 @@ show_text_part_content (GMimeObject *part, GMimeStream *stream_out) g_object_unref(stream_filter); } +#ifdef GMIME_ATLEAST_26 +static const char* +signature_status_to_string (GMimeSignatureStatus x) +{ + switch (x) { + case GMIME_SIGNATURE_STATUS_GOOD: + return "good"; + case GMIME_SIGNATURE_STATUS_BAD: + return "bad"; + case GMIME_SIGNATURE_STATUS_ERROR: + return "error"; + } + return "unknown"; +} +#else static const char* signer_status_to_string (GMimeSignerStatus x) { @@ -501,6 +520,7 @@ signer_status_to_string (GMimeSignerStatus x) } return "unknown"; } +#endif static void format_part_start_text (GMimeObject *part, int *part_count) @@ -592,6 +612,73 @@ format_part_encstatus_json (int status) printf ("}]"); } +#ifdef GMIME_ATLEAST_26 +static void +format_part_sigstatus_json (GMimeSignatureList *siglist) +{ + printf (", \"sigstatus\": ["); + + if (!siglist) { + printf ("]"); + return; + } + + void *ctx_quote = talloc_new (NULL); + int i; + for (i = 0; i < g_mime_signature_list_length (siglist); i++) { + GMimeSignature *signature = g_mime_signature_list_get_signature (siglist, i); + + if (i > 0) + printf (", "); + + printf ("{"); + + /* status */ + GMimeSignatureStatus status = g_mime_signature_get_status (signature); + printf ("\"status\": %s", + json_quote_str (ctx_quote, + signature_status_to_string (status))); + + GMimeCertificate *certificate = g_mime_signature_get_certificate (signature); + if (status == GMIME_SIGNATURE_STATUS_GOOD) { + if (certificate) + printf (", \"fingerprint\": %s", json_quote_str (ctx_quote, g_mime_certificate_get_fingerprint (certificate))); + /* these dates are seconds since the epoch; should we + * provide a more human-readable format string? */ + time_t created = g_mime_signature_get_created (signature); + if (created != -1) + printf (", \"created\": %d", (int) created); + time_t expires = g_mime_signature_get_expires (signature); + if (expires > 0) + printf (", \"expires\": %d", (int) expires); + /* output user id only if validity is FULL or ULTIMATE. */ + /* note that gmime is using the term "trust" here, which + * is WRONG. It's actually user id "validity". */ + if (certificate) { + const char *name = g_mime_certificate_get_name (certificate); + GMimeCertificateTrust trust = g_mime_certificate_get_trust (certificate); + if (name && (trust == GMIME_CERTIFICATE_TRUST_FULLY || trust == GMIME_CERTIFICATE_TRUST_ULTIMATE)) + printf (", \"userid\": %s", json_quote_str (ctx_quote, name)); + } + } else if (certificate) { + const char *key_id = g_mime_certificate_get_key_id (certificate); + if (key_id) + printf (", \"keyid\": %s", json_quote_str (ctx_quote, key_id)); + } + + GMimeSignatureError errors = g_mime_signature_get_errors (signature); + if (errors != GMIME_SIGNATURE_ERROR_NONE) { + printf (", \"errors\": %d", errors); + } + + printf ("}"); + } + + printf ("]"); + + talloc_free (ctx_quote); +} +#else static void format_part_sigstatus_json (const GMimeSignatureValidity* validity) { @@ -652,6 +739,7 @@ format_part_sigstatus_json (const GMimeSignatureValidity* validity) talloc_free (ctx_quote); } +#endif static void format_part_content_json (GMimeObject *part) @@ -1000,13 +1088,20 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) } else if ((STRNCMP_LITERAL (argv[i], "--verify") == 0) || (STRNCMP_LITERAL (argv[i], "--decrypt") == 0)) { if (params.cryptoctx == NULL) { +#ifdef GMIME_ATLEAST_26 + /* TODO: GMimePasswordRequestFunc */ + if (NULL == (params.cryptoctx = g_mime_gpg_context_new(NULL, "gpg"))) +#else GMimeSession* session = g_object_new(g_mime_session_get_type(), NULL); if (NULL == (params.cryptoctx = g_mime_gpg_context_new(session, "gpg"))) +#endif fprintf (stderr, "Failed to construct gpg context.\n"); else g_mime_gpg_context_set_always_trust((GMimeGpgContext*)params.cryptoctx, FALSE); +#ifndef GMIME_ATLEAST_26 g_object_unref (session); session = NULL; +#endif } if (STRNCMP_LITERAL (argv[i], "--decrypt") == 0) params.decrypt = 1; diff --git a/show-message.c b/show-message.c index 8768889..83ecf81 100644 --- a/show-message.c +++ b/show-message.c @@ -48,7 +48,11 @@ show_message_part (mime_node_t *node, format->part_encstatus (node->decrypt_success); if (node->verify_attempted && format->part_sigstatus) +#ifdef GMIME_ATLEAST_26 + format->part_sigstatus (node->sig_list); +#else format->part_sigstatus (node->sig_validity); +#endif format->part_content (part); diff --git a/test/crypto b/test/crypto index 0af4aa8..446a58b 100755 --- a/test/crypto +++ b/test/crypto @@ -104,6 +104,8 @@ test_expect_equal \ "$expected" test_begin_subtest "signature verification with signer key unavailable" +# this is broken with current versions of gmime-2.6 +(ldd $(which notmuch) | grep -Fq gmime-2.6) && test_subtest_known_broken # move the gnupghome temporarily out of the way mv "${GNUPGHOME}"{,.bak} output=$(notmuch show --format=json --verify subject:"test signed message 001" \ -- cgit v1.2.3 From 3f42e87030cf9a30f65ae082af2a53280f6c9100 Mon Sep 17 00:00:00 2001 From: Thomas Jost Date: Sun, 22 Jan 2012 01:20:57 +0100 Subject: show: don't use hex literals in JSON output JSON does not support hex literals (0x..) so numbers must be formatted as %d instead of %x. Currently, the possible values for the gmime error code are 1 (expired signature), 2 (no public key), 4 (expired key) and 8 (revoked key). The other possible value is 16 (unsupported algorithm) but obviously it is much more rare. If this happens, the current code will add '"errors": 10'. This is valid JSON (it looks like a decimal number) but it is incorrect (should be 16, not 10). Since this is just an issue in the JSON encoder, no changes are needed on the Emacs side (or in other UIs using the JSON output). --- notmuch-show.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'notmuch-show.c') diff --git a/notmuch-show.c b/notmuch-show.c index 43ee211..7b40568 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -728,7 +728,7 @@ format_part_sigstatus_json (const GMimeSignatureValidity* validity) printf (", \"keyid\": %s", json_quote_str (ctx_quote, signer->keyid)); } if (signer->errors != GMIME_SIGNER_ERROR_NONE) { - printf (", \"errors\": %x", signer->errors); + printf (", \"errors\": %d", signer->errors); } printf ("}"); -- cgit v1.2.3 From 0bd09f844677ba361318d129bd01e21b0f7c1bd9 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Sun, 22 Jan 2012 21:31:12 -0500 Subject: show: Use consistent header ordering in the text format Previously, top-level message headers were printed as Subject, From, To, Date, while embedded message headers were printed From, To, Subject, Date. This makes both cases use the former order and updates the tests accordingly. Strangely, the raw format also uses this function, so this also fixes the two raw format tests affected by this change. --- notmuch-show.c | 2 +- test/multipart | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) (limited to 'notmuch-show.c') diff --git a/notmuch-show.c b/notmuch-show.c index 7b40568..682aa71 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -364,6 +364,7 @@ format_headers_message_part_text (GMimeMessage *message) InternetAddressList *recipients; const char *recipients_string; + printf ("Subject: %s\n", g_mime_message_get_subject (message)); printf ("From: %s\n", g_mime_message_get_sender (message)); recipients = g_mime_message_get_recipients (message, GMIME_RECIPIENT_TYPE_TO); recipients_string = internet_address_list_to_string (recipients, 0); @@ -375,7 +376,6 @@ format_headers_message_part_text (GMimeMessage *message) if (recipients_string) printf ("Cc: %s\n", recipients_string); - printf ("Subject: %s\n", g_mime_message_get_subject (message)); printf ("Date: %s\n", g_mime_message_get_date_as_string (message)); } diff --git a/test/multipart b/test/multipart index f83526b..2dd73f5 100755 --- a/test/multipart +++ b/test/multipart @@ -121,9 +121,9 @@ Date: Fri, 05 Jan 2001 15:43:57 +0000 part{ ID: 2, Content-type: multipart/mixed part{ ID: 3, Content-type: message/rfc822 header{ +Subject: html message From: Carl Worth To: cworth@cworth.org -Subject: html message Date: Fri, 05 Jan 2001 15:42:57 +0000 header} body{ @@ -162,9 +162,9 @@ cat <EXPECTED part{ ID: 2, Content-type: multipart/mixed part{ ID: 3, Content-type: message/rfc822 header{ +Subject: html message From: Carl Worth To: cworth@cworth.org -Subject: html message Date: Fri, 05 Jan 2001 15:42:57 +0000 header} body{ @@ -200,9 +200,9 @@ cat <EXPECTED part{ ID: 2, Content-type: multipart/mixed part{ ID: 3, Content-type: message/rfc822 header{ +Subject: html message From: Carl Worth To: cworth@cworth.org -Subject: html message Date: Fri, 05 Jan 2001 15:42:57 +0000 header} body{ @@ -233,9 +233,9 @@ notmuch show --format=text --part=3 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OU cat <EXPECTED part{ ID: 3, Content-type: message/rfc822 header{ +Subject: html message From: Carl Worth To: cworth@cworth.org -Subject: html message Date: Fri, 05 Jan 2001 15:42:57 +0000 header} body{ @@ -452,9 +452,9 @@ notmuch show --format=raw --part=1 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUT # output should *not* include newline echo >>OUTPUT cat <EXPECTED +Subject: html message From: Carl Worth To: cworth@cworth.org -Subject: html message Date: Fri, 05 Jan 2001 15:42:57 +0000

This is an embedded message, with a multipart/alternative part.

@@ -476,9 +476,9 @@ test_expect_equal_file OUTPUT EXPECTED test_begin_subtest "--format=raw --part=2, multipart/mixed" notmuch show --format=raw --part=2 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT cat <EXPECTED +Subject: html message From: Carl Worth To: cworth@cworth.org -Subject: html message Date: Fri, 05 Jan 2001 15:42:57 +0000

This is an embedded message, with a multipart/alternative part.

-- cgit v1.2.3 From 7430a42e23ee775818f84ed75f417302da694152 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Mon, 23 Jan 2012 18:33:10 -0500 Subject: show: Introduce mime_node formatter callback This callback is the gateway to the new mime_node_t-based formatters. This maintains backwards compatibility so the formatters can be transitioned one at a time. Once all formatters are converted, the formatter structure can be reduced to only message_set_{start,sep,end} and part, most of show_message can be deleted, and all of show-message.c can be deleted. --- notmuch-client.h | 6 ++++++ notmuch-reply.c | 2 +- notmuch-show.c | 21 +++++++++++++++++---- 3 files changed, 24 insertions(+), 5 deletions(-) (limited to 'notmuch-show.c') diff --git a/notmuch-client.h b/notmuch-client.h index 70f2336..e0eb594 100644 --- a/notmuch-client.h +++ b/notmuch-client.h @@ -62,8 +62,14 @@ #define STRINGIFY(s) STRINGIFY_(s) #define STRINGIFY_(s) #s +struct mime_node; +struct notmuch_show_params; + typedef struct notmuch_show_format { const char *message_set_start; + void (*part) (const void *ctx, + struct mime_node *node, int indent, + const struct notmuch_show_params *params); const char *message_start; void (*message) (const void *ctx, notmuch_message_t *message, diff --git a/notmuch-reply.c b/notmuch-reply.c index bf67960..f55b1d2 100644 --- a/notmuch-reply.c +++ b/notmuch-reply.c @@ -31,7 +31,7 @@ static void reply_part_content (GMimeObject *part); static const notmuch_show_format_t format_reply = { - "", + "", NULL, "", NULL, "", NULL, reply_headers_message_part, ">\n", "", diff --git a/notmuch-show.c b/notmuch-show.c index 682aa71..dec799c 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -42,7 +42,7 @@ static void format_part_end_text (GMimeObject *part); static const notmuch_show_format_t format_text = { - "", + "", NULL, "\fmessage{ ", format_message_text, "\fheader{\n", format_headers_text, format_headers_message_part_text, "\fheader}\n", "\fbody{\n", @@ -89,7 +89,7 @@ static void format_part_end_json (GMimeObject *part); static const notmuch_show_format_t format_json = { - "[", + "[", NULL, "{", format_message_json, "\"headers\": {", format_headers_json, format_headers_message_part_json, "}", ", \"body\": [", @@ -110,7 +110,7 @@ format_message_mbox (const void *ctx, unused (int indent)); static const notmuch_show_format_t format_mbox = { - "", + "", NULL, "", format_message_mbox, "", NULL, NULL, "", "", @@ -129,7 +129,7 @@ static void format_part_content_raw (GMimeObject *part); static const notmuch_show_format_t format_raw = { - "", + "", NULL, "", NULL, "", NULL, format_headers_message_part_text, "\n", "", @@ -850,6 +850,19 @@ show_message (void *ctx, int indent, notmuch_show_params_t *params) { + if (format->part) { + void *local = talloc_new (ctx); + mime_node_t *root, *part; + + if (mime_node_open (local, message, params->cryptoctx, params->decrypt, + &root) == NOTMUCH_STATUS_SUCCESS && + (part = mime_node_seek_dfs (root, (params->part < 0 ? + 0 : params->part)))) + format->part (local, part, indent, params); + talloc_free (local); + return; + } + if (params->part <= 0) { fputs (format->message_start, stdout); if (format->message) -- cgit v1.2.3 From 85fe286b85bda6cddf509308b967f13f44f01331 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Sat, 4 Feb 2012 16:24:25 -0500 Subject: show: Convert text format to the new self-recursive style This is all code movement and a smidgen of glue. This moves the existing text formatter code into one self-recursive function, but doesn't change any of the logic. The next patch will actually take advantage of what the new structure has to offer. Note that this patch retains format_headers_message_part_text because it is also used by the raw format. --- notmuch-show.c | 270 +++++++++++++++++++++++++++++---------------------------- 1 file changed, 139 insertions(+), 131 deletions(-) (limited to 'notmuch-show.c') diff --git a/notmuch-show.c b/notmuch-show.c index dec799c..6a890b2 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -20,41 +20,18 @@ #include "notmuch-client.h" -static void -format_message_text (unused (const void *ctx), - notmuch_message_t *message, - int indent); -static void -format_headers_text (const void *ctx, - notmuch_message_t *message); - static void format_headers_message_part_text (GMimeMessage *message); static void -format_part_start_text (GMimeObject *part, - int *part_count); - -static void -format_part_content_text (GMimeObject *part); - -static void -format_part_end_text (GMimeObject *part); +format_part_text (const void *ctx, mime_node_t *node, + int indent, const notmuch_show_params_t *params); static const notmuch_show_format_t format_text = { - "", NULL, - "\fmessage{ ", format_message_text, - "\fheader{\n", format_headers_text, format_headers_message_part_text, "\fheader}\n", - "\fbody{\n", - format_part_start_text, - NULL, - NULL, - format_part_content_text, - format_part_end_text, - "", - "\fbody}\n", - "\fmessage}\n", "", - "" + .message_set_start = "", + .part = format_part_text, + .message_set_sep = "", + .message_set_end = "" }; static void @@ -190,16 +167,6 @@ _get_one_line_summary (const void *ctx, notmuch_message_t *message) from, relative_date, tags); } -static void -format_message_text (unused (const void *ctx), notmuch_message_t *message, int indent) -{ - printf ("id:%s depth:%d match:%d filename:%s\n", - notmuch_message_get_message_id (message), - indent, - notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH), - notmuch_message_get_filename (message)); -} - static void format_message_json (const void *ctx, notmuch_message_t *message, unused (int indent)) { @@ -338,26 +305,6 @@ format_message_mbox (const void *ctx, fclose (file); } - -static void -format_headers_text (const void *ctx, notmuch_message_t *message) -{ - const char *headers[] = { - "Subject", "From", "To", "Cc", "Bcc", "Date" - }; - const char *name, *value; - unsigned int i; - - printf ("%s\n", _get_one_line_summary (ctx, message)); - - for (i = 0; i < ARRAY_SIZE (headers); i++) { - name = headers[i]; - value = notmuch_message_get_header (message, name); - if (value && strlen (value)) - printf ("%s: %s\n", name, value); - } -} - static void format_headers_message_part_text (GMimeMessage *message) { @@ -522,78 +469,6 @@ signer_status_to_string (GMimeSignerStatus x) } #endif -static void -format_part_start_text (GMimeObject *part, int *part_count) -{ - GMimeContentDisposition *disposition = g_mime_object_get_content_disposition (part); - - if (disposition && - strcmp (disposition->disposition, GMIME_DISPOSITION_ATTACHMENT) == 0) - { - printf ("\fattachment{ ID: %d", *part_count); - - } else { - - printf ("\fpart{ ID: %d", *part_count); - } -} - -static void -format_part_content_text (GMimeObject *part) -{ - const char *cid = g_mime_object_get_content_id (part); - GMimeContentType *content_type = g_mime_object_get_content_type (GMIME_OBJECT (part)); - - if (GMIME_IS_PART (part)) - { - const char *filename = g_mime_part_get_filename (GMIME_PART (part)); - if (filename) - printf (", Filename: %s", filename); - } - - if (cid) - printf (", Content-id: %s", cid); - - printf (", Content-type: %s\n", g_mime_content_type_to_string (content_type)); - - if (g_mime_content_type_is_type (content_type, "text", "*") && - !g_mime_content_type_is_type (content_type, "text", "html")) - { - GMimeStream *stream_stdout = g_mime_stream_file_new (stdout); - g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream_stdout), FALSE); - show_text_part_content (part, stream_stdout); - g_object_unref(stream_stdout); - } - else if (g_mime_content_type_is_type (content_type, "multipart", "*") || - g_mime_content_type_is_type (content_type, "message", "rfc822")) - { - /* Do nothing for multipart since its content will be printed - * when recursing. */ - } - else - { - printf ("Non-text part: %s\n", - g_mime_content_type_to_string (content_type)); - } -} - -static void -format_part_end_text (GMimeObject *part) -{ - GMimeContentDisposition *disposition; - - disposition = g_mime_object_get_content_disposition (part); - if (disposition && - strcmp (disposition->disposition, GMIME_DISPOSITION_ATTACHMENT) == 0) - { - printf ("\fattachment}\n"); - } - else - { - printf ("\fpart}\n"); - } -} - static void format_part_start_json (unused (GMimeObject *part), int *part_count) { @@ -843,6 +718,139 @@ format_part_content_raw (GMimeObject *part) g_object_unref(stream_stdout); } +static void +format_part_text (const void *ctx, mime_node_t *node, + int indent, const notmuch_show_params_t *params) +{ + /* The disposition and content-type metadata are associated with + * the envelope for message parts */ + GMimeObject *meta = node->envelope_part ? + GMIME_OBJECT (node->envelope_part) : node->part; + GMimeContentType *content_type = g_mime_object_get_content_type (meta); + int i; + + if (node->envelope_file) { + notmuch_message_t *message = node->envelope_file; + const char *headers[] = { + "Subject", "From", "To", "Cc", "Bcc", "Date" + }; + const char *name, *value; + unsigned int i; + + printf ("\fmessage{ "); + printf ("id:%s depth:%d match:%d filename:%s\n", + notmuch_message_get_message_id (message), + indent, + notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH), + notmuch_message_get_filename (message)); + + printf ("\fheader{\n"); + + printf ("%s\n", _get_one_line_summary (ctx, message)); + + for (i = 0; i < ARRAY_SIZE (headers); i++) { + name = headers[i]; + value = notmuch_message_get_header (message, name); + if (value && strlen (value)) + printf ("%s: %s\n", name, value); + } + printf ("\fheader}\n"); + } else { + GMimeContentDisposition *disposition = g_mime_object_get_content_disposition (meta); + const char *cid = g_mime_object_get_content_id (meta); + + if (disposition && + strcmp (disposition->disposition, GMIME_DISPOSITION_ATTACHMENT) == 0) + { + printf ("\fattachment{ ID: %d", node->part_num); + + } else { + + printf ("\fpart{ ID: %d", node->part_num); + } + + if (GMIME_IS_PART (node->part)) + { + const char *filename = g_mime_part_get_filename (GMIME_PART (node->part)); + if (filename) + printf (", Filename: %s", filename); + } + + if (cid) + printf (", Content-id: %s", cid); + + printf (", Content-type: %s\n", g_mime_content_type_to_string (content_type)); + } + + if (node->envelope_part) { + GMimeMessage *message = GMIME_MESSAGE (node->part); + InternetAddressList *recipients; + const char *recipients_string; + + printf ("\fheader{\n"); + printf ("Subject: %s\n", g_mime_message_get_subject (message)); + printf ("From: %s\n", g_mime_message_get_sender (message)); + recipients = g_mime_message_get_recipients (message, GMIME_RECIPIENT_TYPE_TO); + recipients_string = internet_address_list_to_string (recipients, 0); + if (recipients_string) + printf ("To: %s\n", recipients_string); + recipients = g_mime_message_get_recipients (message, GMIME_RECIPIENT_TYPE_CC); + recipients_string = internet_address_list_to_string (recipients, 0); + if (recipients_string) + printf ("Cc: %s\n", recipients_string); + printf ("Date: %s\n", g_mime_message_get_date_as_string (message)); + printf ("\fheader}\n"); + } + + if (!node->envelope_file) { + if (g_mime_content_type_is_type (content_type, "text", "*") && + !g_mime_content_type_is_type (content_type, "text", "html")) + { + GMimeStream *stream_stdout = g_mime_stream_file_new (stdout); + g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream_stdout), FALSE); + show_text_part_content (node->part, stream_stdout); + g_object_unref(stream_stdout); + } + else if (g_mime_content_type_is_type (content_type, "multipart", "*") || + g_mime_content_type_is_type (content_type, "message", "rfc822")) + { + /* Do nothing for multipart since its content will be printed + * when recursing. */ + } + else + { + printf ("Non-text part: %s\n", + g_mime_content_type_to_string (content_type)); + } + } + + if (GMIME_IS_MESSAGE (node->part)) + printf ("\fbody{\n"); + + for (i = 0; i < node->nchildren; i++) + format_part_text (ctx, mime_node_child (node, i), indent, params); + + if (GMIME_IS_MESSAGE (node->part)) + printf ("\fbody}\n"); + + if (node->envelope_file) { + printf ("\fmessage}\n"); + } else { + GMimeContentDisposition *disposition; + + disposition = g_mime_object_get_content_disposition (meta); + if (disposition && + strcmp (disposition->disposition, GMIME_DISPOSITION_ATTACHMENT) == 0) + { + printf ("\fattachment}\n"); + } + else + { + printf ("\fpart}\n"); + } + } +} + static void show_message (void *ctx, const notmuch_show_format_t *format, -- cgit v1.2.3 From c0cd09041208abb6d641279c9ae453e6f2fdf4db Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Sat, 4 Feb 2012 16:24:26 -0500 Subject: show: Simplify new text formatter code This makes the text formatter take advantage of the new code structure. The previously duplicated header logic is now unified, several things that we used to compute repeatedly across different callbacks are now computed once, and the code is simpler overall and 32% shorter. Unifying the header logic causes this to format some dates slightly differently, so the two affected test cases are updated. --- notmuch-show.c | 87 ++++++++++++++---------------------------------------- test/crypto | 2 +- test/thread-naming | 16 +++++----- 3 files changed, 31 insertions(+), 74 deletions(-) (limited to 'notmuch-show.c') diff --git a/notmuch-show.c b/notmuch-show.c index 6a890b2..816e0f8 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -727,67 +727,48 @@ format_part_text (const void *ctx, mime_node_t *node, GMimeObject *meta = node->envelope_part ? GMIME_OBJECT (node->envelope_part) : node->part; GMimeContentType *content_type = g_mime_object_get_content_type (meta); + const notmuch_bool_t leaf = GMIME_IS_PART (node->part); + const char *part_type; int i; if (node->envelope_file) { notmuch_message_t *message = node->envelope_file; - const char *headers[] = { - "Subject", "From", "To", "Cc", "Bcc", "Date" - }; - const char *name, *value; - unsigned int i; - - printf ("\fmessage{ "); - printf ("id:%s depth:%d match:%d filename:%s\n", + + part_type = "message"; + printf ("\f%s{ id:%s depth:%d match:%d filename:%s\n", + part_type, notmuch_message_get_message_id (message), indent, notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH), notmuch_message_get_filename (message)); - - printf ("\fheader{\n"); - - printf ("%s\n", _get_one_line_summary (ctx, message)); - - for (i = 0; i < ARRAY_SIZE (headers); i++) { - name = headers[i]; - value = notmuch_message_get_header (message, name); - if (value && strlen (value)) - printf ("%s: %s\n", name, value); - } - printf ("\fheader}\n"); } else { GMimeContentDisposition *disposition = g_mime_object_get_content_disposition (meta); const char *cid = g_mime_object_get_content_id (meta); + const char *filename = leaf ? + g_mime_part_get_filename (GMIME_PART (node->part)) : NULL; if (disposition && strcmp (disposition->disposition, GMIME_DISPOSITION_ATTACHMENT) == 0) - { - printf ("\fattachment{ ID: %d", node->part_num); - - } else { - - printf ("\fpart{ ID: %d", node->part_num); - } - - if (GMIME_IS_PART (node->part)) - { - const char *filename = g_mime_part_get_filename (GMIME_PART (node->part)); - if (filename) - printf (", Filename: %s", filename); - } + part_type = "attachment"; + else + part_type = "part"; + printf ("\f%s{ ID: %d", part_type, node->part_num); + if (filename) + printf (", Filename: %s", filename); if (cid) printf (", Content-id: %s", cid); - printf (", Content-type: %s\n", g_mime_content_type_to_string (content_type)); } - if (node->envelope_part) { + if (GMIME_IS_MESSAGE (node->part)) { GMimeMessage *message = GMIME_MESSAGE (node->part); InternetAddressList *recipients; const char *recipients_string; printf ("\fheader{\n"); + if (node->envelope_file) + printf ("%s\n", _get_one_line_summary (ctx, node->envelope_file)); printf ("Subject: %s\n", g_mime_message_get_subject (message)); printf ("From: %s\n", g_mime_message_get_sender (message)); recipients = g_mime_message_get_recipients (message, GMIME_RECIPIENT_TYPE_TO); @@ -800,9 +781,11 @@ format_part_text (const void *ctx, mime_node_t *node, printf ("Cc: %s\n", recipients_string); printf ("Date: %s\n", g_mime_message_get_date_as_string (message)); printf ("\fheader}\n"); + + printf ("\fbody{\n"); } - if (!node->envelope_file) { + if (leaf) { if (g_mime_content_type_is_type (content_type, "text", "*") && !g_mime_content_type_is_type (content_type, "text", "html")) { @@ -810,45 +793,19 @@ format_part_text (const void *ctx, mime_node_t *node, g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream_stdout), FALSE); show_text_part_content (node->part, stream_stdout); g_object_unref(stream_stdout); - } - else if (g_mime_content_type_is_type (content_type, "multipart", "*") || - g_mime_content_type_is_type (content_type, "message", "rfc822")) - { - /* Do nothing for multipart since its content will be printed - * when recursing. */ - } - else - { + } else { printf ("Non-text part: %s\n", g_mime_content_type_to_string (content_type)); } } - if (GMIME_IS_MESSAGE (node->part)) - printf ("\fbody{\n"); - for (i = 0; i < node->nchildren; i++) format_part_text (ctx, mime_node_child (node, i), indent, params); if (GMIME_IS_MESSAGE (node->part)) printf ("\fbody}\n"); - if (node->envelope_file) { - printf ("\fmessage}\n"); - } else { - GMimeContentDisposition *disposition; - - disposition = g_mime_object_get_content_disposition (meta); - if (disposition && - strcmp (disposition->disposition, GMIME_DISPOSITION_ATTACHMENT) == 0) - { - printf ("\fattachment}\n"); - } - else - { - printf ("\fpart}\n"); - } - } + printf ("\f%s}\n", part_type); } static void diff --git a/test/crypto b/test/crypto index 446a58b..1dbb60a 100755 --- a/test/crypto +++ b/test/crypto @@ -159,7 +159,7 @@ Notmuch Test Suite (2000-01-01) (encrypted inbox) Subject: test encrypted message 001 From: Notmuch Test Suite To: test_suite@notmuchmail.org -Date: 01 Jan 2000 12:00:00 -0000 +Date: Sat, 01 Jan 2000 12:00:00 +0000 header} body{ part{ ID: 1, Content-type: multipart/encrypted diff --git a/test/thread-naming b/test/thread-naming index 2ce9216..942e593 100755 --- a/test/thread-naming +++ b/test/thread-naming @@ -71,7 +71,7 @@ Notmuch Test Suite (2001-01-05) (unread) Subject: thread-naming: Initial thread subject From: Notmuch Test Suite To: Notmuch Test Suite -Date: Fri, 05 Jan 2001 15:43:56 -0000 +Date: Fri, 05 Jan 2001 15:43:56 +0000 header} body{ part{ ID: 1, Content-type: text/plain @@ -85,7 +85,7 @@ Notmuch Test Suite (2001-01-06) (inbox unread) Subject: thread-naming: Older changed subject From: Notmuch Test Suite To: Notmuch Test Suite -Date: Sat, 06 Jan 2001 15:43:56 -0000 +Date: Sat, 06 Jan 2001 15:43:56 +0000 header} body{ part{ ID: 1, Content-type: text/plain @@ -99,7 +99,7 @@ Notmuch Test Suite (2001-01-07) (inbox unread) Subject: thread-naming: Newer changed subject From: Notmuch Test Suite To: Notmuch Test Suite -Date: Sun, 07 Jan 2001 15:43:56 -0000 +Date: Sun, 07 Jan 2001 15:43:56 +0000 header} body{ part{ ID: 1, Content-type: text/plain @@ -113,7 +113,7 @@ Notmuch Test Suite (2001-01-08) (unread) Subject: thread-naming: Final thread subject From: Notmuch Test Suite To: Notmuch Test Suite -Date: Mon, 08 Jan 2001 15:43:56 -0000 +Date: Mon, 08 Jan 2001 15:43:56 +0000 header} body{ part{ ID: 1, Content-type: text/plain @@ -127,7 +127,7 @@ Notmuch Test Suite (2001-01-09) (inbox unread) Subject: Re: thread-naming: Initial thread subject From: Notmuch Test Suite To: Notmuch Test Suite -Date: Tue, 09 Jan 2001 15:43:45 -0000 +Date: Tue, 09 Jan 2001 15:43:45 +0000 header} body{ part{ ID: 1, Content-type: text/plain @@ -141,7 +141,7 @@ Notmuch Test Suite (2001-01-10) (inbox unread) Subject: Aw: thread-naming: Initial thread subject From: Notmuch Test Suite To: Notmuch Test Suite -Date: Wed, 10 Jan 2001 15:43:45 -0000 +Date: Wed, 10 Jan 2001 15:43:45 +0000 header} body{ part{ ID: 1, Content-type: text/plain @@ -155,7 +155,7 @@ Notmuch Test Suite (2001-01-11) (inbox unread) Subject: Vs: thread-naming: Initial thread subject From: Notmuch Test Suite To: Notmuch Test Suite -Date: Thu, 11 Jan 2001 15:43:45 -0000 +Date: Thu, 11 Jan 2001 15:43:45 +0000 header} body{ part{ ID: 1, Content-type: text/plain @@ -169,7 +169,7 @@ Notmuch Test Suite (2001-01-12) (inbox unread) Subject: Sv: thread-naming: Initial thread subject From: Notmuch Test Suite To: Notmuch Test Suite -Date: Fri, 12 Jan 2001 15:43:45 -0000 +Date: Fri, 12 Jan 2001 15:43:45 +0000 header} body{ part{ ID: 1, Content-type: text/plain -- cgit v1.2.3 From c9c5a6f70c8371809f3b079e1aba3de3b4a13f6b Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Mon, 6 Feb 2012 21:57:21 +0200 Subject: cli: use notmuch_bool_t for boolean fields in notmuch_show_params_t Use notmuch_bool_t instead of int for entire_thread, raw, and decrypt boolean fields in notmuch_show_params_t. No functional changes. Signed-off-by: Jani Nikula --- notmuch-client.h | 6 +++--- notmuch-reply.c | 7 +++---- notmuch-show.c | 14 +++++++------- 3 files changed, 13 insertions(+), 14 deletions(-) (limited to 'notmuch-show.c') diff --git a/notmuch-client.h b/notmuch-client.h index e0eb594..60828aa 100644 --- a/notmuch-client.h +++ b/notmuch-client.h @@ -98,15 +98,15 @@ typedef struct notmuch_show_format { } notmuch_show_format_t; typedef struct notmuch_show_params { - int entire_thread; - int raw; + notmuch_bool_t entire_thread; + notmuch_bool_t raw; int part; #ifdef GMIME_ATLEAST_26 GMimeCryptoContext* cryptoctx; #else GMimeCipherContext* cryptoctx; #endif - int decrypt; + notmuch_bool_t decrypt; } notmuch_show_params_t; /* There's no point in continuing when we've detected that we've done diff --git a/notmuch-reply.c b/notmuch-reply.c index f55b1d2..6b244e6 100644 --- a/notmuch-reply.c +++ b/notmuch-reply.c @@ -661,7 +661,6 @@ notmuch_reply_command (void *ctx, int argc, char *argv[]) notmuch_show_params_t params = { .part = -1 }; int format = FORMAT_DEFAULT; int reply_all = TRUE; - notmuch_bool_t decrypt = FALSE; notmuch_opt_desc_t options[] = { { NOTMUCH_OPT_KEYWORD, &format, "format", 'f', @@ -672,7 +671,7 @@ notmuch_reply_command (void *ctx, int argc, char *argv[]) (notmuch_keyword_t []){ { "all", TRUE }, { "sender", FALSE }, { 0, 0 } } }, - { NOTMUCH_OPT_BOOLEAN, &decrypt, "decrypt", 'd', 0 }, + { NOTMUCH_OPT_BOOLEAN, ¶ms.decrypt, "decrypt", 'd', 0 }, { 0, 0, 0, 0, 0 } }; @@ -687,7 +686,7 @@ notmuch_reply_command (void *ctx, int argc, char *argv[]) else reply_format_func = notmuch_reply_format_default; - if (decrypt) { + if (params.decrypt) { #ifdef GMIME_ATLEAST_26 /* TODO: GMimePasswordRequestFunc */ params.cryptoctx = g_mime_gpg_context_new (NULL, "gpg"); @@ -697,8 +696,8 @@ notmuch_reply_command (void *ctx, int argc, char *argv[]) #endif if (params.cryptoctx) { g_mime_gpg_context_set_always_trust ((GMimeGpgContext*) params.cryptoctx, FALSE); - params.decrypt = TRUE; } else { + params.decrypt = FALSE; fprintf (stderr, "Failed to construct gpg context.\n"); } #ifndef GMIME_ATLEAST_26 diff --git a/notmuch-show.c b/notmuch-show.c index 816e0f8..13a6f54 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -1028,11 +1028,11 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) int format_specified = 0; int i; - params.entire_thread = 0; - params.raw = 0; + params.entire_thread = FALSE; + params.raw = FALSE; params.part = -1; params.cryptoctx = NULL; - params.decrypt = 0; + params.decrypt = FALSE; argc--; argv++; /* skip subcommand argument */ @@ -1047,13 +1047,13 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) format = &format_text; } else if (strcmp (opt, "json") == 0) { format = &format_json; - params.entire_thread = 1; + params.entire_thread = TRUE; } else if (strcmp (opt, "mbox") == 0) { format = &format_mbox; mbox = 1; } else if (strcmp (opt, "raw") == 0) { format = &format_raw; - params.raw = 1; + params.raw = TRUE; } else { fprintf (stderr, "Invalid value for --format: %s\n", opt); return 1; @@ -1062,7 +1062,7 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) } else if (STRNCMP_LITERAL (argv[i], "--part=") == 0) { params.part = atoi(argv[i] + sizeof ("--part=") - 1); } else if (STRNCMP_LITERAL (argv[i], "--entire-thread") == 0) { - params.entire_thread = 1; + params.entire_thread = TRUE; } else if ((STRNCMP_LITERAL (argv[i], "--verify") == 0) || (STRNCMP_LITERAL (argv[i], "--decrypt") == 0)) { if (params.cryptoctx == NULL) { @@ -1082,7 +1082,7 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) #endif } if (STRNCMP_LITERAL (argv[i], "--decrypt") == 0) - params.decrypt = 1; + params.decrypt = TRUE; } else { fprintf (stderr, "Unrecognized option: %s\n", argv[i]); return 1; -- cgit v1.2.3 From 2d09775baf1591e51ef7a8911655f2a5e9651c47 Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Mon, 6 Feb 2012 21:57:22 +0200 Subject: cli: convert "notmuch show" to use the new argument parser Use the new notmuch argument parser to handle arguments in "notmuch show". There are three minor functional changes: 1) Also set params.raw = TRUE when defaulting to raw format when part is requested but format is not specified. This was a bug, and --part=0 without --format=raw did not work previously. 2) Set params.decrypt = FALSE if crypto context creation fails. 3) Only use the parameters for the last --format if specified multiple times. Previously this could have resulted in a non-working mixture of parameters. Signed-off-by: Jani Nikula --- notmuch-show.c | 149 +++++++++++++++++++++++++++++---------------------------- 1 file changed, 75 insertions(+), 74 deletions(-) (limited to 'notmuch-show.c') diff --git a/notmuch-show.c b/notmuch-show.c index 13a6f54..8f72e58 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -1014,6 +1014,14 @@ do_show (void *ctx, return 0; } +enum { + NOTMUCH_FORMAT_NOT_SPECIFIED, + NOTMUCH_FORMAT_JSON, + NOTMUCH_FORMAT_TEXT, + NOTMUCH_FORMAT_MBOX, + NOTMUCH_FORMAT_RAW +}; + int notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) { @@ -1021,92 +1029,94 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) notmuch_database_t *notmuch; notmuch_query_t *query; char *query_string; - char *opt; + int opt_index; const notmuch_show_format_t *format = &format_text; - notmuch_show_params_t params; - int mbox = 0; - int format_specified = 0; - int i; + notmuch_show_params_t params = { .part = -1 }; + int format_sel = NOTMUCH_FORMAT_NOT_SPECIFIED; + notmuch_bool_t verify = FALSE; + + notmuch_opt_desc_t options[] = { + { NOTMUCH_OPT_KEYWORD, &format_sel, "format", 'f', + (notmuch_keyword_t []){ { "json", NOTMUCH_FORMAT_JSON }, + { "text", NOTMUCH_FORMAT_TEXT }, + { "mbox", NOTMUCH_FORMAT_MBOX }, + { "raw", NOTMUCH_FORMAT_RAW }, + { 0, 0 } } }, + { NOTMUCH_OPT_INT, ¶ms.part, "part", 'p', 0 }, + { NOTMUCH_OPT_BOOLEAN, ¶ms.entire_thread, "entire-thread", 't', 0 }, + { NOTMUCH_OPT_BOOLEAN, ¶ms.decrypt, "decrypt", 'd', 0 }, + { NOTMUCH_OPT_BOOLEAN, &verify, "verify", 'v', 0 }, + { 0, 0, 0, 0, 0 } + }; - params.entire_thread = FALSE; - params.raw = FALSE; - params.part = -1; - params.cryptoctx = NULL; - params.decrypt = FALSE; + opt_index = parse_arguments (argc, argv, options, 1); + if (opt_index < 0) { + /* diagnostics already printed */ + return 1; + } - argc--; argv++; /* skip subcommand argument */ + if (format_sel == NOTMUCH_FORMAT_NOT_SPECIFIED) { + /* if part was requested and format was not specified, use format=raw */ + if (params.part >= 0) + format_sel = NOTMUCH_FORMAT_RAW; + else + format_sel = NOTMUCH_FORMAT_TEXT; + } - for (i = 0; i < argc && argv[i][0] == '-'; i++) { - if (strcmp (argv[i], "--") == 0) { - i++; - break; + switch (format_sel) { + case NOTMUCH_FORMAT_JSON: + format = &format_json; + params.entire_thread = TRUE; + break; + case NOTMUCH_FORMAT_TEXT: + format = &format_text; + break; + case NOTMUCH_FORMAT_MBOX: + if (params.part > 0) { + fprintf (stderr, "Error: specifying parts is incompatible with mbox output format.\n"); + return 1; } - if (STRNCMP_LITERAL (argv[i], "--format=") == 0) { - opt = argv[i] + sizeof ("--format=") - 1; - if (strcmp (opt, "text") == 0) { - format = &format_text; - } else if (strcmp (opt, "json") == 0) { - format = &format_json; - params.entire_thread = TRUE; - } else if (strcmp (opt, "mbox") == 0) { - format = &format_mbox; - mbox = 1; - } else if (strcmp (opt, "raw") == 0) { - format = &format_raw; - params.raw = TRUE; - } else { - fprintf (stderr, "Invalid value for --format: %s\n", opt); - return 1; - } - format_specified = 1; - } else if (STRNCMP_LITERAL (argv[i], "--part=") == 0) { - params.part = atoi(argv[i] + sizeof ("--part=") - 1); - } else if (STRNCMP_LITERAL (argv[i], "--entire-thread") == 0) { - params.entire_thread = TRUE; - } else if ((STRNCMP_LITERAL (argv[i], "--verify") == 0) || - (STRNCMP_LITERAL (argv[i], "--decrypt") == 0)) { - if (params.cryptoctx == NULL) { + format = &format_mbox; + break; + case NOTMUCH_FORMAT_RAW: + format = &format_raw; + /* If --format=raw specified without specifying part, we can only + * output single message, so set part=0 */ + if (params.part < 0) + params.part = 0; + params.raw = TRUE; + break; + } + + if (params.decrypt || verify) { #ifdef GMIME_ATLEAST_26 - /* TODO: GMimePasswordRequestFunc */ - if (NULL == (params.cryptoctx = g_mime_gpg_context_new(NULL, "gpg"))) + /* TODO: GMimePasswordRequestFunc */ + params.cryptoctx = g_mime_gpg_context_new (NULL, "gpg"); #else - GMimeSession* session = g_object_new(g_mime_session_get_type(), NULL); - if (NULL == (params.cryptoctx = g_mime_gpg_context_new(session, "gpg"))) + GMimeSession* session = g_object_new (g_mime_session_get_type(), NULL); + params.cryptoctx = g_mime_gpg_context_new (session, "gpg"); #endif - fprintf (stderr, "Failed to construct gpg context.\n"); - else - g_mime_gpg_context_set_always_trust((GMimeGpgContext*)params.cryptoctx, FALSE); -#ifndef GMIME_ATLEAST_26 - g_object_unref (session); - session = NULL; -#endif - } - if (STRNCMP_LITERAL (argv[i], "--decrypt") == 0) - params.decrypt = TRUE; + if (params.cryptoctx) { + g_mime_gpg_context_set_always_trust ((GMimeGpgContext*) params.cryptoctx, FALSE); } else { - fprintf (stderr, "Unrecognized option: %s\n", argv[i]); - return 1; + params.decrypt = FALSE; + fprintf (stderr, "Failed to construct gpg context.\n"); } +#ifndef GMIME_ATLEAST_26 + g_object_unref (session); +#endif } - argc -= i; - argv += i; - config = notmuch_config_open (ctx, NULL, NULL); if (config == NULL) return 1; - query_string = query_string_from_args (ctx, argc, argv); + query_string = query_string_from_args (ctx, argc-opt_index, argv+opt_index); if (query_string == NULL) { fprintf (stderr, "Out of memory\n"); return 1; } - if (mbox && params.part > 0) { - fprintf (stderr, "Error: specifying parts is incompatible with mbox output format.\n"); - return 1; - } - if (*query_string == '\0') { fprintf (stderr, "Error: notmuch show requires at least one search term.\n"); return 1; @@ -1123,15 +1133,6 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) return 1; } - /* if part was requested and format was not specified, use format=raw */ - if (params.part >= 0 && !format_specified) - format = &format_raw; - - /* If --format=raw specified without specifying part, we can only - * output single message, so set part=0 */ - if (params.raw && params.part < 0) - params.part = 0; - if (params.part >= 0) return do_show_single (ctx, query, format, ¶ms); else -- cgit v1.2.3 From 2c8959dad8863d185b237ed3e1f947fc46b32c98 Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Mon, 6 Feb 2012 21:57:23 +0200 Subject: cli: reach previously unreachable cleanup code in "notmuch show" The last lines of notmuch_show_command() function were unreachable. Fix it by using a variable for return value. Signed-off-by: Jani Nikula --- notmuch-show.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'notmuch-show.c') diff --git a/notmuch-show.c b/notmuch-show.c index 8f72e58..d930f94 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -1029,7 +1029,7 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) notmuch_database_t *notmuch; notmuch_query_t *query; char *query_string; - int opt_index; + int opt_index, ret; const notmuch_show_format_t *format = &format_text; notmuch_show_params_t params = { .part = -1 }; int format_sel = NOTMUCH_FORMAT_NOT_SPECIFIED; @@ -1134,9 +1134,9 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) } if (params.part >= 0) - return do_show_single (ctx, query, format, ¶ms); + ret = do_show_single (ctx, query, format, ¶ms); else - return do_show (ctx, query, format, ¶ms); + ret = do_show (ctx, query, format, ¶ms); notmuch_query_destroy (query); notmuch_database_close (notmuch); @@ -1144,5 +1144,5 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) if (params.cryptoctx) g_object_unref(params.cryptoctx); - return 0; + return ret; } -- cgit v1.2.3 From 661c35712343408b4c034e13fc6cc8be7d580e21 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Sun, 19 Feb 2012 19:26:23 -0500 Subject: Document the JSON schemata used by show and search --- devel/schemata | 138 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ notmuch-search.c | 3 ++ notmuch-show.c | 2 + 3 files changed, 143 insertions(+) create mode 100644 devel/schemata (limited to 'notmuch-show.c') diff --git a/devel/schemata b/devel/schemata new file mode 100644 index 0000000..24ad775 --- /dev/null +++ b/devel/schemata @@ -0,0 +1,138 @@ +This file describes the schemata used for notmuch's structured output +format (currently JSON). + +[]'s indicate lists. List items can be marked with a '?', meaning +they are optional; or a '*', meaning there can be zero or more of that +item. {}'s indicate an object that maps from field identifiers to +values. An object field marked '?' is optional. |'s indicate +alternates (e.g., int|string means something can be an int or a +string). + +Common non-terminals +-------------------- + +# Number of seconds since the Epoch +unix_time = int + +# Thread ID, sans "thread:" +threadid = string + +# Message ID, sans "id:" +messageid = string + +notmuch show schema +------------------- + +# A top-level set of threads (do_show) +# Returned by notmuch show without a --part argument +thread_set = [thread*] + +# Top-level messages in a thread (show_messages) +thread = [thread_node*] + +# A message and its replies (show_messages) +thread_node = [ + message?, # present if --entire-thread or matched + [thread_node*] # children of message +] + +# A message (show_message) +message = { + # (format_message_json) + id: messageid, + match: bool, + filename: string, + timestamp: unix_time, # date header as unix time + date_relative: string, # user-friendly timestamp + tags: [string*], + + headers: headers, + body: [part] +} + +# A MIME part (show_message_body) +part = { + # format_part_start_json + id: int|string, # part id (currently DFS part number) + + # format_part_encstatus_json + encstatus?: encstatus, + + # format_part_sigstatus_json + sigstatus?: sigstatus, + + # format_part_content_json + content-type: string, + content-id?: string, + # if content-type starts with "multipart/": + content: [part*], + # if content-type is "message/rfc822": + content: [{headers: headers, body: [part]}], + # otherwise (leaf parts): + filename?: string, + content-charset?: string, + # A leaf part's body content is optional, but may be included if + # it can be correctly encoded as a string. Consumers should use + # this in preference to fetching the part content separately. + content?: string +} + +# The headers of a message (format_headers_json with raw headers) or +# a part (format_headers_message_part_json with pretty-printed headers) +headers = { + Subject: string, + From: string, + To?: string, + Cc?: string, + Bcc?: string, + Date: string +} + +# Encryption status (format_part_encstatus_json) +encstatus = [{status: "good"|"bad"}] + +# Signature status (format_part_sigstatus_json) +sigstatus = [signature*] + +signature = { + # signature_status_to_string + status: "none"|"good"|"bad"|"error"|"unknown", + # if status is "good": + fingerprint?: string, + created?: unix_time, + expires?: unix_time, + userid?: string + # if status is not "good": + keyid?: string + # if the signature has errors: + errors?: int +} + +notmuch search schema +--------------------- + +# --output=summary +summary = [thread*] + +# --output=threads +threads = [threadid*] + +# --output=messages +messages = [messageid*] + +# --output=files +files = [string*] + +# --output=tags +tags = [string*] + +thread = { + thread: threadid, + timestamp: unix_time, + date_relative: string, # user-friendly timestamp + matched: int, # number of matched messages + total: int, # total messages in thread + authors: string, # comma-separated names with | between + # matched and unmatched + subject: string +} diff --git a/notmuch-search.c b/notmuch-search.c index d504051..92ce38a 100644 --- a/notmuch-search.c +++ b/notmuch-search.c @@ -90,6 +90,9 @@ format_thread_json (const void *ctx, const int total, const char *authors, const char *subject); + +/* Any changes to the JSON format should be reflected in the file + * devel/schemata. */ static const search_format_t format_json = { "[", "{", diff --git a/notmuch-show.c b/notmuch-show.c index d930f94..93fb16f 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -65,6 +65,8 @@ format_part_content_json (GMimeObject *part); static void format_part_end_json (GMimeObject *part); +/* Any changes to the JSON format should be reflected in the file + * devel/schemata. */ static const notmuch_show_format_t format_json = { "[", NULL, "{", format_message_json, -- cgit v1.2.3 From 44d9656cbf51ade90e372eadbf3542de83549c74 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Sun, 19 Feb 2012 19:26:24 -0500 Subject: show: Convert JSON format to the new self-recursive style As before, this is all code movement and a smidgen of glue. This moves the existing JSON formatter code into one self-recursive function, but doesn't change any of the logic to take advantage of the new structure. In general, "leafs" of the JSON structure are left in helper functions (most of them untouched), so that it's easy to see the overall structure of the format from the main recursive function. --- notmuch-show.c | 273 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 135 insertions(+), 138 deletions(-) (limited to 'notmuch-show.c') diff --git a/notmuch-show.c b/notmuch-show.c index 93fb16f..868b2cd 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -35,52 +35,14 @@ static const notmuch_show_format_t format_text = { }; static void -format_message_json (const void *ctx, - notmuch_message_t *message, - unused (int indent)); -static void -format_headers_json (const void *ctx, - notmuch_message_t *message); - -static void -format_headers_message_part_json (GMimeMessage *message); - -static void -format_part_start_json (unused (GMimeObject *part), - int *part_count); - -static void -format_part_encstatus_json (int status); - -static void -#ifdef GMIME_ATLEAST_26 -format_part_sigstatus_json (GMimeSignatureList* siglist); -#else -format_part_sigstatus_json (const GMimeSignatureValidity* validity); -#endif +format_part_json_entry (const void *ctx, mime_node_t *node, + int indent, const notmuch_show_params_t *params); -static void -format_part_content_json (GMimeObject *part); - -static void -format_part_end_json (GMimeObject *part); - -/* Any changes to the JSON format should be reflected in the file - * devel/schemata. */ static const notmuch_show_format_t format_json = { - "[", NULL, - "{", format_message_json, - "\"headers\": {", format_headers_json, format_headers_message_part_json, "}", - ", \"body\": [", - format_part_start_json, - format_part_encstatus_json, - format_part_sigstatus_json, - format_part_content_json, - format_part_end_json, - ", ", - "]", - "}", ", ", - "]" + .message_set_start = "[", + .part = format_part_json_entry, + .message_set_sep = ", ", + .message_set_end = "]" }; static void @@ -170,7 +132,7 @@ _get_one_line_summary (const void *ctx, notmuch_message_t *message) } static void -format_message_json (const void *ctx, notmuch_message_t *message, unused (int indent)) +format_message_json (const void *ctx, notmuch_message_t *message) { notmuch_tags_t *tags; int first = 1; @@ -471,24 +433,6 @@ signer_status_to_string (GMimeSignerStatus x) } #endif -static void -format_part_start_json (unused (GMimeObject *part), int *part_count) -{ - printf ("{\"id\": %d", *part_count); -} - -static void -format_part_encstatus_json (int status) -{ - printf (", \"encstatus\": [{\"status\": "); - if (status) { - printf ("\"good\""); - } else { - printf ("\"bad\""); - } - printf ("}]"); -} - #ifdef GMIME_ATLEAST_26 static void format_part_sigstatus_json (GMimeSignatureList *siglist) @@ -618,81 +562,6 @@ format_part_sigstatus_json (const GMimeSignatureValidity* validity) } #endif -static void -format_part_content_json (GMimeObject *part) -{ - GMimeContentType *content_type = g_mime_object_get_content_type (GMIME_OBJECT (part)); - GMimeStream *stream_memory = g_mime_stream_mem_new (); - const char *cid = g_mime_object_get_content_id (part); - void *ctx = talloc_new (NULL); - GByteArray *part_content; - - printf (", \"content-type\": %s", - json_quote_str (ctx, g_mime_content_type_to_string (content_type))); - - if (cid != NULL) - printf(", \"content-id\": %s", json_quote_str (ctx, cid)); - - if (GMIME_IS_PART (part)) - { - const char *filename = g_mime_part_get_filename (GMIME_PART (part)); - if (filename) - printf (", \"filename\": %s", json_quote_str (ctx, filename)); - } - - if (g_mime_content_type_is_type (content_type, "text", "*")) - { - /* For non-HTML text parts, we include the content in the - * JSON. Since JSON must be Unicode, we handle charset - * decoding here and do not report a charset to the caller. - * For text/html parts, we do not include the content. If a - * caller is interested in text/html parts, it should retrieve - * them separately and they will not be decoded. Since this - * makes charset decoding the responsibility on the caller, we - * report the charset for text/html parts. - */ - if (g_mime_content_type_is_type (content_type, "text", "html")) - { - const char *content_charset = g_mime_object_get_content_type_parameter (GMIME_OBJECT (part), "charset"); - - if (content_charset != NULL) - printf (", \"content-charset\": %s", json_quote_str (ctx, content_charset)); - } - else - { - show_text_part_content (part, stream_memory); - part_content = g_mime_stream_mem_get_byte_array (GMIME_STREAM_MEM (stream_memory)); - - printf (", \"content\": %s", json_quote_chararray (ctx, (char *) part_content->data, part_content->len)); - } - } - else if (g_mime_content_type_is_type (content_type, "multipart", "*")) - { - printf (", \"content\": ["); - } - else if (g_mime_content_type_is_type (content_type, "message", "rfc822")) - { - printf (", \"content\": [{"); - } - - talloc_free (ctx); - if (stream_memory) - g_object_unref (stream_memory); -} - -static void -format_part_end_json (GMimeObject *part) -{ - GMimeContentType *content_type = g_mime_object_get_content_type (GMIME_OBJECT (part)); - - if (g_mime_content_type_is_type (content_type, "multipart", "*")) - printf ("]"); - else if (g_mime_content_type_is_type (content_type, "message", "rfc822")) - printf ("}]"); - - printf ("}"); -} - static void format_part_content_raw (GMimeObject *part) { @@ -810,6 +679,134 @@ format_part_text (const void *ctx, mime_node_t *node, printf ("\f%s}\n", part_type); } +static void +format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first) +{ + /* Any changes to the JSON format should be reflected in the file + * devel/schemata. */ + + if (node->envelope_file) { + printf ("{"); + format_message_json (ctx, node->envelope_file); + + printf ("\"headers\": {"); + format_headers_json (ctx, node->envelope_file); + printf ("}"); + + printf (", \"body\": ["); + format_part_json (ctx, mime_node_child (node, 0), first); + + printf ("]}"); + return; + } + + void *local = talloc_new (ctx); + /* The disposition and content-type metadata are associated with + * the envelope for message parts */ + GMimeObject *meta = node->envelope_part ? + GMIME_OBJECT (node->envelope_part) : node->part; + GMimeContentType *content_type = g_mime_object_get_content_type (meta); + GMimeStream *stream_memory = g_mime_stream_mem_new (); + const char *cid = g_mime_object_get_content_id (meta); + GByteArray *part_content; + int i; + + if (!first) + printf (", "); + + printf ("{\"id\": %d", node->part_num); + + if (node->decrypt_attempted) { + printf (", \"encstatus\": [{\"status\": "); + if (node->decrypt_success) { + printf ("\"good\""); + } else { + printf ("\"bad\""); + } + printf ("}]"); + } + + if (node->verify_attempted) { +#ifdef GMIME_ATLEAST_26 + format_part_sigstatus_json (node->sig_list); +#else + format_part_sigstatus_json (node->sig_validity); +#endif + } + + printf (", \"content-type\": %s", + json_quote_str (local, g_mime_content_type_to_string (content_type))); + + if (cid != NULL) + printf(", \"content-id\": %s", json_quote_str (local, cid)); + + if (GMIME_IS_PART (node->part)) { + const char *filename = g_mime_part_get_filename (GMIME_PART (node->part)); + if (filename) + printf (", \"filename\": %s", json_quote_str (local, filename)); + } + + if (g_mime_content_type_is_type (content_type, "text", "*")) { + /* For non-HTML text parts, we include the content in the + * JSON. Since JSON must be Unicode, we handle charset + * decoding here and do not report a charset to the caller. + * For text/html parts, we do not include the content. If a + * caller is interested in text/html parts, it should retrieve + * them separately and they will not be decoded. Since this + * makes charset decoding the responsibility on the caller, we + * report the charset for text/html parts. + */ + if (g_mime_content_type_is_type (content_type, "text", "html")) { + const char *content_charset = g_mime_object_get_content_type_parameter (meta, "charset"); + + if (content_charset != NULL) + printf (", \"content-charset\": %s", json_quote_str (local, content_charset)); + } else { + show_text_part_content (node->part, stream_memory); + part_content = g_mime_stream_mem_get_byte_array (GMIME_STREAM_MEM (stream_memory)); + + printf (", \"content\": %s", json_quote_chararray (local, (char *) part_content->data, part_content->len)); + } + } else if (g_mime_content_type_is_type (content_type, "multipart", "*")) { + printf (", \"content\": ["); + } else if (g_mime_content_type_is_type (content_type, "message", "rfc822")) { + printf (", \"content\": [{"); + } + + if (stream_memory) + g_object_unref (stream_memory); + + if (GMIME_IS_MESSAGE (node->part)) { + printf ("\"headers\": {"); + format_headers_message_part_json (GMIME_MESSAGE (node->part)); + printf ("}"); + + printf (", \"body\": ["); + } + + for (i = 0; i < node->nchildren; i++) + format_part_json (ctx, mime_node_child (node, i), i == 0); + + if (GMIME_IS_MESSAGE (node->part)) + printf ("]"); + + if (g_mime_content_type_is_type (content_type, "multipart", "*")) + printf ("]"); + else if (g_mime_content_type_is_type (content_type, "message", "rfc822")) + printf ("}]"); + + printf ("}"); + + talloc_free (local); +} + +static void +format_part_json_entry (const void *ctx, mime_node_t *node, unused (int indent), + unused (const notmuch_show_params_t *params)) +{ + format_part_json (ctx, node, TRUE); +} + static void show_message (void *ctx, const notmuch_show_format_t *format, -- cgit v1.2.3 From 2209d7b9520000fbd6941b1e0e8521da90f443c6 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Sun, 19 Feb 2012 19:26:25 -0500 Subject: show: Use consistent header ordering in the JSON format Previously, top-level message headers were printed as Subject, From, To, Date, while embedded message headers were printed From, To, Subject, Date. This makes both cases use the former order and updates the tests accordingly. --- notmuch-show.c | 6 +++--- test/multipart | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) (limited to 'notmuch-show.c') diff --git a/notmuch-show.c b/notmuch-show.c index 868b2cd..9ca9882 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -328,6 +328,9 @@ format_headers_message_part_json (GMimeMessage *message) const char *recipients_string; printf ("%s: %s", + json_quote_str (ctx_quote, "Subject"), + json_quote_str (ctx_quote, g_mime_message_get_subject (message))); + printf (", %s: %s", json_quote_str (ctx_quote, "From"), json_quote_str (ctx_quote, g_mime_message_get_sender (message))); recipients = g_mime_message_get_recipients (message, GMIME_RECIPIENT_TYPE_TO); @@ -342,9 +345,6 @@ format_headers_message_part_json (GMimeMessage *message) printf (", %s: %s", json_quote_str (ctx_quote, "Cc"), json_quote_str (ctx_quote, recipients_string)); - printf (", %s: %s", - json_quote_str (ctx_quote, "Subject"), - json_quote_str (ctx_quote, g_mime_message_get_subject (message))); printf (", %s: %s", json_quote_str (ctx_quote, "Date"), json_quote_str (ctx_quote, g_mime_message_get_date_as_string (message))); diff --git a/test/multipart b/test/multipart index 2dd73f5..4d14804 100755 --- a/test/multipart +++ b/test/multipart @@ -325,7 +325,7 @@ cat <EXPECTED {"id": "87liy5ap00.fsf@yoom.home.cworth.org", "match": true, "filename": "${MAIL_DIR}/multipart", "timestamp": 978709437, "date_relative": "2001-01-05", "tags": ["attachment","inbox","signed","unread"], "headers": {"Subject": "Multipart message", "From": "Carl Worth ", "To": "cworth@cworth.org", "Cc": "", "Bcc": "", "Date": "Fri, 05 Jan 2001 15:43:57 +0000"}, "body": [ {"id": 1, "content-type": "multipart/signed", "content": [ {"id": 2, "content-type": "multipart/mixed", "content": [ -{"id": 3, "content-type": "message/rfc822", "content": [{"headers": {"From": "Carl Worth ", "To": "cworth@cworth.org", "Subject": "html message", "Date": "Fri, 05 Jan 2001 15:42:57 +0000"}, "body": [ +{"id": 3, "content-type": "message/rfc822", "content": [{"headers": {"Subject": "html message", "From": "Carl Worth ", "To": "cworth@cworth.org", "Date": "Fri, 05 Jan 2001 15:42:57 +0000"}, "body": [ {"id": 4, "content-type": "multipart/alternative", "content": [ {"id": 5, "content-type": "text/html"}, {"id": 6, "content-type": "text/plain", "content": "This is an embedded message, with a multipart/alternative part.\n"}]}]}]}, @@ -342,7 +342,7 @@ cat <EXPECTED {"id": 1, "content-type": "multipart/signed", "content": [ {"id": 2, "content-type": "multipart/mixed", "content": [ -{"id": 3, "content-type": "message/rfc822", "content": [{"headers": {"From": "Carl Worth ", "To": "cworth@cworth.org", "Subject": "html message", "Date": "Fri, 05 Jan 2001 15:42:57 +0000"}, "body": [ +{"id": 3, "content-type": "message/rfc822", "content": [{"headers": {"Subject": "html message", "From": "Carl Worth ", "To": "cworth@cworth.org", "Date": "Fri, 05 Jan 2001 15:42:57 +0000"}, "body": [ {"id": 4, "content-type": "multipart/alternative", "content": [ {"id": 5, "content-type": "text/html"}, {"id": 6, "content-type": "text/plain", "content": "This is an embedded message, with a multipart/alternative part.\n"}]}]}]}, @@ -358,7 +358,7 @@ echo >>OUTPUT # expect *no* newline at end of output cat <EXPECTED {"id": 2, "content-type": "multipart/mixed", "content": [ -{"id": 3, "content-type": "message/rfc822", "content": [{"headers": {"From": "Carl Worth ", "To": "cworth@cworth.org", "Subject": "html message", "Date": "Fri, 05 Jan 2001 15:42:57 +0000"}, "body": [ +{"id": 3, "content-type": "message/rfc822", "content": [{"headers": {"Subject": "html message", "From": "Carl Worth ", "To": "cworth@cworth.org", "Date": "Fri, 05 Jan 2001 15:42:57 +0000"}, "body": [ {"id": 4, "content-type": "multipart/alternative", "content": [ {"id": 5, "content-type": "text/html"}, {"id": 6, "content-type": "text/plain", "content": "This is an embedded message, with a multipart/alternative part.\n"}]}]}]}, @@ -372,7 +372,7 @@ notmuch show --format=json --part=3 'id:87liy5ap00.fsf@yoom.home.cworth.org' | s echo >>OUTPUT # expect *no* newline at end of output cat <EXPECTED -{"id": 3, "content-type": "message/rfc822", "content": [{"headers": {"From": "Carl Worth ", "To": "cworth@cworth.org", "Subject": "html message", "Date": "Fri, 05 Jan 2001 15:42:57 +0000"}, "body": [ +{"id": 3, "content-type": "message/rfc822", "content": [{"headers": {"Subject": "html message", "From": "Carl Worth ", "To": "cworth@cworth.org", "Date": "Fri, 05 Jan 2001 15:42:57 +0000"}, "body": [ {"id": 4, "content-type": "multipart/alternative", "content": [ {"id": 5, "content-type": "text/html"}, {"id": 6, "content-type": "text/plain", "content": "This is an embedded message, with a multipart/alternative part.\n"}]}]}]} -- cgit v1.2.3 From 86f89385c3bc34cd91002cc057f6a615b6ab76a9 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Sun, 19 Feb 2012 19:26:26 -0500 Subject: show: Unify JSON header output for messages and message parts This has three ramifications: - Blank To and Cc headers are no longer output for messages. - Dates are now canonicalized for messages, which means they always have a day of the week and GMT is printed +0000 (never -0000) - Invalid From message headers are handled slightly differently, since they get parsed by GMime now instead of notmuch. --- notmuch-show.c | 35 +++-------------------------------- test/crypto | 35 ++++++++++++++--------------------- test/emacs | 4 ++-- test/json | 6 +++--- test/maildir-sync | 2 -- test/multipart | 2 +- 6 files changed, 23 insertions(+), 61 deletions(-) (limited to 'notmuch-show.c') diff --git a/notmuch-show.c b/notmuch-show.c index 9ca9882..209ff45 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -291,36 +291,7 @@ format_headers_message_part_text (GMimeMessage *message) } static void -format_headers_json (const void *ctx, notmuch_message_t *message) -{ - const char *headers[] = { - "Subject", "From", "To", "Cc", "Bcc", "Date" - }; - const char *name, *value; - unsigned int i; - int first_header = 1; - void *ctx_quote = talloc_new (ctx); - - for (i = 0; i < ARRAY_SIZE (headers); i++) { - name = headers[i]; - value = notmuch_message_get_header (message, name); - if (value) - { - if (!first_header) - fputs (", ", stdout); - first_header = 0; - - printf ("%s: %s", - json_quote_str (ctx_quote, name), - json_quote_str (ctx_quote, value)); - } - } - - talloc_free (ctx_quote); -} - -static void -format_headers_message_part_json (GMimeMessage *message) +format_headers_json (GMimeMessage *message) { void *ctx = talloc_new (NULL); void *ctx_quote = talloc_new (ctx); @@ -690,7 +661,7 @@ format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first) format_message_json (ctx, node->envelope_file); printf ("\"headers\": {"); - format_headers_json (ctx, node->envelope_file); + format_headers_json (GMIME_MESSAGE (node->part)); printf ("}"); printf (", \"body\": ["); @@ -778,7 +749,7 @@ format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first) if (GMIME_IS_MESSAGE (node->part)) { printf ("\"headers\": {"); - format_headers_message_part_json (GMIME_MESSAGE (node->part)); + format_headers_json (GMIME_MESSAGE (node->part)); printf ("}"); printf (", \"body\": ["); diff --git a/test/crypto b/test/crypto index 1dbb60a..7e774c8 100755 --- a/test/crypto +++ b/test/crypto @@ -50,9 +50,8 @@ expected='[[[{"id": "XXXXX", "headers": {"Subject": "test signed message 001", "From": "Notmuch Test Suite ", "To": "test_suite@notmuchmail.org", - "Cc": "", - "Bcc": "", - "Date": "01 Jan 2000 12:00:00 -0000"}, + "Date": "Sat, + 01 Jan 2000 12:00:00 +0000"}, "body": [{"id": 1, "sigstatus": [{"status": "good", "fingerprint": "'$FINGERPRINT'", @@ -84,9 +83,8 @@ expected='[[[{"id": "XXXXX", "headers": {"Subject": "test signed message 001", "From": "Notmuch Test Suite ", "To": "test_suite@notmuchmail.org", - "Cc": "", - "Bcc": "", - "Date": "01 Jan 2000 12:00:00 -0000"}, + "Date": "Sat, + 01 Jan 2000 12:00:00 +0000"}, "body": [{"id": 1, "sigstatus": [{"status": "good", "fingerprint": "'$FINGERPRINT'", @@ -120,9 +118,8 @@ expected='[[[{"id": "XXXXX", "headers": {"Subject": "test signed message 001", "From": "Notmuch Test Suite ", "To": "test_suite@notmuchmail.org", - "Cc": "", - "Bcc": "", - "Date": "01 Jan 2000 12:00:00 -0000"}, + "Date": "Sat, + 01 Jan 2000 12:00:00 +0000"}, "body": [{"id": 1, "sigstatus": [{"status": "error", "keyid": "'$(echo $FINGERPRINT | cut -c 25-)'", @@ -194,9 +191,8 @@ expected='[[[{"id": "XXXXX", "headers": {"Subject": "test encrypted message 001", "From": "Notmuch Test Suite ", "To": "test_suite@notmuchmail.org", - "Cc": "", - "Bcc": "", - "Date": "01 Jan 2000 12:00:00 -0000"}, + "Date": "Sat, + 01 Jan 2000 12:00:00 +0000"}, "body": [{"id": 1, "encstatus": [{"status": "good"}], "sigstatus": [], @@ -249,9 +245,8 @@ expected='[[[{"id": "XXXXX", "headers": {"Subject": "test encrypted message 001", "From": "Notmuch Test Suite ", "To": "test_suite@notmuchmail.org", - "Cc": "", - "Bcc": "", - "Date": "01 Jan 2000 12:00:00 -0000"}, + "Date": "Sat, + 01 Jan 2000 12:00:00 +0000"}, "body": [{"id": 1, "encstatus": [{"status": "bad"}], "content-type": "multipart/encrypted", @@ -284,9 +279,8 @@ expected='[[[{"id": "XXXXX", "headers": {"Subject": "test encrypted message 002", "From": "Notmuch Test Suite ", "To": "test_suite@notmuchmail.org", - "Cc": "", - "Bcc": "", - "Date": "01 Jan 2000 12:00:00 -0000"}, + "Date": "Sat, + 01 Jan 2000 12:00:00 +0000"}, "body": [{"id": 1, "encstatus": [{"status": "good"}], "sigstatus": [{"status": "good", @@ -339,9 +333,8 @@ expected='[[[{"id": "XXXXX", "headers": {"Subject": "test signed message 001", "From": "Notmuch Test Suite ", "To": "test_suite@notmuchmail.org", - "Cc": "", - "Bcc": "", - "Date": "01 Jan 2000 12:00:00 -0000"}, + "Date": "Sat, + 01 Jan 2000 12:00:00 +0000"}, "body": [{"id": 1, "sigstatus": [{"status": "error", "keyid": "6D92612D94E46381", diff --git a/test/emacs b/test/emacs index 7549892..29a489c 100755 --- a/test/emacs +++ b/test/emacs @@ -78,7 +78,7 @@ thread=$(notmuch search --output=threads subject:message-with-invalid-from) test_emacs "(notmuch-show \"$thread\") (test-output)" cat <EXPECTED -Invalid " From (2001-01-05) (inbox) +"Invalid " (2001-01-05) (inbox) Subject: message-with-invalid-from To: Notmuch Test Suite Date: Fri, 05 Jan 2001 15:43:57 +0000 @@ -414,7 +414,7 @@ test_emacs '(notmuch-show "id:\"bought\"") (reverse-region (point-min) (point-max)) (test-output)' cat <EXPECTED -Sat, 01 Jan 2000 12:00:00 -0000 +Sat, 01 Jan 2000 12:00:00 +0000 Some One Some One Else Notmuch diff --git a/test/json b/test/json index 7df4380..1bdffd2 100755 --- a/test/json +++ b/test/json @@ -5,7 +5,7 @@ test_description="--format=json output" test_begin_subtest "Show message: json" add_message "[subject]=\"json-show-subject\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"json-show-message\"" output=$(notmuch show --format=json "json-show-message") -test_expect_equal "$output" "[[[{\"id\": \"${gen_msg_id}\", \"match\": true, \"filename\": \"${gen_msg_filename}\", \"timestamp\": 946728000, \"date_relative\": \"2000-01-01\", \"tags\": [\"inbox\",\"unread\"], \"headers\": {\"Subject\": \"json-show-subject\", \"From\": \"Notmuch Test Suite \", \"To\": \"Notmuch Test Suite \", \"Cc\": \"\", \"Bcc\": \"\", \"Date\": \"Sat, 01 Jan 2000 12:00:00 -0000\"}, \"body\": [{\"id\": 1, \"content-type\": \"text/plain\", \"content\": \"json-show-message\n\"}]}, []]]]" +test_expect_equal "$output" "[[[{\"id\": \"${gen_msg_id}\", \"match\": true, \"filename\": \"${gen_msg_filename}\", \"timestamp\": 946728000, \"date_relative\": \"2000-01-01\", \"tags\": [\"inbox\",\"unread\"], \"headers\": {\"Subject\": \"json-show-subject\", \"From\": \"Notmuch Test Suite \", \"To\": \"Notmuch Test Suite \", \"Date\": \"Sat, 01 Jan 2000 12:00:00 +0000\"}, \"body\": [{\"id\": 1, \"content-type\": \"text/plain\", \"content\": \"json-show-message\n\"}]}, []]]]" test_begin_subtest "Search message: json" add_message "[subject]=\"json-search-subject\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"json-search-message\"" @@ -22,7 +22,7 @@ test_expect_equal "$output" "[{\"thread\": \"XXX\", test_begin_subtest "Show message: json, utf-8" add_message "[subject]=\"json-show-utf8-body-sübjéct\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"jsön-show-méssage\"" output=$(notmuch show --format=json "jsön-show-méssage") -test_expect_equal "$output" "[[[{\"id\": \"${gen_msg_id}\", \"match\": true, \"filename\": \"${gen_msg_filename}\", \"timestamp\": 946728000, \"date_relative\": \"2000-01-01\", \"tags\": [\"inbox\",\"unread\"], \"headers\": {\"Subject\": \"json-show-utf8-body-sübjéct\", \"From\": \"Notmuch Test Suite \", \"To\": \"Notmuch Test Suite \", \"Cc\": \"\", \"Bcc\": \"\", \"Date\": \"Sat, 01 Jan 2000 12:00:00 -0000\"}, \"body\": [{\"id\": 1, \"content-type\": \"text/plain\", \"content\": \"jsön-show-méssage\n\"}]}, []]]]" +test_expect_equal "$output" "[[[{\"id\": \"${gen_msg_id}\", \"match\": true, \"filename\": \"${gen_msg_filename}\", \"timestamp\": 946728000, \"date_relative\": \"2000-01-01\", \"tags\": [\"inbox\",\"unread\"], \"headers\": {\"Subject\": \"json-show-utf8-body-sübjéct\", \"From\": \"Notmuch Test Suite \", \"To\": \"Notmuch Test Suite \", \"Date\": \"Sat, 01 Jan 2000 12:00:00 +0000\"}, \"body\": [{\"id\": 1, \"content-type\": \"text/plain\", \"content\": \"jsön-show-méssage\n\"}]}, []]]]" test_begin_subtest "Show message: json, inline attachment filename" subject='json-show-inline-attachment-filename' @@ -35,7 +35,7 @@ emacs_deliver_message \ (insert \"Message-ID: <$id>\n\")" output=$(notmuch show --format=json "id:$id") filename=$(notmuch search --output=files "id:$id") -test_expect_equal "$output" "[[[{\"id\": \"$id\", \"match\": true, \"filename\": \"$filename\", \"timestamp\": 946728000, \"date_relative\": \"2000-01-01\", \"tags\": [\"inbox\"], \"headers\": {\"Subject\": \"$subject\", \"From\": \"Notmuch Test Suite \", \"To\": \"test_suite@notmuchmail.org\", \"Cc\": \"\", \"Bcc\": \"\", \"Date\": \"01 Jan 2000 12:00:00 -0000\"}, \"body\": [{\"id\": 1, \"content-type\": \"multipart/mixed\", \"content\": [{\"id\": 2, \"content-type\": \"text/plain\", \"content\": \"This is a test message with inline attachment with a filename\"}, {\"id\": 3, \"content-type\": \"application/octet-stream\", \"filename\": \"README\"}]}]}, []]]]" +test_expect_equal "$output" "[[[{\"id\": \"$id\", \"match\": true, \"filename\": \"$filename\", \"timestamp\": 946728000, \"date_relative\": \"2000-01-01\", \"tags\": [\"inbox\"], \"headers\": {\"Subject\": \"$subject\", \"From\": \"Notmuch Test Suite \", \"To\": \"test_suite@notmuchmail.org\", \"Date\": \"Sat, 01 Jan 2000 12:00:00 +0000\"}, \"body\": [{\"id\": 1, \"content-type\": \"multipart/mixed\", \"content\": [{\"id\": 2, \"content-type\": \"text/plain\", \"content\": \"This is a test message with inline attachment with a filename\"}, {\"id\": 3, \"content-type\": \"application/octet-stream\", \"filename\": \"README\"}]}]}, []]]]" test_begin_subtest "Search message: json, utf-8" add_message "[subject]=\"json-search-utf8-body-sübjéct\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"jsön-search-méssage\"" diff --git a/test/maildir-sync b/test/maildir-sync index d5872a5..1ee2db0 100755 --- a/test/maildir-sync +++ b/test/maildir-sync @@ -53,8 +53,6 @@ test_expect_equal "$output" '[[[{"id": "adding-replied-tag@notmuch-test-suite", "headers": {"Subject": "Adding replied tag", "From": "Notmuch Test Suite ", "To": "Notmuch Test Suite ", -"Cc": "", -"Bcc": "", "Date": "Fri, 05 Jan 2001 15:43:57 +0000"}, "body": [{"id": 1, diff --git a/test/multipart b/test/multipart index 4d14804..a3036b4 100755 --- a/test/multipart +++ b/test/multipart @@ -322,7 +322,7 @@ notmuch show --format=json --part=0 'id:87liy5ap00.fsf@yoom.home.cworth.org' | s echo >>OUTPUT # expect *no* newline at end of output cat <EXPECTED -{"id": "87liy5ap00.fsf@yoom.home.cworth.org", "match": true, "filename": "${MAIL_DIR}/multipart", "timestamp": 978709437, "date_relative": "2001-01-05", "tags": ["attachment","inbox","signed","unread"], "headers": {"Subject": "Multipart message", "From": "Carl Worth ", "To": "cworth@cworth.org", "Cc": "", "Bcc": "", "Date": "Fri, 05 Jan 2001 15:43:57 +0000"}, "body": [ +{"id": "87liy5ap00.fsf@yoom.home.cworth.org", "match": true, "filename": "${MAIL_DIR}/multipart", "timestamp": 978709437, "date_relative": "2001-01-05", "tags": ["attachment","inbox","signed","unread"], "headers": {"Subject": "Multipart message", "From": "Carl Worth ", "To": "cworth@cworth.org", "Date": "Fri, 05 Jan 2001 15:43:57 +0000"}, "body": [ {"id": 1, "content-type": "multipart/signed", "content": [ {"id": 2, "content-type": "multipart/mixed", "content": [ {"id": 3, "content-type": "message/rfc822", "content": [{"headers": {"Subject": "html message", "From": "Carl Worth ", "To": "cworth@cworth.org", "Date": "Fri, 05 Jan 2001 15:42:57 +0000"}, "body": [ -- cgit v1.2.3 From 63ee244c8e9fd1fdd63697f3584530499157652f Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Sun, 19 Feb 2012 19:26:27 -0500 Subject: show: Simplify talloc use in format_headers_json Previously there was an unnecessary talloc context. --- notmuch-show.c | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) (limited to 'notmuch-show.c') diff --git a/notmuch-show.c b/notmuch-show.c index 209ff45..6259d30 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -291,37 +291,35 @@ format_headers_message_part_text (GMimeMessage *message) } static void -format_headers_json (GMimeMessage *message) +format_headers_json (const void *ctx, GMimeMessage *message) { - void *ctx = talloc_new (NULL); - void *ctx_quote = talloc_new (ctx); + void *local = talloc_new (ctx); InternetAddressList *recipients; const char *recipients_string; printf ("%s: %s", - json_quote_str (ctx_quote, "Subject"), - json_quote_str (ctx_quote, g_mime_message_get_subject (message))); + json_quote_str (local, "Subject"), + json_quote_str (local, g_mime_message_get_subject (message))); printf (", %s: %s", - json_quote_str (ctx_quote, "From"), - json_quote_str (ctx_quote, g_mime_message_get_sender (message))); + json_quote_str (local, "From"), + json_quote_str (local, g_mime_message_get_sender (message))); recipients = g_mime_message_get_recipients (message, GMIME_RECIPIENT_TYPE_TO); recipients_string = internet_address_list_to_string (recipients, 0); if (recipients_string) printf (", %s: %s", - json_quote_str (ctx_quote, "To"), - json_quote_str (ctx_quote, recipients_string)); + json_quote_str (local, "To"), + json_quote_str (local, recipients_string)); recipients = g_mime_message_get_recipients (message, GMIME_RECIPIENT_TYPE_CC); recipients_string = internet_address_list_to_string (recipients, 0); if (recipients_string) printf (", %s: %s", - json_quote_str (ctx_quote, "Cc"), - json_quote_str (ctx_quote, recipients_string)); + json_quote_str (local, "Cc"), + json_quote_str (local, recipients_string)); printf (", %s: %s", - json_quote_str (ctx_quote, "Date"), - json_quote_str (ctx_quote, g_mime_message_get_date_as_string (message))); + json_quote_str (local, "Date"), + json_quote_str (local, g_mime_message_get_date_as_string (message))); - talloc_free (ctx_quote); - talloc_free (ctx); + talloc_free (local); } /* Write a MIME text part out to the given stream. @@ -661,7 +659,7 @@ format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first) format_message_json (ctx, node->envelope_file); printf ("\"headers\": {"); - format_headers_json (GMIME_MESSAGE (node->part)); + format_headers_json (ctx, GMIME_MESSAGE (node->part)); printf ("}"); printf (", \"body\": ["); @@ -749,7 +747,7 @@ format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first) if (GMIME_IS_MESSAGE (node->part)) { printf ("\"headers\": {"); - format_headers_json (GMIME_MESSAGE (node->part)); + format_headers_json (local, GMIME_MESSAGE (node->part)); printf ("}"); printf (", \"body\": ["); -- cgit v1.2.3 From 1f0ead385a11422be4a06c574d8836eaa65a5d39 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Sun, 19 Feb 2012 19:26:28 -0500 Subject: show: Make JSON helper functions print complete objects This makes the main recursive function easier to follow because helper functions don't add fields to the running object. --- notmuch-show.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) (limited to 'notmuch-show.c') diff --git a/notmuch-show.c b/notmuch-show.c index 6259d30..8fb6fa6 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -297,7 +297,7 @@ format_headers_json (const void *ctx, GMimeMessage *message) InternetAddressList *recipients; const char *recipients_string; - printf ("%s: %s", + printf ("{%s: %s", json_quote_str (local, "Subject"), json_quote_str (local, g_mime_message_get_subject (message))); printf (", %s: %s", @@ -315,7 +315,7 @@ format_headers_json (const void *ctx, GMimeMessage *message) printf (", %s: %s", json_quote_str (local, "Cc"), json_quote_str (local, recipients_string)); - printf (", %s: %s", + printf (", %s: %s}", json_quote_str (local, "Date"), json_quote_str (local, g_mime_message_get_date_as_string (message))); @@ -406,7 +406,7 @@ signer_status_to_string (GMimeSignerStatus x) static void format_part_sigstatus_json (GMimeSignatureList *siglist) { - printf (", \"sigstatus\": ["); + printf ("["); if (!siglist) { printf ("]"); @@ -472,7 +472,7 @@ format_part_sigstatus_json (GMimeSignatureList *siglist) static void format_part_sigstatus_json (const GMimeSignatureValidity* validity) { - printf (", \"sigstatus\": ["); + printf ("["); if (!validity) { printf ("]"); @@ -658,9 +658,8 @@ format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first) printf ("{"); format_message_json (ctx, node->envelope_file); - printf ("\"headers\": {"); + printf ("\"headers\": "); format_headers_json (ctx, GMIME_MESSAGE (node->part)); - printf ("}"); printf (", \"body\": ["); format_part_json (ctx, mime_node_child (node, 0), first); @@ -696,6 +695,7 @@ format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first) } if (node->verify_attempted) { + printf (", \"sigstatus\": "); #ifdef GMIME_ATLEAST_26 format_part_sigstatus_json (node->sig_list); #else @@ -746,9 +746,8 @@ format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first) g_object_unref (stream_memory); if (GMIME_IS_MESSAGE (node->part)) { - printf ("\"headers\": {"); + printf ("\"headers\": "); format_headers_json (local, GMIME_MESSAGE (node->part)); - printf ("}"); printf (", \"body\": ["); } -- cgit v1.2.3 From 99789e77f2a18fd89198fd9cebdb7a756dd367e3 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Sun, 19 Feb 2012 19:26:29 -0500 Subject: show: Make format_part_sigstatus_json's API consistent between GMIME 2.4 and 2.6 The implementation is still different for GMIME 2.4 and 2.6, but at least now the caller doesn't have to be aware of this. --- notmuch-show.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'notmuch-show.c') diff --git a/notmuch-show.c b/notmuch-show.c index 8fb6fa6..07276c7 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -404,8 +404,10 @@ signer_status_to_string (GMimeSignerStatus x) #ifdef GMIME_ATLEAST_26 static void -format_part_sigstatus_json (GMimeSignatureList *siglist) +format_part_sigstatus_json (mime_node_t *node) { + GMimeSignatureList *siglist = node->sig_list; + printf ("["); if (!siglist) { @@ -470,8 +472,10 @@ format_part_sigstatus_json (GMimeSignatureList *siglist) } #else static void -format_part_sigstatus_json (const GMimeSignatureValidity* validity) +format_part_sigstatus_json (mime_node_t *node) { + const GMimeSignatureValidity* validity = node->sig_validity; + printf ("["); if (!validity) { @@ -696,11 +700,7 @@ format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first) if (node->verify_attempted) { printf (", \"sigstatus\": "); -#ifdef GMIME_ATLEAST_26 - format_part_sigstatus_json (node->sig_list); -#else - format_part_sigstatus_json (node->sig_validity); -#endif + format_part_sigstatus_json (node); } printf (", \"content-type\": %s", -- cgit v1.2.3 From 4fa77d031838db923dec29024df638520586c925 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Sun, 19 Feb 2012 19:26:30 -0500 Subject: show: Further general simplifications of the JSON formatter --- notmuch-show.c | 61 +++++++++++++++++++++------------------------------------- 1 file changed, 22 insertions(+), 39 deletions(-) (limited to 'notmuch-show.c') diff --git a/notmuch-show.c b/notmuch-show.c index 07276c7..6a171a4 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -678,9 +678,10 @@ format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first) GMimeObject *meta = node->envelope_part ? GMIME_OBJECT (node->envelope_part) : node->part; GMimeContentType *content_type = g_mime_object_get_content_type (meta); - GMimeStream *stream_memory = g_mime_stream_mem_new (); const char *cid = g_mime_object_get_content_id (meta); - GByteArray *part_content; + const char *filename = GMIME_IS_PART (node->part) ? + g_mime_part_get_filename (GMIME_PART (node->part)) : NULL; + const char *terminator = ""; int i; if (!first) @@ -688,15 +689,9 @@ format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first) printf ("{\"id\": %d", node->part_num); - if (node->decrypt_attempted) { - printf (", \"encstatus\": [{\"status\": "); - if (node->decrypt_success) { - printf ("\"good\""); - } else { - printf ("\"bad\""); - } - printf ("}]"); - } + if (node->decrypt_attempted) + printf (", \"encstatus\": [{\"status\": \"%s\"}]", + node->decrypt_success ? "good" : "bad"); if (node->verify_attempted) { printf (", \"sigstatus\": "); @@ -706,16 +701,13 @@ format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first) printf (", \"content-type\": %s", json_quote_str (local, g_mime_content_type_to_string (content_type))); - if (cid != NULL) - printf(", \"content-id\": %s", json_quote_str (local, cid)); + if (cid) + printf (", \"content-id\": %s", json_quote_str (local, cid)); - if (GMIME_IS_PART (node->part)) { - const char *filename = g_mime_part_get_filename (GMIME_PART (node->part)); - if (filename) - printf (", \"filename\": %s", json_quote_str (local, filename)); - } + if (filename) + printf (", \"filename\": %s", json_quote_str (local, filename)); - if (g_mime_content_type_is_type (content_type, "text", "*")) { + if (GMIME_IS_PART (node->part)) { /* For non-HTML text parts, we include the content in the * JSON. Since JSON must be Unicode, we handle charset * decoding here and do not report a charset to the caller. @@ -730,42 +722,33 @@ format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first) if (content_charset != NULL) printf (", \"content-charset\": %s", json_quote_str (local, content_charset)); - } else { + } else if (g_mime_content_type_is_type (content_type, "text", "*")) { + GMimeStream *stream_memory = g_mime_stream_mem_new (); + GByteArray *part_content; show_text_part_content (node->part, stream_memory); part_content = g_mime_stream_mem_get_byte_array (GMIME_STREAM_MEM (stream_memory)); printf (", \"content\": %s", json_quote_chararray (local, (char *) part_content->data, part_content->len)); + g_object_unref (stream_memory); } - } else if (g_mime_content_type_is_type (content_type, "multipart", "*")) { + } else if (GMIME_IS_MULTIPART (node->part)) { printf (", \"content\": ["); - } else if (g_mime_content_type_is_type (content_type, "message", "rfc822")) { + terminator = "]"; + } else if (GMIME_IS_MESSAGE (node->part)) { printf (", \"content\": [{"); - } - - if (stream_memory) - g_object_unref (stream_memory); - - if (GMIME_IS_MESSAGE (node->part)) { printf ("\"headers\": "); format_headers_json (local, GMIME_MESSAGE (node->part)); printf (", \"body\": ["); + terminator = "]}]"; } + talloc_free (local); + for (i = 0; i < node->nchildren; i++) format_part_json (ctx, mime_node_child (node, i), i == 0); - if (GMIME_IS_MESSAGE (node->part)) - printf ("]"); - - if (g_mime_content_type_is_type (content_type, "multipart", "*")) - printf ("]"); - else if (g_mime_content_type_is_type (content_type, "message", "rfc822")) - printf ("}]"); - - printf ("}"); - - talloc_free (local); + printf ("%s}", terminator); } static void -- cgit v1.2.3 From ebe5e6712a9386071ee73195214ec4cb788a38b2 Mon Sep 17 00:00:00 2001 From: Mark Walters Date: Thu, 1 Mar 2012 22:30:40 +0000 Subject: cli: Make notmuch-show respect excludes. This adds the excludes to notmuch-show.c. We do not exclude when only a single message (or part) is requested. notmuch-show will output the exclude information when either text or json format is requested. As this changes the output from notmuch-show it breaks many tests (in a trivial and expected fashion). --- notmuch-show.c | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) (limited to 'notmuch-show.c') diff --git a/notmuch-show.c b/notmuch-show.c index 6a171a4..8c0b925 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -143,9 +143,10 @@ format_message_json (const void *ctx, notmuch_message_t *message) date = notmuch_message_get_date (message); relative_date = notmuch_time_relative_date (ctx, date); - printf ("\"id\": %s, \"match\": %s, \"filename\": %s, \"timestamp\": %ld, \"date_relative\": \"%s\", \"tags\": [", + printf ("\"id\": %s, \"match\": %s, \"excluded\": %s, \"filename\": %s, \"timestamp\": %ld, \"date_relative\": \"%s\", \"tags\": [", json_quote_str (ctx_quote, notmuch_message_get_message_id (message)), notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH) ? "true" : "false", + notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED) ? "true" : "false", json_quote_str (ctx_quote, notmuch_message_get_filename (message)), date, relative_date); @@ -579,11 +580,12 @@ format_part_text (const void *ctx, mime_node_t *node, notmuch_message_t *message = node->envelope_file; part_type = "message"; - printf ("\f%s{ id:%s depth:%d match:%d filename:%s\n", + printf ("\f%s{ id:%s depth:%d match:%d excluded:%d filename:%s\n", part_type, notmuch_message_get_message_id (message), indent, - notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH), + notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH) ? 1 : 0, + notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED) ? 1 : 0, notmuch_message_get_filename (message)); } else { GMimeContentDisposition *disposition = g_mime_object_get_content_disposition (meta); @@ -984,6 +986,7 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) notmuch_show_params_t params = { .part = -1 }; int format_sel = NOTMUCH_FORMAT_NOT_SPECIFIED; notmuch_bool_t verify = FALSE; + notmuch_bool_t no_exclude = FALSE; notmuch_opt_desc_t options[] = { { NOTMUCH_OPT_KEYWORD, &format_sel, "format", 'f', @@ -996,6 +999,7 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) { NOTMUCH_OPT_BOOLEAN, ¶ms.entire_thread, "entire-thread", 't', 0 }, { NOTMUCH_OPT_BOOLEAN, ¶ms.decrypt, "decrypt", 'd', 0 }, { NOTMUCH_OPT_BOOLEAN, &verify, "verify", 'v', 0 }, + { NOTMUCH_OPT_BOOLEAN, &no_exclude, "no-exclude", 'n', 0 }, { 0, 0, 0, 0, 0 } }; @@ -1083,10 +1087,23 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) return 1; } + /* If a single message is requested we do not use search_excludes. */ if (params.part >= 0) ret = do_show_single (ctx, query, format, ¶ms); - else + else { + if (!no_exclude) { + const char **search_exclude_tags; + size_t search_exclude_tags_length; + unsigned int i; + + search_exclude_tags = notmuch_config_get_search_exclude_tags + (config, &search_exclude_tags_length); + for (i = 0; i < search_exclude_tags_length; i++) + notmuch_query_add_tag_exclude (query, search_exclude_tags[i]); + } ret = do_show (ctx, query, format, ¶ms); + } + notmuch_query_destroy (query); notmuch_database_close (notmuch); -- cgit v1.2.3 From 7a1beb9e7c7fdf8971a60d810ae24e58c2d09673 Mon Sep 17 00:00:00 2001 From: Mark Walters Date: Thu, 1 Mar 2012 22:30:43 +0000 Subject: cli: omit excluded messages in results where appropriate. In all cases of notmuch count/search/show where the results returned cannot reflect the exclude flag return just the matched not-excluded results. If the caller wishes to have all the matched results (i.e., including the excluded ones) they should call with the --no-exclude option. The relevant cases are count: both threads and messages search: all cases except the summary view show: mbox format --- notmuch-count.c | 2 ++ notmuch-search.c | 9 +++++++++ notmuch-show.c | 6 ++++++ 3 files changed, 17 insertions(+) (limited to 'notmuch-show.c') diff --git a/notmuch-count.c b/notmuch-count.c index 5364507..46b76ae 100644 --- a/notmuch-count.c +++ b/notmuch-count.c @@ -88,6 +88,8 @@ notmuch_count_command (void *ctx, int argc, char *argv[]) notmuch_query_add_tag_exclude (query, search_exclude_tags[i]); } + notmuch_query_set_omit_excluded_messages (query, TRUE); + switch (output) { case OUTPUT_MESSAGES: printf ("%u\n", notmuch_query_count_messages (query)); diff --git a/notmuch-search.c b/notmuch-search.c index 6d6c0e6..f6061e4 100644 --- a/notmuch-search.c +++ b/notmuch-search.c @@ -210,6 +210,9 @@ do_search_threads (const search_format_t *format, int first_thread = 1; int i; + if (output == OUTPUT_THREADS) + notmuch_query_set_omit_excluded_messages (query, TRUE); + if (offset < 0) { offset += notmuch_query_count_threads (query); if (offset < 0) @@ -300,6 +303,8 @@ do_search_messages (const search_format_t *format, int first_message = 1; int i; + notmuch_query_set_omit_excluded_messages (query, TRUE); + if (offset < 0) { offset += notmuch_query_count_messages (query); if (offset < 0) @@ -371,6 +376,10 @@ do_search_tags (notmuch_database_t *notmuch, const char *tag; int first_tag = 1; + notmuch_query_set_omit_excluded_messages (query, TRUE); + /* should the following only special case if no excluded terms + * specified? */ + /* Special-case query of "*" for better performance. */ if (strcmp (notmuch_query_get_query_string (query), "*") == 0) { tags = notmuch_database_get_all_tags (notmuch); diff --git a/notmuch-show.c b/notmuch-show.c index 8c0b925..05d51b2 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -1030,6 +1030,7 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) fprintf (stderr, "Error: specifying parts is incompatible with mbox output format.\n"); return 1; } + format = &format_mbox; break; case NOTMUCH_FORMAT_RAW: @@ -1087,6 +1088,11 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) return 1; } + /* if format=mbox then we can not output excluded messages as + * there is no way to make the exclude flag available */ + if (format_sel == NOTMUCH_FORMAT_MBOX) + notmuch_query_set_omit_excluded_messages (query, TRUE); + /* If a single message is requested we do not use search_excludes. */ if (params.part >= 0) ret = do_show_single (ctx, query, format, ¶ms); -- cgit v1.2.3 From 6a4df1b796ea82545fa0861d341aa6769f72ac02 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Tue, 6 Mar 2012 18:48:39 +0000 Subject: show: Allow formatters to return errors Formatter errors are propagated to the exit status of notmuch show. This isn't used by the JSON or text formatters, but it will be useful for the raw format, which is pickier. --- notmuch-client.h | 6 ++--- notmuch-show.c | 71 ++++++++++++++++++++++++++++++++++++-------------------- 2 files changed, 49 insertions(+), 28 deletions(-) (limited to 'notmuch-show.c') diff --git a/notmuch-client.h b/notmuch-client.h index f4a62cc..a220fe4 100644 --- a/notmuch-client.h +++ b/notmuch-client.h @@ -67,9 +67,9 @@ struct notmuch_show_params; typedef struct notmuch_show_format { const char *message_set_start; - void (*part) (const void *ctx, - struct mime_node *node, int indent, - const struct notmuch_show_params *params); + notmuch_status_t (*part) (const void *ctx, + struct mime_node *node, int indent, + const struct notmuch_show_params *params); const char *message_start; void (*message) (const void *ctx, notmuch_message_t *message, diff --git a/notmuch-show.c b/notmuch-show.c index 05d51b2..8a8c0fc 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -23,7 +23,7 @@ static void format_headers_message_part_text (GMimeMessage *message); -static void +static notmuch_status_t format_part_text (const void *ctx, mime_node_t *node, int indent, const notmuch_show_params_t *params); @@ -34,7 +34,7 @@ static const notmuch_show_format_t format_text = { .message_set_end = "" }; -static void +static notmuch_status_t format_part_json_entry (const void *ctx, mime_node_t *node, int indent, const notmuch_show_params_t *params); @@ -563,7 +563,7 @@ format_part_content_raw (GMimeObject *part) g_object_unref(stream_stdout); } -static void +static notmuch_status_t format_part_text (const void *ctx, mime_node_t *node, int indent, const notmuch_show_params_t *params) { @@ -652,6 +652,8 @@ format_part_text (const void *ctx, mime_node_t *node, printf ("\fbody}\n"); printf ("\f%s}\n", part_type); + + return NOTMUCH_STATUS_SUCCESS; } static void @@ -753,14 +755,16 @@ format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first) printf ("%s}", terminator); } -static void +static notmuch_status_t format_part_json_entry (const void *ctx, mime_node_t *node, unused (int indent), unused (const notmuch_show_params_t *params)) { format_part_json (ctx, node, TRUE); + + return NOTMUCH_STATUS_SUCCESS; } -static void +static notmuch_status_t show_message (void *ctx, const notmuch_show_format_t *format, notmuch_message_t *message, @@ -770,14 +774,18 @@ show_message (void *ctx, if (format->part) { void *local = talloc_new (ctx); mime_node_t *root, *part; - - if (mime_node_open (local, message, params->cryptoctx, params->decrypt, - &root) == NOTMUCH_STATUS_SUCCESS && - (part = mime_node_seek_dfs (root, (params->part < 0 ? - 0 : params->part)))) - format->part (local, part, indent, params); + notmuch_status_t status; + + status = mime_node_open (local, message, params->cryptoctx, + params->decrypt, &root); + if (status) + goto DONE; + part = mime_node_seek_dfs (root, (params->part < 0 ? 0 : params->part)); + if (part) + status = format->part (local, part, indent, params); + DONE: talloc_free (local); - return; + return status; } if (params->part <= 0) { @@ -801,9 +809,11 @@ show_message (void *ctx, fputs (format->message_end, stdout); } + + return NOTMUCH_STATUS_SUCCESS; } -static void +static notmuch_status_t show_messages (void *ctx, const notmuch_show_format_t *format, notmuch_messages_t *messages, @@ -814,6 +824,7 @@ show_messages (void *ctx, notmuch_bool_t match; int first_set = 1; int next_indent; + notmuch_status_t status, res = NOTMUCH_STATUS_SUCCESS; fputs (format->message_set_start, stdout); @@ -834,17 +845,22 @@ show_messages (void *ctx, next_indent = indent; if (match || params->entire_thread) { - show_message (ctx, format, message, indent, params); + status = show_message (ctx, format, message, indent, params); + if (status && !res) + res = status; next_indent = indent + 1; - fputs (format->message_set_sep, stdout); + if (!status) + fputs (format->message_set_sep, stdout); } - show_messages (ctx, - format, - notmuch_message_get_replies (message), - next_indent, - params); + status = show_messages (ctx, + format, + notmuch_message_get_replies (message), + next_indent, + params); + if (status && !res) + res = status; notmuch_message_destroy (message); @@ -852,6 +868,8 @@ show_messages (void *ctx, } fputs (format->message_set_end, stdout); + + return res; } /* Formatted output of single message */ @@ -916,13 +934,13 @@ do_show_single (void *ctx, fclose (file); + return 0; + } else { - show_message (ctx, format, message, 0, params); + return show_message (ctx, format, message, 0, params) != NOTMUCH_STATUS_SUCCESS; } - - return 0; } /* Formatted output of threads */ @@ -936,6 +954,7 @@ do_show (void *ctx, notmuch_thread_t *thread; notmuch_messages_t *messages; int first_toplevel = 1; + notmuch_status_t status, res = NOTMUCH_STATUS_SUCCESS; fputs (format->message_set_start, stdout); @@ -955,7 +974,9 @@ do_show (void *ctx, fputs (format->message_set_sep, stdout); first_toplevel = 0; - show_messages (ctx, format, messages, 0, params); + status = show_messages (ctx, format, messages, 0, params); + if (status && !res) + res = status; notmuch_thread_destroy (thread); @@ -963,7 +984,7 @@ do_show (void *ctx, fputs (format->message_set_end, stdout); - return 0; + return res != NOTMUCH_STATUS_SUCCESS; } enum { -- cgit v1.2.3 From 8d01b0749c16bab0f0e01066a9afa9786193e739 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Tue, 6 Mar 2012 18:48:40 +0000 Subject: show: Move format_message_mbox with the other new-style formats Just code motion. --- notmuch-show.c | 100 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 50 insertions(+), 50 deletions(-) (limited to 'notmuch-show.c') diff --git a/notmuch-show.c b/notmuch-show.c index 8a8c0fc..f13e187 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -220,56 +220,6 @@ _is_from_line (const char *line) return 0; } -/* Print a message in "mboxrd" format as documented, for example, - * here: - * - * http://qmail.org/qmail-manual-html/man5/mbox.html - */ -static void -format_message_mbox (const void *ctx, - notmuch_message_t *message, - unused (int indent)) -{ - const char *filename; - FILE *file; - const char *from; - - time_t date; - struct tm date_gmtime; - char date_asctime[26]; - - char *line = NULL; - size_t line_size; - ssize_t line_len; - - filename = notmuch_message_get_filename (message); - file = fopen (filename, "r"); - if (file == NULL) { - fprintf (stderr, "Failed to open %s: %s\n", - filename, strerror (errno)); - return; - } - - from = notmuch_message_get_header (message, "from"); - from = _extract_email_address (ctx, from); - - date = notmuch_message_get_date (message); - gmtime_r (&date, &date_gmtime); - asctime_r (&date_gmtime, date_asctime); - - printf ("From %s %s", from, date_asctime); - - while ((line_len = getline (&line, &line_size, file)) != -1 ) { - if (_is_from_line (line)) - putchar ('>'); - printf ("%s", line); - } - - printf ("\n"); - - fclose (file); -} - static void format_headers_message_part_text (GMimeMessage *message) { @@ -764,6 +714,56 @@ format_part_json_entry (const void *ctx, mime_node_t *node, unused (int indent), return NOTMUCH_STATUS_SUCCESS; } +/* Print a message in "mboxrd" format as documented, for example, + * here: + * + * http://qmail.org/qmail-manual-html/man5/mbox.html + */ +static void +format_message_mbox (const void *ctx, + notmuch_message_t *message, + unused (int indent)) +{ + const char *filename; + FILE *file; + const char *from; + + time_t date; + struct tm date_gmtime; + char date_asctime[26]; + + char *line = NULL; + size_t line_size; + ssize_t line_len; + + filename = notmuch_message_get_filename (message); + file = fopen (filename, "r"); + if (file == NULL) { + fprintf (stderr, "Failed to open %s: %s\n", + filename, strerror (errno)); + return; + } + + from = notmuch_message_get_header (message, "from"); + from = _extract_email_address (ctx, from); + + date = notmuch_message_get_date (message); + gmtime_r (&date, &date_gmtime); + asctime_r (&date_gmtime, date_asctime); + + printf ("From %s %s", from, date_asctime); + + while ((line_len = getline (&line, &line_size, file)) != -1 ) { + if (_is_from_line (line)) + putchar ('>'); + printf ("%s", line); + } + + printf ("\n"); + + fclose (file); +} + static notmuch_status_t show_message (void *ctx, const notmuch_show_format_t *format, -- cgit v1.2.3 From d4312393530f70416612cf88dccc2620412a2bc6 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Tue, 6 Mar 2012 18:48:41 +0000 Subject: show: Convert mbox format to new self-recursive style Given the lack of recursion, this is pretty easy. --- notmuch-show.c | 40 ++++++++++++++++++---------------------- 1 file changed, 18 insertions(+), 22 deletions(-) (limited to 'notmuch-show.c') diff --git a/notmuch-show.c b/notmuch-show.c index f13e187..353bda5 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -45,25 +45,15 @@ static const notmuch_show_format_t format_json = { .message_set_end = "]" }; -static void -format_message_mbox (const void *ctx, - notmuch_message_t *message, - unused (int indent)); +static notmuch_status_t +format_part_mbox (const void *ctx, mime_node_t *node, + int indent, const notmuch_show_params_t *params); static const notmuch_show_format_t format_mbox = { - "", NULL, - "", format_message_mbox, - "", NULL, NULL, "", - "", - NULL, - NULL, - NULL, - NULL, - NULL, - "", - "", - "", "", - "" + .message_set_start = "", + .part = format_part_mbox, + .message_set_sep = "", + .message_set_end = "" }; static void @@ -719,11 +709,12 @@ format_part_json_entry (const void *ctx, mime_node_t *node, unused (int indent), * * http://qmail.org/qmail-manual-html/man5/mbox.html */ -static void -format_message_mbox (const void *ctx, - notmuch_message_t *message, - unused (int indent)) +static notmuch_status_t +format_part_mbox (const void *ctx, mime_node_t *node, unused (int indent), + unused (const notmuch_show_params_t *params)) { + notmuch_message_t *message = node->envelope_file; + const char *filename; FILE *file; const char *from; @@ -736,12 +727,15 @@ format_message_mbox (const void *ctx, size_t line_size; ssize_t line_len; + if (!message) + INTERNAL_ERROR ("format_part_mbox requires a root part"); + filename = notmuch_message_get_filename (message); file = fopen (filename, "r"); if (file == NULL) { fprintf (stderr, "Failed to open %s: %s\n", filename, strerror (errno)); - return; + return NOTMUCH_STATUS_FILE_ERROR; } from = notmuch_message_get_header (message, "from"); @@ -762,6 +756,8 @@ format_message_mbox (const void *ctx, printf ("\n"); fclose (file); + + return NOTMUCH_STATUS_SUCCESS; } static notmuch_status_t -- cgit v1.2.3 From 7e1742a82c4b52f5337d8db53347d547a4ac67b4 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Tue, 6 Mar 2012 18:48:42 +0000 Subject: show: Move format_part_content_raw with the other new-style formats Just code motion. --- notmuch-show.c | 54 +++++++++++++++++++++++++++--------------------------- 1 file changed, 27 insertions(+), 27 deletions(-) (limited to 'notmuch-show.c') diff --git a/notmuch-show.c b/notmuch-show.c index 353bda5..341634d 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -476,33 +476,6 @@ format_part_sigstatus_json (mime_node_t *node) } #endif -static void -format_part_content_raw (GMimeObject *part) -{ - if (! GMIME_IS_PART (part)) - return; - - GMimeStream *stream_stdout; - GMimeStream *stream_filter = NULL; - GMimeDataWrapper *wrapper; - - stream_stdout = g_mime_stream_file_new (stdout); - g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream_stdout), FALSE); - - stream_filter = g_mime_stream_filter_new (stream_stdout); - - wrapper = g_mime_part_get_content_object (GMIME_PART (part)); - - if (wrapper && stream_filter) - g_mime_data_wrapper_write_to_stream (wrapper, stream_filter); - - if (stream_filter) - g_object_unref (stream_filter); - - if (stream_stdout) - g_object_unref(stream_stdout); -} - static notmuch_status_t format_part_text (const void *ctx, mime_node_t *node, int indent, const notmuch_show_params_t *params) @@ -760,6 +733,33 @@ format_part_mbox (const void *ctx, mime_node_t *node, unused (int indent), return NOTMUCH_STATUS_SUCCESS; } +static void +format_part_content_raw (GMimeObject *part) +{ + if (! GMIME_IS_PART (part)) + return; + + GMimeStream *stream_stdout; + GMimeStream *stream_filter = NULL; + GMimeDataWrapper *wrapper; + + stream_stdout = g_mime_stream_file_new (stdout); + g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream_stdout), FALSE); + + stream_filter = g_mime_stream_filter_new (stream_stdout); + + wrapper = g_mime_part_get_content_object (GMIME_PART (part)); + + if (wrapper && stream_filter) + g_mime_data_wrapper_write_to_stream (wrapper, stream_filter); + + if (stream_filter) + g_object_unref (stream_filter); + + if (stream_stdout) + g_object_unref(stream_stdout); +} + static notmuch_status_t show_message (void *ctx, const notmuch_show_format_t *format, -- cgit v1.2.3 From b1130bc71c02efb504ad723b56f86618fd186a67 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Tue, 6 Mar 2012 18:48:43 +0000 Subject: show: Convert raw format to the new self-recursive style, properly support interior parts This is fully compatible for root and leaf parts, but now has proper support for interior parts. This requires some design decisions that were guided by what I would want if I were to save a part. Specifically: - Leaf parts are printed without headers and with transfer decoding. This is what makes sense for saving attachments. (Furthermore, the transfer decoding is necessary since, without the headers, the caller would not be able to interpret non-transfer-decoded output.) - Message parts are printed with their message headers, but without enclosing part headers. This is what makes sense for saving a message as a whole (which is a message part) and for saving attached messages. This is symmetric for whole messages and for attached messages, though we special-case the whole message for performance reasons (and corner-case correctness reasons: given malformed input, GMime may not be able to reproduce it from the parsed representation). - Multipart parts are printed with their headers and all child parts. It's not clear what the best thing to do for multipart is, but this was the most natural to implement and can be justified because such parts can't be interpreted without their headers. As an added benefit, we can move the special-case code for part 0 into the raw formatter. --- notmuch-show.c | 159 ++++++++++++++++++++++++--------------------------------- test/multipart | 74 +++++++++++++++++---------- 2 files changed, 116 insertions(+), 117 deletions(-) (limited to 'notmuch-show.c') diff --git a/notmuch-show.c b/notmuch-show.c index 341634d..a7463dc 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -20,9 +20,6 @@ #include "notmuch-client.h" -static void -format_headers_message_part_text (GMimeMessage *message); - static notmuch_status_t format_part_text (const void *ctx, mime_node_t *node, int indent, const notmuch_show_params_t *params); @@ -56,23 +53,16 @@ static const notmuch_show_format_t format_mbox = { .message_set_end = "" }; -static void -format_part_content_raw (GMimeObject *part); +static notmuch_status_t +format_part_raw (unused (const void *ctx), mime_node_t *node, + unused (int indent), + unused (const notmuch_show_params_t *params)); static const notmuch_show_format_t format_raw = { - "", NULL, - "", NULL, - "", NULL, format_headers_message_part_text, "\n", - "", - NULL, - NULL, - NULL, - format_part_content_raw, - NULL, - "", - "", - "", "", - "" + .message_set_start = "", + .part = format_part_raw, + .message_set_sep = "", + .message_set_end = "" }; static const char * @@ -210,27 +200,6 @@ _is_from_line (const char *line) return 0; } -static void -format_headers_message_part_text (GMimeMessage *message) -{ - InternetAddressList *recipients; - const char *recipients_string; - - printf ("Subject: %s\n", g_mime_message_get_subject (message)); - printf ("From: %s\n", g_mime_message_get_sender (message)); - recipients = g_mime_message_get_recipients (message, GMIME_RECIPIENT_TYPE_TO); - recipients_string = internet_address_list_to_string (recipients, 0); - if (recipients_string) - printf ("To: %s\n", - recipients_string); - recipients = g_mime_message_get_recipients (message, GMIME_RECIPIENT_TYPE_CC); - recipients_string = internet_address_list_to_string (recipients, 0); - if (recipients_string) - printf ("Cc: %s\n", - recipients_string); - printf ("Date: %s\n", g_mime_message_get_date_as_string (message)); -} - static void format_headers_json (const void *ctx, GMimeMessage *message) { @@ -733,31 +702,82 @@ format_part_mbox (const void *ctx, mime_node_t *node, unused (int indent), return NOTMUCH_STATUS_SUCCESS; } -static void -format_part_content_raw (GMimeObject *part) +static notmuch_status_t +format_part_raw (unused (const void *ctx), mime_node_t *node, + unused (int indent), + unused (const notmuch_show_params_t *params)) { - if (! GMIME_IS_PART (part)) - return; + if (node->envelope_file) { + /* Special case the entire message to avoid MIME parsing. */ + const char *filename; + FILE *file; + size_t size; + char buf[4096]; + + filename = notmuch_message_get_filename (node->envelope_file); + if (filename == NULL) { + fprintf (stderr, "Error: Cannot get message filename.\n"); + return NOTMUCH_STATUS_FILE_ERROR; + } + + file = fopen (filename, "r"); + if (file == NULL) { + fprintf (stderr, "Error: Cannot open file %s: %s\n", filename, strerror (errno)); + return NOTMUCH_STATUS_FILE_ERROR; + } + + while (!feof (file)) { + size = fread (buf, 1, sizeof (buf), file); + if (ferror (file)) { + fprintf (stderr, "Error: Read failed from %s\n", filename); + fclose (file); + return NOTMUCH_STATUS_FILE_ERROR; + } + + if (fwrite (buf, size, 1, stdout) != 1) { + fprintf (stderr, "Error: Write failed\n"); + fclose (file); + return NOTMUCH_STATUS_FILE_ERROR; + } + } + + fclose (file); + return NOTMUCH_STATUS_SUCCESS; + } GMimeStream *stream_stdout; GMimeStream *stream_filter = NULL; - GMimeDataWrapper *wrapper; stream_stdout = g_mime_stream_file_new (stdout); g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream_stdout), FALSE); stream_filter = g_mime_stream_filter_new (stream_stdout); - wrapper = g_mime_part_get_content_object (GMIME_PART (part)); + if (GMIME_IS_PART (node->part)) { + /* For leaf parts, we emit only the transfer-decoded + * body. */ + GMimeDataWrapper *wrapper; + wrapper = g_mime_part_get_content_object (GMIME_PART (node->part)); - if (wrapper && stream_filter) - g_mime_data_wrapper_write_to_stream (wrapper, stream_filter); + if (wrapper && stream_filter) + g_mime_data_wrapper_write_to_stream (wrapper, stream_filter); + } else { + /* Write out the whole part. For message parts (the root + * part and embedded message parts), this will be the + * message including its headers (but not the + * encapsulating part's headers). For multipart parts, + * this will include the headers. */ + if (stream_filter) + g_mime_object_write_to_stream (node->part, stream_filter); + } if (stream_filter) g_object_unref (stream_filter); if (stream_stdout) g_object_unref(stream_stdout); + + return NOTMUCH_STATUS_SUCCESS; } static notmuch_status_t @@ -893,50 +913,7 @@ do_show_single (void *ctx, notmuch_message_set_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH, 1); - /* Special case for --format=raw of full single message, just cat out file */ - if (params->raw && 0 == params->part) { - - const char *filename; - FILE *file; - size_t size; - char buf[4096]; - - filename = notmuch_message_get_filename (message); - if (filename == NULL) { - fprintf (stderr, "Error: Cannot message filename.\n"); - return 1; - } - - file = fopen (filename, "r"); - if (file == NULL) { - fprintf (stderr, "Error: Cannot open file %s: %s\n", filename, strerror (errno)); - return 1; - } - - while (!feof (file)) { - size = fread (buf, 1, sizeof (buf), file); - if (ferror (file)) { - fprintf (stderr, "Error: Read failed from %s\n", filename); - fclose (file); - return 1; - } - - if (fwrite (buf, size, 1, stdout) != 1) { - fprintf (stderr, "Error: Write failed\n"); - fclose (file); - return 1; - } - } - - fclose (file); - - return 0; - - } else { - - return show_message (ctx, format, message, 0, params) != NOTMUCH_STATUS_SUCCESS; - - } + return show_message (ctx, format, message, 0, params) != NOTMUCH_STATUS_SUCCESS; } /* Formatted output of threads */ diff --git a/test/multipart b/test/multipart index e73cd8b..afc4fc8 100755 --- a/test/multipart +++ b/test/multipart @@ -450,58 +450,80 @@ test_expect_equal_file OUTPUT "${MAIL_DIR}"/multipart test_begin_subtest "--format=raw --part=1, message body" notmuch show --format=raw --part=1 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT -# output should *not* include newline -echo >>OUTPUT -cat <EXPECTED -Subject: html message -From: Carl Worth -To: cworth@cworth.org -Date: Fri, 05 Jan 2001 15:42:57 +0000 - -

This is an embedded message, with a multipart/alternative part.

-This is an embedded message, with a multipart/alternative part. -This is a text attachment. -And this message is signed. - --Carl ------BEGIN PGP SIGNATURE----- -Version: GnuPG v1.4.11 (GNU/Linux) - -iEYEARECAAYFAk3SA/gACgkQ6JDdNq8qSWj0sACghqVJEQJUs3yV8zbTzhgnSIcD -W6cAmQE4dcYrx/LPLtYLZm1jsGauE5hE -=zkga ------END PGP SIGNATURE----- -EOF -test_expect_equal_file OUTPUT EXPECTED +test_expect_equal_file OUTPUT "${MAIL_DIR}"/multipart test_begin_subtest "--format=raw --part=2, multipart/mixed" notmuch show --format=raw --part=2 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT cat <EXPECTED -Subject: html message +Content-Type: multipart/mixed; boundary="=-=-=" + +--=-=-= +Content-Type: message/rfc822 +Content-Disposition: inline + From: Carl Worth To: cworth@cworth.org +Subject: html message Date: Fri, 05 Jan 2001 15:42:57 +0000 +User-Agent: Notmuch/0.5 (http://notmuchmail.org) Emacs/23.3.1 (i486-pc-linux-gnu) +Message-ID: <87liy5ap01.fsf@yoom.home.cworth.org> +MIME-Version: 1.0 +Content-Type: multipart/alternative; boundary="==-=-==" + +--==-=-== +Content-Type: text/html

This is an embedded message, with a multipart/alternative part.

+ +--==-=-== +Content-Type: text/plain + This is an embedded message, with a multipart/alternative part. + +--==-=-==-- + +--=-=-= +Content-Disposition: attachment; filename=attachment + This is a text attachment. + +--=-=-= + And this message is signed. -Carl + +--=-=-=-- EOF test_expect_equal_file OUTPUT EXPECTED test_begin_subtest "--format=raw --part=3, rfc822 part" -test_subtest_known_broken - notmuch show --format=raw --part=3 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT test_expect_equal_file OUTPUT embedded_message test_begin_subtest "--format=raw --part=4, rfc822's multipart" notmuch show --format=raw --part=4 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT cat <EXPECTED +From: Carl Worth +To: cworth@cworth.org +Subject: html message +Date: Fri, 05 Jan 2001 15:42:57 +0000 +User-Agent: Notmuch/0.5 (http://notmuchmail.org) Emacs/23.3.1 (i486-pc-linux-gnu) +Message-ID: <87liy5ap01.fsf@yoom.home.cworth.org> +MIME-Version: 1.0 +Content-Type: multipart/alternative; boundary="==-=-==" + +--==-=-== +Content-Type: text/html +

This is an embedded message, with a multipart/alternative part.

+ +--==-=-== +Content-Type: text/plain + This is an embedded message, with a multipart/alternative part. + +--==-=-==-- EOF test_expect_equal_file OUTPUT EXPECTED -- cgit v1.2.3 From 1904b01b96c1b731afb9649e7b5bceffce901b88 Mon Sep 17 00:00:00 2001 From: Adam Wolfe Gordon Date: Sun, 18 Mar 2012 10:32:36 -0600 Subject: reply: Add a JSON reply format. This new JSON format for replies includes headers generated for a reply message as well as the headers 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. --- notmuch-client.h | 12 +++++++++--- notmuch-reply.c | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ notmuch-show.c | 29 +++++++++++++++++++++-------- test/multipart | 1 - 4 files changed, 79 insertions(+), 12 deletions(-) (limited to 'notmuch-show.c') diff --git a/notmuch-client.h b/notmuch-client.h index a220fe4..fa04fa2 100644 --- a/notmuch-client.h +++ b/notmuch-client.h @@ -62,7 +62,7 @@ #define STRINGIFY(s) STRINGIFY_(s) #define STRINGIFY_(s) #s -struct mime_node; +typedef struct mime_node mime_node_t; struct notmuch_show_params; typedef struct notmuch_show_format { @@ -191,6 +191,12 @@ show_message_body (notmuch_message_t *message, notmuch_status_t show_one_part (const char *filename, int part); +void +format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first); + +void +format_headers_json (const void *ctx, GMimeMessage *message, notmuch_bool_t reply); + char * json_quote_chararray (const void *ctx, const char *str, const size_t len); @@ -288,7 +294,7 @@ debugger_is_active (void); * parts. Message-type parts have one child, multipart-type parts * have multiple children, and leaf parts have zero children. */ -typedef struct mime_node { +struct mime_node { /* The MIME object of this part. This will be a GMimeMessage, * GMimePart, GMimeMultipart, or a subclass of one of these. * @@ -351,7 +357,7 @@ typedef struct mime_node { * number to assign it (or -1 if unknown). */ int next_child; int next_part_num; -} mime_node_t; +}; /* Construct a new MIME node pointing to the root message part of * message. If cryptoctx is non-NULL, it will be used to verify diff --git a/notmuch-reply.c b/notmuch-reply.c index f1478cc..e2b6c25 100644 --- a/notmuch-reply.c +++ b/notmuch-reply.c @@ -604,6 +604,51 @@ notmuch_reply_format_default(void *ctx, return 0; } +static int +notmuch_reply_format_json(void *ctx, + notmuch_config_t *config, + notmuch_query_t *query, + notmuch_show_params_t *params, + notmuch_bool_t reply_all) +{ + GMimeMessage *reply; + notmuch_messages_t *messages; + notmuch_message_t *message; + mime_node_t *node; + + if (notmuch_query_count_messages (query) != 1) { + fprintf (stderr, "Error: search term did not match precisely one message.\n"); + return 1; + } + + messages = notmuch_query_search_messages (query); + message = notmuch_messages_get (messages); + if (mime_node_open (ctx, message, params->cryptoctx, params->decrypt, + &node) != NOTMUCH_STATUS_SUCCESS) + return 1; + + reply = create_reply_message (ctx, config, message, reply_all); + if (!reply) + return 1; + + /* The headers of the reply message we've created */ + printf ("{\"reply-headers\": "); + format_headers_json (ctx, reply, TRUE); + g_object_unref (G_OBJECT (reply)); + reply = NULL; + + /* Start the original */ + printf (", \"original\": "); + + format_part_json (ctx, node, TRUE); + + /* End */ + printf ("}\n"); + notmuch_message_destroy (message); + + return 0; +} + /* This format is currently tuned for a git send-email --notmuch hook */ static int notmuch_reply_format_headers_only(void *ctx, @@ -666,6 +711,7 @@ notmuch_reply_format_headers_only(void *ctx, enum { FORMAT_DEFAULT, + FORMAT_JSON, FORMAT_HEADERS_ONLY, }; @@ -685,6 +731,7 @@ notmuch_reply_command (void *ctx, int argc, char *argv[]) notmuch_opt_desc_t options[] = { { NOTMUCH_OPT_KEYWORD, &format, "format", 'f', (notmuch_keyword_t []){ { "default", FORMAT_DEFAULT }, + { "json", FORMAT_JSON }, { "headers-only", FORMAT_HEADERS_ONLY }, { 0, 0 } } }, { NOTMUCH_OPT_KEYWORD, &reply_all, "reply-to", 'r', @@ -703,6 +750,8 @@ notmuch_reply_command (void *ctx, int argc, char *argv[]) if (format == FORMAT_HEADERS_ONLY) reply_format_func = notmuch_reply_format_headers_only; + else if (format == FORMAT_JSON) + reply_format_func = notmuch_reply_format_json; else reply_format_func = notmuch_reply_format_default; diff --git a/notmuch-show.c b/notmuch-show.c index a7463dc..ff9d427 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -200,8 +200,8 @@ _is_from_line (const char *line) return 0; } -static void -format_headers_json (const void *ctx, GMimeMessage *message) +void +format_headers_json (const void *ctx, GMimeMessage *message, notmuch_bool_t reply) { void *local = talloc_new (ctx); InternetAddressList *recipients; @@ -225,9 +225,22 @@ format_headers_json (const void *ctx, GMimeMessage *message) printf (", %s: %s", json_quote_str (local, "Cc"), json_quote_str (local, recipients_string)); - printf (", %s: %s}", - json_quote_str (local, "Date"), - json_quote_str (local, g_mime_message_get_date_as_string (message))); + + if (reply) { + printf (", %s: %s", + json_quote_str (local, "In-reply-to"), + json_quote_str (local, g_mime_object_get_header (GMIME_OBJECT (message), "In-reply-to"))); + + printf (", %s: %s", + json_quote_str (local, "References"), + json_quote_str (local, g_mime_object_get_header (GMIME_OBJECT (message), "References"))); + } else { + printf (", %s: %s", + json_quote_str (local, "Date"), + json_quote_str (local, g_mime_message_get_date_as_string (message))); + } + + printf ("}"); talloc_free (local); } @@ -538,7 +551,7 @@ format_part_text (const void *ctx, mime_node_t *node, return NOTMUCH_STATUS_SUCCESS; } -static void +void format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first) { /* Any changes to the JSON format should be reflected in the file @@ -549,7 +562,7 @@ format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first) format_message_json (ctx, node->envelope_file); printf ("\"headers\": "); - format_headers_json (ctx, GMIME_MESSAGE (node->part)); + format_headers_json (ctx, GMIME_MESSAGE (node->part), FALSE); printf (", \"body\": ["); format_part_json (ctx, mime_node_child (node, 0), first); @@ -623,7 +636,7 @@ format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first) } else if (GMIME_IS_MESSAGE (node->part)) { printf (", \"content\": [{"); printf ("\"headers\": "); - format_headers_json (local, GMIME_MESSAGE (node->part)); + format_headers_json (local, GMIME_MESSAGE (node->part), FALSE); printf (", \"body\": ["); terminator = "]}]"; diff --git a/test/multipart b/test/multipart index e5de5d3..72d3927 100755 --- a/test/multipart +++ b/test/multipart @@ -613,7 +613,6 @@ EOF test_expect_equal_file OUTPUT EXPECTED test_begin_subtest "'notmuch reply' to a multipart message with json format" -test_subtest_known_broken notmuch reply --format=json 'id:87liy5ap00.fsf@yoom.home.cworth.org' | notmuch_json_show_sanitize >OUTPUT cat <EXPECTED {"reply-headers": {"Subject": "Re: Multipart message", -- cgit v1.2.3 From ea4fd50f45b0bc0888070125c632ea2326eb18f7 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Tue, 27 Mar 2012 17:59:50 -0400 Subject: show/reply: Unify the code that extracts text parts Previously, show and reply had separate implementations of decoding and printing text parts. Now both use show's implementation, which was more complete. Show's implementation has been extended with an option to add reply quoting to the extracted part (this is implemented as a named flag to avoid naked booleans, even though it's the only flag it can take). --- notmuch-client.h | 8 ++++++++ notmuch-reply.c | 28 ++++------------------------ notmuch-show.c | 23 +++++++++++++++++++---- 3 files changed, 31 insertions(+), 28 deletions(-) (limited to 'notmuch-show.c') diff --git a/notmuch-client.h b/notmuch-client.h index fa04fa2..203ac49 100644 --- a/notmuch-client.h +++ b/notmuch-client.h @@ -197,6 +197,14 @@ format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first); void format_headers_json (const void *ctx, GMimeMessage *message, notmuch_bool_t reply); +typedef enum { + NOTMUCH_SHOW_TEXT_PART_REPLY = 1 << 0, +} notmuch_show_text_part_flags; + +void +show_text_part_content (GMimeObject *part, GMimeStream *stream_out, + notmuch_show_text_part_flags flags); + char * json_quote_chararray (const void *ctx, const char *str, const size_t len); diff --git a/notmuch-reply.c b/notmuch-reply.c index e2b6c25..2f5ed3d 100644 --- a/notmuch-reply.c +++ b/notmuch-reply.c @@ -21,7 +21,6 @@ */ #include "notmuch-client.h" -#include "gmime-filter-reply.h" #include "gmime-filter-headers.h" static void @@ -106,29 +105,10 @@ reply_part_content (GMimeObject *part) else if (g_mime_content_type_is_type (content_type, "text", "*") && !g_mime_content_type_is_type (content_type, "text", "html")) { - GMimeStream *stream_stdout = NULL, *stream_filter = NULL; - GMimeDataWrapper *wrapper; - const char *charset; - - charset = g_mime_object_get_content_type_parameter (part, "charset"); - stream_stdout = g_mime_stream_file_new (stdout); - if (stream_stdout) { - g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream_stdout), FALSE); - stream_filter = g_mime_stream_filter_new(stream_stdout); - if (charset) { - g_mime_stream_filter_add(GMIME_STREAM_FILTER(stream_filter), - g_mime_filter_charset_new(charset, "UTF-8")); - } - } - g_mime_stream_filter_add(GMIME_STREAM_FILTER(stream_filter), - g_mime_filter_reply_new(TRUE)); - wrapper = g_mime_part_get_content_object (GMIME_PART (part)); - if (wrapper && stream_filter) - g_mime_data_wrapper_write_to_stream (wrapper, stream_filter); - if (stream_filter) - g_object_unref(stream_filter); - if (stream_stdout) - g_object_unref(stream_stdout); + GMimeStream *stream_stdout = g_mime_stream_file_new (stdout); + g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream_stdout), FALSE); + show_text_part_content (part, stream_stdout, NOTMUCH_SHOW_TEXT_PART_REPLY); + g_object_unref(stream_stdout); } else { diff --git a/notmuch-show.c b/notmuch-show.c index ff9d427..0bf5e21 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -19,6 +19,7 @@ */ #include "notmuch-client.h" +#include "gmime-filter-reply.h" static notmuch_status_t format_part_text (const void *ctx, mime_node_t *node, @@ -246,14 +247,18 @@ format_headers_json (const void *ctx, GMimeMessage *message, notmuch_bool_t repl } /* Write a MIME text part out to the given stream. + * + * If (flags & NOTMUCH_SHOW_TEXT_PART_REPLY), this prepends "> " to + * each output line. * * Both line-ending conversion (CRLF->LF) and charset conversion ( -> * UTF-8) will be performed, so it is inappropriate to call this * function with a non-text part. Doing so will trigger an internal * error. */ -static void -show_text_part_content (GMimeObject *part, GMimeStream *stream_out) +void +show_text_part_content (GMimeObject *part, GMimeStream *stream_out, + notmuch_show_text_part_flags flags) { GMimeContentType *content_type = g_mime_object_get_content_type (GMIME_OBJECT (part)); GMimeStream *stream_filter = NULL; @@ -286,6 +291,16 @@ show_text_part_content (GMimeObject *part, GMimeStream *stream_out) } + if (flags & NOTMUCH_SHOW_TEXT_PART_REPLY) { + GMimeFilter *reply_filter; + reply_filter = g_mime_filter_reply_new (TRUE); + if (reply_filter) { + g_mime_stream_filter_add (GMIME_STREAM_FILTER (stream_filter), + reply_filter); + g_object_unref (reply_filter); + } + } + wrapper = g_mime_part_get_content_object (GMIME_PART (part)); if (wrapper && stream_filter) g_mime_data_wrapper_write_to_stream (wrapper, stream_filter); @@ -532,7 +547,7 @@ format_part_text (const void *ctx, mime_node_t *node, { GMimeStream *stream_stdout = g_mime_stream_file_new (stdout); g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream_stdout), FALSE); - show_text_part_content (node->part, stream_stdout); + show_text_part_content (node->part, stream_stdout, 0); g_object_unref(stream_stdout); } else { printf ("Non-text part: %s\n", @@ -624,7 +639,7 @@ format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first) } else if (g_mime_content_type_is_type (content_type, "text", "*")) { GMimeStream *stream_memory = g_mime_stream_mem_new (); GByteArray *part_content; - show_text_part_content (node->part, stream_memory); + show_text_part_content (node->part, stream_memory, 0); part_content = g_mime_stream_mem_get_byte_array (GMIME_STREAM_MEM (stream_memory)); printf (", \"content\": %s", json_quote_chararray (local, (char *) part_content->data, part_content->len)); -- cgit v1.2.3 From 903327279c38d6750e8347eba23262fa487c6951 Mon Sep 17 00:00:00 2001 From: Mark Walters Date: Sat, 7 Apr 2012 17:10:06 +0100 Subject: cli: move show to the new --exclude= option naming scheme. This moves notmuch show to the --exclude=(true|false) naming scheme. When exclude=false show returns all threads that match including those that only match in an excluded message. The excluded messages are flagged. When exclude=true the behaviour depends on whether --entire-thread is set. If it is not set then show only returns the messages which match and are not excluded. If it is set then show returns all messages in the threads that match in a non-excluded message, flagging the excluded messages in these threads. The rationale is that it is awkward to use a thread with some missing messages. --- man/man1/notmuch-show.1 | 16 ++++++++++++++-- notmuch-client.h | 1 + notmuch-show.c | 50 ++++++++++++++++++++++++++++++------------------- 3 files changed, 46 insertions(+), 21 deletions(-) (limited to 'notmuch-show.c') diff --git a/man/man1/notmuch-show.1 b/man/man1/notmuch-show.1 index b81cce6..83cc575 100644 --- a/man/man1/notmuch-show.1 +++ b/man/man1/notmuch-show.1 @@ -135,9 +135,21 @@ content. .RS 4 .TP 4 -.B \-\-no-exclude +.BR \-\-exclude=(true|false) + +Specify whether to omit threads only matching search.tag_exclude from +the search results (the default) or not. In either case the excluded +message will be marked with the exclude flag (except when output=mbox +when there is nowhere to put the flag). + +If --entire-thread is specified then complete threads are returned +regardless (with the excluded flag being set when appropriate) but +threads that only match in an excluded message are not returned when +.B --exclude=true. + +The default is +.B --exclude=true. -Do not exclude the messages matching search.exclude_tags in the config file. .RE A common use of diff --git a/notmuch-client.h b/notmuch-client.h index 203ac49..880b153 100644 --- a/notmuch-client.h +++ b/notmuch-client.h @@ -99,6 +99,7 @@ typedef struct notmuch_show_format { typedef struct notmuch_show_params { notmuch_bool_t entire_thread; + notmuch_bool_t omit_excluded; notmuch_bool_t raw; int part; #ifdef GMIME_ATLEAST_26 diff --git a/notmuch-show.c b/notmuch-show.c index 0bf5e21..7af8e64 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -866,6 +866,7 @@ show_messages (void *ctx, { notmuch_message_t *message; notmuch_bool_t match; + notmuch_bool_t excluded; int first_set = 1; int next_indent; notmuch_status_t status, res = NOTMUCH_STATUS_SUCCESS; @@ -885,10 +886,11 @@ show_messages (void *ctx, message = notmuch_messages_get (messages); match = notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH); + excluded = notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED); next_indent = indent; - if (match || params->entire_thread) { + if ((match && (!excluded || !params->omit_excluded)) || params->entire_thread) { status = show_message (ctx, format, message, indent, params); if (status && !res) res = status; @@ -996,6 +998,12 @@ enum { NOTMUCH_FORMAT_RAW }; +/* The following is to allow future options to be added more easily */ +enum { + EXCLUDE_TRUE, + EXCLUDE_FALSE, +}; + int notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) { @@ -1005,10 +1013,10 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) char *query_string; int opt_index, ret; const notmuch_show_format_t *format = &format_text; - notmuch_show_params_t params = { .part = -1 }; + notmuch_show_params_t params = { .part = -1, .omit_excluded = TRUE }; int format_sel = NOTMUCH_FORMAT_NOT_SPECIFIED; notmuch_bool_t verify = FALSE; - notmuch_bool_t no_exclude = FALSE; + int exclude = EXCLUDE_TRUE; notmuch_opt_desc_t options[] = { { NOTMUCH_OPT_KEYWORD, &format_sel, "format", 'f', @@ -1017,11 +1025,14 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) { "mbox", NOTMUCH_FORMAT_MBOX }, { "raw", NOTMUCH_FORMAT_RAW }, { 0, 0 } } }, + { NOTMUCH_OPT_KEYWORD, &exclude, "exclude", 'x', + (notmuch_keyword_t []){ { "true", EXCLUDE_TRUE }, + { "false", EXCLUDE_FALSE }, + { 0, 0 } } }, { NOTMUCH_OPT_INT, ¶ms.part, "part", 'p', 0 }, { NOTMUCH_OPT_BOOLEAN, ¶ms.entire_thread, "entire-thread", 't', 0 }, { NOTMUCH_OPT_BOOLEAN, ¶ms.decrypt, "decrypt", 'd', 0 }, { NOTMUCH_OPT_BOOLEAN, &verify, "verify", 'v', 0 }, - { NOTMUCH_OPT_BOOLEAN, &no_exclude, "no-exclude", 'n', 0 }, { 0, 0, 0, 0, 0 } }; @@ -1110,29 +1121,30 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) return 1; } - /* if format=mbox then we can not output excluded messages as - * there is no way to make the exclude flag available */ - if (format_sel == NOTMUCH_FORMAT_MBOX) - notmuch_query_set_omit_excluded_messages (query, TRUE); - /* If a single message is requested we do not use search_excludes. */ if (params.part >= 0) ret = do_show_single (ctx, query, format, ¶ms); else { - if (!no_exclude) { - const char **search_exclude_tags; - size_t search_exclude_tags_length; - unsigned int i; - - search_exclude_tags = notmuch_config_get_search_exclude_tags - (config, &search_exclude_tags_length); - for (i = 0; i < search_exclude_tags_length; i++) - notmuch_query_add_tag_exclude (query, search_exclude_tags[i]); + /* We always apply set the exclude flag. The + * exclude=true|false option controls whether or not we return + * threads that only match in an excluded message */ + const char **search_exclude_tags; + size_t search_exclude_tags_length; + unsigned int i; + + search_exclude_tags = notmuch_config_get_search_exclude_tags + (config, &search_exclude_tags_length); + for (i = 0; i < search_exclude_tags_length; i++) + notmuch_query_add_tag_exclude (query, search_exclude_tags[i]); + + if (exclude == EXCLUDE_FALSE) { + notmuch_query_set_omit_excluded (query, FALSE); + params.omit_excluded = FALSE; } + ret = do_show (ctx, query, format, ¶ms); } - notmuch_query_destroy (query); notmuch_database_close (notmuch); -- cgit v1.2.3 From b92f15d014604452c148e75b5b8bd38b3f382f20 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Sat, 7 Apr 2012 20:57:46 -0400 Subject: show: Remove support for old-style formatters in show_message show_message used to have a compatibility path for old-style formatters. This removes that. --- notmuch-show.c | 52 +++++++++++++--------------------------------------- 1 file changed, 13 insertions(+), 39 deletions(-) (limited to 'notmuch-show.c') diff --git a/notmuch-show.c b/notmuch-show.c index 7af8e64..69164bd 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -815,46 +815,20 @@ show_message (void *ctx, int indent, notmuch_show_params_t *params) { - if (format->part) { - void *local = talloc_new (ctx); - mime_node_t *root, *part; - notmuch_status_t status; - - status = mime_node_open (local, message, params->cryptoctx, - params->decrypt, &root); - if (status) - goto DONE; - part = mime_node_seek_dfs (root, (params->part < 0 ? 0 : params->part)); - if (part) - status = format->part (local, part, indent, params); - DONE: - talloc_free (local); - return status; - } - - if (params->part <= 0) { - fputs (format->message_start, stdout); - if (format->message) - format->message(ctx, message, indent); - - fputs (format->header_start, stdout); - if (format->header) - format->header(ctx, message); - fputs (format->header_end, stdout); - - fputs (format->body_start, stdout); - } - - if (format->part_content) - show_message_body (message, format, params); - - if (params->part <= 0) { - fputs (format->body_end, stdout); - - fputs (format->message_end, stdout); - } + void *local = talloc_new (ctx); + mime_node_t *root, *part; + notmuch_status_t status; - return NOTMUCH_STATUS_SUCCESS; + status = mime_node_open (local, message, params->cryptoctx, + params->decrypt, &root); + if (status) + goto DONE; + part = mime_node_seek_dfs (root, (params->part < 0 ? 0 : params->part)); + if (part) + status = format->part (local, part, indent, params); + DONE: + talloc_free (local); + return status; } static notmuch_status_t -- cgit v1.2.3 From 67da35222c73672d61050c0561757c0b739e9195 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Sat, 7 Apr 2012 20:57:49 -0400 Subject: show: Support NULL values for message_set_{start, sep, end} Many formats don't need these, so it's more convenient if they don't have to set them at all. --- notmuch-show.c | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) (limited to 'notmuch-show.c') diff --git a/notmuch-show.c b/notmuch-show.c index 69164bd..aa90932 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -845,17 +845,19 @@ show_messages (void *ctx, int next_indent; notmuch_status_t status, res = NOTMUCH_STATUS_SUCCESS; - fputs (format->message_set_start, stdout); + if (format->message_set_start) + fputs (format->message_set_start, stdout); for (; notmuch_messages_valid (messages); notmuch_messages_move_to_next (messages)) { - if (!first_set) + if (!first_set && format->message_set_sep) fputs (format->message_set_sep, stdout); first_set = 0; - fputs (format->message_set_start, stdout); + if (format->message_set_start) + fputs (format->message_set_start, stdout); message = notmuch_messages_get (messages); @@ -870,7 +872,7 @@ show_messages (void *ctx, res = status; next_indent = indent + 1; - if (!status) + if (!status && format->message_set_sep) fputs (format->message_set_sep, stdout); } @@ -884,10 +886,12 @@ show_messages (void *ctx, notmuch_message_destroy (message); - fputs (format->message_set_end, stdout); + if (format->message_set_end) + fputs (format->message_set_end, stdout); } - fputs (format->message_set_end, stdout); + if (format->message_set_end) + fputs (format->message_set_end, stdout); return res; } @@ -933,7 +937,8 @@ do_show (void *ctx, int first_toplevel = 1; notmuch_status_t status, res = NOTMUCH_STATUS_SUCCESS; - fputs (format->message_set_start, stdout); + if (format->message_set_start) + fputs (format->message_set_start, stdout); for (threads = notmuch_query_search_threads (query); notmuch_threads_valid (threads); @@ -947,7 +952,7 @@ do_show (void *ctx, INTERNAL_ERROR ("Thread %s has no toplevel messages.\n", notmuch_thread_get_thread_id (thread)); - if (!first_toplevel) + if (!first_toplevel && format->message_set_sep) fputs (format->message_set_sep, stdout); first_toplevel = 0; @@ -959,7 +964,8 @@ do_show (void *ctx, } - fputs (format->message_set_end, stdout); + if (format->message_set_end) + fputs (format->message_set_end, stdout); return res != NOTMUCH_STATUS_SUCCESS; } -- cgit v1.2.3 From 2886af551eaefb0ddc841753c11de6973e4baff0 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Sat, 7 Apr 2012 20:57:50 -0400 Subject: show: Remove empty message_set_{start,sep,end} fields Setting these to NULL is equivalent to the empty string now. --- notmuch-show.c | 9 --------- 1 file changed, 9 deletions(-) (limited to 'notmuch-show.c') diff --git a/notmuch-show.c b/notmuch-show.c index aa90932..da4a797 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -26,10 +26,7 @@ format_part_text (const void *ctx, mime_node_t *node, int indent, const notmuch_show_params_t *params); static const notmuch_show_format_t format_text = { - .message_set_start = "", .part = format_part_text, - .message_set_sep = "", - .message_set_end = "" }; static notmuch_status_t @@ -48,10 +45,7 @@ format_part_mbox (const void *ctx, mime_node_t *node, int indent, const notmuch_show_params_t *params); static const notmuch_show_format_t format_mbox = { - .message_set_start = "", .part = format_part_mbox, - .message_set_sep = "", - .message_set_end = "" }; static notmuch_status_t @@ -60,10 +54,7 @@ format_part_raw (unused (const void *ctx), mime_node_t *node, unused (const notmuch_show_params_t *params)); static const notmuch_show_format_t format_raw = { - .message_set_start = "", .part = format_part_raw, - .message_set_sep = "", - .message_set_end = "" }; static const char * -- cgit v1.2.3 From 6f7469f54744656f90ce215f365d5731e16acd3c Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Sun, 22 Apr 2012 14:07:53 +0200 Subject: Use notmuch_database_destroy instead of notmuch_database_close Adapt the notmuch binaries source to the notmuch_database_close split. Signed-off-by: Justus Winter <4winter@informatik.uni-hamburg.de> --- notmuch-count.c | 2 +- notmuch-dump.c | 2 +- notmuch-new.c | 2 +- notmuch-reply.c | 2 +- notmuch-restore.c | 2 +- notmuch-search.c | 2 +- notmuch-show.c | 2 +- notmuch-tag.c | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) (limited to 'notmuch-show.c') diff --git a/notmuch-count.c b/notmuch-count.c index b76690c..9c2ad7b 100644 --- a/notmuch-count.c +++ b/notmuch-count.c @@ -107,7 +107,7 @@ notmuch_count_command (void *ctx, int argc, char *argv[]) } notmuch_query_destroy (query); - notmuch_database_close (notmuch); + notmuch_database_destroy (notmuch); return 0; } diff --git a/notmuch-dump.c b/notmuch-dump.c index a735875..71ab0ea 100644 --- a/notmuch-dump.c +++ b/notmuch-dump.c @@ -116,7 +116,7 @@ notmuch_dump_command (unused (void *ctx), int argc, char *argv[]) fclose (output); notmuch_query_destroy (query); - notmuch_database_close (notmuch); + notmuch_database_destroy (notmuch); return 0; } diff --git a/notmuch-new.c b/notmuch-new.c index 473201e..3ff6304 100644 --- a/notmuch-new.c +++ b/notmuch-new.c @@ -1041,7 +1041,7 @@ notmuch_new_command (void *ctx, int argc, char *argv[]) fprintf (stderr, "Note: A fatal error was encountered: %s\n", notmuch_status_to_string (ret)); - notmuch_database_close (notmuch); + notmuch_database_destroy (notmuch); if (run_hooks && !ret && !interrupted) ret = notmuch_run_hook (db_path, "post-new"); diff --git a/notmuch-reply.c b/notmuch-reply.c index 0949d9f..da99a13 100644 --- a/notmuch-reply.c +++ b/notmuch-reply.c @@ -755,7 +755,7 @@ notmuch_reply_command (void *ctx, int argc, char *argv[]) return 1; notmuch_query_destroy (query); - notmuch_database_close (notmuch); + notmuch_database_destroy (notmuch); if (params.cryptoctx) g_object_unref(params.cryptoctx); diff --git a/notmuch-restore.c b/notmuch-restore.c index d3b9246..02b563c 100644 --- a/notmuch-restore.c +++ b/notmuch-restore.c @@ -192,7 +192,7 @@ notmuch_restore_command (unused (void *ctx), int argc, char *argv[]) if (line) free (line); - notmuch_database_close (notmuch); + notmuch_database_destroy (notmuch); if (input != stdin) fclose (input); diff --git a/notmuch-search.c b/notmuch-search.c index 1cc8430..7dfd270 100644 --- a/notmuch-search.c +++ b/notmuch-search.c @@ -545,7 +545,7 @@ notmuch_search_command (void *ctx, int argc, char *argv[]) } notmuch_query_destroy (query); - notmuch_database_close (notmuch); + notmuch_database_destroy (notmuch); return ret; } diff --git a/notmuch-show.c b/notmuch-show.c index da4a797..3b6667c 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -1117,7 +1117,7 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) } notmuch_query_destroy (query); - notmuch_database_close (notmuch); + notmuch_database_destroy (notmuch); if (params.cryptoctx) g_object_unref(params.cryptoctx); diff --git a/notmuch-tag.c b/notmuch-tag.c index 05feed3..bd56fd1 100644 --- a/notmuch-tag.c +++ b/notmuch-tag.c @@ -238,7 +238,7 @@ notmuch_tag_command (void *ctx, int argc, char *argv[]) ret = tag_query (ctx, notmuch, query_string, tag_ops, synchronize_flags); - notmuch_database_close (notmuch); + notmuch_database_destroy (notmuch); return ret; } -- cgit v1.2.3 From 5fddc07dc31481453c1af186bf7da241c00cdbf1 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Mon, 30 Apr 2012 12:25:33 -0400 Subject: lib/cli: Make notmuch_database_open return a status code It has been a long-standing issue that notmuch_database_open doesn't return any indication of why it failed. This patch changes its prototype to return a notmuch_status_t and set an out-argument to the database itself, like other functions that return both a status and an object. In the interest of atomicity, this also updates every use in the CLI so that notmuch still compiles. Since this patch does not update the bindings, the Python bindings test fails. --- lib/database.cc | 28 +++++++++++++++++++++++----- lib/notmuch.h | 26 +++++++++++++++++++------- notmuch-count.c | 5 ++--- notmuch-dump.c | 5 ++--- notmuch-new.c | 5 ++--- notmuch-reply.c | 5 ++--- notmuch-restore.c | 5 ++--- notmuch-search.c | 5 ++--- notmuch-show.c | 5 ++--- notmuch-tag.c | 5 ++--- test/symbol-test.cc | 3 ++- 11 files changed, 60 insertions(+), 37 deletions(-) (limited to 'notmuch-show.c') diff --git a/lib/database.cc b/lib/database.cc index 2fefcad..1e66599 100644 --- a/lib/database.cc +++ b/lib/database.cc @@ -556,8 +556,9 @@ notmuch_database_create (const char *path) goto DONE; } - notmuch = notmuch_database_open (path, - NOTMUCH_DATABASE_MODE_READ_WRITE); + notmuch_database_open (path, + NOTMUCH_DATABASE_MODE_READ_WRITE, + ¬much); notmuch_database_upgrade (notmuch, NULL, NULL); DONE: @@ -578,10 +579,12 @@ _notmuch_database_ensure_writable (notmuch_database_t *notmuch) return NOTMUCH_STATUS_SUCCESS; } -notmuch_database_t * +notmuch_status_t notmuch_database_open (const char *path, - notmuch_database_mode_t mode) + notmuch_database_mode_t mode, + notmuch_database_t **database) { + notmuch_status_t status = NOTMUCH_STATUS_SUCCESS; void *local = talloc_new (NULL); notmuch_database_t *notmuch = NULL; char *notmuch_path, *xapian_path; @@ -590,8 +593,15 @@ notmuch_database_open (const char *path, unsigned int i, version; static int initialized = 0; + if (path == NULL) { + fprintf (stderr, "Error: Cannot open a database for a NULL path.\n"); + status = NOTMUCH_STATUS_NULL_POINTER; + goto DONE; + } + if (! (notmuch_path = talloc_asprintf (local, "%s/%s", path, ".notmuch"))) { fprintf (stderr, "Out of memory\n"); + status = NOTMUCH_STATUS_OUT_OF_MEMORY; goto DONE; } @@ -599,11 +609,13 @@ notmuch_database_open (const char *path, if (err) { fprintf (stderr, "Error opening database at %s: %s\n", notmuch_path, strerror (errno)); + status = NOTMUCH_STATUS_FILE_ERROR; goto DONE; } if (! (xapian_path = talloc_asprintf (local, "%s/%s", notmuch_path, "xapian"))) { fprintf (stderr, "Out of memory\n"); + status = NOTMUCH_STATUS_OUT_OF_MEMORY; goto DONE; } @@ -644,6 +656,7 @@ notmuch_database_open (const char *path, notmuch->mode = NOTMUCH_DATABASE_MODE_READ_ONLY; notmuch_database_destroy (notmuch); notmuch = NULL; + status = NOTMUCH_STATUS_FILE_ERROR; goto DONE; } @@ -704,12 +717,17 @@ notmuch_database_open (const char *path, error.get_msg().c_str()); notmuch_database_destroy (notmuch); notmuch = NULL; + status = NOTMUCH_STATUS_XAPIAN_EXCEPTION; } DONE: talloc_free (local); - return notmuch; + if (database) + *database = notmuch; + else + talloc_free (notmuch); + return status; } void diff --git a/lib/notmuch.h b/lib/notmuch.h index 7d9e092..44b0c46 100644 --- a/lib/notmuch.h +++ b/lib/notmuch.h @@ -151,9 +151,6 @@ typedef enum { NOTMUCH_DATABASE_MODE_READ_WRITE } notmuch_database_mode_t; -/* XXX: I think I'd like this to take an extra argument of - * notmuch_status_t* for returning a status value on failure. */ - /* Open an existing notmuch database located at 'path'. * * The database should have been created at some time in the past, @@ -168,12 +165,27 @@ typedef enum { * The caller should call notmuch_database_destroy when finished with * this database. * - * In case of any failure, this function returns NULL, (after printing - * an error message on stderr). + * In case of any failure, this function returns an error status and + * sets *database to NULL (after printing an error message on stderr). + * + * Return value: + * + * NOTMUCH_STATUS_SUCCESS: Successfully opened the database. + * + * NOTMUCH_STATUS_NULL_POINTER: The given 'path' argument is NULL. + * + * NOTMUCH_STATUS_OUT_OF_MEMORY: Out of memory. + * + * NOTMUCH_STATUS_FILE_ERROR: An error occurred trying to open the + * database file (such as permission denied, or file not found, + * etc.), or the database version is unknown. + * + * NOTMUCH_STATUS_XAPIAN_EXCEPTION: A Xapian exception occurred. */ -notmuch_database_t * +notmuch_status_t notmuch_database_open (const char *path, - notmuch_database_mode_t mode); + notmuch_database_mode_t mode, + notmuch_database_t **database); /* Close the given notmuch database. * diff --git a/notmuch-count.c b/notmuch-count.c index 9c2ad7b..2f98128 100644 --- a/notmuch-count.c +++ b/notmuch-count.c @@ -66,9 +66,8 @@ notmuch_count_command (void *ctx, int argc, char *argv[]) if (config == NULL) return 1; - notmuch = notmuch_database_open (notmuch_config_get_database_path (config), - NOTMUCH_DATABASE_MODE_READ_ONLY); - if (notmuch == NULL) + if (notmuch_database_open (notmuch_config_get_database_path (config), + NOTMUCH_DATABASE_MODE_READ_ONLY, ¬much)) return 1; query_str = query_string_from_args (ctx, argc-opt_index, argv+opt_index); diff --git a/notmuch-dump.c b/notmuch-dump.c index 71ab0ea..3743214 100644 --- a/notmuch-dump.c +++ b/notmuch-dump.c @@ -36,9 +36,8 @@ notmuch_dump_command (unused (void *ctx), int argc, char *argv[]) if (config == NULL) return 1; - notmuch = notmuch_database_open (notmuch_config_get_database_path (config), - NOTMUCH_DATABASE_MODE_READ_ONLY); - if (notmuch == NULL) + if (notmuch_database_open (notmuch_config_get_database_path (config), + NOTMUCH_DATABASE_MODE_READ_ONLY, ¬much)) return 1; char *output_file_name = NULL; diff --git a/notmuch-new.c b/notmuch-new.c index 3ff6304..7788743 100644 --- a/notmuch-new.c +++ b/notmuch-new.c @@ -903,9 +903,8 @@ notmuch_new_command (void *ctx, int argc, char *argv[]) notmuch = notmuch_database_create (db_path); add_files_state.total_files = count; } else { - notmuch = notmuch_database_open (db_path, - NOTMUCH_DATABASE_MODE_READ_WRITE); - if (notmuch == NULL) + if (notmuch_database_open (db_path, NOTMUCH_DATABASE_MODE_READ_WRITE, + ¬much)) return 1; if (notmuch_database_needs_upgrade (notmuch)) { diff --git a/notmuch-reply.c b/notmuch-reply.c index da99a13..7184a5d 100644 --- a/notmuch-reply.c +++ b/notmuch-reply.c @@ -740,9 +740,8 @@ notmuch_reply_command (void *ctx, int argc, char *argv[]) return 1; } - notmuch = notmuch_database_open (notmuch_config_get_database_path (config), - NOTMUCH_DATABASE_MODE_READ_ONLY); - if (notmuch == NULL) + if (notmuch_database_open (notmuch_config_get_database_path (config), + NOTMUCH_DATABASE_MODE_READ_ONLY, ¬much)) return 1; query = notmuch_query_create (notmuch, query_string); diff --git a/notmuch-restore.c b/notmuch-restore.c index 02b563c..4f4096e 100644 --- a/notmuch-restore.c +++ b/notmuch-restore.c @@ -115,9 +115,8 @@ notmuch_restore_command (unused (void *ctx), int argc, char *argv[]) if (config == NULL) return 1; - notmuch = notmuch_database_open (notmuch_config_get_database_path (config), - NOTMUCH_DATABASE_MODE_READ_WRITE); - if (notmuch == NULL) + if (notmuch_database_open (notmuch_config_get_database_path (config), + NOTMUCH_DATABASE_MODE_READ_WRITE, ¬much)) return 1; synchronize_flags = notmuch_config_get_maildir_synchronize_flags (config); diff --git a/notmuch-search.c b/notmuch-search.c index 7dfd270..3be296d 100644 --- a/notmuch-search.c +++ b/notmuch-search.c @@ -486,9 +486,8 @@ notmuch_search_command (void *ctx, int argc, char *argv[]) if (config == NULL) return 1; - notmuch = notmuch_database_open (notmuch_config_get_database_path (config), - NOTMUCH_DATABASE_MODE_READ_ONLY); - if (notmuch == NULL) + if (notmuch_database_open (notmuch_config_get_database_path (config), + NOTMUCH_DATABASE_MODE_READ_ONLY, ¬much)) return 1; query_str = query_string_from_args (notmuch, argc-opt_index, argv+opt_index); diff --git a/notmuch-show.c b/notmuch-show.c index 3b6667c..95427d4 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -1081,9 +1081,8 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) return 1; } - notmuch = notmuch_database_open (notmuch_config_get_database_path (config), - NOTMUCH_DATABASE_MODE_READ_ONLY); - if (notmuch == NULL) + if (notmuch_database_open (notmuch_config_get_database_path (config), + NOTMUCH_DATABASE_MODE_READ_ONLY, ¬much)) return 1; query = notmuch_query_create (notmuch, query_string); diff --git a/notmuch-tag.c b/notmuch-tag.c index bd56fd1..7d18639 100644 --- a/notmuch-tag.c +++ b/notmuch-tag.c @@ -229,9 +229,8 @@ notmuch_tag_command (void *ctx, int argc, char *argv[]) if (config == NULL) return 1; - notmuch = notmuch_database_open (notmuch_config_get_database_path (config), - NOTMUCH_DATABASE_MODE_READ_WRITE); - if (notmuch == NULL) + if (notmuch_database_open (notmuch_config_get_database_path (config), + NOTMUCH_DATABASE_MODE_READ_WRITE, ¬much)) return 1; synchronize_flags = notmuch_config_get_maildir_synchronize_flags (config); diff --git a/test/symbol-test.cc b/test/symbol-test.cc index 1548ca4..3e96c03 100644 --- a/test/symbol-test.cc +++ b/test/symbol-test.cc @@ -4,7 +4,8 @@ int main() { - (void) notmuch_database_open("fakedb", NOTMUCH_DATABASE_MODE_READ_ONLY); + notmuch_database_t *notmuch; + notmuch_database_open("fakedb", NOTMUCH_DATABASE_MODE_READ_ONLY, ¬much); try { (void) new Xapian::WritableDatabase("./nonexistant", Xapian::DB_OPEN); -- cgit v1.2.3 From c3eba1c3f85394b977f513059a0585d89a9a4e2d Mon Sep 17 00:00:00 2001 From: Jameson Graef Rollins Date: Sat, 26 May 2012 11:45:42 -0700 Subject: cli: modify show and reply to use new crypto struct notmuch_show_params_t is modified to use the new notmuch_crypto_t, and notmuch-show and notmuch-reply are modified accordingly. --- notmuch-client.h | 3 +-- notmuch-reply.c | 29 ++++++++++++++++------------- notmuch-show.c | 30 +++++++++++++++++------------- 3 files changed, 34 insertions(+), 28 deletions(-) (limited to 'notmuch-show.c') diff --git a/notmuch-client.h b/notmuch-client.h index 6664075..ead7fbd 100644 --- a/notmuch-client.h +++ b/notmuch-client.h @@ -87,8 +87,7 @@ typedef struct notmuch_show_params { notmuch_bool_t omit_excluded; notmuch_bool_t raw; int part; - notmuch_crypto_context_t* cryptoctx; - notmuch_bool_t decrypt; + notmuch_crypto_t crypto; } notmuch_show_params_t; /* There's no point in continuing when we've detected that we've done diff --git a/notmuch-reply.c b/notmuch-reply.c index 0f92a2e..11f269f 100644 --- a/notmuch-reply.c +++ b/notmuch-reply.c @@ -575,7 +575,7 @@ notmuch_reply_format_default(void *ctx, g_object_unref (G_OBJECT (reply)); reply = NULL; - if (mime_node_open (ctx, message, params->cryptoctx, params->decrypt, + if (mime_node_open (ctx, message, params->crypto.gpgctx, params->crypto.decrypt, &root) == NOTMUCH_STATUS_SUCCESS) { format_part_reply (root); talloc_free (root); @@ -605,7 +605,7 @@ notmuch_reply_format_json(void *ctx, messages = notmuch_query_search_messages (query); message = notmuch_messages_get (messages); - if (mime_node_open (ctx, message, params->cryptoctx, params->decrypt, + if (mime_node_open (ctx, message, params->crypto.gpgctx, params->crypto.decrypt, &node) != NOTMUCH_STATUS_SUCCESS) return 1; @@ -706,7 +706,12 @@ notmuch_reply_command (void *ctx, int argc, char *argv[]) char *query_string; int opt_index, ret = 0; int (*reply_format_func)(void *ctx, notmuch_config_t *config, notmuch_query_t *query, notmuch_show_params_t *params, notmuch_bool_t reply_all); - notmuch_show_params_t params = { .part = -1 }; + notmuch_show_params_t params = { + .part = -1, + .crypto = { + .decrypt = FALSE + } + }; int format = FORMAT_DEFAULT; int reply_all = TRUE; @@ -720,7 +725,7 @@ notmuch_reply_command (void *ctx, int argc, char *argv[]) (notmuch_keyword_t []){ { "all", TRUE }, { "sender", FALSE }, { 0, 0 } } }, - { NOTMUCH_OPT_BOOLEAN, ¶ms.decrypt, "decrypt", 'd', 0 }, + { NOTMUCH_OPT_BOOLEAN, ¶ms.crypto.decrypt, "decrypt", 'd', 0 }, { 0, 0, 0, 0, 0 } }; @@ -737,18 +742,18 @@ notmuch_reply_command (void *ctx, int argc, char *argv[]) else reply_format_func = notmuch_reply_format_default; - if (params.decrypt) { + if (params.crypto.decrypt) { #ifdef GMIME_ATLEAST_26 /* TODO: GMimePasswordRequestFunc */ - params.cryptoctx = g_mime_gpg_context_new (NULL, "gpg"); + params.crypto.gpgctx = g_mime_gpg_context_new (NULL, "gpg"); #else GMimeSession* session = g_object_new (g_mime_session_get_type(), NULL); - params.cryptoctx = g_mime_gpg_context_new (session, "gpg"); + params.crypto.gpgctx = g_mime_gpg_context_new (session, "gpg"); #endif - if (params.cryptoctx) { - g_mime_gpg_context_set_always_trust ((GMimeGpgContext*) params.cryptoctx, FALSE); + if (params.crypto.gpgctx) { + g_mime_gpg_context_set_always_trust ((GMimeGpgContext*) params.crypto.gpgctx, FALSE); } else { - params.decrypt = FALSE; + params.crypto.decrypt = FALSE; fprintf (stderr, "Failed to construct gpg context.\n"); } #ifndef GMIME_ATLEAST_26 @@ -784,11 +789,9 @@ notmuch_reply_command (void *ctx, int argc, char *argv[]) if (reply_format_func (ctx, config, query, ¶ms, reply_all) != 0) return 1; + notmuch_crypto_cleanup (¶ms.crypto); notmuch_query_destroy (query); notmuch_database_destroy (notmuch); - if (params.cryptoctx) - g_object_unref(params.cryptoctx); - return ret; } diff --git a/notmuch-show.c b/notmuch-show.c index 95427d4..cc509a6 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -810,8 +810,8 @@ show_message (void *ctx, mime_node_t *root, *part; notmuch_status_t status; - status = mime_node_open (local, message, params->cryptoctx, - params->decrypt, &root); + status = mime_node_open (local, message, params->crypto.gpgctx, + params->crypto.decrypt, &root); if (status) goto DONE; part = mime_node_seek_dfs (root, (params->part < 0 ? 0 : params->part)); @@ -984,7 +984,13 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) char *query_string; int opt_index, ret; const notmuch_show_format_t *format = &format_text; - notmuch_show_params_t params = { .part = -1, .omit_excluded = TRUE }; + notmuch_show_params_t params = { + .part = -1, + .omit_excluded = TRUE, + .crypto = { + .decrypt = FALSE + } + }; int format_sel = NOTMUCH_FORMAT_NOT_SPECIFIED; notmuch_bool_t verify = FALSE; int exclude = EXCLUDE_TRUE; @@ -1002,7 +1008,7 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) { 0, 0 } } }, { NOTMUCH_OPT_INT, ¶ms.part, "part", 'p', 0 }, { NOTMUCH_OPT_BOOLEAN, ¶ms.entire_thread, "entire-thread", 't', 0 }, - { NOTMUCH_OPT_BOOLEAN, ¶ms.decrypt, "decrypt", 'd', 0 }, + { NOTMUCH_OPT_BOOLEAN, ¶ms.crypto.decrypt, "decrypt", 'd', 0 }, { NOTMUCH_OPT_BOOLEAN, &verify, "verify", 'v', 0 }, { 0, 0, 0, 0, 0 } }; @@ -1047,18 +1053,18 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) break; } - if (params.decrypt || verify) { + if (params.crypto.decrypt || verify) { #ifdef GMIME_ATLEAST_26 /* TODO: GMimePasswordRequestFunc */ - params.cryptoctx = g_mime_gpg_context_new (NULL, "gpg"); + params.crypto.gpgctx = g_mime_gpg_context_new (NULL, "gpg"); #else GMimeSession* session = g_object_new (g_mime_session_get_type(), NULL); - params.cryptoctx = g_mime_gpg_context_new (session, "gpg"); + params.crypto.gpgctx = g_mime_gpg_context_new (session, "gpg"); #endif - if (params.cryptoctx) { - g_mime_gpg_context_set_always_trust ((GMimeGpgContext*) params.cryptoctx, FALSE); + if (params.crypto.gpgctx) { + g_mime_gpg_context_set_always_trust ((GMimeGpgContext*) params.crypto.gpgctx, FALSE); } else { - params.decrypt = FALSE; + params.crypto.decrypt = FALSE; fprintf (stderr, "Failed to construct gpg context.\n"); } #ifndef GMIME_ATLEAST_26 @@ -1115,11 +1121,9 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) ret = do_show (ctx, query, format, ¶ms); } + notmuch_crypto_cleanup (¶ms.crypto); notmuch_query_destroy (query); notmuch_database_destroy (notmuch); - if (params.cryptoctx) - g_object_unref(params.cryptoctx); - return ret; } -- cgit v1.2.3 From 429ebf5d20a943fb520d7321c5dde721265b0155 Mon Sep 17 00:00:00 2001 From: Jameson Graef Rollins Date: Sat, 26 May 2012 11:45:43 -0700 Subject: cli: modify mime_node_open to take new crypto struct as argument This simplifies the interface considerably. --- mime-node.c | 7 +++---- notmuch-client.h | 10 +++++----- notmuch-reply.c | 6 ++---- notmuch-show.c | 3 +-- 4 files changed, 11 insertions(+), 15 deletions(-) (limited to 'notmuch-show.c') diff --git a/mime-node.c b/mime-node.c index a5645e5..67f4b16 100644 --- a/mime-node.c +++ b/mime-node.c @@ -57,8 +57,7 @@ _mime_node_context_free (mime_node_context_t *res) notmuch_status_t mime_node_open (const void *ctx, notmuch_message_t *message, - notmuch_crypto_context_t *cryptoctx, - notmuch_bool_t decrypt, mime_node_t **root_out) + notmuch_crypto_t *crypto, mime_node_t **root_out) { const char *filename = notmuch_message_get_filename (message); mime_node_context_t *mctx; @@ -110,8 +109,8 @@ mime_node_open (const void *ctx, notmuch_message_t *message, goto DONE; } - mctx->cryptoctx = cryptoctx; - mctx->decrypt = decrypt; + mctx->cryptoctx = crypto->gpgctx; + mctx->decrypt = crypto->decrypt; /* Create the root node */ root->part = GMIME_OBJECT (mctx->mime_message); diff --git a/notmuch-client.h b/notmuch-client.h index ead7fbd..962c747 100644 --- a/notmuch-client.h +++ b/notmuch-client.h @@ -350,9 +350,10 @@ struct mime_node { }; /* Construct a new MIME node pointing to the root message part of - * message. If cryptoctx is non-NULL, it will be used to verify - * signatures on any child parts. If decrypt is true, then cryptoctx - * will additionally be used to decrypt any encrypted child parts. + * message. If crypto->gpgctx is non-NULL, it will be used to verify + * signatures on any child parts. If crypto->decrypt is true, then + * crypto.gpgctx will additionally be used to decrypt any encrypted + * child parts. * * Return value: * @@ -364,8 +365,7 @@ struct mime_node { */ notmuch_status_t mime_node_open (const void *ctx, notmuch_message_t *message, - notmuch_crypto_context_t *cryptoctx, - notmuch_bool_t decrypt, mime_node_t **node_out); + notmuch_crypto_t *crypto, mime_node_t **node_out); /* Return a new MIME node for the requested child part of parent. * parent will be used as the talloc context for the returned child diff --git a/notmuch-reply.c b/notmuch-reply.c index 11f269f..6f368c9 100644 --- a/notmuch-reply.c +++ b/notmuch-reply.c @@ -575,8 +575,7 @@ notmuch_reply_format_default(void *ctx, g_object_unref (G_OBJECT (reply)); reply = NULL; - if (mime_node_open (ctx, message, params->crypto.gpgctx, params->crypto.decrypt, - &root) == NOTMUCH_STATUS_SUCCESS) { + if (mime_node_open (ctx, message, &(params->crypto), &root) == NOTMUCH_STATUS_SUCCESS) { format_part_reply (root); talloc_free (root); } @@ -605,8 +604,7 @@ notmuch_reply_format_json(void *ctx, messages = notmuch_query_search_messages (query); message = notmuch_messages_get (messages); - if (mime_node_open (ctx, message, params->crypto.gpgctx, params->crypto.decrypt, - &node) != NOTMUCH_STATUS_SUCCESS) + if (mime_node_open (ctx, message, &(params->crypto), &node) != NOTMUCH_STATUS_SUCCESS) return 1; reply = create_reply_message (ctx, config, message, reply_all); diff --git a/notmuch-show.c b/notmuch-show.c index cc509a6..fb5e9b6 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -810,8 +810,7 @@ show_message (void *ctx, mime_node_t *root, *part; notmuch_status_t status; - status = mime_node_open (local, message, params->crypto.gpgctx, - params->crypto.decrypt, &root); + status = mime_node_open (local, message, &(params->crypto), &root); if (status) goto DONE; part = mime_node_seek_dfs (root, (params->part < 0 ? 0 : params->part)); -- cgit v1.2.3 From b2c8fdee53a1b06dd19fafe23e53ac8555d294af Mon Sep 17 00:00:00 2001 From: Jameson Graef Rollins Date: Sat, 26 May 2012 11:45:45 -0700 Subject: cli: new crypto verify flag to handle verification Use this flag rather than depend on the existence of an initialized gpgctx, to determine whether we should verify a multipart/signed. We will be moving to create the ctx lazily, so we don't want to depend on it being previously initialized if it's not needed. --- mime-node.c | 5 ++--- notmuch-client.h | 8 ++++---- notmuch-reply.c | 1 + notmuch-show.c | 14 +++++++++++--- 4 files changed, 18 insertions(+), 10 deletions(-) (limited to 'notmuch-show.c') diff --git a/mime-node.c b/mime-node.c index a838224..73e28c5 100644 --- a/mime-node.c +++ b/mime-node.c @@ -183,8 +183,7 @@ _mime_node_create (mime_node_t *parent, GMimeObject *part) } /* Handle PGP/MIME parts */ - if (GMIME_IS_MULTIPART_ENCRYPTED (part) - && node->ctx->crypto->gpgctx && node->ctx->crypto->decrypt) { + if (GMIME_IS_MULTIPART_ENCRYPTED (part) && node->ctx->crypto->decrypt) { if (node->nchildren != 2) { /* this violates RFC 3156 section 4, so we won't bother with it. */ fprintf (stderr, "Error: %d part(s) for a multipart/encrypted " @@ -218,7 +217,7 @@ _mime_node_create (mime_node_t *parent, GMimeObject *part) (err ? err->message : "no error explanation given")); } } - } else if (GMIME_IS_MULTIPART_SIGNED (part) && node->ctx->crypto->gpgctx) { + } else if (GMIME_IS_MULTIPART_SIGNED (part) && node->ctx->crypto->verify) { if (node->nchildren != 2) { /* this violates RFC 3156 section 5, so we won't bother with it. */ fprintf (stderr, "Error: %d part(s) for a multipart/signed message " diff --git a/notmuch-client.h b/notmuch-client.h index 962c747..0f29a83 100644 --- a/notmuch-client.h +++ b/notmuch-client.h @@ -79,6 +79,7 @@ typedef struct notmuch_show_format { typedef struct notmuch_crypto { notmuch_crypto_context_t* gpgctx; + notmuch_bool_t verify; notmuch_bool_t decrypt; } notmuch_crypto_t; @@ -350,10 +351,9 @@ struct mime_node { }; /* Construct a new MIME node pointing to the root message part of - * message. If crypto->gpgctx is non-NULL, it will be used to verify - * signatures on any child parts. If crypto->decrypt is true, then - * crypto.gpgctx will additionally be used to decrypt any encrypted - * child parts. + * message. If crypto->verify is true, signed child parts will be + * verified. If crypto->decrypt is true, encrypted child parts will be + * decrypted. * * Return value: * diff --git a/notmuch-reply.c b/notmuch-reply.c index 6f368c9..1ab3db9 100644 --- a/notmuch-reply.c +++ b/notmuch-reply.c @@ -707,6 +707,7 @@ notmuch_reply_command (void *ctx, int argc, char *argv[]) notmuch_show_params_t params = { .part = -1, .crypto = { + .verify = FALSE, .decrypt = FALSE } }; diff --git a/notmuch-show.c b/notmuch-show.c index fb5e9b6..3c06792 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -987,11 +987,11 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) .part = -1, .omit_excluded = TRUE, .crypto = { + .verify = FALSE, .decrypt = FALSE } }; int format_sel = NOTMUCH_FORMAT_NOT_SPECIFIED; - notmuch_bool_t verify = FALSE; int exclude = EXCLUDE_TRUE; notmuch_opt_desc_t options[] = { @@ -1008,7 +1008,7 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) { NOTMUCH_OPT_INT, ¶ms.part, "part", 'p', 0 }, { NOTMUCH_OPT_BOOLEAN, ¶ms.entire_thread, "entire-thread", 't', 0 }, { NOTMUCH_OPT_BOOLEAN, ¶ms.crypto.decrypt, "decrypt", 'd', 0 }, - { NOTMUCH_OPT_BOOLEAN, &verify, "verify", 'v', 0 }, + { NOTMUCH_OPT_BOOLEAN, ¶ms.crypto.verify, "verify", 'v', 0 }, { 0, 0, 0, 0, 0 } }; @@ -1018,6 +1018,10 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) return 1; } + /* decryption implies verification */ + if (params.crypto.decrypt) + params.crypto.verify = TRUE; + if (format_sel == NOTMUCH_FORMAT_NOT_SPECIFIED) { /* if part was requested and format was not specified, use format=raw */ if (params.part >= 0) @@ -1052,7 +1056,7 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) break; } - if (params.crypto.decrypt || verify) { + if (params.crypto.decrypt || params.crypto.verify) { #ifdef GMIME_ATLEAST_26 /* TODO: GMimePasswordRequestFunc */ params.crypto.gpgctx = g_mime_gpg_context_new (NULL, "gpg"); @@ -1063,6 +1067,10 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) if (params.crypto.gpgctx) { g_mime_gpg_context_set_always_trust ((GMimeGpgContext*) params.crypto.gpgctx, FALSE); } else { + /* If we fail to create the gpgctx set the verify and + * decrypt flags to FALSE so we don't try to do any + * further verification or decryption */ + params.crypto.verify = FALSE; params.crypto.decrypt = FALSE; fprintf (stderr, "Failed to construct gpg context.\n"); } -- cgit v1.2.3 From e04b18cf3624e1ba29a45cd1f15715e1da244021 Mon Sep 17 00:00:00 2001 From: Jameson Graef Rollins Date: Sat, 26 May 2012 11:45:46 -0700 Subject: cli: use new notmuch_crypto_get_context in mime-node.c This has the affect of lazily creating the crypto contexts only when needed. This removes code duplication from notmuch-show and notmuch-reply, and should speed up these functions considerably if the crypto flags are provided but the messages don't have any cryptographic parts. --- mime-node.c | 20 ++++++++++++++------ notmuch-client.h | 3 ++- notmuch-reply.c | 19 ------------------- notmuch-show.c | 23 ----------------------- 4 files changed, 16 insertions(+), 49 deletions(-) (limited to 'notmuch-show.c') diff --git a/mime-node.c b/mime-node.c index 73e28c5..97e8b48 100644 --- a/mime-node.c +++ b/mime-node.c @@ -150,6 +150,7 @@ _mime_node_create (mime_node_t *parent, GMimeObject *part) { mime_node_t *node = talloc_zero (parent, mime_node_t); GError *err = NULL; + notmuch_crypto_context_t *cryptoctx = NULL; /* Set basic node properties */ node->part = part; @@ -182,8 +183,15 @@ _mime_node_create (mime_node_t *parent, GMimeObject *part) return NULL; } + if ((GMIME_IS_MULTIPART_ENCRYPTED (part) && node->ctx->crypto->decrypt) + || (GMIME_IS_MULTIPART_SIGNED (part) && node->ctx->crypto->verify)) { + GMimeContentType *content_type = g_mime_object_get_content_type (part); + const char *protocol = g_mime_content_type_get_parameter (content_type, "protocol"); + cryptoctx = notmuch_crypto_get_context (node->ctx->crypto, protocol); + } + /* Handle PGP/MIME parts */ - if (GMIME_IS_MULTIPART_ENCRYPTED (part) && node->ctx->crypto->decrypt) { + if (GMIME_IS_MULTIPART_ENCRYPTED (part) && node->ctx->crypto->decrypt && cryptoctx) { if (node->nchildren != 2) { /* this violates RFC 3156 section 4, so we won't bother with it. */ fprintf (stderr, "Error: %d part(s) for a multipart/encrypted " @@ -196,10 +204,10 @@ _mime_node_create (mime_node_t *parent, GMimeObject *part) #ifdef GMIME_ATLEAST_26 GMimeDecryptResult *decrypt_result = NULL; node->decrypted_child = g_mime_multipart_encrypted_decrypt - (encrypteddata, node->ctx->crypto->gpgctx, &decrypt_result, &err); + (encrypteddata, cryptoctx, &decrypt_result, &err); #else node->decrypted_child = g_mime_multipart_encrypted_decrypt - (encrypteddata, node->ctx->crypto->gpgctx, &err); + (encrypteddata, cryptoctx, &err); #endif if (node->decrypted_child) { node->decrypt_success = node->verify_attempted = TRUE; @@ -217,7 +225,7 @@ _mime_node_create (mime_node_t *parent, GMimeObject *part) (err ? err->message : "no error explanation given")); } } - } else if (GMIME_IS_MULTIPART_SIGNED (part) && node->ctx->crypto->verify) { + } else if (GMIME_IS_MULTIPART_SIGNED (part) && node->ctx->crypto->verify && cryptoctx) { if (node->nchildren != 2) { /* this violates RFC 3156 section 5, so we won't bother with it. */ fprintf (stderr, "Error: %d part(s) for a multipart/signed message " @@ -226,7 +234,7 @@ _mime_node_create (mime_node_t *parent, GMimeObject *part) } else { #ifdef GMIME_ATLEAST_26 node->sig_list = g_mime_multipart_signed_verify - (GMIME_MULTIPART_SIGNED (part), node->ctx->crypto->gpgctx, &err); + (GMIME_MULTIPART_SIGNED (part), cryptoctx, &err); node->verify_attempted = TRUE; if (!node->sig_list) @@ -242,7 +250,7 @@ _mime_node_create (mime_node_t *parent, GMimeObject *part) * In GMime 2.6, they're both non-const, so we'll be able * to clean up this asymmetry. */ GMimeSignatureValidity *sig_validity = g_mime_multipart_signed_verify - (GMIME_MULTIPART_SIGNED (part), node->ctx->crypto->gpgctx, &err); + (GMIME_MULTIPART_SIGNED (part), cryptoctx, &err); node->verify_attempted = TRUE; node->sig_validity = sig_validity; if (sig_validity) { diff --git a/notmuch-client.h b/notmuch-client.h index 0f29a83..9b63eae 100644 --- a/notmuch-client.h +++ b/notmuch-client.h @@ -353,7 +353,8 @@ struct mime_node { /* Construct a new MIME node pointing to the root message part of * message. If crypto->verify is true, signed child parts will be * verified. If crypto->decrypt is true, encrypted child parts will be - * decrypted. + * decrypted. If crypto->gpgctx is NULL, it will be lazily + * initialized. * * Return value: * diff --git a/notmuch-reply.c b/notmuch-reply.c index 1ab3db9..3a038ed 100644 --- a/notmuch-reply.c +++ b/notmuch-reply.c @@ -741,25 +741,6 @@ notmuch_reply_command (void *ctx, int argc, char *argv[]) else reply_format_func = notmuch_reply_format_default; - if (params.crypto.decrypt) { -#ifdef GMIME_ATLEAST_26 - /* TODO: GMimePasswordRequestFunc */ - params.crypto.gpgctx = g_mime_gpg_context_new (NULL, "gpg"); -#else - GMimeSession* session = g_object_new (g_mime_session_get_type(), NULL); - params.crypto.gpgctx = g_mime_gpg_context_new (session, "gpg"); -#endif - if (params.crypto.gpgctx) { - g_mime_gpg_context_set_always_trust ((GMimeGpgContext*) params.crypto.gpgctx, FALSE); - } else { - params.crypto.decrypt = FALSE; - fprintf (stderr, "Failed to construct gpg context.\n"); - } -#ifndef GMIME_ATLEAST_26 - g_object_unref (session); -#endif - } - config = notmuch_config_open (ctx, NULL, NULL); if (config == NULL) return 1; diff --git a/notmuch-show.c b/notmuch-show.c index 3c06792..8247f1d 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -1056,29 +1056,6 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) break; } - if (params.crypto.decrypt || params.crypto.verify) { -#ifdef GMIME_ATLEAST_26 - /* TODO: GMimePasswordRequestFunc */ - params.crypto.gpgctx = g_mime_gpg_context_new (NULL, "gpg"); -#else - GMimeSession* session = g_object_new (g_mime_session_get_type(), NULL); - params.crypto.gpgctx = g_mime_gpg_context_new (session, "gpg"); -#endif - if (params.crypto.gpgctx) { - g_mime_gpg_context_set_always_trust ((GMimeGpgContext*) params.crypto.gpgctx, FALSE); - } else { - /* If we fail to create the gpgctx set the verify and - * decrypt flags to FALSE so we don't try to do any - * further verification or decryption */ - params.crypto.verify = FALSE; - params.crypto.decrypt = FALSE; - fprintf (stderr, "Failed to construct gpg context.\n"); - } -#ifndef GMIME_ATLEAST_26 - g_object_unref (session); -#endif - } - config = notmuch_config_open (ctx, NULL, NULL); if (config == NULL) return 1; -- cgit v1.2.3 From 4d3bfba98316ef703c430bb3202dc4e258f05ae5 Mon Sep 17 00:00:00 2001 From: Mark Walters Date: Sat, 16 Jun 2012 11:21:43 +0100 Subject: cli: Let json output "null" messages for non --entire-thread All formats except Json can output empty messages for non entire-thread, but in Json format we output "null" to keep the other elements (e.g. the replies to the omitted message) in the correct place. --- notmuch-client.h | 1 + notmuch-show.c | 20 ++++++++++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) (limited to 'notmuch-show.c') diff --git a/notmuch-client.h b/notmuch-client.h index 9b63eae..0c17b79 100644 --- a/notmuch-client.h +++ b/notmuch-client.h @@ -75,6 +75,7 @@ typedef struct notmuch_show_format { const struct notmuch_show_params *params); const char *message_set_sep; const char *message_set_end; + const char *null_message; } notmuch_show_format_t; typedef struct notmuch_crypto { diff --git a/notmuch-show.c b/notmuch-show.c index 8247f1d..b004468 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -37,7 +37,8 @@ static const notmuch_show_format_t format_json = { .message_set_start = "[", .part = format_part_json_entry, .message_set_sep = ", ", - .message_set_end = "]" + .message_set_end = "]", + .null_message = "null" }; static notmuch_status_t @@ -799,6 +800,15 @@ format_part_raw (unused (const void *ctx), mime_node_t *node, return NOTMUCH_STATUS_SUCCESS; } +static notmuch_status_t +show_null_message (const notmuch_show_format_t *format) +{ + /* Output a null message. Currently empty for all formats except Json */ + if (format->null_message) + printf ("%s", format->null_message); + return NOTMUCH_STATUS_SUCCESS; +} + static notmuch_status_t show_message (void *ctx, const notmuch_show_format_t *format, @@ -861,11 +871,13 @@ show_messages (void *ctx, if (status && !res) res = status; next_indent = indent + 1; - - if (!status && format->message_set_sep) - fputs (format->message_set_sep, stdout); + } else { + status = show_null_message (format); } + if (!status && format->message_set_sep) + fputs (format->message_set_sep, stdout); + status = show_messages (ctx, format, notmuch_message_get_replies (message), -- cgit v1.2.3 From 15904cde125d317c7e59923104e270c6fbe502f7 Mon Sep 17 00:00:00 2001 From: Mark Walters Date: Sat, 16 Jun 2012 11:21:44 +0100 Subject: cli: make --entire-thread=false work for format=json. The --entire-thread option in notmuch-show.c defaults to true when format=json. Previously there was no way to turn this off. This patch makes it respect --entire-thread=false. To do this the patch moves the --entire-thread option to be a keyword option using the new command line parsing to allow the existing --entire-thread to keep working. --- notmuch-show.c | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) (limited to 'notmuch-show.c') diff --git a/notmuch-show.c b/notmuch-show.c index b004468..e6eb625 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -980,6 +980,12 @@ enum { NOTMUCH_FORMAT_RAW }; +enum { + ENTIRE_THREAD_DEFAULT, + ENTIRE_THREAD_TRUE, + ENTIRE_THREAD_FALSE, +}; + /* The following is to allow future options to be added more easily */ enum { EXCLUDE_TRUE, @@ -1005,6 +1011,7 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) }; int format_sel = NOTMUCH_FORMAT_NOT_SPECIFIED; int exclude = EXCLUDE_TRUE; + int entire_thread = ENTIRE_THREAD_DEFAULT; notmuch_opt_desc_t options[] = { { NOTMUCH_OPT_KEYWORD, &format_sel, "format", 'f', @@ -1017,8 +1024,12 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) (notmuch_keyword_t []){ { "true", EXCLUDE_TRUE }, { "false", EXCLUDE_FALSE }, { 0, 0 } } }, + { NOTMUCH_OPT_KEYWORD, &entire_thread, "entire-thread", 't', + (notmuch_keyword_t []){ { "true", ENTIRE_THREAD_TRUE }, + { "false", ENTIRE_THREAD_FALSE }, + { "", ENTIRE_THREAD_TRUE }, + { 0, 0 } } }, { NOTMUCH_OPT_INT, ¶ms.part, "part", 'p', 0 }, - { NOTMUCH_OPT_BOOLEAN, ¶ms.entire_thread, "entire-thread", 't', 0 }, { NOTMUCH_OPT_BOOLEAN, ¶ms.crypto.decrypt, "decrypt", 'd', 0 }, { NOTMUCH_OPT_BOOLEAN, ¶ms.crypto.verify, "verify", 'v', 0 }, { 0, 0, 0, 0, 0 } @@ -1045,7 +1056,6 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) switch (format_sel) { case NOTMUCH_FORMAT_JSON: format = &format_json; - params.entire_thread = TRUE; break; case NOTMUCH_FORMAT_TEXT: format = &format_text; @@ -1068,6 +1078,19 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) break; } + /* Default is entire-thread = FALSE except for format=json. */ + if (entire_thread == ENTIRE_THREAD_DEFAULT) { + if (format == &format_json) + entire_thread = ENTIRE_THREAD_TRUE; + else + entire_thread = ENTIRE_THREAD_FALSE; + } + + if (entire_thread == ENTIRE_THREAD_TRUE) + params.entire_thread = TRUE; + else + params.entire_thread = FALSE; + config = notmuch_config_open (ctx, NULL, NULL); if (config == NULL) return 1; -- cgit v1.2.3 From eff5f9126f64723873687bc5a17ae2b17f4376ca Mon Sep 17 00:00:00 2001 From: Mark Walters Date: Sat, 16 Jun 2012 11:21:47 +0100 Subject: cli: notmuch-show.c fix whitespace error Fix an existing whitespace error since it is right next to the changes of this series. --- notmuch-show.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'notmuch-show.c') diff --git a/notmuch-show.c b/notmuch-show.c index e6eb625..8f3c60e 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -1020,10 +1020,10 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) { "mbox", NOTMUCH_FORMAT_MBOX }, { "raw", NOTMUCH_FORMAT_RAW }, { 0, 0 } } }, - { NOTMUCH_OPT_KEYWORD, &exclude, "exclude", 'x', - (notmuch_keyword_t []){ { "true", EXCLUDE_TRUE }, - { "false", EXCLUDE_FALSE }, - { 0, 0 } } }, + { NOTMUCH_OPT_KEYWORD, &exclude, "exclude", 'x', + (notmuch_keyword_t []){ { "true", EXCLUDE_TRUE }, + { "false", EXCLUDE_FALSE }, + { 0, 0 } } }, { NOTMUCH_OPT_KEYWORD, &entire_thread, "entire-thread", 't', (notmuch_keyword_t []){ { "true", ENTIRE_THREAD_TRUE }, { "false", ENTIRE_THREAD_FALSE }, -- cgit v1.2.3 From 0e63372efe28f2fff0791b293240695b19bfefd2 Mon Sep 17 00:00:00 2001 From: Mark Walters Date: Tue, 24 Jul 2012 19:23:27 +0100 Subject: cli: add --body=true|false option to notmuch-show.c This option allows the caller to suppress the output of the bodies of the messages. Currently this is only implemented for format=json. This is used by notmuch-pick.el (although not needed) because it gives a speed-up of at least a factor of a two (and in some cases a speed up of more than a factor of 8); moreover it reduces the memory usage in emacs hugely. --- notmuch-client.h | 3 ++- notmuch-reply.c | 2 +- notmuch-show.c | 30 ++++++++++++++++++++++-------- 3 files changed, 25 insertions(+), 10 deletions(-) (limited to 'notmuch-show.c') diff --git a/notmuch-client.h b/notmuch-client.h index 0c17b79..f930798 100644 --- a/notmuch-client.h +++ b/notmuch-client.h @@ -87,6 +87,7 @@ typedef struct notmuch_crypto { typedef struct notmuch_show_params { notmuch_bool_t entire_thread; notmuch_bool_t omit_excluded; + notmuch_bool_t output_body; notmuch_bool_t raw; int part; notmuch_crypto_t crypto; @@ -176,7 +177,7 @@ notmuch_status_t show_one_part (const char *filename, int part); void -format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first); +format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first, notmuch_bool_t output_body); void format_headers_json (const void *ctx, GMimeMessage *message, notmuch_bool_t reply); diff --git a/notmuch-reply.c b/notmuch-reply.c index 3a038ed..de21f3b 100644 --- a/notmuch-reply.c +++ b/notmuch-reply.c @@ -620,7 +620,7 @@ notmuch_reply_format_json(void *ctx, /* Start the original */ printf (", \"original\": "); - format_part_json (ctx, node, TRUE); + format_part_json (ctx, node, TRUE, TRUE); /* End */ printf ("}\n"); diff --git a/notmuch-show.c b/notmuch-show.c index 8f3c60e..d3419e4 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -559,7 +559,7 @@ format_part_text (const void *ctx, mime_node_t *node, } void -format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first) +format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first, notmuch_bool_t output_body) { /* Any changes to the JSON format should be reflected in the file * devel/schemata. */ @@ -571,10 +571,12 @@ format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first) printf ("\"headers\": "); format_headers_json (ctx, GMIME_MESSAGE (node->part), FALSE); - printf (", \"body\": ["); - format_part_json (ctx, mime_node_child (node, 0), first); - - printf ("]}"); + if (output_body) { + printf (", \"body\": ["); + format_part_json (ctx, mime_node_child (node, 0), first, TRUE); + printf ("]"); + } + printf ("}"); return; } @@ -652,16 +654,16 @@ format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first) talloc_free (local); for (i = 0; i < node->nchildren; i++) - format_part_json (ctx, mime_node_child (node, i), i == 0); + format_part_json (ctx, mime_node_child (node, i), i == 0, TRUE); printf ("%s}", terminator); } static notmuch_status_t format_part_json_entry (const void *ctx, mime_node_t *node, unused (int indent), - unused (const notmuch_show_params_t *params)) + const notmuch_show_params_t *params) { - format_part_json (ctx, node, TRUE); + format_part_json (ctx, node, TRUE, params->output_body); return NOTMUCH_STATUS_SUCCESS; } @@ -1004,6 +1006,7 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) notmuch_show_params_t params = { .part = -1, .omit_excluded = TRUE, + .output_body = TRUE, .crypto = { .verify = FALSE, .decrypt = FALSE @@ -1032,6 +1035,7 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) { NOTMUCH_OPT_INT, ¶ms.part, "part", 'p', 0 }, { NOTMUCH_OPT_BOOLEAN, ¶ms.crypto.decrypt, "decrypt", 'd', 0 }, { NOTMUCH_OPT_BOOLEAN, ¶ms.crypto.verify, "verify", 'v', 0 }, + { NOTMUCH_OPT_BOOLEAN, ¶ms.output_body, "body", 'b', 0 }, { 0, 0, 0, 0, 0 } }; @@ -1086,6 +1090,16 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) entire_thread = ENTIRE_THREAD_FALSE; } + if (!params.output_body) { + if (params.part > 0) { + fprintf (stderr, "Warning: --body=false is incompatible with --part > 0. Disabling.\n"); + params.output_body = TRUE; + } else { + if (format != &format_json) + fprintf (stderr, "Warning: --body=false only implemented for format=json\n"); + } + } + if (entire_thread == ENTIRE_THREAD_TRUE) params.entire_thread = TRUE; else -- cgit v1.2.3 From d79b24b98d26e08f7529df6c404d523739175347 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Thu, 2 Aug 2012 21:14:50 -0400 Subject: show: Associate an sprinter with each format This associates an sprinter constructor with each show format and uses this to construct the appropriate sprinter. Currently nothing is done with this sprinter, but the following patches will weave it through the layers of notmuch show. --- notmuch-client.h | 1 + notmuch-show.c | 9 +++++++++ 2 files changed, 10 insertions(+) (limited to 'notmuch-show.c') diff --git a/notmuch-client.h b/notmuch-client.h index f930798..bbc0a11 100644 --- a/notmuch-client.h +++ b/notmuch-client.h @@ -69,6 +69,7 @@ typedef struct mime_node mime_node_t; struct notmuch_show_params; typedef struct notmuch_show_format { + struct sprinter *(*new_sprinter) (const void *ctx, FILE *stream); const char *message_set_start; notmuch_status_t (*part) (const void *ctx, struct mime_node *node, int indent, diff --git a/notmuch-show.c b/notmuch-show.c index d3419e4..d04943f 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -20,12 +20,14 @@ #include "notmuch-client.h" #include "gmime-filter-reply.h" +#include "sprinter.h" static notmuch_status_t format_part_text (const void *ctx, mime_node_t *node, int indent, const notmuch_show_params_t *params); static const notmuch_show_format_t format_text = { + .new_sprinter = sprinter_text_create, .part = format_part_text, }; @@ -34,6 +36,7 @@ format_part_json_entry (const void *ctx, mime_node_t *node, int indent, const notmuch_show_params_t *params); static const notmuch_show_format_t format_json = { + .new_sprinter = sprinter_json_create, .message_set_start = "[", .part = format_part_json_entry, .message_set_sep = ", ", @@ -46,6 +49,7 @@ format_part_mbox (const void *ctx, mime_node_t *node, int indent, const notmuch_show_params_t *params); static const notmuch_show_format_t format_mbox = { + .new_sprinter = sprinter_text_create, .part = format_part_mbox, }; @@ -55,6 +59,7 @@ format_part_raw (unused (const void *ctx), mime_node_t *node, unused (const notmuch_show_params_t *params)); static const notmuch_show_format_t format_raw = { + .new_sprinter = sprinter_text_create, .part = format_part_raw, }; @@ -1003,6 +1008,7 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) char *query_string; int opt_index, ret; const notmuch_show_format_t *format = &format_text; + sprinter_t *sprinter; notmuch_show_params_t params = { .part = -1, .omit_excluded = TRUE, @@ -1130,6 +1136,9 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) return 1; } + /* Create structure printer. */ + sprinter = format->new_sprinter(ctx, stdout); + /* If a single message is requested we do not use search_excludes. */ if (params.part >= 0) ret = do_show_single (ctx, query, format, ¶ms); -- cgit v1.2.3 From 3a08341e504d7f9b27f8a67a2fed223a38edb706 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Thu, 2 Aug 2012 21:14:52 -0400 Subject: show: Feed the sprinter down to part formatters There are several levels of function calls between where we create the sprinter and the call to the part formatter in show_message. This feeds the sprinter through all of them and into the part formatters. --- notmuch-client.h | 6 ++++-- notmuch-reply.c | 2 +- notmuch-show.c | 50 +++++++++++++++++++++++++++++--------------------- 3 files changed, 34 insertions(+), 24 deletions(-) (limited to 'notmuch-show.c') diff --git a/notmuch-client.h b/notmuch-client.h index bbc0a11..112574c 100644 --- a/notmuch-client.h +++ b/notmuch-client.h @@ -66,12 +66,13 @@ typedef GMimeCipherContext notmuch_crypto_context_t; #define STRINGIFY_(s) #s typedef struct mime_node mime_node_t; +struct sprinter; struct notmuch_show_params; typedef struct notmuch_show_format { struct sprinter *(*new_sprinter) (const void *ctx, FILE *stream); const char *message_set_start; - notmuch_status_t (*part) (const void *ctx, + notmuch_status_t (*part) (const void *ctx, struct sprinter *sprinter, struct mime_node *node, int indent, const struct notmuch_show_params *params); const char *message_set_sep; @@ -178,7 +179,8 @@ notmuch_status_t show_one_part (const char *filename, int part); void -format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first, notmuch_bool_t output_body); +format_part_json (const void *ctx, struct sprinter *sp, mime_node_t *node, + notmuch_bool_t first, notmuch_bool_t output_body); void format_headers_json (const void *ctx, GMimeMessage *message, notmuch_bool_t reply); diff --git a/notmuch-reply.c b/notmuch-reply.c index e42ba79..07d4452 100644 --- a/notmuch-reply.c +++ b/notmuch-reply.c @@ -624,7 +624,7 @@ notmuch_reply_format_json(void *ctx, /* Start the original */ printf (", \"original\": "); - format_part_json (ctx, node, TRUE, TRUE); + format_part_json (ctx, sp, node, TRUE, TRUE); /* End */ printf ("}\n"); diff --git a/notmuch-show.c b/notmuch-show.c index d04943f..b258f65 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -23,7 +23,7 @@ #include "sprinter.h" static notmuch_status_t -format_part_text (const void *ctx, mime_node_t *node, +format_part_text (const void *ctx, sprinter_t *sp, mime_node_t *node, int indent, const notmuch_show_params_t *params); static const notmuch_show_format_t format_text = { @@ -32,7 +32,7 @@ static const notmuch_show_format_t format_text = { }; static notmuch_status_t -format_part_json_entry (const void *ctx, mime_node_t *node, +format_part_json_entry (const void *ctx, sprinter_t *sp, mime_node_t *node, int indent, const notmuch_show_params_t *params); static const notmuch_show_format_t format_json = { @@ -45,7 +45,7 @@ static const notmuch_show_format_t format_json = { }; static notmuch_status_t -format_part_mbox (const void *ctx, mime_node_t *node, +format_part_mbox (const void *ctx, sprinter_t *sp, mime_node_t *node, int indent, const notmuch_show_params_t *params); static const notmuch_show_format_t format_mbox = { @@ -54,7 +54,7 @@ static const notmuch_show_format_t format_mbox = { }; static notmuch_status_t -format_part_raw (unused (const void *ctx), mime_node_t *node, +format_part_raw (unused (const void *ctx), sprinter_t *sp, mime_node_t *node, unused (int indent), unused (const notmuch_show_params_t *params)); @@ -471,7 +471,7 @@ format_part_sigstatus_json (mime_node_t *node) #endif static notmuch_status_t -format_part_text (const void *ctx, mime_node_t *node, +format_part_text (const void *ctx, sprinter_t *sp, mime_node_t *node, int indent, const notmuch_show_params_t *params) { /* The disposition and content-type metadata are associated with @@ -553,7 +553,7 @@ format_part_text (const void *ctx, mime_node_t *node, } for (i = 0; i < node->nchildren; i++) - format_part_text (ctx, mime_node_child (node, i), indent, params); + format_part_text (ctx, sp, mime_node_child (node, i), indent, params); if (GMIME_IS_MESSAGE (node->part)) printf ("\fbody}\n"); @@ -564,7 +564,8 @@ format_part_text (const void *ctx, mime_node_t *node, } void -format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first, notmuch_bool_t output_body) +format_part_json (const void *ctx, sprinter_t *sp, mime_node_t *node, + notmuch_bool_t first, notmuch_bool_t output_body) { /* Any changes to the JSON format should be reflected in the file * devel/schemata. */ @@ -578,7 +579,7 @@ format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first, notm if (output_body) { printf (", \"body\": ["); - format_part_json (ctx, mime_node_child (node, 0), first, TRUE); + format_part_json (ctx, sp, mime_node_child (node, 0), first, TRUE); printf ("]"); } printf ("}"); @@ -659,16 +660,17 @@ format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first, notm talloc_free (local); for (i = 0; i < node->nchildren; i++) - format_part_json (ctx, mime_node_child (node, i), i == 0, TRUE); + format_part_json (ctx, sp, mime_node_child (node, i), i == 0, TRUE); printf ("%s}", terminator); } static notmuch_status_t -format_part_json_entry (const void *ctx, mime_node_t *node, unused (int indent), +format_part_json_entry (const void *ctx, sprinter_t *sp, + mime_node_t *node, unused (int indent), const notmuch_show_params_t *params) { - format_part_json (ctx, node, TRUE, params->output_body); + format_part_json (ctx, sp, node, TRUE, params->output_body); return NOTMUCH_STATUS_SUCCESS; } @@ -679,7 +681,8 @@ format_part_json_entry (const void *ctx, mime_node_t *node, unused (int indent), * http://qmail.org/qmail-manual-html/man5/mbox.html */ static notmuch_status_t -format_part_mbox (const void *ctx, mime_node_t *node, unused (int indent), +format_part_mbox (const void *ctx, unused (sprinter_t *sp), mime_node_t *node, + unused (int indent), unused (const notmuch_show_params_t *params)) { notmuch_message_t *message = node->envelope_file; @@ -730,8 +733,8 @@ format_part_mbox (const void *ctx, mime_node_t *node, unused (int indent), } static notmuch_status_t -format_part_raw (unused (const void *ctx), mime_node_t *node, - unused (int indent), +format_part_raw (unused (const void *ctx), unused (sprinter_t *sp), + mime_node_t *node, unused (int indent), unused (const notmuch_show_params_t *params)) { if (node->envelope_file) { @@ -819,6 +822,7 @@ show_null_message (const notmuch_show_format_t *format) static notmuch_status_t show_message (void *ctx, const notmuch_show_format_t *format, + sprinter_t *sp, notmuch_message_t *message, int indent, notmuch_show_params_t *params) @@ -832,7 +836,7 @@ show_message (void *ctx, goto DONE; part = mime_node_seek_dfs (root, (params->part < 0 ? 0 : params->part)); if (part) - status = format->part (local, part, indent, params); + status = format->part (local, sp, part, indent, params); DONE: talloc_free (local); return status; @@ -841,6 +845,7 @@ show_message (void *ctx, static notmuch_status_t show_messages (void *ctx, const notmuch_show_format_t *format, + sprinter_t *sp, notmuch_messages_t *messages, int indent, notmuch_show_params_t *params) @@ -874,7 +879,7 @@ show_messages (void *ctx, next_indent = indent; if ((match && (!excluded || !params->omit_excluded)) || params->entire_thread) { - status = show_message (ctx, format, message, indent, params); + status = show_message (ctx, format, sp, message, indent, params); if (status && !res) res = status; next_indent = indent + 1; @@ -886,7 +891,7 @@ show_messages (void *ctx, fputs (format->message_set_sep, stdout); status = show_messages (ctx, - format, + format, sp, notmuch_message_get_replies (message), next_indent, params); @@ -910,6 +915,7 @@ static int do_show_single (void *ctx, notmuch_query_t *query, const notmuch_show_format_t *format, + sprinter_t *sp, notmuch_show_params_t *params) { notmuch_messages_t *messages; @@ -930,7 +936,8 @@ do_show_single (void *ctx, notmuch_message_set_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH, 1); - return show_message (ctx, format, message, 0, params) != NOTMUCH_STATUS_SUCCESS; + return show_message (ctx, format, sp, message, 0, params) + != NOTMUCH_STATUS_SUCCESS; } /* Formatted output of threads */ @@ -938,6 +945,7 @@ static int do_show (void *ctx, notmuch_query_t *query, const notmuch_show_format_t *format, + sprinter_t *sp, notmuch_show_params_t *params) { notmuch_threads_t *threads; @@ -965,7 +973,7 @@ do_show (void *ctx, fputs (format->message_set_sep, stdout); first_toplevel = 0; - status = show_messages (ctx, format, messages, 0, params); + status = show_messages (ctx, format, sp, messages, 0, params); if (status && !res) res = status; @@ -1141,7 +1149,7 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) /* If a single message is requested we do not use search_excludes. */ if (params.part >= 0) - ret = do_show_single (ctx, query, format, ¶ms); + ret = do_show_single (ctx, query, format, sprinter, ¶ms); else { /* We always apply set the exclude flag. The * exclude=true|false option controls whether or not we return @@ -1160,7 +1168,7 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) params.omit_excluded = FALSE; } - ret = do_show (ctx, query, format, ¶ms); + ret = do_show (ctx, query, format, sprinter, ¶ms); } notmuch_crypto_cleanup (¶ms.crypto); -- cgit v1.2.3 From 7018fc58b4ebf6e2c52102c0443169f8120db261 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Thu, 2 Aug 2012 21:14:53 -0400 Subject: show: Convert format_headers_json to use sprinter This no longer requires a talloc context (not that it really did before since it didn't return anything), so we remove its context argument. --- notmuch-client.h | 3 ++- notmuch-reply.c | 2 +- notmuch-show.c | 58 +++++++++++++++++++++++++++----------------------------- 3 files changed, 31 insertions(+), 32 deletions(-) (limited to 'notmuch-show.c') diff --git a/notmuch-client.h b/notmuch-client.h index 112574c..b11caff 100644 --- a/notmuch-client.h +++ b/notmuch-client.h @@ -183,7 +183,8 @@ format_part_json (const void *ctx, struct sprinter *sp, mime_node_t *node, notmuch_bool_t first, notmuch_bool_t output_body); void -format_headers_json (const void *ctx, GMimeMessage *message, notmuch_bool_t reply); +format_headers_json (struct sprinter *sp, GMimeMessage *message, + notmuch_bool_t reply); typedef enum { NOTMUCH_SHOW_TEXT_PART_REPLY = 1 << 0, diff --git a/notmuch-reply.c b/notmuch-reply.c index 07d4452..fa6665f 100644 --- a/notmuch-reply.c +++ b/notmuch-reply.c @@ -617,7 +617,7 @@ notmuch_reply_format_json(void *ctx, /* The headers of the reply message we've created */ printf ("{\"reply-headers\": "); - format_headers_json (ctx, reply, TRUE); + format_headers_json (sp, reply, TRUE); g_object_unref (G_OBJECT (reply)); reply = NULL; diff --git a/notmuch-show.c b/notmuch-show.c index b258f65..9852119 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -199,48 +199,46 @@ _is_from_line (const char *line) } void -format_headers_json (const void *ctx, GMimeMessage *message, notmuch_bool_t reply) +format_headers_json (sprinter_t *sp, GMimeMessage *message, + notmuch_bool_t reply) { - void *local = talloc_new (ctx); InternetAddressList *recipients; const char *recipients_string; - printf ("{%s: %s", - json_quote_str (local, "Subject"), - json_quote_str (local, g_mime_message_get_subject (message))); - printf (", %s: %s", - json_quote_str (local, "From"), - json_quote_str (local, g_mime_message_get_sender (message))); + sp->begin_map (sp); + + sp->map_key (sp, "Subject"); + sp->string (sp, g_mime_message_get_subject (message)); + + sp->map_key (sp, "From"); + sp->string (sp, g_mime_message_get_sender (message)); + recipients = g_mime_message_get_recipients (message, GMIME_RECIPIENT_TYPE_TO); recipients_string = internet_address_list_to_string (recipients, 0); - if (recipients_string) - printf (", %s: %s", - json_quote_str (local, "To"), - json_quote_str (local, recipients_string)); + if (recipients_string) { + sp->map_key (sp, "To"); + sp->string (sp, recipients_string); + } + recipients = g_mime_message_get_recipients (message, GMIME_RECIPIENT_TYPE_CC); recipients_string = internet_address_list_to_string (recipients, 0); - if (recipients_string) - printf (", %s: %s", - json_quote_str (local, "Cc"), - json_quote_str (local, recipients_string)); + if (recipients_string) { + sp->map_key (sp, "Cc"); + sp->string (sp, recipients_string); + } if (reply) { - printf (", %s: %s", - json_quote_str (local, "In-reply-to"), - json_quote_str (local, g_mime_object_get_header (GMIME_OBJECT (message), "In-reply-to"))); + sp->map_key (sp, "In-reply-to"); + sp->string (sp, g_mime_object_get_header (GMIME_OBJECT (message), "In-reply-to")); - printf (", %s: %s", - json_quote_str (local, "References"), - json_quote_str (local, g_mime_object_get_header (GMIME_OBJECT (message), "References"))); + sp->map_key (sp, "References"); + sp->string (sp, g_mime_object_get_header (GMIME_OBJECT (message), "References")); } else { - printf (", %s: %s", - json_quote_str (local, "Date"), - json_quote_str (local, g_mime_message_get_date_as_string (message))); + sp->map_key (sp, "Date"); + sp->string (sp, g_mime_message_get_date_as_string (message)); } - printf ("}"); - - talloc_free (local); + sp->end (sp); } /* Write a MIME text part out to the given stream. @@ -575,7 +573,7 @@ format_part_json (const void *ctx, sprinter_t *sp, mime_node_t *node, format_message_json (ctx, node->envelope_file); printf ("\"headers\": "); - format_headers_json (ctx, GMIME_MESSAGE (node->part), FALSE); + format_headers_json (sp, GMIME_MESSAGE (node->part), FALSE); if (output_body) { printf (", \"body\": ["); @@ -651,7 +649,7 @@ format_part_json (const void *ctx, sprinter_t *sp, mime_node_t *node, } else if (GMIME_IS_MESSAGE (node->part)) { printf (", \"content\": [{"); printf ("\"headers\": "); - format_headers_json (local, GMIME_MESSAGE (node->part), FALSE); + format_headers_json (sp, GMIME_MESSAGE (node->part), FALSE); printf (", \"body\": ["); terminator = "]}]"; -- cgit v1.2.3 From 85b326f13ce3aa89c012a356e7073c04734a801e Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Thu, 2 Aug 2012 21:14:54 -0400 Subject: show: Convert format_part_sigstatus_json to use sprinter --- notmuch-show.c | 118 +++++++++++++++++++++++++++++---------------------------- 1 file changed, 61 insertions(+), 57 deletions(-) (limited to 'notmuch-show.c') diff --git a/notmuch-show.c b/notmuch-show.c index 9852119..3ff32df 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -337,134 +337,138 @@ signer_status_to_string (GMimeSignerStatus x) #ifdef GMIME_ATLEAST_26 static void -format_part_sigstatus_json (mime_node_t *node) +format_part_sigstatus_json (sprinter_t *sp, mime_node_t *node) { GMimeSignatureList *siglist = node->sig_list; - printf ("["); + sp->begin_list (sp); if (!siglist) { - printf ("]"); + sp->end (sp); return; } - void *ctx_quote = talloc_new (NULL); int i; for (i = 0; i < g_mime_signature_list_length (siglist); i++) { GMimeSignature *signature = g_mime_signature_list_get_signature (siglist, i); - if (i > 0) - printf (", "); - - printf ("{"); + sp->begin_map (sp); /* status */ GMimeSignatureStatus status = g_mime_signature_get_status (signature); - printf ("\"status\": %s", - json_quote_str (ctx_quote, - signature_status_to_string (status))); + sp->map_key (sp, "status"); + sp->string (sp, signature_status_to_string (status)); GMimeCertificate *certificate = g_mime_signature_get_certificate (signature); if (status == GMIME_SIGNATURE_STATUS_GOOD) { - if (certificate) - printf (", \"fingerprint\": %s", json_quote_str (ctx_quote, g_mime_certificate_get_fingerprint (certificate))); + if (certificate) { + sp->map_key (sp, "fingerprint"); + sp->string (sp, g_mime_certificate_get_fingerprint (certificate)); + } /* these dates are seconds since the epoch; should we * provide a more human-readable format string? */ time_t created = g_mime_signature_get_created (signature); - if (created != -1) - printf (", \"created\": %d", (int) created); + if (created != -1) { + sp->map_key (sp, "created"); + sp->integer (sp, created); + } time_t expires = g_mime_signature_get_expires (signature); - if (expires > 0) - printf (", \"expires\": %d", (int) expires); + if (expires > 0) { + sp->map_key (sp, "expires"); + sp->integer (sp, expires); + } /* output user id only if validity is FULL or ULTIMATE. */ /* note that gmime is using the term "trust" here, which * is WRONG. It's actually user id "validity". */ if (certificate) { const char *name = g_mime_certificate_get_name (certificate); GMimeCertificateTrust trust = g_mime_certificate_get_trust (certificate); - if (name && (trust == GMIME_CERTIFICATE_TRUST_FULLY || trust == GMIME_CERTIFICATE_TRUST_ULTIMATE)) - printf (", \"userid\": %s", json_quote_str (ctx_quote, name)); + if (name && (trust == GMIME_CERTIFICATE_TRUST_FULLY || trust == GMIME_CERTIFICATE_TRUST_ULTIMATE)) { + sp->map_key (sp, "userid"); + sp->string (sp, name); + } } } else if (certificate) { const char *key_id = g_mime_certificate_get_key_id (certificate); - if (key_id) - printf (", \"keyid\": %s", json_quote_str (ctx_quote, key_id)); + if (key_id) { + sp->map_key (sp, "keyid"); + sp->string (sp, key_id); + } } GMimeSignatureError errors = g_mime_signature_get_errors (signature); if (errors != GMIME_SIGNATURE_ERROR_NONE) { - printf (", \"errors\": %d", errors); + sp->map_key (sp, "errors"); + sp->integer (sp, errors); } - printf ("}"); + sp->end (sp); } - printf ("]"); - - talloc_free (ctx_quote); + sp->end (sp); } #else static void -format_part_sigstatus_json (mime_node_t *node) +format_part_sigstatus_json (sprinter_t *sp, mime_node_t *node) { const GMimeSignatureValidity* validity = node->sig_validity; - printf ("["); + sp->begin_list (sp); if (!validity) { - printf ("]"); + sp->end (sp); return; } const GMimeSigner *signer = g_mime_signature_validity_get_signers (validity); - int first = 1; - void *ctx_quote = talloc_new (NULL); - while (signer) { - if (first) - first = 0; - else - printf (", "); - - printf ("{"); + sp->begin_map (sp); /* status */ - printf ("\"status\": %s", - json_quote_str (ctx_quote, - signer_status_to_string (signer->status))); + sp->map_key (sp, "status"); + sp->string (sp, signer_status_to_string (signer->status)); if (signer->status == GMIME_SIGNER_STATUS_GOOD) { - if (signer->fingerprint) - printf (", \"fingerprint\": %s", json_quote_str (ctx_quote, signer->fingerprint)); + if (signer->fingerprint) { + sp->map_key (sp, "fingerprint"); + sp->string (sp, signer->fingerprint); + } /* these dates are seconds since the epoch; should we * provide a more human-readable format string? */ - if (signer->created) - printf (", \"created\": %d", (int) signer->created); - if (signer->expires) - printf (", \"expires\": %d", (int) signer->expires); + if (signer->created) { + sp->map_key (sp, "created"); + sp->integer (sp, signer->created); + } + if (signer->expires) { + sp->map_key (sp, "expires"); + sp->integer (sp, signer->expires); + } /* output user id only if validity is FULL or ULTIMATE. */ /* note that gmime is using the term "trust" here, which * is WRONG. It's actually user id "validity". */ if ((signer->name) && (signer->trust)) { - if ((signer->trust == GMIME_SIGNER_TRUST_FULLY) || (signer->trust == GMIME_SIGNER_TRUST_ULTIMATE)) - printf (", \"userid\": %s", json_quote_str (ctx_quote, signer->name)); + if ((signer->trust == GMIME_SIGNER_TRUST_FULLY) || (signer->trust == GMIME_SIGNER_TRUST_ULTIMATE)) { + sp->map_key (sp, "userid"); + sp->string (sp, signer->name); + } } } else { - if (signer->keyid) - printf (", \"keyid\": %s", json_quote_str (ctx_quote, signer->keyid)); + if (signer->keyid) { + sp->map_key (sp, "keyid"); + sp->string (sp, signer->keyid); + } } if (signer->errors != GMIME_SIGNER_ERROR_NONE) { - printf (", \"errors\": %d", signer->errors); + sp->map_key (sp, "errors"); + sp->integer (sp, signer->errors); } - printf ("}"); + sp->end (sp); signer = signer->next; } - printf ("]"); - - talloc_free (ctx_quote); + sp->end (sp); } #endif @@ -607,7 +611,7 @@ format_part_json (const void *ctx, sprinter_t *sp, mime_node_t *node, if (node->verify_attempted) { printf (", \"sigstatus\": "); - format_part_sigstatus_json (node); + format_part_sigstatus_json (sp, node); } printf (", \"content-type\": %s", -- cgit v1.2.3 From 6da306b40a5ba8293f2412b1ab07127ac3f055ca Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Thu, 2 Aug 2012 21:14:55 -0400 Subject: show: Convert non-envelope format_part_json to use sprinter --- notmuch-show.c | 74 ++++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 46 insertions(+), 28 deletions(-) (limited to 'notmuch-show.c') diff --git a/notmuch-show.c b/notmuch-show.c index 3ff32df..afbd9d0 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -588,7 +588,6 @@ format_part_json (const void *ctx, sprinter_t *sp, mime_node_t *node, return; } - void *local = talloc_new (ctx); /* The disposition and content-type metadata are associated with * the envelope for message parts */ GMimeObject *meta = node->envelope_part ? @@ -597,31 +596,41 @@ format_part_json (const void *ctx, sprinter_t *sp, mime_node_t *node, const char *cid = g_mime_object_get_content_id (meta); const char *filename = GMIME_IS_PART (node->part) ? g_mime_part_get_filename (GMIME_PART (node->part)) : NULL; - const char *terminator = ""; + int nclose = 0; int i; - if (!first) - printf (", "); + sp->begin_map (sp); - printf ("{\"id\": %d", node->part_num); + sp->map_key (sp, "id"); + sp->integer (sp, node->part_num); - if (node->decrypt_attempted) - printf (", \"encstatus\": [{\"status\": \"%s\"}]", - node->decrypt_success ? "good" : "bad"); + if (node->decrypt_attempted) { + sp->map_key (sp, "encstatus"); + sp->begin_list (sp); + sp->begin_map (sp); + sp->map_key (sp, "status"); + sp->string (sp, node->decrypt_success ? "good" : "bad"); + sp->end (sp); + sp->end (sp); + } if (node->verify_attempted) { - printf (", \"sigstatus\": "); + sp->map_key (sp, "sigstatus"); format_part_sigstatus_json (sp, node); } - printf (", \"content-type\": %s", - json_quote_str (local, g_mime_content_type_to_string (content_type))); + sp->map_key (sp, "content-type"); + sp->string (sp, g_mime_content_type_to_string (content_type)); - if (cid) - printf (", \"content-id\": %s", json_quote_str (local, cid)); + if (cid) { + sp->map_key (sp, "content-id"); + sp->string (sp, cid); + } - if (filename) - printf (", \"filename\": %s", json_quote_str (local, filename)); + if (filename) { + sp->map_key (sp, "filename"); + sp->string (sp, filename); + } if (GMIME_IS_PART (node->part)) { /* For non-HTML text parts, we include the content in the @@ -636,35 +645,44 @@ format_part_json (const void *ctx, sprinter_t *sp, mime_node_t *node, if (g_mime_content_type_is_type (content_type, "text", "html")) { const char *content_charset = g_mime_object_get_content_type_parameter (meta, "charset"); - if (content_charset != NULL) - printf (", \"content-charset\": %s", json_quote_str (local, content_charset)); + if (content_charset != NULL) { + sp->map_key (sp, "content-charset"); + sp->string (sp, content_charset); + } } else if (g_mime_content_type_is_type (content_type, "text", "*")) { GMimeStream *stream_memory = g_mime_stream_mem_new (); GByteArray *part_content; show_text_part_content (node->part, stream_memory, 0); part_content = g_mime_stream_mem_get_byte_array (GMIME_STREAM_MEM (stream_memory)); - - printf (", \"content\": %s", json_quote_chararray (local, (char *) part_content->data, part_content->len)); + sp->map_key (sp, "content"); + sp->string_len (sp, (char *) part_content->data, part_content->len); g_object_unref (stream_memory); } } else if (GMIME_IS_MULTIPART (node->part)) { - printf (", \"content\": ["); - terminator = "]"; + sp->map_key (sp, "content"); + sp->begin_list (sp); + nclose = 1; } else if (GMIME_IS_MESSAGE (node->part)) { - printf (", \"content\": [{"); - printf ("\"headers\": "); + sp->map_key (sp, "content"); + sp->begin_list (sp); + sp->begin_map (sp); + + sp->map_key (sp, "headers"); format_headers_json (sp, GMIME_MESSAGE (node->part), FALSE); - printf (", \"body\": ["); - terminator = "]}]"; + sp->map_key (sp, "body"); + sp->begin_list (sp); + nclose = 3; } - talloc_free (local); - for (i = 0; i < node->nchildren; i++) format_part_json (ctx, sp, mime_node_child (node, i), i == 0, TRUE); - printf ("%s}", terminator); + /* Close content structures */ + for (i = 0; i < nclose; i++) + sp->end (sp); + /* Close part map */ + sp->end (sp); } static notmuch_status_t -- cgit v1.2.3 From 26ba4abe535f5757d280401bc7de1cfacbd081b5 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Thu, 2 Aug 2012 21:14:56 -0400 Subject: show: Convert envelope format_part_json to use sprinter --- notmuch-show.c | 57 ++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 34 insertions(+), 23 deletions(-) (limited to 'notmuch-show.c') diff --git a/notmuch-show.c b/notmuch-show.c index afbd9d0..b9d9f5d 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -109,35 +109,45 @@ _get_one_line_summary (const void *ctx, notmuch_message_t *message) from, relative_date, tags); } +/* Emit a sequence of key/value pairs for the metadata of message. + * The caller should begin a map before calling this. */ static void -format_message_json (const void *ctx, notmuch_message_t *message) +format_message_json (sprinter_t *sp, notmuch_message_t *message) { + void *local = talloc_new (NULL); notmuch_tags_t *tags; - int first = 1; - void *ctx_quote = talloc_new (ctx); time_t date; const char *relative_date; + sp->map_key (sp, "id"); + sp->string (sp, notmuch_message_get_message_id (message)); + + sp->map_key (sp, "match"); + sp->boolean (sp, notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH)); + + sp->map_key (sp, "excluded"); + sp->boolean (sp, notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED)); + + sp->map_key (sp, "filename"); + sp->string (sp, notmuch_message_get_filename (message)); + + sp->map_key (sp, "timestamp"); date = notmuch_message_get_date (message); - relative_date = notmuch_time_relative_date (ctx, date); + sp->integer (sp, date); - printf ("\"id\": %s, \"match\": %s, \"excluded\": %s, \"filename\": %s, \"timestamp\": %ld, \"date_relative\": \"%s\", \"tags\": [", - json_quote_str (ctx_quote, notmuch_message_get_message_id (message)), - notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH) ? "true" : "false", - notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED) ? "true" : "false", - json_quote_str (ctx_quote, notmuch_message_get_filename (message)), - date, relative_date); + sp->map_key (sp, "date_relative"); + relative_date = notmuch_time_relative_date (local, date); + sp->string (sp, relative_date); + sp->map_key (sp, "tags"); + sp->begin_list (sp); for (tags = notmuch_message_get_tags (message); notmuch_tags_valid (tags); notmuch_tags_move_to_next (tags)) - { - printf("%s%s", first ? "" : ",", - json_quote_str (ctx_quote, notmuch_tags_get (tags))); - first = 0; - } - printf("], "); - talloc_free (ctx_quote); + sp->string (sp, notmuch_tags_get (tags)); + sp->end (sp); + + talloc_free (local); } /* Extract just the email address from the contents of a From: @@ -573,18 +583,19 @@ format_part_json (const void *ctx, sprinter_t *sp, mime_node_t *node, * devel/schemata. */ if (node->envelope_file) { - printf ("{"); - format_message_json (ctx, node->envelope_file); + sp->begin_map (sp); + format_message_json (sp, node->envelope_file); - printf ("\"headers\": "); + sp->map_key (sp, "headers"); format_headers_json (sp, GMIME_MESSAGE (node->part), FALSE); if (output_body) { - printf (", \"body\": ["); + sp->map_key (sp, "body"); + sp->begin_list (sp); format_part_json (ctx, sp, mime_node_child (node, 0), first, TRUE); - printf ("]"); + sp->end (sp); } - printf ("}"); + sp->end (sp); return; } -- cgit v1.2.3 From 305a7ade1e20e162ff71a007e414ca301e4b90f5 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Thu, 2 Aug 2012 21:14:57 -0400 Subject: show: Convert show_message to use sprinter Unlike the previous patches, this function is used for all formats. However, for formats other than the JSON format, the sprinter methods used by show_message are all no-ops, so this code continues to function correctly for all of the formats. Converting show_message eliminates show_null_message in the process, since this maps directly to an sprinter method. --- notmuch-show.c | 31 +++++-------------------------- 1 file changed, 5 insertions(+), 26 deletions(-) (limited to 'notmuch-show.c') diff --git a/notmuch-show.c b/notmuch-show.c index b9d9f5d..ec3e861 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -841,15 +841,6 @@ format_part_raw (unused (const void *ctx), unused (sprinter_t *sp), return NOTMUCH_STATUS_SUCCESS; } -static notmuch_status_t -show_null_message (const notmuch_show_format_t *format) -{ - /* Output a null message. Currently empty for all formats except Json */ - if (format->null_message) - printf ("%s", format->null_message); - return NOTMUCH_STATUS_SUCCESS; -} - static notmuch_status_t show_message (void *ctx, const notmuch_show_format_t *format, @@ -884,23 +875,16 @@ show_messages (void *ctx, notmuch_message_t *message; notmuch_bool_t match; notmuch_bool_t excluded; - int first_set = 1; int next_indent; notmuch_status_t status, res = NOTMUCH_STATUS_SUCCESS; - if (format->message_set_start) - fputs (format->message_set_start, stdout); + sp->begin_list (sp); for (; notmuch_messages_valid (messages); notmuch_messages_move_to_next (messages)) { - if (!first_set && format->message_set_sep) - fputs (format->message_set_sep, stdout); - first_set = 0; - - if (format->message_set_start) - fputs (format->message_set_start, stdout); + sp->begin_list (sp); message = notmuch_messages_get (messages); @@ -915,12 +899,9 @@ show_messages (void *ctx, res = status; next_indent = indent + 1; } else { - status = show_null_message (format); + sp->null (sp); } - if (!status && format->message_set_sep) - fputs (format->message_set_sep, stdout); - status = show_messages (ctx, format, sp, notmuch_message_get_replies (message), @@ -931,12 +912,10 @@ show_messages (void *ctx, notmuch_message_destroy (message); - if (format->message_set_end) - fputs (format->message_set_end, stdout); + sp->end (sp); } - if (format->message_set_end) - fputs (format->message_set_end, stdout); + sp->end (sp); return res; } -- cgit v1.2.3 From e41417d7b42558f13f74cdf4506b316f9942de48 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Thu, 2 Aug 2012 21:14:58 -0400 Subject: show: Convert do_show to use sprinter --- notmuch-show.c | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) (limited to 'notmuch-show.c') diff --git a/notmuch-show.c b/notmuch-show.c index ec3e861..89bf2e7 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -961,11 +961,9 @@ do_show (void *ctx, notmuch_threads_t *threads; notmuch_thread_t *thread; notmuch_messages_t *messages; - int first_toplevel = 1; notmuch_status_t status, res = NOTMUCH_STATUS_SUCCESS; - if (format->message_set_start) - fputs (format->message_set_start, stdout); + sp->begin_list (sp); for (threads = notmuch_query_search_threads (query); notmuch_threads_valid (threads); @@ -979,10 +977,6 @@ do_show (void *ctx, INTERNAL_ERROR ("Thread %s has no toplevel messages.\n", notmuch_thread_get_thread_id (thread)); - if (!first_toplevel && format->message_set_sep) - fputs (format->message_set_sep, stdout); - first_toplevel = 0; - status = show_messages (ctx, format, sp, messages, 0, params); if (status && !res) res = status; @@ -991,8 +985,7 @@ do_show (void *ctx, } - if (format->message_set_end) - fputs (format->message_set_end, stdout); + sp->end (sp); return res != NOTMUCH_STATUS_SUCCESS; } -- cgit v1.2.3 From 1cbaad158e36a1402f1f9bc49ba7f2b56640c0d1 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Thu, 2 Aug 2012 21:14:59 -0400 Subject: show: Remove now unused fields from notmuch_show_format The message_set_{begin,sep,end} and null_message fields are no longer used because we now use the structure printer provided by the format. --- notmuch-client.h | 4 ---- notmuch-show.c | 4 ---- 2 files changed, 8 deletions(-) (limited to 'notmuch-show.c') diff --git a/notmuch-client.h b/notmuch-client.h index b11caff..ae9344b 100644 --- a/notmuch-client.h +++ b/notmuch-client.h @@ -71,13 +71,9 @@ struct notmuch_show_params; typedef struct notmuch_show_format { struct sprinter *(*new_sprinter) (const void *ctx, FILE *stream); - const char *message_set_start; notmuch_status_t (*part) (const void *ctx, struct sprinter *sprinter, struct mime_node *node, int indent, const struct notmuch_show_params *params); - const char *message_set_sep; - const char *message_set_end; - const char *null_message; } notmuch_show_format_t; typedef struct notmuch_crypto { diff --git a/notmuch-show.c b/notmuch-show.c index 89bf2e7..3556293 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -37,11 +37,7 @@ format_part_json_entry (const void *ctx, sprinter_t *sp, mime_node_t *node, static const notmuch_show_format_t format_json = { .new_sprinter = sprinter_json_create, - .message_set_start = "[", .part = format_part_json_entry, - .message_set_sep = ", ", - .message_set_end = "]", - .null_message = "null" }; static notmuch_status_t -- cgit v1.2.3