]> granicus.if.org Git - neomutt/commitdiff
add basic functions
authorKarel Zak <kzak@redhat.com>
Wed, 14 Dec 2011 15:30:43 +0000 (16:30 +0100)
committerRichard Russon <rich@flatcap.org>
Mon, 4 Apr 2016 15:30:04 +0000 (16:30 +0100)
Signed-off-by: Karel Zak <kzak@redhat.com>
mutt.h
mutt_notmuch.c
mutt_notmuch.h

diff --git a/mutt.h b/mutt.h
index d583c99145694355b4e711af48f67215194e4afe..7a49a047ba0eb8a607b97ac83c6cd4f7d88fca3b 100644 (file)
--- a/mutt.h
+++ b/mutt.h
@@ -807,7 +807,7 @@ typedef struct header
   int refno;                   /* message number on server */
 #endif
 
-#if defined USE_POP || defined USE_IMAP
+#if defined USE_POP || defined USE_IMAP || defined USE_NOTMUCH
   void *data;                  /* driver-specific data */
 #endif
   
index 1aae62c6478b04c8c4081951a9c2d77f2b55ef56..a0198b7ee8829c9bb6e272de320cc11be3cd24b1 100644 (file)
@@ -3,3 +3,587 @@
  *
  * Copyright (C) 2011 Karel Zak <kzak@redhat.com>
  */
+#if HAVE_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 <dirent.h>
+#include <fcntl.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <utime.h>
+
+#include <notmuch.h>
+
+#include "mutt_notmuch.h"
+#include "mutt_curses.h"
+
+/*
+ * Parsed URI arguments
+ */
+struct uri_tag {
+       char *name;
+       char *value;
+       struct uri_tag *next;
+};
+
+/*
+ * HEADER->data
+ */
+struct nm_hdrdata {
+       char *folder;
+       char *tags;
+       int magic;
+};
+
+/*
+ * CONTEXT->data
+ */
+struct nm_data {
+       notmuch_database_t *db;
+
+       char *db_filename;
+       char *db_query;
+       int db_limit;
+
+       struct uri_tag *query_items;
+};
+
+
+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;
+       }
+}
+
+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 ? strndup(p, e - p) : safe_strdup(p);
+       if (!*filename)
+               return -1;
+
+       if (!e)
+               return 0;
+
+       if (url_pct_decode(*filename) < 0)
+               goto err;
+       if (!e)
+               return 0;       /* only filename */
+
+       ++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 ? strndup(p, e - p) : 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 ? strndup(p, e - p) : 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;
+}
+
+static void free_hdrdata(HEADER *h)
+{
+       struct nm_hdrdata *data = h->data;
+
+       if (data) {
+               FREE(&data->folder);
+               FREE(&data->tags);
+               FREE(&data);
+       }
+       h->data = NULL;
+}
+
+static int free_data(CONTEXT *ctx)
+{
+       struct nm_data *data = ctx->data;
+       int i;
+
+       for (i = 0; i < ctx->msgcount; i++) {
+               HEADER *h = ctx->hdrs[i];
+
+               if (h)
+                       free_hdrdata(h);
+       }
+
+       if (data) {
+               if (data->db)
+                       notmuch_database_close(data->db);
+               FREE(&data->db_filename);
+               FREE(&data->db_query);
+               url_free_tags(data->query_items);
+               FREE(&data);
+       }
+
+       ctx->data = NULL;
+       return 0;
+}
+
+char *nm_header_get_folder(HEADER *h)
+{
+       return h && h->data ? ((struct nm_hdrdata *) h->data)->folder : NULL;
+}
+
+char *nm_header_get_tags(HEADER *h)
+{
+       return h && h->data ? ((struct nm_hdrdata *) h->data)->tags : NULL;
+}
+
+int nm_header_get_magic(HEADER *h)
+{
+       return h && h->data ? ((struct nm_hdrdata *) h->data)->magic : 0;
+}
+
+char *nm_header_get_fullpath(HEADER *h, char *buf, size_t bufsz)
+{
+       snprintf(buf, bufsz, "%s/%s", nm_header_get_folder(h), h->path);
+       return buf;
+}
+
+static int init_data(CONTEXT *ctx)
+{
+       struct nm_data *data = ctx->data;
+
+       if (data)
+               return 0;
+
+       data = safe_calloc(1, sizeof(struct nm_data));
+       ctx->data = (void *) data;
+       ctx->mx_close = free_data;
+
+       if (url_parse_query(ctx->path, &data->db_filename, &data->query_items)) {
+               mutt_error(_("failed to parse notmuch uri: %s"), ctx->path);
+               data->db_filename = NULL;
+               data->query_items = NULL;
+               return -1;
+       }
+
+       return 0;
+}
+
+static struct nm_data *get_data(CONTEXT *ctx)
+{
+       return ctx->data;
+}
+
+static char *get_query(CONTEXT *ctx)
+{
+       struct nm_data *data = get_data(ctx);
+       struct uri_tag *item;
+
+       if (!data)
+               return NULL;
+       if (data->db_query)
+               return data->db_query;
+
+       for (item = data->query_items; item; item = item->next) {
+               if (!item->value)
+                       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);
+
+               } else if (strcmp(item->name, "query") == 0)
+                       data->db_query = safe_strdup(item->value);
+       }
+
+       return data->db_query;
+}
+
+static int get_limit(CONTEXT *ctx)
+{
+       struct nm_data *data = get_data(ctx);
+       return data->db_limit;
+}
+
+static notmuch_database_t *get_db(CONTEXT *ctx, int writable)
+{
+       struct nm_data *data = get_data(ctx);
+
+       if (!data)
+              return NULL;
+       if (!data->db) {
+               data->db = notmuch_database_open(data->db_filename,
+                               writable ? NOTMUCH_DATABASE_MODE_READ_WRITE :
+                               NOTMUCH_DATABASE_MODE_READ_ONLY);
+               if (!data->db)
+                       mutt_error (_("Cannot open notmuch database: %s"),
+                                       data->db_filename);
+       }
+
+       return data->db;
+}
+
+static void release_db(CONTEXT *ctx)
+{
+       struct nm_data *data = get_data(ctx);
+
+       if (data && data->db) {
+               notmuch_database_close(data->db);
+               data->db = NULL;
+       }
+}
+
+static struct nm_hdrdata *create_hdrdata(HEADER *h, const char *path,
+                                             notmuch_message_t *msg)
+{
+       struct nm_hdrdata *data =
+                       safe_calloc(1, sizeof(struct nm_hdrdata));
+       notmuch_tags_t *tags;
+       char *tstr = NULL;
+       size_t sz = 0;
+       char *p;
+
+       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 = M_MAILDIR;
+                       p -= 3;
+                       h->path = safe_strdup(p);
+                       data->folder = strndup(path, p - path - 1);
+       }
+
+       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);
+               size_t xsz = t ? strlen(t) : 0;
+
+               if (!xsz)
+                       continue;
+
+               if (NotmuchHiddenTags) {
+                       p = strstr(NotmuchHiddenTags, t);
+
+                       if (p && (p == NotmuchHiddenTags
+                                 || *(p - 1) == ','
+                                 || *(p - 1) == ' ')
+                           && (*(p + xsz) == '\0'
+                                 || *(p + xsz) == ','
+                                 || *(p + xsz) == ' '))
+                               continue;
+               }
+
+               safe_realloc(&tstr, sz + (sz ? 1 : 0) + xsz + 1);
+               p = tstr + sz;
+               if (sz) {
+                       *p++ = ' ';
+                       sz++;
+               }
+               memcpy(p, t, xsz + 1);
+               sz += xsz;
+       }
+
+       data->tags = tstr;
+       h->data = data;
+       return data;
+}
+
+int nm_read_query(CONTEXT *ctx)
+{
+       notmuch_database_t *db;
+       notmuch_query_t *q;
+       notmuch_messages_t *msgs;
+       int limit;
+
+       if (!ctx->data && init_data(ctx))
+               return -1;
+
+       db = get_db(ctx, FALSE);
+       if (!db)
+               return -1;
+
+       q = notmuch_query_create(db, get_query(ctx));
+       notmuch_query_set_sort(q, NOTMUCH_SORT_NEWEST_FIRST);
+
+       msgs = notmuch_query_search_messages(q);
+
+       limit = get_limit(ctx);
+
+       for (msgs = notmuch_query_search_messages(q);
+            notmuch_messages_valid(msgs) &&
+               (limit == 0 || ctx->msgcount < limit);
+            notmuch_messages_move_to_next(msgs)) {
+
+               notmuch_message_t *m = notmuch_messages_get(msgs);
+               const char *path = notmuch_message_get_filename(m);
+               HEADER *h;
+
+               if (!path)
+                       continue;
+
+               if (ctx->msgcount >= ctx->hdrmax)
+                       mx_alloc_memory(ctx);
+
+               h = maildir_parse_message(M_MAILDIR, path, 0, NULL);
+               if (!h)
+                       continue;
+
+               create_hdrdata(h, path, m);
+
+               ctx->size += h->content->length;
+               ctx->hdrs[ctx->msgcount] = h;
+               ctx->msgcount++;
+       }
+
+       notmuch_query_destroy(q);
+       release_db(ctx);
+
+       return 0;
+}
+
+char *nm_uri_from_query(CONTEXT *ctx, char *buf, size_t bufsz)
+{
+       struct nm_data *data;
+       char uri[_POSIX_PATH_MAX];
+
+       if (ctx && ctx->magic == M_NOTMUCH && (data = get_data(ctx)))
+               snprintf(uri, sizeof(uri), "notmuch://%s?query=%s", data->db_filename, buf);
+       else if (NotmuchDefaultUri)
+               snprintf(uri, sizeof(uri), "%s?query=%s", NotmuchDefaultUri, buf);
+       else
+               return NULL;
+
+       strncpy(buf, uri, bufsz);
+       buf[bufsz - 1] = '\0';
+       return buf;
+}
+
+static int _nm_update_filename(notmuch_database_t *db,
+                       const char *old, const char *new)
+{
+       notmuch_status_t st;
+
+       if (!db)
+               return -1;
+
+       st = notmuch_database_add_message(db, new, NULL);
+       if (st == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID ||
+           st == NOTMUCH_STATUS_SUCCESS)
+               notmuch_database_remove_message(db, old);
+
+       return 0;
+}
+
+int nm_update_filename(CONTEXT *ctx, const char *old, const char *new)
+{
+       if (ctx->magic != M_NOTMUCH
+           || (!ctx->data && init_data(ctx))
+           || !new
+           || !old)
+               return -1;
+
+       return _nm_update_filename(get_db(ctx, TRUE), old, new);
+}
+
+int nm_sync(CONTEXT *ctx, int *index_hint)
+{
+       int i, rc = 0;
+       char msgbuf[STRING];
+       progress_t progress;
+       char *uri = ctx->path;
+       notmuch_database_t *db = NULL;
+
+       if (!ctx->data && init_data(ctx))
+               return -1;
+
+       if (!ctx->quiet) {
+               snprintf(msgbuf, sizeof (msgbuf), _("Writing %s..."), ctx->path);
+               mutt_progress_init(&progress, msgbuf, M_PROGRESS_MSG,
+                                  WriteInc, ctx->msgcount);
+       }
+
+       for (i = 0; i < ctx->msgcount; i++) {
+               char old[_POSIX_PATH_MAX], new[_POSIX_PATH_MAX];
+               HEADER *h = ctx->hdrs[i];
+               struct nm_hdrdata *data = h->data;
+
+               if (!ctx->quiet)
+                       mutt_progress_update(&progress, i, -1);
+
+
+               nm_header_get_fullpath(h, old, sizeof(old));
+
+               ctx->path = data->folder;
+               ctx->magic = data->magic;
+
+#if USE_HCACHE
+               rc = mh_sync_mailbox_message(ctx, i, NULL);
+#else
+               rc = mh_sync_mailbox_message(ctx, i);
+#endif
+               if (rc)
+                       break;
+
+               nm_header_get_fullpath(h, new, sizeof(new));
+
+               if (h->deleted || strcmp(old, new)) {
+                       /* email renamed or deleted -- update DB */
+                       if (!db)
+                               db = get_db(ctx, TRUE);
+
+                       if (h->deleted)
+                               notmuch_database_remove_message(db, old);
+                       else if (new && old)
+                               _nm_update_filename(db, old, new);
+               }
+       }
+
+       ctx->path = uri;
+       ctx->magic = M_NOTMUCH;
+       release_db(ctx);
+
+       return rc;
+}
+
+int nm_check_database(CONTEXT * ctx, int *index_hint)
+{
+       fprintf(stderr, "nm_check_database() not implemented yet");
+       return 0;
+}
+
+static unsigned count_query(notmuch_database_t *db, const char *qstr)
+{
+       unsigned res = 0;
+       notmuch_query_t *q = notmuch_query_create(db, qstr);
+       if (q) {
+               res = notmuch_query_count_messages(q);
+               notmuch_query_destroy(q);
+       }
+       return res;
+}
+
+int nm_get_count(char *path, unsigned *all, unsigned *new)
+{
+       struct uri_tag *query_items = NULL, *item;
+       char *db_filename = NULL, *db_query = NULL;
+       notmuch_database_t *db = NULL;
+
+       int rc = -1;
+
+       if (url_parse_query(path, &db_filename, &query_items)) {
+               mutt_error(_("failed to parse notmuch uri: %s"), path);
+               goto done;
+       }
+       if (!db_filename || !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;
+
+       db = notmuch_database_open(db_filename, NOTMUCH_DATABASE_MODE_READ_ONLY);
+       if (!db) {
+               mutt_error (_("Cannot open notmuch database: %s"), db_filename);
+               goto done;
+       }
+
+       /* all emails */
+       if (all)
+               *all = count_query(db, db_query);
+
+       /* new messages */
+       if (new) {
+               if (strstr(db_query, NotmuchUnreadTag))
+                       *new = all ? *all : count_query(db, db_query);
+               else {
+                       size_t qsz = strlen(db_query)
+                                       + sizeof(" and ")
+                                       + strlen(NotmuchUnreadTag);
+                       char *qstr = safe_malloc(qsz + 10);
+
+                       if (!qstr)
+                               goto done;
+
+                       snprintf(qstr, qsz, "%s and %s", db_query, NotmuchUnreadTag);
+                       *new = count_query(db, qstr);
+                       FREE(&qstr);
+               }
+       }
+
+       rc = 0;
+done:
+       if (db)
+               notmuch_database_close(db);
+       FREE(&db_filename);
+       url_free_tags(query_items);
+       return rc;
+}
index dc7ae150a37966fc2cb75417158ea2a900268d5e..d25f6a29d26295c8e7098cc62322c1adb1eb7c33 100644 (file)
@@ -4,4 +4,15 @@
 #ifndef _MUTT_NOTMUCH_H_
 #define _MUTT_NOTMUCH_H_ 1
 
+int nm_read_query(CONTEXT *ctx);
+int nm_sync(CONTEXT * ctx, int *index_hint);
+int nm_check_database(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);
+char *nm_header_get_tags(HEADER *h);
+int nm_get_count(char *path, unsigned *all, unsigned *new);
+int nm_update_filename(CONTEXT *ctx, const char *old, const char *new);
+char *nm_uri_from_query(CONTEXT *ctx, char *buf, size_t bufsz);
+
 #endif /* _MUTT_NOTMUCH_H_ */