From: Richard Russon Date: Sun, 15 Jan 2017 01:22:48 +0000 (+0000) Subject: reformat the source to mutt standards X-Git-Tag: neomutt-20170128~33 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=bd9b13d37e99432f540bdba2fa4978b110f5e066;p=neomutt reformat the source to mutt standards reindent - 2 spaces braces {} on new line indent case statements bracket logic - () around complex bools rename static functions - drop "nm_" prefix move functions - static first move mx_ops functions to end fix typos reorder includes add some doxygen comments --- diff --git a/mutt_notmuch.c b/mutt_notmuch.c index c16a3a163..a9c9da2b2 100644 --- a/mutt_notmuch.c +++ b/mutt_notmuch.c @@ -1,9 +1,29 @@ -/* - * Notmuch support for mutt +/** @file + * # NotMuch Support for Mutt * - * Copyright (C) 2011, 2012 Karel Zak + * ## Authors + * * Copyright (C) 2011-2016 Karel Zak + * * Copyright (C) 2016-2017 Richard Russon + * * Copyright (C) 2016 Kevin Velghe + * * Copyright (C) 2017 Bernard 'Guyzmo' Pratz * - * Notes: + * ## License + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place - Suite 330, Boston, MA 02111, USA. + * + * ## Notes * * - notmuch uses private CONTEXT->data and private HEADER->data * @@ -15,65 +35,78 @@ * - exception are nm_nonctx_* functions -- these functions use nm_default_uri * (or parse URI from another resource) */ + #if HAVE_CONFIG_H -# include "config.h" +#include "config.h" #endif -#include "mutt.h" -#include "mx.h" -#include "rfc2047.h" -#include "sort.h" -#include "mailbox.h" -#include "copy.h" -#include "keymap.h" -#include "url.h" -#include "buffy.h" - +#include #include +#include #include +#include +#include +#include #include #include -#include #include -#include -#include -#include #include -#include - -#include "mutt_notmuch.h" +#include "mutt.h" +#include "buffy.h" +#include "copy.h" +#include "keymap.h" +#include "mailbox.h" #include "mutt_curses.h" +#include "mutt_notmuch.h" +#include "mx.h" +#include "rfc2047.h" +#include "sort.h" +#include "url.h" #ifdef LIBNOTMUCH_CHECK_VERSION #undef LIBNOTMUCH_CHECK_VERSION #endif -/* The definition in is broken */ -#define LIBNOTMUCH_CHECK_VERSION(major, minor, micro) \ - (LIBNOTMUCH_MAJOR_VERSION > (major) || \ - (LIBNOTMUCH_MAJOR_VERSION == (major) && LIBNOTMUCH_MINOR_VERSION > (minor)) || \ - (LIBNOTMUCH_MAJOR_VERSION == (major) && LIBNOTMUCH_MINOR_VERSION == (minor) && \ - LIBNOTMUCH_MICRO_VERSION >= (micro))) +/* @def The definition in is broken */ +#define LIBNOTMUCH_CHECK_VERSION(major, minor, micro) \ + (LIBNOTMUCH_MAJOR_VERSION > (major) || \ + (LIBNOTMUCH_MAJOR_VERSION == (major) && LIBNOTMUCH_MINOR_VERSION > (minor)) || \ + (LIBNOTMUCH_MAJOR_VERSION == (major) && LIBNOTMUCH_MINOR_VERSION == (minor) && \ + LIBNOTMUCH_MICRO_VERSION >= (micro))) - -/* read whole-thread or matching messages only? */ -enum { - NM_QUERY_TYPE_MESGS = 1, /* default */ - NM_QUERY_TYPE_THREADS +/** + * enum anonymous - Query Types + * + * Read whole-thread or matching messages only? + */ +enum +{ + NM_QUERY_TYPE_MESGS = 1, /**< Default: Messages only */ + NM_QUERY_TYPE_THREADS /**< Whole threads */ }; -/* - * Parsed URI arguments +/** + * struct uri_tag - Parsed NotMuch-URI arguments + * + * The arguments in a URI are saved in a linked list. + * + * @sa nm_ctxdata#query_items */ -struct uri_tag { - char *name; - char *value; - struct uri_tag *next; +struct uri_tag +{ + char *name; + char *value; + struct uri_tag *next; }; -/* - * HEADER->(nm_hdrdata *)data->tag_list node +/** + * nm_hdrtag - NotMuch Mail Header Tags + * + * Keep a linked list of header tags and their transformed values. + * Textual tags can be transformed to symbols to save space. + * + * @sa nm_hdrdata#tag_list */ struct nm_hdrtag { @@ -82,2005 +115,2067 @@ struct nm_hdrtag struct nm_hdrtag *next; }; -/* - * HEADER->data +/** + * struct nm_hdrdata - NotMuch data attached to an email + * + * This stores all the NotMuch data associated with an email. + * + * @sa HEADER#data, MUTT_MBOX */ -struct nm_hdrdata { - char *folder; - char *tags; - char *tags_transformed; - struct nm_hdrtag *tag_list; - char *oldpath; - char *virtual_id; - int magic; +struct nm_hdrdata +{ + char *folder; /**< Location of the email */ + char *tags; + char *tags_transformed; + struct nm_hdrtag *tag_list; + char *oldpath; + char *virtual_id; /**< Unique NotMuch Id */ + int magic; /**< Type of mailbox the email is in */ }; -/* - * CONTEXT->data +/** + * struct nm_ctxdata - NotMuch data attached to a context + * + * This stores the global NotMuch data, such as the database connection. + * + * @sa CONTEXT#data, NotmuchDBLimit, NM_QUERY_TYPE_MESGS */ -struct nm_ctxdata { - notmuch_database_t *db; - - char *db_filename; - char *db_query; - int db_limit; - int query_type; +struct nm_ctxdata +{ + notmuch_database_t *db; - struct uri_tag *query_items; + char *db_filename; /**< Filename of the NotMuch database */ + char *db_query; /**< Previous query */ + int db_limit; /**< Maximum number of results to return */ + int query_type; /**< Messages or Threads */ - progress_t progress; - int oldmsgcount; - int ignmsgcount; /* ingored messages */ + struct uri_tag *query_items; - unsigned int noprogress : 1, - longrun : 1, - trans : 1, - progress_ready : 1; + progress_t progress; /**< A progress bar */ + int oldmsgcount; + int ignmsgcount; /**< Ignored messages */ + unsigned int noprogress : 1; /**< Don't show the progress bar */ + unsigned int longrun : 1; /**< A long-lived action is in progress */ + unsigned int trans : 1; /**< Atomic transacion in progress */ + unsigned int progress_ready : 1; /**< A progress bar has been initialised */ }; -static HEADER *get_mutt_header(CONTEXT *ctx, notmuch_message_t *msg); -static notmuch_message_t *get_nm_message(notmuch_database_t *db, HEADER *hdr); -static void url_free_tags(struct uri_tag *tags) +#if 0 +/** + * debug_print_filenames - Show a message's filenames + * @param msg NotMuch Message + * + * Print a list of all the filenames associated with a NotMuch message. + */ +static void debug_print_filenames(notmuch_message_t *msg) { - while (tags) { - struct uri_tag *next = tags->next; - FREE(&tags->name); - FREE(&tags->value); - FREE(&tags); - tags = next; - } + notmuch_filenames_t *ls; + const char *id = notmuch_message_get_message_id(msg); + + for (ls = notmuch_message_get_filenames(msg); + ls && notmuch_filenames_valid(ls); + notmuch_filenames_move_to_next(ls)) + { + dprint(2, (debugfile, "nm: %s: %s\n", id, notmuch_filenames_get(ls))); + } } -static int url_parse_query(char *url, char **filename, struct uri_tag **tags) +/** + * debug_print_tags - Show a message's tags + * @param msg NotMuch Message + * + * Print a list of all the tags associated with a NotMuch message. + */ +static void debug_print_tags(notmuch_message_t *msg) { - char *p = strstr(url, "://"); /* remote unsupported */ - char *e; - struct uri_tag *tag, *last = NULL; - - *filename = NULL; - *tags = NULL; - - if (!p || !*(p + 3)) - return -1; - - p += 3; - *filename = p; - - e = strchr(p, '?'); - - *filename = e ? e == p ? NULL : mutt_substrdup(p, e) : safe_strdup(p); - if (!e) - return 0; /* only filename */ - - if (*filename && url_pct_decode(*filename) < 0) - goto err; + notmuch_tags_t *tags; + const char *id = notmuch_message_get_message_id(msg); - ++e; /* skip '?' */ - p = e; - - while (p && *p) { - tag = safe_calloc(1, sizeof(struct uri_tag)); - if (!tag) - goto err; - - if (!*tags) - last = *tags = tag; - else { - last->next = tag; - last = tag; - } - - e = strchr(p, '='); - if (!e) - e = strchr(p, '&'); - tag->name = e ? mutt_substrdup(p, e) : safe_strdup(p); - if (!tag->name || url_pct_decode(tag->name) < 0) - goto err; - if (!e) - break; - - p = e + 1; - - if (*e == '&') - continue; + for (tags = notmuch_message_get_tags(msg); + tags && notmuch_tags_valid(tags); + notmuch_tags_move_to_next(tags)) + { + dprint(2, (debugfile, "nm: %s: %s\n", id, notmuch_tags_get(tags))); + } +} +#endif - e = strchr(p, '&'); - tag->value = e ? mutt_substrdup(p, e) : safe_strdup(p); - if (!tag->value || url_pct_decode(tag->value) < 0) - goto err; - if (!e) - break; - p = e + 1; - } +/** + * url_free_tags - Free a list of tags + * @param tags List of tags + */ +static void url_free_tags(struct uri_tag *tags) +{ + while (tags) + { + struct uri_tag *next = tags->next; + FREE(&tags->name); + FREE(&tags->value); + FREE(&tags); + tags = next; + } +} - return 0; +/** + * url_parse_query - Extract the tokens from a query URI + * @param[in] url URI to parse + * @param[out] filename Save the filename + * @param[out] tags Save the list of tags + * + * @retval 0 Success + * @retval -1 Error: Bad format + * + * Parse a NotMuch URI, such as: + * * notmuch:///path/to/db?query=tag:lkml&limit=1000 + * * notmuch://?query=neomutt + * + * Extract the database filename (optional) and any search parameters (tags). + * The tags will be saved in a linked list (#uri_tag). + */ +static int url_parse_query(char *url, char **filename, struct uri_tag **tags) +{ + char *p = strstr(url, "://"); /* remote unsupported */ + char *e; + struct uri_tag *tag, *last = NULL; + + *filename = NULL; + *tags = NULL; + + if (!p || !*(p + 3)) + return -1; + + p += 3; + *filename = p; + + e = strchr(p, '?'); + + *filename = e ? (e == p) ? NULL : mutt_substrdup(p, e) : safe_strdup(p); + if (!e) + return 0; /* only filename */ + + if (*filename && (url_pct_decode(*filename) < 0)) + goto err; + + e++; /* skip '?' */ + p = e; + + while (p && *p) + { + tag = safe_calloc(1, sizeof(struct uri_tag)); + if (!tag) + goto err; + + if (!*tags) + last = *tags = tag; + else + { + last->next = tag; + last = tag; + } + + e = strchr(p, '='); + if (!e) + e = strchr(p, '&'); + tag->name = e ? mutt_substrdup(p, e) : safe_strdup(p); + if (!tag->name || (url_pct_decode(tag->name) < 0)) + goto err; + if (!e) + break; + + p = e + 1; + + if (*e == '&') + continue; + + e = strchr(p, '&'); + tag->value = e ? mutt_substrdup(p, e) : safe_strdup(p); + if (!tag->value || (url_pct_decode(tag->value) < 0)) + goto err; + if (!e) + break; + p = e + 1; + } + + return 0; err: - FREE(&(*filename)); - url_free_tags(*tags); - return -1; + FREE(&(*filename)); + url_free_tags(*tags); + return -1; } static void free_tag_list(struct nm_hdrtag **tag_list) { - struct nm_hdrtag *tmp; + struct nm_hdrtag *tmp; - while ((tmp = *tag_list) != NULL) - { - *tag_list = tmp->next; - FREE(&tmp->tag); - FREE(&tmp->transformed); - FREE(&tmp); - } + while ((tmp = *tag_list) != NULL) + { + *tag_list = tmp->next; + FREE(&tmp->tag); + FREE(&tmp->transformed); + FREE(&tmp); + } - *tag_list = 0; + *tag_list = 0; } static void free_hdrdata(struct nm_hdrdata *data) { - if (!data) - return; + if (!data) + return; - dprint(2, (debugfile, "nm: freeing header %p\n", data)); - FREE(&data->folder); - FREE(&data->tags); - FREE(&data->tags_transformed); - free_tag_list(&data->tag_list); - FREE(&data->oldpath); - FREE(&data->virtual_id); - FREE(&data); + dprint(2, (debugfile, "nm: freeing header %p\n", data)); + FREE(&data->folder); + FREE(&data->tags); + FREE(&data->tags_transformed); + free_tag_list(&data->tag_list); + FREE(&data->oldpath); + FREE(&data->virtual_id); + FREE(&data); } static void free_ctxdata(struct nm_ctxdata *data) { - if (!data) - return; + if (!data) + return; - dprint(1, (debugfile, "nm: freeing context data %p\n", data)); + dprint(1, (debugfile, "nm: freeing context data %p\n", data)); - if (data->db) + if (data->db) #ifdef NOTMUCH_API_3 - notmuch_database_destroy(data->db); + notmuch_database_destroy(data->db); #else - notmuch_database_close(data->db); + notmuch_database_close(data->db); #endif - data->db = NULL; + data->db = NULL; - FREE(&data->db_filename); - FREE(&data->db_query); - url_free_tags(data->query_items); - FREE(&data); + FREE(&data->db_filename); + FREE(&data->db_query); + url_free_tags(data->query_items); + FREE(&data); } static struct nm_ctxdata *new_ctxdata(char *uri) { - struct nm_ctxdata *data; + struct nm_ctxdata *data; - if (!uri) - return NULL; + if (!uri) + return NULL; - data = safe_calloc(1, sizeof(struct nm_ctxdata)); - dprint(1, (debugfile, "nm: initialize context data %p\n", data)); + data = safe_calloc(1, sizeof(struct nm_ctxdata)); + dprint(1, (debugfile, "nm: initialize context data %p\n", data)); - data->db_limit = NotmuchDBLimit; + data->db_limit = NotmuchDBLimit; - if (url_parse_query(uri, &data->db_filename, &data->query_items)) { - mutt_error(_("failed to parse notmuch uri: %s"), uri); - FREE(&data); - return NULL; - } + if (url_parse_query(uri, &data->db_filename, &data->query_items)) + { + mutt_error(_("failed to parse notmuch uri: %s"), uri); + FREE(&data); + return NULL; + } - return data; -} - -static int deinit_context(CONTEXT *ctx) -{ - int i; - - if (!ctx || ctx->magic != MUTT_NOTMUCH) - return -1; - - for (i = 0; i < ctx->msgcount; i++) { - HEADER *h = ctx->hdrs[i]; - - if (h) { - free_hdrdata(h->data); - h->data = NULL; - } - } - - free_ctxdata(ctx->data); - ctx->data = NULL; - return 0; + return data; } static int init_context(CONTEXT *ctx) { - if (!ctx || ctx->magic != MUTT_NOTMUCH) - return -1; + if (!ctx || (ctx->magic != MUTT_NOTMUCH)) + return -1; - if (ctx->data) - return 0; + if (ctx->data) + return 0; - ctx->data = new_ctxdata(ctx->path); - if (!ctx->data) - return -1; - - return 0; -} + ctx->data = new_ctxdata(ctx->path); + if (!ctx->data) + return -1; -char *nm_header_get_folder(HEADER *h) -{ - return h && h->data ? ((struct nm_hdrdata *) h->data)->folder : NULL; + return 0; } -/* returns all unhidden tags */ -char *nm_header_get_tags(HEADER *h) +static char *header_get_id(HEADER *h) { - return h && h->data ? ((struct nm_hdrdata *) h->data)->tags : NULL; + return (h && h->data) ? ((struct nm_hdrdata *) h->data)->virtual_id : NULL; } -char *nm_header_get_tags_transformed(HEADER *h) +static char *header_get_fullpath(HEADER *h, char *buf, size_t bufsz) { - return h && h->data ? ((struct nm_hdrdata *) h->data)->tags_transformed : NULL; + snprintf(buf, bufsz, "%s/%s", nm_header_get_folder(h), h->path); + /*dprint(2, (debugfile, "nm: returns fullpath '%s'\n", buf));*/ + return buf; } -char *nm_header_get_tag_transformed(char *tag, HEADER *h) -{ - struct nm_hdrtag *tmp; - - if (!h || !h->data) - return NULL; - - for (tmp = ((struct nm_hdrdata *) h->data)->tag_list; - tmp != NULL; - tmp = tmp->next) - { - if (strcmp(tag, tmp->tag) == 0) - return tmp->transformed; - } - - return NULL; -} - -int nm_header_get_magic(HEADER *h) -{ - return h && h->data ? ((struct nm_hdrdata *) h->data)->magic : 0; -} - -/* - * Returns notmuch message Id. - */ -static char *nm_header_get_id(HEADER *h) -{ - return h && h->data ? ((struct nm_hdrdata *) h->data)->virtual_id : NULL; -} - - -char *nm_header_get_fullpath(HEADER *h, char *buf, size_t bufsz) -{ - snprintf(buf, bufsz, "%s/%s", nm_header_get_folder(h), h->path); - /*dprint(2, (debugfile, "nm: returns fullpath '%s'\n", buf));*/ - return buf; -} - - static struct nm_ctxdata *get_ctxdata(CONTEXT *ctx) { - if (ctx && ctx->magic == MUTT_NOTMUCH) - return ctx->data; + if (ctx && (ctx->magic == MUTT_NOTMUCH)) + return ctx->data; - return NULL; + return NULL; } -static int string_to_guery_type(const char *str) +static int string_to_query_type(const char *str) { - if (!str) - str = NotmuchQueryType; /* user's default */ - if (!str) - return NM_QUERY_TYPE_MESGS; /* hardcoded default */ + if (!str) + str = NotmuchQueryType; /* user's default */ + if (!str) + return NM_QUERY_TYPE_MESGS; /* hardcoded default */ - if (strcmp(str, "threads") == 0) - return NM_QUERY_TYPE_THREADS; - else if (strcmp(str, "messages") == 0) - return NM_QUERY_TYPE_MESGS; + if (strcmp(str, "threads") == 0) + return NM_QUERY_TYPE_THREADS; + else if (strcmp(str, "messages") == 0) + return NM_QUERY_TYPE_MESGS; - mutt_error (_("failed to parse notmuch query type: %s"), str); - return NM_QUERY_TYPE_MESGS; + mutt_error(_("failed to parse notmuch query type: %s"), str); + return NM_QUERY_TYPE_MESGS; } static char *get_query_string(struct nm_ctxdata *data) { - struct uri_tag *item; + struct uri_tag *item; - if (!data) - return NULL; - if (data->db_query) - return data->db_query; + if (!data) + return NULL; + if (data->db_query) + return data->db_query; - for (item = data->query_items; item; item = item->next) { - if (!item->value || !item->name) - continue; + for (item = data->query_items; item; item = item->next) + { + if (!item->value || !item->name) + continue; - if (strcmp(item->name, "limit") == 0) { - if (mutt_atoi(item->value, &data->db_limit)) - mutt_error (_("failed to parse notmuch limit: %s"), item->value); + if (strcmp(item->name, "limit") == 0) + { + if (mutt_atoi(item->value, &data->db_limit)) + mutt_error(_("failed to parse notmuch limit: %s"), item->value); + } + else if (strcmp(item->name, "type") == 0) + data->query_type = string_to_query_type(item->value); - } else if (strcmp(item->name, "type") == 0) - data->query_type = string_to_guery_type(item->value); + else if (strcmp(item->name, "query") == 0) + data->db_query = safe_strdup(item->value); + } - else if (strcmp(item->name, "query") == 0) - data->db_query = safe_strdup(item->value); - } + if (!data->query_type) + data->query_type = string_to_query_type(NULL); - if (!data->query_type) - data->query_type = string_to_guery_type(NULL); + dprint(2, (debugfile, "nm: query '%s'\n", data->db_query)); - dprint(2, (debugfile, "nm: query '%s'\n", data->db_query)); - - return data->db_query; + return data->db_query; } static int get_limit(struct nm_ctxdata *data) { - return data ? data->db_limit : 0; + return data ? data->db_limit : 0; } static int get_query_type(struct nm_ctxdata *data) { - return (data && data->query_type) ? data->query_type : string_to_guery_type(NULL); + return (data && data->query_type) ? data->query_type : string_to_query_type(NULL); } static const char *get_db_filename(struct nm_ctxdata *data) { - char *db_filename; + char *db_filename; - if (!data) - return NULL; + if (!data) + return NULL; - db_filename = data->db_filename ? data->db_filename : NotmuchDefaultUri; - if (!db_filename) - db_filename = Maildir; - if (!db_filename) - return NULL; - if (strncmp(db_filename, "notmuch://", 10) == 0) - db_filename += 10; + db_filename = data->db_filename ? data->db_filename : NotmuchDefaultUri; + if (!db_filename) + db_filename = Maildir; + if (!db_filename) + return NULL; + if (strncmp(db_filename, "notmuch://", 10) == 0) + db_filename += 10; - dprint(2, (debugfile, "nm: db filename '%s'\n", db_filename)); - return db_filename; + dprint(2, (debugfile, "nm: db filename '%s'\n", db_filename)); + return db_filename; } -static notmuch_database_t *do_database_open(const char *filename, - int writable, int verbose) +static notmuch_database_t *do_database_open(const char *filename, int writable, + int verbose) { - notmuch_database_t *db = NULL; - unsigned int ct = 0; - notmuch_status_t st = NOTMUCH_STATUS_SUCCESS; + notmuch_database_t *db = NULL; + int ct = 0; + notmuch_status_t st = NOTMUCH_STATUS_SUCCESS; - dprint(1, (debugfile, "nm: db open '%s' %s (timeout %d)\n", filename, - writable ? "[WRITE]" : "[READ]", NotmuchOpenTimeout)); - do { + dprint(1, (debugfile, "nm: db open '%s' %s (timeout %d)\n", filename, + writable ? "[WRITE]" : "[READ]", NotmuchOpenTimeout)); + do + { #ifdef NOTMUCH_API_3 - st = notmuch_database_open(filename, - writable ? NOTMUCH_DATABASE_MODE_READ_WRITE : - NOTMUCH_DATABASE_MODE_READ_ONLY, &db); + st = notmuch_database_open(filename, + writable ? NOTMUCH_DATABASE_MODE_READ_WRITE : + NOTMUCH_DATABASE_MODE_READ_ONLY, + &db); #else - db = notmuch_database_open(filename, - writable ? NOTMUCH_DATABASE_MODE_READ_WRITE : - NOTMUCH_DATABASE_MODE_READ_ONLY); + db = notmuch_database_open(filename, writable ? + NOTMUCH_DATABASE_MODE_READ_WRITE : + NOTMUCH_DATABASE_MODE_READ_ONLY); #endif - if (db || !NotmuchOpenTimeout || ct / 2 > NotmuchOpenTimeout) - break; - - if (verbose && ct && ct % 2 == 0) - mutt_error(_("Waiting for notmuch DB... (%d sec)"), ct / 2); - usleep(500000); - ct++; - } while (1); - - if (verbose) { - if (!db) - mutt_error (_("Cannot open notmuch database: %s: %s"), - filename, - st ? notmuch_status_to_string(st) : - _("unknown reason")); - else if (ct > 1) - mutt_clear_error(); - } - return db; + if (db || !NotmuchOpenTimeout || ((ct / 2) > NotmuchOpenTimeout)) + break; + + if (verbose && ct && ((ct % 2) == 0)) + mutt_error(_("Waiting for notmuch DB... (%d sec)"), ct / 2); + usleep(500000); + ct++; + } while (1); + + if (verbose) + { + if (!db) + mutt_error(_("Cannot open notmuch database: %s: %s"), filename, + st ? notmuch_status_to_string(st) : _("unknown reason")); + else if (ct > 1) + mutt_clear_error(); + } + return db; } static notmuch_database_t *get_db(struct nm_ctxdata *data, int writable) { - if (!data) - return NULL; - if (!data->db) { - const char *db_filename = get_db_filename(data); + if (!data) + return NULL; + if (!data->db) + { + const char *db_filename = get_db_filename(data); - if (db_filename) - data->db = do_database_open(db_filename, writable, TRUE); - } - return data->db; + if (db_filename) + data->db = do_database_open(db_filename, writable, TRUE); + } + return data->db; } static int release_db(struct nm_ctxdata *data) { - if (data && data->db) { - dprint(1, (debugfile, "nm: db close\n")); + if (data && data->db) + { + dprint(1, (debugfile, "nm: db close\n")); #ifdef NOTMUCH_API_3 - notmuch_database_destroy(data->db); + notmuch_database_destroy(data->db); #else - notmuch_database_close(data->db); + notmuch_database_close(data->db); #endif - data->db = NULL; - data->longrun = 0; - return 0; - } + data->db = NULL; + data->longrun = 0; + return 0; + } - return -1; + return -1; } -/* returns: < 0 = error - * 1 = new transaction started - * 0 = already within transaction - */ static int db_trans_begin(struct nm_ctxdata *data) { - if (!data || !data->db) - return -1; + if (!data || !data->db) + return -1; - if (!data->trans) { - dprint(2, (debugfile, "nm: db trans start\n")); - if (notmuch_database_begin_atomic(data->db)) - return -1; - data->trans = 1; - return 1; - } + if (!data->trans) + { + dprint(2, (debugfile, "nm: db trans start\n")); + if (notmuch_database_begin_atomic(data->db)) + return -1; + data->trans = 1; + return 1; + } - return 0; + return 0; } static int db_trans_end(struct nm_ctxdata *data) { - if (!data || !data->db) - return -1; - - if (data->trans) { - dprint(2, (debugfile, "nm: db trans end\n")); - data->trans = 0; - if (notmuch_database_end_atomic(data->db)) - return -1; - } - - return 0; -} - -void nm_longrun_init(CONTEXT *ctx, int writable) -{ - struct nm_ctxdata *data = get_ctxdata(ctx); + if (!data || !data->db) + return -1; - if (data && get_db(data, writable)) { - data->longrun = 1; - dprint(2, (debugfile, "nm: long run initialized\n")); - } -} + if (data->trans) + { + dprint(2, (debugfile, "nm: db trans end\n")); + data->trans = 0; + if (notmuch_database_end_atomic(data->db)) + return -1; + } -void nm_longrun_done(CONTEXT *ctx) -{ - struct nm_ctxdata *data = get_ctxdata(ctx); - - if (data && release_db(data) == 0) - dprint(2, (debugfile, "nm: long run deinitialized\n")); + return 0; } static int is_longrun(struct nm_ctxdata *data) { - return data && data->longrun; -} - -void nm_debug_check(CONTEXT *ctx) -{ - struct nm_ctxdata *data = get_ctxdata(ctx); - - if (!data) - return; - - if (data->db) { - dprint(1, (debugfile, "nm: ERROR: db is open, closing\n")); - release_db(data); - } + return data && data->longrun; } static int get_database_mtime(struct nm_ctxdata *data, time_t *mtime) { - char path[_POSIX_PATH_MAX]; - struct stat st; + char path[_POSIX_PATH_MAX]; + struct stat st; - if (!data) - return -1; + if (!data) + return -1; - snprintf(path, sizeof(path), "%s/.notmuch/xapian", get_db_filename(data)); - dprint(2, (debugfile, "nm: checking '%s' mtime\n", path)); + snprintf(path, sizeof(path), "%s/.notmuch/xapian", get_db_filename(data)); + dprint(2, (debugfile, "nm: checking '%s' mtime\n", path)); - if (stat(path, &st)) - return -1; + if (stat(path, &st)) + return -1; - if (mtime) - *mtime = st.st_mtime; + if (mtime) + *mtime = st.st_mtime; - return 0; + return 0; } static void apply_exclude_tags(notmuch_query_t *query) { - char *buf, *p, *end = NULL, *tag = NULL; - - if (!NotmuchExcludeTags || !*NotmuchExcludeTags) - return; - buf = safe_strdup(NotmuchExcludeTags); - - for (p = buf; p && *p; p++) { - if (!tag && isspace(*p)) - continue; - if (!tag) - tag = p; /* begin of the tag */ - if (*p == ',' || *p == ' ') - end = p; /* terminate the tag */ - else if (*(p + 1) == '\0') - end = p + 1; /* end of optstr */ - if (!tag || !end) - continue; - if (tag >= end) - break; - *end = '\0'; - - dprint(2, (debugfile, "nm: query exclude tag '%s'\n", tag)); - notmuch_query_add_tag_exclude(query, tag); - end = tag = NULL; - } - notmuch_query_set_omit_excluded(query, 1); - FREE(&buf); + char *buf, *p, *end = NULL, *tag = NULL; + + if (!NotmuchExcludeTags || !*NotmuchExcludeTags) + return; + buf = safe_strdup(NotmuchExcludeTags); + + for (p = buf; p && *p; p++) + { + if (!tag && isspace(*p)) + continue; + if (!tag) + tag = p; /* begin of the tag */ + if ((*p == ',') || (*p == ' ')) + end = p; /* terminate the tag */ + else if (*(p + 1) == '\0') + end = p + 1; /* end of optstr */ + if (!tag || !end) + continue; + if (tag >= end) + break; + *end = '\0'; + + dprint(2, (debugfile, "nm: query exclude tag '%s'\n", tag)); + notmuch_query_add_tag_exclude(query, tag); + end = tag = NULL; + } + notmuch_query_set_omit_excluded(query, 1); + FREE(&buf); } static notmuch_query_t *get_query(struct nm_ctxdata *data, int writable) { - notmuch_database_t *db = NULL; - notmuch_query_t *q = NULL; - const char *str; + notmuch_database_t *db = NULL; + notmuch_query_t *q = NULL; + const char *str; - if (!data) - return NULL; + if (!data) + return NULL; - db = get_db(data, writable); - str = get_query_string(data); + db = get_db(data, writable); + str = get_query_string(data); - if (!db || !str) - goto err; + if (!db || !str) + goto err; - q = notmuch_query_create(db, str); - if (!q) - goto err; + q = notmuch_query_create(db, str); + if (!q) + goto err; - apply_exclude_tags(q); - notmuch_query_set_sort(q, NOTMUCH_SORT_NEWEST_FIRST); - dprint(2, (debugfile, "nm: query successfully initialized\n")); - return q; + apply_exclude_tags(q); + notmuch_query_set_sort(q, NOTMUCH_SORT_NEWEST_FIRST); + dprint(2, (debugfile, "nm: query successfully initialized\n")); + return q; err: - if (!is_longrun(data)) - release_db(data); - return NULL; + if (!is_longrun(data)) + release_db(data); + return NULL; } static void append_str_item(char **str, const char *item, int sep) { - char *p; - size_t sz = strlen(item); - size_t ssz = *str ? strlen(*str) : 0; + char *p; + size_t sz = strlen(item); + size_t ssz = *str ? strlen(*str) : 0; - safe_realloc(str, ssz + (ssz && sep ? 1 : 0) + sz + 1); - p = *str + ssz; - if (sep && ssz) - *p++ = sep; - memcpy(p, item, sz + 1); + safe_realloc(str, ssz + ((ssz && sep) ? 1 : 0) + sz + 1); + p = *str + ssz; + if (sep && ssz) + *p++ = sep; + memcpy(p, item, sz + 1); } static int update_header_tags(HEADER *h, notmuch_message_t *msg) { - struct nm_hdrdata *data = h->data; - notmuch_tags_t *tags; - char *tstr = NULL, *ttstr = NULL; - struct nm_hdrtag *tag_list = NULL, *tmp; + struct nm_hdrdata *data = h->data; + notmuch_tags_t *tags; + char *tstr = NULL, *ttstr = NULL; + struct nm_hdrtag *tag_list = NULL, *tmp; - dprint(2, (debugfile, "nm: tags update requested (%s)\n", data->virtual_id)); + dprint(2, (debugfile, "nm: tags update requested (%s)\n", data->virtual_id)); - for (tags = notmuch_message_get_tags(msg); - tags && notmuch_tags_valid(tags); - notmuch_tags_move_to_next(tags)) { + for (tags = notmuch_message_get_tags(msg); tags && notmuch_tags_valid(tags); + notmuch_tags_move_to_next(tags)) + { + const char *t = notmuch_tags_get(tags); + const char *tt = NULL; - const char *t = notmuch_tags_get(tags); - const char *tt = NULL; + if (!t || !*t) + continue; - if (!t || !*t) - continue; + tt = hash_find(TagTransforms, t); + if (!tt) + tt = t; - tt = hash_find(TagTransforms, t); - if (!tt) - tt = t; + /* tags list contains all tags */ + tmp = safe_calloc(1, sizeof(*tmp)); + tmp->tag = safe_strdup(t); + tmp->transformed = safe_strdup(tt); + tmp->next = tag_list; + tag_list = tmp; - /* tags list contains all tags */ - tmp = safe_calloc(1, sizeof(*tmp)); - tmp->tag = safe_strdup(t); - tmp->transformed = safe_strdup(tt); - tmp->next = tag_list; - tag_list = tmp; + /* filter out hidden tags */ + if (NotmuchHiddenTags) + { + char *p = strstr(NotmuchHiddenTags, t); + size_t xsz = p ? strlen(t) : 0; - /* filter out hidden tags */ - if (NotmuchHiddenTags) { - char *p = strstr(NotmuchHiddenTags, t); - size_t xsz = p ? strlen(t) : 0; + if (p && ((p == NotmuchHiddenTags) + || (*(p - 1) == ',') + || (*(p - 1) == ' ')) + && ((*(p + xsz) == '\0') + || (*(p + xsz) == ',') + || (*(p + xsz) == ' '))) + continue; + } - if (p && (p == NotmuchHiddenTags - || *(p - 1) == ',' - || *(p - 1) == ' ') - && (*(p + xsz) == '\0' - || *(p + xsz) == ',' - || *(p + xsz) == ' ')) - continue; - } + /* expand the transformed tag string */ + append_str_item(&ttstr, tt, ' '); - /* expand the transformed tag string */ - append_str_item(&ttstr, tt, ' '); + /* expand the un-transformed tag string */ + append_str_item(&tstr, t, ' '); + } - /* expand the un-transformed tag string */ - append_str_item(&tstr, t, ' '); - } + free_tag_list(&data->tag_list); + data->tag_list = tag_list; - free_tag_list(&data->tag_list); - data->tag_list = tag_list; + if (data->tags && tstr && (strcmp(data->tags, tstr) == 0)) + { + FREE(&tstr); + FREE(&ttstr); + dprint(2, (debugfile, "nm: tags unchanged\n")); + return 1; + } - if (data->tags && tstr && strcmp(data->tags, tstr) == 0) { - FREE(&tstr); - FREE(&ttstr); - dprint(2, (debugfile, "nm: tags unchanged\n")); - return 1; - } + /* free old version */ + FREE(&data->tags); + FREE(&data->tags_transformed); - /* free old version */ - FREE(&data->tags); - FREE(&data->tags_transformed); + /* new version */ + data->tags = tstr; + dprint(2, (debugfile, "nm: new tags: '%s'\n", tstr)); - /* new version */ - data->tags = tstr; - dprint(2, (debugfile, "nm: new tags: '%s'\n", tstr)); + data->tags_transformed = ttstr; + dprint(2, (debugfile, "nm: new tag transforms: '%s'\n", ttstr)); - data->tags_transformed = ttstr; - dprint(2, (debugfile, "nm: new tag transforms: '%s'\n", ttstr)); - - return 0; + return 0; } -/* - * set/update HEADER->path and HEADER->data->path - */ static int update_message_path(HEADER *h, const char *path) { - struct nm_hdrdata *data = h->data; - char *p; + struct nm_hdrdata *data = h->data; + char *p; - dprint(2, (debugfile, "nm: path update requested path=%s, (%s)\n", - path, data->virtual_id)); + dprint(2, (debugfile, "nm: path update requested path=%s, (%s)\n", path, + data->virtual_id)); - p = strrchr(path, '/'); - if (p && p - path > 3 && - (strncmp(p - 3, "cur", 3) == 0 || - strncmp(p - 3, "new", 3) == 0 || - strncmp(p - 3, "tmp", 3) == 0)) { + p = strrchr(path, '/'); + if (p && ((p - path) > 3) && + ((strncmp(p - 3, "cur", 3) == 0) || + (strncmp(p - 3, "new", 3) == 0) || + (strncmp(p - 3, "tmp", 3) == 0))) + { + data->magic = MUTT_MAILDIR; - data->magic = MUTT_MAILDIR; + FREE(&h->path); + FREE(&data->folder); - FREE(&h->path); - FREE(&data->folder); + p -= 3; /* skip subfolder (e.g. "new") */ + h->path = safe_strdup(p); - p -= 3; /* skip subfolder (e.g. "new") */ - h->path = safe_strdup(p); + for (; (p > path) && (*(p - 1) == '/'); p--) + ; - for (; p > path && *(p - 1) == '/'; p--); + data->folder = mutt_substrdup(path, p); - data->folder = mutt_substrdup(path, p); + dprint(2, (debugfile, "nm: folder='%s', file='%s'\n", data->folder, h->path)); + return 0; + } - dprint(2, (debugfile, "nm: folder='%s', file='%s'\n", data->folder, h->path)); - return 0; - } - - return 1; + return 1; } static char *get_folder_from_path(const char *path) { - char *p = strrchr(path, '/'); - - if (p && p - path > 3 && - (strncmp(p - 3, "cur", 3) == 0 || - strncmp(p - 3, "new", 3) == 0 || - strncmp(p - 3, "tmp", 3) == 0)) { + char *p = strrchr(path, '/'); - p -= 3; - for (; p > path && *(p - 1) == '/'; p--); + if (p && ((p - path) > 3) && + ((strncmp(p - 3, "cur", 3) == 0) || + (strncmp(p - 3, "new", 3) == 0) || + (strncmp(p - 3, "tmp", 3) == 0))) + { + p -= 3; + for (; (p > path) && (*(p - 1) == '/'); p--) + ; - return mutt_substrdup(path, p); - } + return mutt_substrdup(path, p); + } - return NULL; + return NULL; } static void deinit_header(HEADER *h) { - if (h) { - free_hdrdata(h->data); - h->data = NULL; - } + if (h) + { + free_hdrdata(h->data); + h->data = NULL; + } } -/* converts notmuch message Id to mutt message */ static char *nm2mutt_message_id(const char *id) { - size_t sz; - char *mid; + size_t sz; + char *mid; - if (!id) - return NULL; - sz = strlen(id) + 3; - mid = safe_malloc(sz); + if (!id) + return NULL; + sz = strlen(id) + 3; + mid = safe_malloc(sz); - snprintf(mid, sz, "<%s>", id); - return mid; + snprintf(mid, sz, "<%s>", id); + return mid; } static int init_header(HEADER *h, const char *path, notmuch_message_t *msg) { - const char *id; + const char *id; - if (h->data) - return 0; + if (h->data) + return 0; - id = notmuch_message_get_message_id(msg); + id = notmuch_message_get_message_id(msg); - h->data = safe_calloc(1, sizeof(struct nm_hdrdata)); - h->free_cb = deinit_header; + h->data = safe_calloc(1, sizeof(struct nm_hdrdata)); + h->free_cb = deinit_header; - /* - * Notmuch ensures that message Id exists (if not notmuch Notmuch will - * generate an ID), so it's more safe than use mutt HEADER->env->id - */ - ((struct nm_hdrdata *) h->data)->virtual_id = safe_strdup( id ); + /* + * Notmuch ensures that message Id exists (if not notmuch Notmuch will + * generate an ID), so it's more safe than use mutt HEADER->env->id + */ + ((struct nm_hdrdata *) h->data)->virtual_id = safe_strdup(id); - dprint(2, (debugfile, "nm: initialize header data: [hdr=%p, data=%p] (%s)\n", - h, h->data, id)); + dprint(2, (debugfile, "nm: initialize header data: [hdr=%p, data=%p] (%s)\n", + h, h->data, id)); - if (!h->env->message_id) - h->env->message_id = nm2mutt_message_id( id ); + if (!h->env->message_id) + h->env->message_id = nm2mutt_message_id(id); - if (update_message_path(h, path)) - return -1; + if (update_message_path(h, path)) + return -1; - update_header_tags(h, msg); + update_header_tags(h, msg); - return 0; + return 0; } -/** -static void debug_print_filenames(notmuch_message_t *msg) -{ - notmuch_filenames_t *ls; - const char *id = notmuch_message_get_message_id(msg); - - for (ls = notmuch_message_get_filenames(msg); - ls && notmuch_filenames_valid(ls); - notmuch_filenames_move_to_next(ls)) { - - dprint(2, (debugfile, "nm: %s: %s\n", id, notmuch_filenames_get(ls))); - } -} - -static void debug_print_tags(notmuch_message_t *msg) -{ - notmuch_tags_t *tags; - const char *id = notmuch_message_get_message_id(msg); - - for (tags = notmuch_message_get_tags(msg); - tags && notmuch_tags_valid(tags); - notmuch_tags_move_to_next(tags)) { - - dprint(2, (debugfile, "nm: %s: %s\n", id, notmuch_tags_get(tags))); - } -} -***/ - static const char *get_message_last_filename(notmuch_message_t *msg) { - notmuch_filenames_t *ls; - const char *name = NULL; + notmuch_filenames_t *ls; + const char *name = NULL; - for (ls = notmuch_message_get_filenames(msg); - ls && notmuch_filenames_valid(ls); - notmuch_filenames_move_to_next(ls)) { + for (ls = notmuch_message_get_filenames(msg); + ls && notmuch_filenames_valid(ls); notmuch_filenames_move_to_next(ls)) + { + name = notmuch_filenames_get(ls); + } - name = notmuch_filenames_get(ls); - } - - return name; + return name; } -static void nm_progress_reset(CONTEXT *ctx) +static void progress_reset(CONTEXT *ctx) { - struct nm_ctxdata *data; + struct nm_ctxdata *data; - if (ctx->quiet) - return; + if (ctx->quiet) + return; - data = get_ctxdata(ctx); - if (!data) - return; + data = get_ctxdata(ctx); + if (!data) + return; - memset(&data->progress, 0, sizeof(data->progress)); - data->oldmsgcount = ctx->msgcount; - data->ignmsgcount = 0; - data->noprogress = 0; - data->progress_ready = 0; + memset(&data->progress, 0, sizeof(data->progress)); + data->oldmsgcount = ctx->msgcount; + data->ignmsgcount = 0; + data->noprogress = 0; + data->progress_ready = 0; } -static void nm_progress_update(CONTEXT *ctx, notmuch_query_t *q) +static void progress_update(CONTEXT *ctx, notmuch_query_t *q) { - struct nm_ctxdata *data = get_ctxdata(ctx); + struct nm_ctxdata *data = get_ctxdata(ctx); - if (ctx->quiet || !data || data->noprogress) - return; + if (ctx->quiet || !data || data->noprogress) + return; - if (!data->progress_ready && q) { - unsigned count; - static char msg[STRING]; - snprintf(msg, sizeof(msg), _("Reading messages...")); + if (!data->progress_ready && q) + { + unsigned count; + static char msg[STRING]; + snprintf(msg, sizeof(msg), _("Reading messages...")); #if LIBNOTMUCH_CHECK_VERSION(4,3,0) - if (notmuch_query_count_messages_st (q, &count) != NOTMUCH_STATUS_SUCCESS) - count = 0; /* may not be defined on error */ + if (notmuch_query_count_messages_st(q, &count) != NOTMUCH_STATUS_SUCCESS) + count = 0; /* may not be defined on error */ #else - count = notmuch_query_count_messages(q); + count = notmuch_query_count_messages(q); #endif - mutt_progress_init(&data->progress, msg, MUTT_PROGRESS_MSG, - ReadInc, count); - data->progress_ready = 1; - } - - if (data->progress_ready) - mutt_progress_update(&data->progress, - ctx->msgcount + data->ignmsgcount - - data->oldmsgcount, -1); -} - -static void append_message(CONTEXT *ctx, - notmuch_query_t *q, - notmuch_message_t *msg, - int dedup) -{ - char *newpath = NULL; - const char *path; - HEADER *h = NULL; - - struct nm_ctxdata *data = get_ctxdata(ctx); - if (!data) - return; - - /* deduplicate */ - if (dedup && get_mutt_header(ctx, msg)) { - data->ignmsgcount++; - nm_progress_update(ctx, q); - dprint(2, (debugfile, "nm: ignore id=%s, already in the context\n", - notmuch_message_get_message_id(msg))); - return; - } - - path = get_message_last_filename(msg); - if (!path) - return; - - dprint(2, (debugfile, "nm: appending message, i=%d, id=%s, path=%s\n", - ctx->msgcount, - notmuch_message_get_message_id(msg), - path)); - - if (ctx->msgcount >= ctx->hdrmax) { - dprint(2, (debugfile, "nm: allocate mx memory\n")); - mx_alloc_memory(ctx); - } - if (access(path, F_OK) == 0) - h = maildir_parse_message(MUTT_MAILDIR, path, 0, NULL); - else { - /* maybe moved try find it... */ - char *folder = get_folder_from_path(path); - - if (folder) { - FILE *f = maildir_open_find_message(folder, path, &newpath); - if (f) { - h = maildir_parse_stream(MUTT_MAILDIR, f, newpath, 0, NULL); - fclose(f); - - dprint(1, (debugfile, "nm: not up-to-date: %s -> %s\n", - path, newpath)); - } - } - FREE(&folder); - } - - if (!h) { - dprint(1, (debugfile, "nm: failed to parse message: %s\n", path)); - goto done; - } - if (init_header(h, newpath ? newpath : path, msg) != 0) { - mutt_free_header(&h); - dprint(1, (debugfile, "nm: failed to append header!\n")); - goto done; - } - - h->active = 1; - h->index = ctx->msgcount; - ctx->size += h->content->length - + h->content->offset - - h->content->hdr_offset; - ctx->hdrs[ctx->msgcount] = h; - ctx->msgcount++; - - if (newpath) { - /* remember that file has been moved -- nm_sync_mailbox() will update the DB */ - struct nm_hdrdata *hd = (struct nm_hdrdata *) h->data; - - if (hd) { - dprint(1, (debugfile, "nm: remember obsolete path: %s\n", path)); - hd->oldpath = safe_strdup(path); - } - } - nm_progress_update(ctx, q); -done: - FREE(&newpath); + mutt_progress_init(&data->progress, msg, MUTT_PROGRESS_MSG, ReadInc, count); + data->progress_ready = 1; + } + + if (data->progress_ready) + mutt_progress_update(&data->progress, + ctx->msgcount + data->ignmsgcount - data->oldmsgcount, + -1); } -/* - * add all the replies to a given messages into the display. - * Careful, this calls itself recursively to make sure we get - * everything. - */ -static void append_replies(CONTEXT *ctx, - notmuch_query_t *q, - notmuch_message_t *top, - int dedup) +static HEADER *get_mutt_header(CONTEXT *ctx, notmuch_message_t *msg) { - notmuch_messages_t *msgs; + char *mid; + const char *id; + HEADER *h; + + if (!ctx || !msg) + return NULL; + + id = notmuch_message_get_message_id(msg); + if (!id) + return NULL; + + dprint(2, (debugfile, "nm: mutt header, id='%s'\n", id)); + + if (!ctx->id_hash) + { + dprint(2, (debugfile, "nm: init hash\n")); + ctx->id_hash = mutt_make_id_hash(ctx); + if (!ctx->id_hash) + return NULL; + } + + mid = nm2mutt_message_id(id); + dprint(2, (debugfile, "nm: mutt id='%s'\n", mid)); + + h = hash_find(ctx->id_hash, mid); + FREE(&mid); + return h; +} + +static void append_message(CONTEXT *ctx, notmuch_query_t *q, + notmuch_message_t *msg, int dedup) +{ + char *newpath = NULL; + const char *path; + HEADER *h = NULL; + + struct nm_ctxdata *data = get_ctxdata(ctx); + if (!data) + return; + + /* deduplicate */ + if (dedup && get_mutt_header(ctx, msg)) + { + data->ignmsgcount++; + progress_update(ctx, q); + dprint(2, (debugfile, "nm: ignore id=%s, already in the context\n", + notmuch_message_get_message_id(msg))); + return; + } + + path = get_message_last_filename(msg); + if (!path) + return; + + dprint(2, (debugfile, "nm: appending message, i=%d, id=%s, path=%s\n", + ctx->msgcount, notmuch_message_get_message_id(msg), path)); + + if (ctx->msgcount >= ctx->hdrmax) + { + dprint(2, (debugfile, "nm: allocate mx memory\n")); + mx_alloc_memory(ctx); + } + if (access(path, F_OK) == 0) + h = maildir_parse_message(MUTT_MAILDIR, path, 0, NULL); + else + { + /* maybe moved try find it... */ + char *folder = get_folder_from_path(path); + + if (folder) + { + FILE *f = maildir_open_find_message(folder, path, &newpath); + if (f) + { + h = maildir_parse_stream(MUTT_MAILDIR, f, newpath, 0, NULL); + fclose(f); + + dprint(1, (debugfile, "nm: not up-to-date: %s -> %s\n", path, newpath)); + } + } + FREE(&folder); + } + + if (!h) + { + dprint(1, (debugfile, "nm: failed to parse message: %s\n", path)); + goto done; + } + if (init_header(h, newpath ? newpath : path, msg) != 0) + { + mutt_free_header(&h); + dprint(1, (debugfile, "nm: failed to append header!\n")); + goto done; + } + + h->active = 1; + h->index = ctx->msgcount; + ctx->size += h->content->length + h->content->offset - h->content->hdr_offset; + ctx->hdrs[ctx->msgcount] = h; + ctx->msgcount++; + + if (newpath) + { + /* remember that file has been moved -- nm_sync_mailbox() will update the DB */ + struct nm_hdrdata *hd = (struct nm_hdrdata *) h->data; + + if (hd) + { + dprint(1, (debugfile, "nm: remember obsolete path: %s\n", path)); + hd->oldpath = safe_strdup(path); + } + } + progress_update(ctx, q); +done: + FREE(&newpath); +} - for (msgs = notmuch_message_get_replies(top); - notmuch_messages_valid(msgs); - notmuch_messages_move_to_next(msgs)) { +static void append_replies(CONTEXT *ctx, notmuch_query_t *q, + notmuch_message_t *top, int dedup) +{ + notmuch_messages_t *msgs; - notmuch_message_t *m = notmuch_messages_get(msgs); - append_message(ctx, q, m, dedup); - /* recurse through all the replies to this message too */ - append_replies(ctx, q, m, dedup); - notmuch_message_destroy(m); - } + for (msgs = notmuch_message_get_replies(top); notmuch_messages_valid(msgs); + notmuch_messages_move_to_next(msgs)) + { + notmuch_message_t *m = notmuch_messages_get(msgs); + append_message(ctx, q, m, dedup); + /* recurse through all the replies to this message too */ + append_replies(ctx, q, m, dedup); + notmuch_message_destroy(m); + } } -/* - * add each top level reply in the thread, and then add each - * reply to the top level replies - */ -static void append_thread(CONTEXT *ctx, - notmuch_query_t *q, - notmuch_thread_t *thread, - int dedup) +static void append_thread(CONTEXT *ctx, notmuch_query_t *q, + notmuch_thread_t *thread, int dedup) { - notmuch_messages_t *msgs; + notmuch_messages_t *msgs; - for (msgs = notmuch_thread_get_toplevel_messages(thread); - notmuch_messages_valid(msgs); - notmuch_messages_move_to_next(msgs)) { - - notmuch_message_t *m = notmuch_messages_get(msgs); - append_message(ctx, q, m, dedup); - append_replies(ctx, q, m, dedup); - notmuch_message_destroy(m); - } + for (msgs = notmuch_thread_get_toplevel_messages(thread); + notmuch_messages_valid(msgs); notmuch_messages_move_to_next(msgs)) + { + notmuch_message_t *m = notmuch_messages_get(msgs); + append_message(ctx, q, m, dedup); + append_replies(ctx, q, m, dedup); + notmuch_message_destroy(m); + } } static void read_mesgs_query(CONTEXT *ctx, notmuch_query_t *q, int dedup) { - struct nm_ctxdata *data = get_ctxdata(ctx); - int limit; - notmuch_messages_t *msgs; + struct nm_ctxdata *data = get_ctxdata(ctx); + int limit; + notmuch_messages_t *msgs; - if (!data) - return; + if (!data) + return; - limit = get_limit(data); + limit = get_limit(data); #if LIBNOTMUCH_CHECK_VERSION(4,3,0) - if (notmuch_query_search_messages_st (q, &msgs) != NOTMUCH_STATUS_SUCCESS) - return; + if (notmuch_query_search_messages_st(q, &msgs) != NOTMUCH_STATUS_SUCCESS) + return; #else - msgs = notmuch_query_search_messages(q); + msgs = notmuch_query_search_messages(q); #endif - for (; notmuch_messages_valid(msgs) && - (limit == 0 || ctx->msgcount < limit); - notmuch_messages_move_to_next(msgs)) { - - notmuch_message_t *m = notmuch_messages_get(msgs); - append_message(ctx, q, m, dedup); - notmuch_message_destroy(m); - } + for (; notmuch_messages_valid(msgs) && ((limit == 0) || (ctx->msgcount < limit)); + notmuch_messages_move_to_next(msgs)) + { + notmuch_message_t *m = notmuch_messages_get(msgs); + append_message(ctx, q, m, dedup); + notmuch_message_destroy(m); + } } -static void read_threads_query(CONTEXT *ctx, notmuch_query_t *q, int dedup, int limit) +static void read_threads_query(CONTEXT *ctx, notmuch_query_t *q, int dedup, + int limit) { - struct nm_ctxdata *data = get_ctxdata(ctx); - notmuch_threads_t *threads; + struct nm_ctxdata *data = get_ctxdata(ctx); + notmuch_threads_t *threads; - if (!data) - return; + if (!data) + return; #if LIBNOTMUCH_CHECK_VERSION(4,3,0) - if (notmuch_query_search_threads_st (q, &threads) != NOTMUCH_STATUS_SUCCESS) - return; + if (notmuch_query_search_threads_st(q, &threads) != NOTMUCH_STATUS_SUCCESS) + return; #else - threads = notmuch_query_search_threads(q); + threads = notmuch_query_search_threads(q); #endif - for (; notmuch_threads_valid(threads) && - (limit == 0 || ctx->msgcount < limit); - notmuch_threads_move_to_next(threads)) { - - notmuch_thread_t *thread = notmuch_threads_get(threads); - append_thread(ctx, q, thread, dedup); - notmuch_thread_destroy(thread); - } + for (; notmuch_threads_valid(threads) && + ((limit == 0) || (ctx->msgcount < limit)); + notmuch_threads_move_to_next(threads)) + { + notmuch_thread_t *thread = notmuch_threads_get(threads); + append_thread(ctx, q, thread, dedup); + notmuch_thread_destroy(thread); + } } -int nm_read_query(CONTEXT *ctx) +static notmuch_message_t *get_nm_message(notmuch_database_t *db, HEADER *hdr) { - notmuch_query_t *q; - struct nm_ctxdata *data; - int rc = -1; + notmuch_message_t *msg = NULL; + char *id = header_get_id(hdr); - if (init_context(ctx) != 0) - return -1; + dprint(2, (debugfile, "nm: find message (%s)\n", id)); - data = get_ctxdata(ctx); - if (!data) - return -1; + if (id && db) + notmuch_database_find_message(db, id, &msg); - dprint(1, (debugfile, "nm: reading messages...[current count=%d]\n", - ctx->msgcount)); + return msg; +} - nm_progress_reset(ctx); +static int update_tags(notmuch_message_t *msg, const char *tags) +{ + char *tag = NULL, *end = NULL, *p; + char *buf = safe_strdup(tags); + + if (!buf) + return -1; + + notmuch_message_freeze(msg); + + for (p = buf; p && *p; p++) + { + if (!tag && isspace(*p)) + continue; + if (!tag) + tag = p; /* begin of the tag */ + if ((*p == ',') || (*p == ' ')) + end = p; /* terminate the tag */ + else if (*(p + 1) == '\0') + end = p + 1; /* end of optstr */ + if (!tag || !end) + continue; + if (tag >= end) + break; + + *end = '\0'; + + if (*tag == '-') + { + dprint(1, (debugfile, "nm: remove tag: '%s'\n", tag + 1)); + notmuch_message_remove_tag(msg, tag + 1); + } + else + { + dprint(1, (debugfile, "nm: add tag: '%s'\n", (*tag == '+') ? tag + 1 : tag)); + notmuch_message_add_tag(msg, (*tag == '+') ? tag + 1 : tag); + } + end = tag = NULL; + } + + notmuch_message_thaw(msg); + FREE(&buf); + return 0; +} - q = get_query(data, FALSE); - if (q) { - switch(get_query_type(data)) { - case NM_QUERY_TYPE_MESGS: - read_mesgs_query(ctx, q, 0); - break; - case NM_QUERY_TYPE_THREADS: - read_threads_query(ctx, q, 0, get_limit(data)); - break; - } - notmuch_query_destroy(q); - rc = 0; +static int update_header_flags(CONTEXT *ctx, HEADER *hdr, const char *tags) +{ + char *tag = NULL, *end = NULL, *p; + char *buf = safe_strdup(tags); + + if (!buf) + return -1; + + for (p = buf; p && *p; p++) + { + if (!tag && isspace(*p)) + continue; + if (!tag) + tag = p; /* begin of the tag */ + if ((*p == ',') || (*p == ' ')) + end = p; /* terminate the tag */ + else if (*(p + 1) == '\0') + end = p + 1; /* end of optstr */ + if (!tag || !end) + continue; + if (tag >= end) + break; + + *end = '\0'; + + if (*tag == '-') + { + tag = tag + 1; + if (strcmp(tag, "unread") == 0) + mutt_set_flag(ctx, hdr, MUTT_READ, 1); + else if (strcmp(tag, "replied") == 0) + mutt_set_flag(ctx, hdr, MUTT_REPLIED, 0); + else if (strcmp(tag, "flagged") == 0) + mutt_set_flag(ctx, hdr, MUTT_FLAG, 0); + } + else + { + tag = (*tag == '+') ? tag + 1 : tag; + if (strcmp(tag, "unread") == 0) + mutt_set_flag(ctx, hdr, MUTT_READ, 0); + else if (strcmp(tag, "replied") == 0) + mutt_set_flag(ctx, hdr, MUTT_REPLIED, 1); + else if (strcmp(tag, "flagged") == 0) + mutt_set_flag(ctx, hdr, MUTT_FLAG, 1); + } + end = tag = NULL; + } + + FREE(&buf); + return 0; +} + +static int rename_maildir_filename(const char *old, char *newpath, size_t newsz, + HEADER *h) +{ + char filename[_POSIX_PATH_MAX]; + char suffix[_POSIX_PATH_MAX]; + char folder[_POSIX_PATH_MAX]; + char *p; + + strfcpy(folder, old, sizeof(folder)); + p = strrchr(folder, '/'); + if (p) { + *p = '\0'; + p++; + } else { + p = folder; + } + + strfcpy(filename, p, sizeof(filename)); + + /* remove (new,cur,...) from folder path */ + p = strrchr(folder, '/'); + if (p) + *p = '\0'; + + /* remove old flags from filename */ + p = strchr(filename, ':'); + if (p) + *p = '\0'; + + /* compose new flags */ + maildir_flags(suffix, sizeof(suffix), h); + + snprintf(newpath, newsz, "%s/%s/%s%s", folder, + (h->read || h->old) ? "cur" : "new", filename, suffix); + + if (strcmp(old, newpath) == 0) + return 1; + + if (rename(old, newpath) != 0) + { + dprint(1, (debugfile, "nm: rename(2) failed %s -> %s\n", old, newpath)); + return -1; + } + + return 0; +} - } +static int remove_filename(struct nm_ctxdata *data, const char *path) +{ + notmuch_status_t st; + notmuch_filenames_t *ls; + notmuch_message_t *msg = NULL; + notmuch_database_t *db = get_db(data, TRUE); + int trans; + + dprint(2, (debugfile, "nm: remove filename '%s'\n", path)); + + if (!db) + return -1; + st = notmuch_database_find_message_by_filename(db, path, &msg); + if (st || !msg) + return -1; + trans = db_trans_begin(data); + if (trans < 0) + return -1; + + /* + * note that unlink() is probably unnecessary here, it's already removed + * by mh_sync_mailbox_message(), but for sure... + */ + st = notmuch_database_remove_message(db, path); + switch (st) + { + case NOTMUCH_STATUS_SUCCESS: + dprint(2, (debugfile, "nm: remove success, call unlink\n")); + unlink(path); + break; + case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID: + dprint(2, (debugfile, "nm: remove success (duplicate), call unlink\n")); + unlink(path); + for (ls = notmuch_message_get_filenames(msg); + ls && notmuch_filenames_valid(ls); + notmuch_filenames_move_to_next(ls)) + { + path = notmuch_filenames_get(ls); + + dprint(2, (debugfile, "nm: remove duplicate: '%s'\n", path)); + unlink(path); + notmuch_database_remove_message(db, path); + } + break; + default: + dprint(1, (debugfile, "nm: failed to remove '%s' [st=%d]\n", path, + (int) st)); + break; + } + + notmuch_message_destroy(msg); + if (trans) + db_trans_end(data); + return 0; +} + +static int rename_filename(struct nm_ctxdata *data, const char *old, + const char *new, HEADER *h) +{ + int rc = -1; + notmuch_status_t st; + notmuch_filenames_t *ls; + notmuch_message_t *msg; + notmuch_database_t *db = get_db(data, TRUE); + int trans; + + if (!db || !new || !old || (access(new, F_OK) != 0)) + return -1; + + dprint(1, (debugfile, "nm: rename filename, %s -> %s\n", old, new)); + trans = db_trans_begin(data); + if (trans < 0) + return -1; + + dprint(2, (debugfile, "nm: rename: add '%s'\n", new)); + st = notmuch_database_add_message(db, new, &msg); + + if ((st != NOTMUCH_STATUS_SUCCESS) && + (st != NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID)) + { + dprint(1, (debugfile, "nm: failed to add '%s' [st=%d]\n", new, (int) st)); + goto done; + } + + dprint(2, (debugfile, "nm: rename: rem '%s'\n", old)); + st = notmuch_database_remove_message(db, old); + switch (st) + { + case NOTMUCH_STATUS_SUCCESS: + break; + case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID: + dprint(2, (debugfile, "nm: rename: syncing duplicate filename\n")); + notmuch_message_destroy(msg); + msg = NULL; + notmuch_database_find_message_by_filename(db, new, &msg); + + for (ls = notmuch_message_get_filenames(msg); + msg && ls && notmuch_filenames_valid(ls); + notmuch_filenames_move_to_next(ls)) + { + const char *path = notmuch_filenames_get(ls); + char newpath[_POSIX_PATH_MAX]; + + if (strcmp(new, path) == 0) + continue; + + dprint(2, (debugfile, "nm: rename: syncing duplicate: %s\n", path)); + + if (rename_maildir_filename(path, newpath, sizeof(newpath), h) == 0) + { + dprint(2, (debugfile, "nm: rename dup %s -> %s\n", path, newpath)); + notmuch_database_remove_message(db, path); + notmuch_database_add_message(db, newpath, NULL); + } + } + notmuch_message_destroy(msg); + msg = NULL; + notmuch_database_find_message_by_filename(db, new, &msg); + st = NOTMUCH_STATUS_SUCCESS; + break; + default: + dprint(1, (debugfile, "nm: failed to remove '%s' [st=%d]\n", old, (int) st)); + break; + } + + if ((st == NOTMUCH_STATUS_SUCCESS) && h && msg) + { + notmuch_message_maildir_flags_to_tags(msg); + update_header_tags(h, msg); + update_tags(msg, nm_header_get_tags(h)); + } + + rc = 0; +done: + if (msg) + notmuch_message_destroy(msg); + if (trans) + db_trans_end(data); + return rc; +} - if (!is_longrun(data)) - release_db(data); +static unsigned count_query(notmuch_database_t *db, const char *qstr) +{ + unsigned res = 0; + notmuch_query_t *q = notmuch_query_create(db, qstr); - ctx->mtime = time(NULL); + if (q) + { + apply_exclude_tags(q); +#if LIBNOTMUCH_CHECK_VERSION(4,3,0) + if (notmuch_query_count_messages_st(q, &res) != NOTMUCH_STATUS_SUCCESS) + res = 0; /* may not be defined on error */ +#else + res = notmuch_query_count_messages(q); +#endif + notmuch_query_destroy(q); + dprint(1, (debugfile, "nm: count '%s', result=%d\n", qstr, res)); + } + return res; +} - mx_update_context(ctx, ctx->msgcount); - data->oldmsgcount = 0; - dprint(1, (debugfile, "nm: reading messages... done [rc=%d, count=%d]\n", - rc, ctx->msgcount)); - return rc; +char *nm_header_get_folder(HEADER *h) +{ + return (h && h->data) ? ((struct nm_hdrdata *) h->data)->folder : NULL; } -int nm_read_entire_thread(CONTEXT *ctx, HEADER *h) +char *nm_header_get_tags(HEADER *h) { - struct nm_ctxdata *data = get_ctxdata(ctx); - const char *id; - char *qstr = NULL; - notmuch_query_t *q = NULL; - notmuch_database_t *db = NULL; - notmuch_message_t *msg = NULL; - int rc = -1; - - if (!data) - return -1; - if (!(db = get_db(data, FALSE)) || !(msg = get_nm_message(db, h))) - goto done; - - dprint(1, (debugfile, "nm: reading entire-thread messages...[current count=%d]\n", - ctx->msgcount)); - - nm_progress_reset(ctx); - id = notmuch_message_get_thread_id(msg); - if (!id) - goto done; - append_str_item(&qstr, "thread:", 0); - append_str_item(&qstr, id, 0); - - q = notmuch_query_create(db, qstr); - FREE(&qstr); - if (!q) - goto done; - apply_exclude_tags(q); - notmuch_query_set_sort(q, NOTMUCH_SORT_NEWEST_FIRST); - - read_threads_query(ctx, q, 1, 0); - ctx->mtime = time(NULL); - rc = 0; - - if (ctx->msgcount > data->oldmsgcount) - mx_update_context(ctx, ctx->msgcount - data->oldmsgcount); -done: - if (q) - notmuch_query_destroy(q); - if (!is_longrun(data)) - release_db(data); - - if (ctx->msgcount == data->oldmsgcount) - mutt_message (_("No more messages in the thread.")); + return (h && h->data) ? ((struct nm_hdrdata *) h->data)->tags : NULL; +} - data->oldmsgcount = 0; - dprint(1, (debugfile, "nm: reading entire-thread messages... done [rc=%d, count=%d]\n", - rc, ctx->msgcount)); - return rc; +char *nm_header_get_tags_transformed(HEADER *h) +{ + return (h && h->data) ? ((struct nm_hdrdata *) h->data)->tags_transformed : NULL; } -char *nm_uri_from_query(CONTEXT *ctx, char *buf, size_t bufsz) +char *nm_header_get_tag_transformed(char *tag, HEADER *h) { - struct nm_ctxdata *data = get_ctxdata(ctx); - char uri[_POSIX_PATH_MAX + LONG_STRING + 32]; /* path to DB + query + URI "decoration" */ + struct nm_hdrtag *tmp; - if (data) - snprintf(uri, sizeof(uri), "notmuch://%s?query=%s", - get_db_filename(data), buf); - else if (NotmuchDefaultUri) - snprintf(uri, sizeof(uri), "%s?query=%s", NotmuchDefaultUri, buf); - else if (Maildir) - snprintf(uri, sizeof(uri), "notmuch://%s?query=%s", Maildir, buf); - else - return NULL; + if (!h || !h->data) + return NULL; - strncpy(buf, uri, bufsz); - buf[bufsz - 1] = '\0'; + for (tmp = ((struct nm_hdrdata *) h->data)->tag_list; + tmp != NULL; + tmp = tmp->next) + { + if (strcmp(tag, tmp->tag) == 0) + return tmp->transformed; + } - dprint(1, (debugfile, "nm: uri from query '%s'\n", buf)); - return buf; + return NULL; } -/* - * returns message from notmuch database - */ -static notmuch_message_t *get_nm_message(notmuch_database_t *db, HEADER *hdr) +void nm_longrun_init(CONTEXT *ctx, int writable) { - notmuch_message_t *msg = NULL; - char *id = nm_header_get_id(hdr); - - dprint(2, (debugfile, "nm: find message (%s)\n", id)); - - if (id && db) - notmuch_database_find_message(db, id, &msg); + struct nm_ctxdata *data = get_ctxdata(ctx); - return msg; + if (data && get_db(data, writable)) + { + data->longrun = 1; + dprint(2, (debugfile, "nm: long run initialized\n")); + } } -static int update_tags(notmuch_message_t *msg, const char *tags) -{ - char *tag = NULL, *end = NULL, *p; - char *buf = safe_strdup(tags); - - if (!buf) - return -1; - - notmuch_message_freeze(msg); - - for (p = buf; p && *p; p++) { - if (!tag && isspace(*p)) - continue; - if (!tag) - tag = p; /* begin of the tag */ - if (*p == ',' || *p == ' ') - end = p; /* terminate the tag */ - else if (*(p + 1) == '\0') - end = p + 1; /* end of optstr */ - if (!tag || !end) - continue; - if (tag >= end) - break; - - *end = '\0'; - - if (*tag == '-') { - dprint(1, (debugfile, "nm: remove tag: '%s'\n", tag + 1)); - notmuch_message_remove_tag(msg, tag + 1); - } else { - dprint(1, (debugfile, "nm: add tag: '%s'\n", *tag == '+' ? tag + 1 : tag)); - notmuch_message_add_tag(msg, *tag == '+' ? tag + 1 : tag); - } - end = tag = NULL; - } - - notmuch_message_thaw(msg); - FREE(&buf); - return 0; -} - -/* TODO: extract parsing of string to separate function, join - * update_header_tags and update_header_flags, which are given an array of - * tags. */ -static int update_header_flags(CONTEXT *ctx, HEADER *hdr, const char *tags) +void nm_longrun_done(CONTEXT *ctx) { - char *tag = NULL, *end = NULL, *p; - char *buf = safe_strdup(tags); - - if (!buf) - return -1; - - for (p = buf; p && *p; p++) { - if (!tag && isspace(*p)) - continue; - if (!tag) - tag = p; /* begin of the tag */ - if (*p == ',' || *p == ' ') - end = p; /* terminate the tag */ - else if (*(p + 1) == '\0') - end = p + 1; /* end of optstr */ - if (!tag || !end) - continue; - if (tag >= end) - break; - - *end = '\0'; - - if (*tag == '-') { - tag = tag + 1; - if (strcmp(tag, "unread") == 0) - mutt_set_flag (ctx, hdr, MUTT_READ, 1); - else if (strcmp(tag, "replied") == 0) - mutt_set_flag (ctx, hdr, MUTT_REPLIED, 0); - else if (strcmp(tag, "flagged") == 0) - mutt_set_flag (ctx, hdr, MUTT_FLAG, 0); - } else { - tag = *tag == '+' ? tag + 1 : tag; - if (strcmp(tag, "unread") == 0) - mutt_set_flag (ctx, hdr, MUTT_READ, 0); - else if (strcmp(tag, "replied") == 0) - mutt_set_flag (ctx, hdr, MUTT_REPLIED, 1); - else if (strcmp(tag, "flagged") == 0) - mutt_set_flag (ctx, hdr, MUTT_FLAG, 1); - } - end = tag = NULL; - } - - FREE(&buf); - return 0; + struct nm_ctxdata *data = get_ctxdata(ctx); + + if (data && (release_db(data) == 0)) + dprint(2, (debugfile, "nm: long run deinitialized\n")); } -int nm_modify_message_tags(CONTEXT *ctx, HEADER *hdr, char *buf) +void nm_debug_check(CONTEXT *ctx) { - struct nm_ctxdata *data = get_ctxdata(ctx); - notmuch_database_t *db = NULL; - notmuch_message_t *msg = NULL; - int rc = -1; - - if (!buf || !*buf || !data) - return -1; + struct nm_ctxdata *data = get_ctxdata(ctx); + if (!data) + return; - if (!(db = get_db(data, TRUE)) || !(msg = get_nm_message(db, hdr))) - goto done; + if (data->db) + { + dprint(1, (debugfile, "nm: ERROR: db is open, closing\n")); + release_db(data); + } +} - dprint(1, (debugfile, "nm: tags modify: '%s'\n", buf)); +int nm_read_entire_thread(CONTEXT *ctx, HEADER *h) +{ + struct nm_ctxdata *data = get_ctxdata(ctx); + const char *id; + char *qstr = NULL; + notmuch_query_t *q = NULL; + notmuch_database_t *db = NULL; + notmuch_message_t *msg = NULL; + int rc = -1; + + if (!data) + return -1; + if (!(db = get_db(data, FALSE)) || !(msg = get_nm_message(db, h))) + goto done; + + dprint(1, (debugfile, + "nm: reading entire-thread messages...[current count=%d]\n", + ctx->msgcount)); + + progress_reset(ctx); + id = notmuch_message_get_thread_id(msg); + if (!id) + goto done; + append_str_item(&qstr, "thread:", 0); + append_str_item(&qstr, id, 0); + + q = notmuch_query_create(db, qstr); + FREE(&qstr); + if (!q) + goto done; + apply_exclude_tags(q); + notmuch_query_set_sort(q, NOTMUCH_SORT_NEWEST_FIRST); + + read_threads_query(ctx, q, 1, 0); + ctx->mtime = time(NULL); + rc = 0; + + if (ctx->msgcount > data->oldmsgcount) + mx_update_context(ctx, ctx->msgcount - data->oldmsgcount); +done: + if (q) + notmuch_query_destroy(q); + if (!is_longrun(data)) + release_db(data); - update_tags(msg, buf); - update_header_flags(ctx, hdr, buf); - update_header_tags(hdr, msg); - mutt_set_header_color(ctx, hdr); + if (ctx->msgcount == data->oldmsgcount) + mutt_message(_("No more messages in the thread.")); - rc = 0; - hdr->changed = TRUE; -done: - if (!is_longrun(data)) - release_db(data); - if (hdr->changed) - ctx->mtime = time(NULL); - dprint(1, (debugfile, "nm: tags modify done [rc=%d]\n", rc)); - return rc; + data->oldmsgcount = 0; + dprint(1, (debugfile, + "nm: reading entire-thread messages... done [rc=%d, count=%d]\n", + rc, ctx->msgcount)); + return rc; } -static int rename_maildir_filename(const char *old, char *newpath, size_t newsz, HEADER *h) +char *nm_uri_from_query(CONTEXT *ctx, char *buf, size_t bufsz) { - char filename[_POSIX_PATH_MAX]; - char suffix[_POSIX_PATH_MAX]; - char folder[_POSIX_PATH_MAX]; - char *p; - - strfcpy(folder, old, sizeof(folder)); - p = strrchr(folder, '/'); - if (p) { - *p = '\0'; - p++; - } else { - p = folder; - } + struct nm_ctxdata *data = get_ctxdata(ctx); + char uri[_POSIX_PATH_MAX + LONG_STRING + 32]; /* path to DB + query + URI "decoration" */ - strfcpy(filename, p, sizeof(filename)); + if (data) + snprintf(uri, sizeof(uri), "notmuch://%s?query=%s", get_db_filename(data), buf); + else if (NotmuchDefaultUri) + snprintf(uri, sizeof(uri), "%s?query=%s", NotmuchDefaultUri, buf); + else if (Maildir) + snprintf(uri, sizeof(uri), "notmuch://%s?query=%s", Maildir, buf); + else + return NULL; - /* remove (new,cur,...) from folder path */ - p = strrchr(folder, '/'); - if (p) - *p = '\0'; + strncpy(buf, uri, bufsz); + buf[bufsz - 1] = '\0'; - /* remove old flags from filename */ - if ((p = strchr(filename, ':'))) - *p = '\0'; + dprint(1, (debugfile, "nm: uri from query '%s'\n", buf)); + return buf; +} - /* compose new flags */ - maildir_flags(suffix, sizeof(suffix), h); +int nm_modify_message_tags(CONTEXT *ctx, HEADER *hdr, char *buf) +{ + struct nm_ctxdata *data = get_ctxdata(ctx); + notmuch_database_t *db = NULL; + notmuch_message_t *msg = NULL; + int rc = -1; - snprintf(newpath, newsz, "%s/%s/%s%s", - folder, - (h->read || h->old) ? "cur" : "new", - filename, - suffix); + if (!buf || !*buf || !data) + return -1; - if (strcmp(old, newpath) == 0) - return 1; + if (!(db = get_db(data, TRUE)) || !(msg = get_nm_message(db, hdr))) + goto done; - if (rename(old, newpath) != 0) { - dprint(1, (debugfile, "nm: rename(2) failed %s -> %s\n", old, newpath)); - return -1; - } + dprint(1, (debugfile, "nm: tags modify: '%s'\n", buf)); - return 0; -} + update_tags(msg, buf); + update_header_flags(ctx, hdr, buf); + update_header_tags(hdr, msg); + mutt_set_header_color(ctx, hdr); -static int remove_filename(struct nm_ctxdata *data, const char *path) -{ - notmuch_status_t st; - notmuch_filenames_t *ls; - notmuch_message_t *msg = NULL; - notmuch_database_t *db = get_db(data, TRUE); - int trans; - - dprint(2, (debugfile, "nm: remove filename '%s'\n", path)); - - if (!db) - return -1; - st = notmuch_database_find_message_by_filename(db, path, &msg); - if (st || !msg) - return -1; - trans = db_trans_begin(data); - if (trans < 0) - return -1; - - /* - * note that unlink() is probably unnecessary here, it's already removed - * by mh_sync_mailbox_message(), but for sure... - */ - st = notmuch_database_remove_message(db, path); - switch (st) { - case NOTMUCH_STATUS_SUCCESS: - dprint(2, (debugfile, "nm: remove success, call unlink\n")); - unlink(path); - break; - case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID: - dprint(2, (debugfile, "nm: remove success (duplicate), call unlink\n")); - unlink(path); - for (ls = notmuch_message_get_filenames(msg); - ls && notmuch_filenames_valid(ls); - notmuch_filenames_move_to_next(ls)) { - - path = notmuch_filenames_get(ls); - - dprint(2, (debugfile, "nm: remove duplicate: '%s'\n", path)); - unlink(path); - notmuch_database_remove_message(db, path); - } - break; - default: - dprint(1, (debugfile, "nm: failed to remove '%s' [st=%d]\n", path, (int) st)); - break; - } - - notmuch_message_destroy(msg); - if (trans) - db_trans_end(data); - return 0; -} - -static int rename_filename(struct nm_ctxdata *data, - const char *old, const char *new, HEADER *h) -{ - int rc = -1; - notmuch_status_t st; - notmuch_filenames_t *ls; - notmuch_message_t *msg; - notmuch_database_t *db = get_db(data, TRUE); - int trans; - - if (!db || !new || !old || access(new, F_OK) != 0) - return -1; - - dprint(1, (debugfile, "nm: rename filename, %s -> %s\n", old, new)); - trans = db_trans_begin(data); - if (trans < 0) - return -1; - - dprint(2, (debugfile, "nm: rename: add '%s'\n", new)); - st = notmuch_database_add_message(db, new, &msg); - - if (st != NOTMUCH_STATUS_SUCCESS && - st != NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID) { - dprint(1, (debugfile, "nm: failed to add '%s' [st=%d]\n", new, (int) st)); - goto done; - } - - dprint(2, (debugfile, "nm: rename: rem '%s'\n", old)); - st = notmuch_database_remove_message(db, old); - switch (st) { - case NOTMUCH_STATUS_SUCCESS: - break; - case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID: - dprint(2, (debugfile, "nm: rename: syncing duplicate filename\n")); - notmuch_message_destroy(msg); - msg = NULL; - notmuch_database_find_message_by_filename(db, new, &msg); - - for (ls = notmuch_message_get_filenames(msg); - msg && ls && notmuch_filenames_valid(ls); - notmuch_filenames_move_to_next(ls)) { - - const char *path = notmuch_filenames_get(ls); - char newpath[_POSIX_PATH_MAX]; - - if (strcmp(new, path) == 0) - continue; - - dprint(2, (debugfile, "nm: rename: syncing duplicate: %s\n", path)); - - if (rename_maildir_filename(path, newpath, sizeof(newpath), h) == 0) { - dprint(2, (debugfile, "nm: rename dup %s -> %s\n", path, newpath)); - notmuch_database_remove_message(db, path); - notmuch_database_add_message(db, newpath, NULL); - } - } - notmuch_message_destroy(msg); - msg = NULL; - notmuch_database_find_message_by_filename(db, new, &msg); - st = NOTMUCH_STATUS_SUCCESS; - break; - default: - dprint(1, (debugfile, "nm: failed to remove '%s' [st=%d]\n", - old, (int) st)); - break; - } - - if (st == NOTMUCH_STATUS_SUCCESS && h && msg) { - notmuch_message_maildir_flags_to_tags(msg); - update_header_tags(h, msg); - update_tags(msg, nm_header_get_tags(h)); - } - - rc = 0; + rc = 0; + hdr->changed = TRUE; done: - if (msg) - notmuch_message_destroy(msg); - if (trans) - db_trans_end(data); - return rc; + if (!is_longrun(data)) + release_db(data); + if (hdr->changed) + ctx->mtime = time(NULL); + dprint(1, (debugfile, "nm: tags modify done [rc=%d]\n", rc)); + return rc; } -int nm_update_filename(CONTEXT *ctx, const char *old, const char *new, HEADER *h) +int nm_update_filename(CONTEXT *ctx, const char *old, const char *new, + HEADER *h) { - char buf[PATH_MAX]; - int rc; - struct nm_ctxdata *data = get_ctxdata(ctx); + char buf[PATH_MAX]; + int rc; + struct nm_ctxdata *data = get_ctxdata(ctx); - if (!data || !new) - return -1; + if (!data || !new) + return -1; - if (!old && h && h->data) { - nm_header_get_fullpath(h, buf, sizeof(buf)); - old = buf; - } + if (!old && h && h->data) + { + header_get_fullpath(h, buf, sizeof(buf)); + old = buf; + } - rc = rename_filename(data, old, new, h); + rc = rename_filename(data, old, new, h); - if (!is_longrun(data)) - release_db(data); - ctx->mtime = time(NULL); - return rc; + if (!is_longrun(data)) + release_db(data); + ctx->mtime = time(NULL); + return rc; } -int nm_sync_mailbox(CONTEXT *ctx, int *index_hint) +int nm_nonctx_get_count(char *path, int *all, int *new) { - struct nm_ctxdata *data = get_ctxdata(ctx); - int i, rc = 0; - char msgbuf[STRING]; - progress_t progress; - char *uri = ctx->path; - int changed = 0; + struct uri_tag *query_items = NULL, *item; + char *db_filename = NULL, *db_query = NULL; + notmuch_database_t *db = NULL; + int rc = -1, dflt = 0; + + dprint(1, (debugfile, "nm: count\n")); + + if (url_parse_query(path, &db_filename, &query_items)) + { + mutt_error(_("failed to parse notmuch uri: %s"), path); + goto done; + } + if (!query_items) + goto done; + + for (item = query_items; item; item = item->next) + { + if (item->value && (strcmp(item->name, "query") == 0)) + { + db_query = item->value; + break; + } + } + + if (!db_query) + goto done; + + if (!db_filename) + { + if (NotmuchDefaultUri) + { + if (strncmp(NotmuchDefaultUri, "notmuch://", 10) == 0) + db_filename = NotmuchDefaultUri + 10; + else + db_filename = NotmuchDefaultUri; + } + else if (Maildir) + db_filename = Maildir; + dflt = 1; + } + + /* don't be verbose about connection, as we're called from + * sidebar/buffy very often */ + db = do_database_open(db_filename, FALSE, FALSE); + if (!db) + goto done; + + /* all emails */ + if (all) + *all = count_query(db, db_query); + + /* new messages */ + if (new) + { + char *qstr; + + safe_asprintf(&qstr, "( %s ) tag:%s", db_query, NotmuchUnreadTag); + *new = count_query(db, qstr); + FREE(&qstr); + } + + rc = 0; +done: + if (db) + { +#ifdef NOTMUCH_API_3 + notmuch_database_destroy(db); +#else + notmuch_database_close(db); +#endif + dprint(1, (debugfile, "nm: count close DB\n")); + } + if (!dflt) + FREE(&db_filename); + url_free_tags(query_items); - if (!data) - return -1; + dprint(1, (debugfile, "nm: count done [rc=%d]\n", rc)); + return rc; +} - dprint(1, (debugfile, "nm: sync start ...\n")); +char *nm_get_description(CONTEXT *ctx) +{ + BUFFY *p; - if (!ctx->quiet) { - /* all is in this function so we don't use data->progress here */ - snprintf(msgbuf, sizeof (msgbuf), _("Writing %s..."), ctx->path); - mutt_progress_init(&progress, msgbuf, MUTT_PROGRESS_MSG, - WriteInc, ctx->msgcount); - } + for (p = VirtIncoming; p; p = p->next) + if (p->desc && (strcmp(p->path, ctx->path) == 0)) + return p->desc; - for (i = 0; i < ctx->msgcount; i++) { - char old[_POSIX_PATH_MAX], new[_POSIX_PATH_MAX]; - HEADER *h = ctx->hdrs[i]; - struct nm_hdrdata *hd = h->data; + return NULL; +} - if (!ctx->quiet) - mutt_progress_update(&progress, i, -1); +int nm_description_to_path(const char *desc, char *buf, size_t bufsz) +{ + BUFFY *p; - *old = *new = '\0'; + if (!desc || !buf || !bufsz) + return -EINVAL; - if (hd->oldpath) { - strncpy(old, hd->oldpath, sizeof(old)); - old[sizeof(old) - 1] = '\0'; - dprint(2, (debugfile, "nm: fixing obsolete path '%s'\n", old)); - } else - nm_header_get_fullpath(h, old, sizeof(old)); + for (p = VirtIncoming; p; p = p->next) + if (p->desc && (strcmp(desc, p->desc) == 0)) + { + strncpy(buf, p->path, bufsz); + buf[bufsz - 1] = '\0'; + return 0; + } - ctx->path = hd->folder; - ctx->magic = hd->magic; -#if USE_HCACHE - rc = mh_sync_mailbox_message(ctx, i, NULL); -#else - rc = mh_sync_mailbox_message(ctx, i); -#endif - ctx->path = uri; - ctx->magic = MUTT_NOTMUCH; + return -1; +} - if (rc) - break; +int nm_record_message(CONTEXT *ctx, char *path, HEADER *h) +{ + notmuch_database_t *db; + notmuch_status_t st; + notmuch_message_t *msg = NULL; + int rc = -1, trans; + struct nm_ctxdata *data = get_ctxdata(ctx); + + if (!path || !data || (access(path, F_OK) != 0)) + return 0; + db = get_db(data, TRUE); + if (!db) + return -1; + + dprint(1, (debugfile, "nm: record message: %s\n", path)); + trans = db_trans_begin(data); + if (trans < 0) + goto done; + + st = notmuch_database_add_message(db, path, &msg); + + if ((st != NOTMUCH_STATUS_SUCCESS) && + (st != NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID)) + { + dprint(1, (debugfile, "nm: failed to add '%s' [st=%d]\n", path, (int) st)); + goto done; + } + + if (st == NOTMUCH_STATUS_SUCCESS && msg) + { + notmuch_message_maildir_flags_to_tags(msg); + if (h) + update_tags(msg, nm_header_get_tags(h)); + if (NotmuchRecordTags) + update_tags(msg, NotmuchRecordTags); + } + + rc = 0; +done: + if (msg) + notmuch_message_destroy(msg); + if (trans == 1) + db_trans_end(data); + if (!is_longrun(data)) + release_db(data); + return rc; +} - if (!h->deleted) - nm_header_get_fullpath(h, new, sizeof(new)); +int nm_get_all_tags(CONTEXT *ctx, char **tag_list, int *tag_count) +{ + struct nm_ctxdata *data = get_ctxdata(ctx); + notmuch_database_t *db = NULL; + notmuch_tags_t *tags = NULL; + int rc = -1; - if (h->deleted || strcmp(old, new) != 0) { - if (h->deleted && remove_filename(data, old) == 0) - changed = 1; - else if (*new && *old && rename_filename(data, old, new, h) == 0) - changed = 1; - } + if (!data) + return -1; - FREE(&hd->oldpath); - } + if (!(db = get_db(data, FALSE)) || + !(tags = notmuch_database_get_all_tags(db))) + goto done; - ctx->path = uri; - ctx->magic = MUTT_NOTMUCH; + *tag_count = 0; + dprint(1, (debugfile, "nm: get all tags\n")); - if (!is_longrun(data)) - release_db(data); - if (changed) - ctx->mtime = time(NULL); + while (notmuch_tags_valid(tags)) + { + if (tag_list != NULL) + { + tag_list[*tag_count] = safe_strdup(notmuch_tags_get(tags)); + } + (*tag_count)++; + notmuch_tags_move_to_next(tags); + } - dprint(1, (debugfile, "nm: .... sync done [rc=%d]\n", rc)); - return rc; -} + rc = 0; +done: + if (tags) + notmuch_tags_destroy(tags); -static unsigned count_query(notmuch_database_t *db, const char *qstr) -{ - unsigned res = 0; - notmuch_query_t *q = notmuch_query_create(db, qstr); + if (!is_longrun(data)) + release_db(data); - if (q) { - apply_exclude_tags(q); -#if LIBNOTMUCH_CHECK_VERSION(4,3,0) - if (notmuch_query_count_messages_st (q, &res) != NOTMUCH_STATUS_SUCCESS) - res = 0; /* may not be defined on error */ -#else - res = notmuch_query_count_messages(q); -#endif - notmuch_query_destroy(q); - dprint(1, (debugfile, "nm: count '%s', result=%d\n", qstr, res)); - } - return res; + dprint(1, (debugfile, "nm: get all tags done [rc=%d tag_count=%u]\n", rc, + *tag_count)); + return rc; } -int nm_nonctx_get_count(char *path, int *all, int *new) + +static int nm_open_mailbox(CONTEXT *ctx) { - struct uri_tag *query_items = NULL, *item; - char *db_filename = NULL, *db_query = NULL; - notmuch_database_t *db = NULL; - int rc = -1, dflt = 0; - - dprint(1, (debugfile, "nm: count\n")); - - if (url_parse_query(path, &db_filename, &query_items)) { - mutt_error(_("failed to parse notmuch uri: %s"), path); - goto done; - } - if (!query_items) - goto done; - - for (item = query_items; item; item = item->next) { - if (item->value && strcmp(item->name, "query") == 0) { - db_query = item->value; - break; - } - } - - if (!db_query) - goto done; - - if (!db_filename) { - if (NotmuchDefaultUri) { - if (strncmp(NotmuchDefaultUri, "notmuch://", 10) == 0) - db_filename = NotmuchDefaultUri + 10; - else - db_filename = NotmuchDefaultUri; - } else if (Maildir) - db_filename = Maildir; - dflt = 1; - } - - /* don't be verbose about connection, as we're called from - * sidebar/buffy very often */ - db = do_database_open(db_filename, FALSE, FALSE); - if (!db) - goto done; - - /* all emails */ - if (all) - *all = count_query(db, db_query); - - /* new messages */ - if (new) { - char *qstr; - - safe_asprintf(&qstr, "( %s ) tag:%s", - db_query, NotmuchUnreadTag); - *new = count_query(db, qstr); - FREE(&qstr); - } - - rc = 0; -done: - if (db) { -#ifdef NOTMUCH_API_3 - notmuch_database_destroy(db); -#else - notmuch_database_close(db); -#endif - dprint(1, (debugfile, "nm: count close DB\n")); - } - if (!dflt) - FREE(&db_filename); - url_free_tags(query_items); + notmuch_query_t *q; + struct nm_ctxdata *data; + int rc = -1; - dprint(1, (debugfile, "nm: count done [rc=%d]\n", rc)); - return rc; -} + if (init_context(ctx) != 0) + return -1; -char *nm_get_description(CONTEXT *ctx) -{ - BUFFY *p; + data = get_ctxdata(ctx); + if (!data) + return -1; - for (p = VirtIncoming; p; p = p->next) - if (p->desc && strcmp(p->path, ctx->path) == 0) - return p->desc; + dprint(1, (debugfile, "nm: reading messages...[current count=%d]\n", + ctx->msgcount)); - return NULL; -} + progress_reset(ctx); -int nm_description_to_path(const char *desc, char *buf, size_t bufsz) -{ - BUFFY *p; + q = get_query(data, FALSE); + if (q) + { + switch (get_query_type(data)) + { + case NM_QUERY_TYPE_MESGS: + read_mesgs_query(ctx, q, 0); + break; + case NM_QUERY_TYPE_THREADS: + read_threads_query(ctx, q, 0, get_limit(data)); + break; + } + notmuch_query_destroy(q); + rc = 0; + } - if (!desc || !buf || !bufsz) - return -EINVAL; + if (!is_longrun(data)) + release_db(data); - for (p = VirtIncoming; p; p = p->next) - if (p->desc && strcmp(desc, p->desc) == 0) { - strncpy(buf, p->path, bufsz); - buf[bufsz - 1] = '\0'; - return 0; - } + ctx->mtime = time(NULL); - return -1; + mx_update_context(ctx, ctx->msgcount); + data->oldmsgcount = 0; + + dprint(1, (debugfile, "nm: reading messages... done [rc=%d, count=%d]\n", rc, + ctx->msgcount)); + return rc; } -/* - * returns header from mutt context - */ -static HEADER *get_mutt_header(CONTEXT *ctx, notmuch_message_t *msg) +static int nm_close_mailbox(CONTEXT *ctx) { - char *mid; - const char *id; - HEADER *h; - - if (!ctx || !msg) - return NULL; - - id = notmuch_message_get_message_id(msg); - if (!id) - return NULL; + int i; - dprint(2, (debugfile, "nm: mutt header, id='%s'\n", id)); + if (!ctx || (ctx->magic != MUTT_NOTMUCH)) + return -1; - if (!ctx->id_hash) { - dprint(2, (debugfile, "nm: init hash\n")); - ctx->id_hash = mutt_make_id_hash(ctx); - if (!ctx->id_hash) - return NULL; - } + for (i = 0; i < ctx->msgcount; i++) + { + HEADER *h = ctx->hdrs[i]; - mid = nm2mutt_message_id( id ); - dprint(2, (debugfile, "nm: mutt id='%s'\n", mid)); + if (h) + { + free_hdrdata(h->data); + h->data = NULL; + } + } - h = hash_find(ctx->id_hash, mid); - FREE(&mid); - return h; + free_ctxdata(ctx->data); + ctx->data = NULL; + return 0; } -static int nm_check_database(CONTEXT *ctx, int *index_hint) +static int nm_check_mailbox(CONTEXT *ctx, int *index_hint) { - struct nm_ctxdata *data = get_ctxdata(ctx); - time_t mtime = 0; - notmuch_query_t *q; - notmuch_messages_t *msgs; - int i, limit, occult = 0, new_flags = 0; + struct nm_ctxdata *data = get_ctxdata(ctx); + time_t mtime = 0; + notmuch_query_t *q; + notmuch_messages_t *msgs; + int i, limit, occult = 0, new_flags = 0; - if (!data || get_database_mtime(data, &mtime) != 0) - return -1; + if (!data || (get_database_mtime(data, &mtime) != 0)) + return -1; - if (ctx->mtime >= mtime) { - dprint(2, (debugfile, "nm: check unnecessary (db=%d ctx=%d)\n", mtime, ctx->mtime)); - return 0; - } + if (ctx->mtime >= mtime) + { + dprint(2, (debugfile, "nm: check unnecessary (db=%d ctx=%d)\n", mtime, + ctx->mtime)); + return 0; + } - dprint(1, (debugfile, "nm: checking (db=%d ctx=%d)\n", mtime, ctx->mtime)); + dprint(1, (debugfile, "nm: checking (db=%d ctx=%d)\n", mtime, ctx->mtime)); - q = get_query(data, FALSE); - if (!q) - goto done; + q = get_query(data, FALSE); + if (!q) + goto done; - dprint(1, (debugfile, "nm: start checking (count=%d)\n", ctx->msgcount)); - data->oldmsgcount = ctx->msgcount; - data->noprogress = 1; + dprint(1, (debugfile, "nm: start checking (count=%d)\n", ctx->msgcount)); + data->oldmsgcount = ctx->msgcount; + data->noprogress = 1; - for (i = 0; i < ctx->msgcount; i++) - ctx->hdrs[i]->active = 0; + for (i = 0; i < ctx->msgcount; i++) + ctx->hdrs[i]->active = 0; - limit = get_limit(data); + limit = get_limit(data); #if LIBNOTMUCH_CHECK_VERSION(4,3,0) - if (notmuch_query_search_messages_st (q, &msgs) != NOTMUCH_STATUS_SUCCESS) - goto done; + if (notmuch_query_search_messages_st(q, &msgs) != NOTMUCH_STATUS_SUCCESS) + goto done; #else - msgs = notmuch_query_search_messages(q); + msgs = notmuch_query_search_messages(q); #endif - for (i = 0; - notmuch_messages_valid(msgs) && (limit == 0 || i < limit); - notmuch_messages_move_to_next(msgs), i++) { - - char old[_POSIX_PATH_MAX]; - const char *new; - - notmuch_message_t *m = notmuch_messages_get(msgs); - HEADER *h = get_mutt_header(ctx, m); - - if (!h) { - /* new email */ - append_message(ctx, NULL, m, 0); - notmuch_message_destroy(m); - continue; - } - - /* message already exists, merge flags */ - h->active = 1; - - /* check to see if the message has moved to a different - * subdirectory. If so, update the associated filename. - */ - new = get_message_last_filename(m); - nm_header_get_fullpath(h, old, sizeof(old)); - - if (mutt_strcmp(old, new) != 0) - update_message_path(h, new); - - if (!h->changed) { - /* if the user hasn't modified the flags on - * this message, update the flags we just - * detected. - */ - HEADER tmp; - memset(&tmp, 0, sizeof(tmp)); - maildir_parse_flags(&tmp, new); - maildir_update_flags(ctx, h, &tmp); - } - - if (update_header_tags(h, m) == 0) - new_flags++; - - notmuch_message_destroy(m); - } - - for (i = 0; i < ctx->msgcount; i++) { - if (ctx->hdrs[i]->active == 0) { - occult = 1; - break; - } - } - - if (ctx->msgcount > data->oldmsgcount) - mx_update_context(ctx, ctx->msgcount - data->oldmsgcount); + for (i = 0; notmuch_messages_valid(msgs) && ((limit == 0) || (i < limit)); + notmuch_messages_move_to_next(msgs), i++) + { + char old[_POSIX_PATH_MAX]; + const char *new; + + notmuch_message_t *m = notmuch_messages_get(msgs); + HEADER *h = get_mutt_header(ctx, m); + + if (!h) + { + /* new email */ + append_message(ctx, NULL, m, 0); + notmuch_message_destroy(m); + continue; + } + + /* message already exists, merge flags */ + h->active = 1; + + /* check to see if the message has moved to a different + * subdirectory. If so, update the associated filename. + */ + new = get_message_last_filename(m); + header_get_fullpath(h, old, sizeof(old)); + + if (mutt_strcmp(old, new) != 0) + update_message_path(h, new); + + if (!h->changed) + { + /* if the user hasn't modified the flags on + * this message, update the flags we just + * detected. + */ + HEADER tmp; + memset(&tmp, 0, sizeof(tmp)); + maildir_parse_flags(&tmp, new); + maildir_update_flags(ctx, h, &tmp); + } + + if (update_header_tags(h, m) == 0) + new_flags++; + + notmuch_message_destroy(m); + } + + for (i = 0; i < ctx->msgcount; i++) + { + if (ctx->hdrs[i]->active == 0) + { + occult = 1; + break; + } + } + + if (ctx->msgcount > data->oldmsgcount) + mx_update_context(ctx, ctx->msgcount - data->oldmsgcount); done: - if (q) - notmuch_query_destroy(q); + if (q) + notmuch_query_destroy(q); - if (!is_longrun(data)) - release_db(data); + if (!is_longrun(data)) + release_db(data); - ctx->mtime = time(NULL); + ctx->mtime = time(NULL); - dprint(1, (debugfile, "nm: ... check done [count=%d, new_flags=%d, occult=%d]\n", - ctx->msgcount, new_flags, occult)); + dprint(1, (debugfile, "nm: ... check done [count=%d, new_flags=%d, occult=%d]\n", + ctx->msgcount, new_flags, occult)); - return occult ? MUTT_REOPENED : - ctx->msgcount > data->oldmsgcount ? MUTT_NEW_MAIL : - new_flags ? MUTT_FLAGS : 0; + return occult ? MUTT_REOPENED : (ctx->msgcount > data->oldmsgcount) ? + MUTT_NEW_MAIL : + new_flags ? MUTT_FLAGS : 0; } -int nm_record_message(CONTEXT *ctx, char *path, HEADER *h) +static int nm_sync_mailbox(CONTEXT *ctx, int *index_hint) { - notmuch_database_t *db; - notmuch_status_t st; - notmuch_message_t *msg = NULL; - int rc = -1, trans; - struct nm_ctxdata *data = get_ctxdata(ctx); - - if (!path || !data || access(path, F_OK) != 0) - return 0; - db = get_db(data, TRUE); - if (!db) - return -1; - - dprint(1, (debugfile, "nm: record message: %s\n", path)); - trans = db_trans_begin(data); - if (trans < 0) - goto done; - - st = notmuch_database_add_message(db, path, &msg); - - if (st != NOTMUCH_STATUS_SUCCESS && - st != NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID) { - dprint(1, (debugfile, "nm: failed to add '%s' [st=%d]\n", path, (int) st)); - goto done; - } - - if (st == NOTMUCH_STATUS_SUCCESS && msg) { - notmuch_message_maildir_flags_to_tags(msg); - if (h) - update_tags(msg, nm_header_get_tags(h)); - if (NotmuchRecordTags) - update_tags(msg, NotmuchRecordTags); - } - - rc = 0; -done: - if (msg) - notmuch_message_destroy(msg); - if (trans == 1) - db_trans_end(data); - if (!is_longrun(data)) - release_db(data); - return rc; -} + struct nm_ctxdata *data = get_ctxdata(ctx); + int i, rc = 0; + char msgbuf[STRING]; + progress_t progress; + char *uri = ctx->path; + int changed = 0; -/* - * Fill a list with all notmuch tags. - * - * If tag_list is NULL, just count the tags. - */ -int nm_get_all_tags(CONTEXT *ctx, char **tag_list, int *tag_count) -{ - struct nm_ctxdata *data = get_ctxdata(ctx); - notmuch_database_t *db = NULL; - notmuch_tags_t *tags = NULL; - int rc = -1; + if (!data) + return -1; - if (!data) - return -1; + dprint(1, (debugfile, "nm: sync start ...\n")); - if (!(db = get_db(data, FALSE)) || - !(tags = notmuch_database_get_all_tags(db))) - goto done; + if (!ctx->quiet) + { + /* all is in this function so we don't use data->progress here */ + snprintf(msgbuf, sizeof(msgbuf), _("Writing %s..."), ctx->path); + mutt_progress_init(&progress, msgbuf, MUTT_PROGRESS_MSG, WriteInc, + ctx->msgcount); + } - *tag_count = 0; - dprint(1, (debugfile, "nm: get all tags\n")); + for (i = 0; i < ctx->msgcount; i++) + { + char old[_POSIX_PATH_MAX], new[_POSIX_PATH_MAX]; + HEADER *h = ctx->hdrs[i]; + struct nm_hdrdata *hd = h->data; - while (notmuch_tags_valid(tags)) { - if (tag_list != NULL) { - tag_list[*tag_count] = safe_strdup(notmuch_tags_get(tags)); - } - (*tag_count)++; - notmuch_tags_move_to_next(tags); - } + if (!ctx->quiet) + mutt_progress_update(&progress, i, -1); - rc = 0; -done: - if (tags) - notmuch_tags_destroy(tags); + *old = *new = '\0'; + + if (hd->oldpath) + { + strncpy(old, hd->oldpath, sizeof(old)); + old[sizeof(old) - 1] = '\0'; + dprint(2, (debugfile, "nm: fixing obsolete path '%s'\n", old)); + } + else + header_get_fullpath(h, old, sizeof(old)); + + ctx->path = hd->folder; + ctx->magic = hd->magic; +#if USE_HCACHE + rc = mh_sync_mailbox_message(ctx, i, NULL); +#else + rc = mh_sync_mailbox_message(ctx, i); +#endif + ctx->path = uri; + ctx->magic = MUTT_NOTMUCH; - if (!is_longrun(data)) - release_db(data); + if (rc) + break; - dprint(1, (debugfile, "nm: get all tags done [rc=%d tag_count=%u]\n", rc, - *tag_count)); - return rc; + if (!h->deleted) + header_get_fullpath(h, new, sizeof(new)); + + if (h->deleted || (strcmp(old, new) != 0)) + { + if (h->deleted && (remove_filename(data, old) == 0)) + changed = 1; + else if (*new && *old && (rename_filename(data, old, new, h) == 0)) + changed = 1; + } + + FREE(&hd->oldpath); + } + + ctx->path = uri; + ctx->magic = MUTT_NOTMUCH; + + if (!is_longrun(data)) + release_db(data); + if (changed) + ctx->mtime = time(NULL); + + dprint(1, (debugfile, "nm: .... sync done [rc=%d]\n", rc)); + return rc; } -static int nm_open_message (CONTEXT *ctx, MESSAGE *msg, int msgno) +static int nm_open_message(CONTEXT *ctx, MESSAGE *msg, int msgno) { - if (!ctx || !msg) - return 1; - HEADER *cur = ctx->hdrs[msgno]; - char *folder = ctx->path; - char path[_POSIX_PATH_MAX]; - folder = nm_header_get_folder(cur); + if (!ctx || !msg) + return 1; + HEADER *cur = ctx->hdrs[msgno]; + char *folder = ctx->path; + char path[_POSIX_PATH_MAX]; + folder = nm_header_get_folder(cur); - snprintf (path, sizeof (path), "%s/%s", folder, cur->path); + snprintf(path, sizeof(path), "%s/%s", folder, cur->path); - msg->fp = fopen (path, "r"); - if ((msg->fp == NULL) && (errno == ENOENT) && ((ctx->magic == MUTT_MAILDIR) || (ctx->magic == MUTT_NOTMUCH))) - msg->fp = maildir_open_find_message (folder, cur->path, NULL); + msg->fp = fopen(path, "r"); + if ((msg->fp == NULL) && (errno == ENOENT) && + ((ctx->magic == MUTT_MAILDIR) || (ctx->magic == MUTT_NOTMUCH))) + msg->fp = maildir_open_find_message(folder, cur->path, NULL); - dprint(1, (debugfile, "%s\n", __func__)); - return 0; + dprint(1, (debugfile, "%s\n", __func__)); + return 0; } -static int nm_close_message (CONTEXT *ctx, MESSAGE *msg) +static int nm_close_message(CONTEXT *ctx, MESSAGE *msg) { - if (!msg) - return 1; - safe_fclose (&(msg->fp)); - return 0; + if (!msg) + return 1; + safe_fclose(&(msg->fp)); + return 0; } -static int nm_commit_message (CONTEXT *ctx, MESSAGE *msg) +static int nm_commit_message(CONTEXT *ctx, MESSAGE *msg) { - mutt_perror _("Can't write to virtual folder."); - return 1; + mutt_error(_("Can't write to virtual folder.")); + return -1; } -struct mx_ops mx_notmuch_ops = { - .open = nm_read_query, /* calls init_context() */ - .open_append = NULL, - .close = deinit_context, - .check = nm_check_database, - .sync = nm_sync_mailbox, - .open_msg = nm_open_message, - .close_msg = nm_close_message, - .commit_msg = nm_commit_message, - .open_new_msg = NULL + +/** + * struct mx_notmuch_ops - Mailbox API + * + * These functions are common to all mailbox types. + */ +struct mx_ops mx_notmuch_ops = +{ + .open = nm_open_mailbox, /* calls init_context() */ + .open_append = NULL, + .close = nm_close_mailbox, + .check = nm_check_mailbox, + .sync = nm_sync_mailbox, + .open_msg = nm_open_message, + .close_msg = nm_close_message, + .commit_msg = nm_commit_message, + .open_new_msg = NULL }; diff --git a/mutt_notmuch.h b/mutt_notmuch.h index 8267b1fd1..91814d49a 100644 --- a/mutt_notmuch.h +++ b/mutt_notmuch.h @@ -1,35 +1,46 @@ /* * Copyright (C) 2011 Karel Zak + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. */ -#ifndef _MUTT_NOTMUCH_H_ -#define _MUTT_NOTMUCH_H_ 1 -int nm_read_query(CONTEXT *ctx); -int nm_read_entire_thread(CONTEXT *ctx, HEADER *h); +#ifndef _MUTT_NOTMUCH_H +#define _MUTT_NOTMUCH_H + +int nm_read_entire_thread(CONTEXT *ctx, HEADER *h); -int nm_sync(CONTEXT * ctx, int *index_hint); char *nm_header_get_folder(HEADER *h); -int nm_header_get_magic(HEADER *h); -char *nm_header_get_fullpath(HEADER *h, char *buf, size_t bufsz); -int nm_update_filename(CONTEXT *ctx, const char *o, const char *n, HEADER *h); +int nm_update_filename(CONTEXT *ctx, const char *old, const char *new, HEADER *h); char *nm_uri_from_query(CONTEXT *ctx, char *buf, size_t bufsz); -int nm_modify_message_tags(CONTEXT *ctx, HEADER *hdr, char *tags); +int nm_modify_message_tags(CONTEXT *ctx, HEADER *hdr, char *buf); -void nm_longrun_init(CONTEXT *cxt, int writable); -void nm_longrun_done(CONTEXT *cxt); +void nm_longrun_init(CONTEXT *ctx, int writable); +void nm_longrun_done(CONTEXT *ctx); char *nm_get_description(CONTEXT *ctx); -int nm_description_to_path(const char *desc, char *buf, size_t bufsz); +int nm_description_to_path(const char *desc, char *buf, size_t bufsz); -int nm_record_message(CONTEXT *ctx, char *path, HEADER *h); +int nm_record_message(CONTEXT *ctx, char *path, HEADER *h); -void nm_debug_check(CONTEXT *ctx); -int nm_get_all_tags(CONTEXT *ctx, char **tag_list, int *tag_count); +void nm_debug_check(CONTEXT *ctx); +int nm_get_all_tags(CONTEXT *ctx, char **tag_list, int *tag_count); /* * functions usable outside notmuch CONTEXT */ -int nm_nonctx_get_count(char *path, int *all, int *new); +int nm_nonctx_get_count(char *path, int *all, int *new); char *nm_header_get_tag_transformed(char *tag, HEADER *h); char *nm_header_get_tags_transformed(HEADER *h); @@ -37,4 +48,4 @@ char *nm_header_get_tags(HEADER *h); extern struct mx_ops mx_notmuch_ops; -#endif /* _MUTT_NOTMUCH_H_ */ +#endif /* _MUTT_NOTMUCH_H */