NeoMutt  2021-02-05-666-ge300cd
Teaching an old dog new tricks
DOXYGEN
notmuch.c
Go to the documentation of this file.
1 
46 #include "config.h"
47 #include <errno.h>
48 #include <limits.h>
49 #include <notmuch.h>
50 #include <stdbool.h>
51 #include <stdint.h>
52 #include <stdio.h>
53 #include <string.h>
54 #include <time.h>
55 #include <unistd.h>
56 #include "private.h"
57 #include "mutt/lib.h"
58 #include "config/lib.h"
59 #include "email/lib.h"
60 #include "core/lib.h"
61 #include "gui/lib.h"
62 #include "mutt.h"
63 #include "lib.h"
64 #include "hcache/lib.h"
65 #include "index/lib.h"
66 #include "maildir/lib.h"
67 #include "progress/lib.h"
68 #include "adata.h"
69 #include "command_parse.h"
70 #include "edata.h"
71 #include "maildir/edata.h"
72 #include "mdata.h"
73 #include "mutt_commands.h"
74 #include "mutt_globals.h"
75 #include "mutt_thread.h"
76 #include "mx.h"
77 #include "protos.h"
78 #include "query.h"
79 #include "tag.h"
80 #ifdef ENABLE_NLS
81 #include <libintl.h>
82 #endif
83 
84 struct stat;
85 
86 static const struct Command nm_commands[] = {
87  // clang-format off
88  { "unvirtual-mailboxes", parse_unmailboxes, 0 },
89  { "virtual-mailboxes", parse_mailboxes, MUTT_NAMED },
90  // clang-format on
91 };
92 
93 const char NmUrlProtocol[] = "notmuch://";
94 const int NmUrlProtocolLen = sizeof(NmUrlProtocol) - 1;
95 
99 void nm_init(void)
100 {
101  COMMANDS_REGISTER(nm_commands);
102 }
103 
109 static struct HeaderCache *nm_hcache_open(struct Mailbox *m)
110 {
111 #ifdef USE_HCACHE
112  const char *const c_header_cache =
113  cs_subset_path(NeoMutt->sub, "header_cache");
114  return mutt_hcache_open(c_header_cache, mailbox_path(m), NULL);
115 #else
116  return NULL;
117 #endif
118 }
119 
124 static void nm_hcache_close(struct HeaderCache *h)
125 {
126 #ifdef USE_HCACHE
128 #endif
129 }
130 
136 static char *nm_get_default_url(void)
137 {
138  // path to DB + query + url "decoration"
139  size_t len = PATH_MAX + 1024 + 32;
140  char *url = mutt_mem_malloc(len);
141 
142  // Try to use `$nm_default_url` or `$folder`.
143  // If neither are set, it is impossible to create a Notmuch URL.
144  const char *const c_nm_default_url =
145  cs_subset_string(NeoMutt->sub, "nm_default_url");
146  const char *const c_folder = cs_subset_string(NeoMutt->sub, "folder");
147  if (c_nm_default_url)
148  snprintf(url, len, "%s", c_nm_default_url);
149  else if (c_folder)
150  snprintf(url, len, "notmuch://%s", c_folder);
151  else
152  {
153  FREE(&url);
154  return NULL;
155  }
156 
157  return url;
158 }
159 
165 static struct NmMboxData *nm_get_default_data(void)
166 {
167  // path to DB + query + url "decoration"
168  char *url = nm_get_default_url();
169  if (!url)
170  return NULL;
171 
172  struct NmMboxData *default_data = nm_mdata_new(url);
173  FREE(&url);
174 
175  return default_data;
176 }
177 
188 static int init_mailbox(struct Mailbox *m)
189 {
190  if (!m || (m->type != MUTT_NOTMUCH))
191  return -1;
192 
193  if (m->mdata)
194  return 0;
195 
197  if (!m->mdata)
198  return -1;
199 
201  return 0;
202 }
203 
210 static char *email_get_id(struct Email *e)
211 {
212  struct NmEmailData *edata = nm_edata_get(e);
213  if (!edata)
214  return NULL;
215 
216  return edata->virtual_id;
217 }
218 
226 static char *email_get_fullpath(struct Email *e, char *buf, size_t buflen)
227 {
228  snprintf(buf, buflen, "%s/%s", nm_email_get_folder(e), e->path);
229  return buf;
230 }
231 
241 static void query_window_reset(void)
242 {
243  mutt_debug(LL_DEBUG2, "entering\n");
244  cs_subset_str_native_set(NeoMutt->sub, "nm_query_window_current_position", 0, NULL);
245 }
246 
271 static bool windowed_query_from_query(const char *query, char *buf, size_t buflen)
272 {
273  mutt_debug(LL_DEBUG2, "nm: %s\n", query);
274 
275  const bool c_nm_query_window_enable =
276  cs_subset_bool(NeoMutt->sub, "nm_query_window_enable");
277  const short c_nm_query_window_duration =
278  cs_subset_number(NeoMutt->sub, "nm_query_window_duration");
279  const short c_nm_query_window_current_position =
280  cs_subset_number(NeoMutt->sub, "nm_query_window_current_position");
281  const char *const c_nm_query_window_current_search =
282  cs_subset_string(NeoMutt->sub, "nm_query_window_current_search");
283  const char *const c_nm_query_window_timebase =
284  cs_subset_string(NeoMutt->sub, "nm_query_window_timebase");
285  const char *const c_nm_query_window_or_terms =
286  cs_subset_string(NeoMutt->sub, "nm_query_window_or_terms");
287 
288  /* if the query has changed, reset the window position */
289  if (!c_nm_query_window_current_search ||
290  (strcmp(query, c_nm_query_window_current_search) != 0))
291  {
293  }
294 
296  buf, buflen, c_nm_query_window_enable, c_nm_query_window_duration,
297  c_nm_query_window_current_position, c_nm_query_window_current_search,
298  c_nm_query_window_timebase, c_nm_query_window_or_terms);
299 
300  switch (rc)
301  {
303  {
304  mutt_debug(LL_DEBUG2, "nm: %s -> %s\n", query, buf);
305  break;
306  }
308  {
310  return false;
311  }
313  {
314  mutt_message(
315  // L10N: The values 'hour', 'day', 'week', 'month', 'year' are literal.
316  // They should not be translated.
317  _("Invalid nm_query_window_timebase value (valid values are: "
318  "hour, day, week, month, year)"));
319  mutt_debug(LL_DEBUG2, "Invalid nm_query_window_timebase value\n");
320  return false;
321  }
322  }
323 
324  return true;
325 }
326 
343 static char *get_query_string(struct NmMboxData *mdata, bool window)
344 {
345  mutt_debug(LL_DEBUG2, "nm: %s\n", window ? "true" : "false");
346 
347  if (!mdata)
348  return NULL;
349  if (mdata->db_query && !window)
350  return mdata->db_query;
351 
352  const char *const c_nm_query_type =
353  cs_subset_string(NeoMutt->sub, "nm_query_type");
354  mdata->query_type = nm_string_to_query_type(c_nm_query_type); /* user's default */
355 
356  struct UrlQuery *item = NULL;
357  STAILQ_FOREACH(item, &mdata->db_url->query_strings, entries)
358  {
359  if (!item->value || !item->name)
360  continue;
361 
362  if (strcmp(item->name, "limit") == 0)
363  {
364  if (mutt_str_atoi(item->value, &mdata->db_limit))
365  mutt_error(_("failed to parse notmuch limit: %s"), item->value);
366  }
367  else if (strcmp(item->name, "type") == 0)
368  mdata->query_type = nm_string_to_query_type(item->value);
369  else if (strcmp(item->name, "query") == 0)
370  mdata->db_query = mutt_str_dup(item->value);
371  }
372 
373  if (!mdata->db_query)
374  return NULL;
375 
376  if (window)
377  {
378  char buf[1024];
379  cs_subset_str_string_set(NeoMutt->sub, "nm_query_window_current_search",
380  mdata->db_query, NULL);
381 
382  /* if a date part is defined, do not apply windows (to avoid the risk of
383  * having a non-intersected date frame). A good improvement would be to
384  * accept if they intersect */
385  if (!strstr(mdata->db_query, "date:") &&
386  windowed_query_from_query(mdata->db_query, buf, sizeof(buf)))
387  {
388  mdata->db_query = mutt_str_dup(buf);
389  }
390 
391  mutt_debug(LL_DEBUG2, "nm: query (windowed) '%s'\n", mdata->db_query);
392  }
393  else
394  mutt_debug(LL_DEBUG2, "nm: query '%s'\n", mdata->db_query);
395 
396  return mdata->db_query;
397 }
398 
404 static int get_limit(struct NmMboxData *mdata)
405 {
406  return mdata ? mdata->db_limit : 0;
407 }
408 
413 static void apply_exclude_tags(notmuch_query_t *query)
414 {
415  const char *const c_nm_exclude_tags =
416  cs_subset_string(NeoMutt->sub, "nm_exclude_tags");
417  if (!c_nm_exclude_tags || !query)
418  return;
419 
420  struct TagArray tags = nm_tag_str_to_tags(c_nm_exclude_tags);
421 
422  char **tag = NULL;
423  ARRAY_FOREACH(tag, &tags.tags)
424  {
425  mutt_debug(LL_DEBUG2, "nm: query exclude tag '%s'\n", *tag);
426  notmuch_query_add_tag_exclude(query, *tag);
427  }
428 
429  notmuch_query_set_omit_excluded(query, 1);
430  nm_tag_array_free(&tags);
431 }
432 
440 static notmuch_query_t *get_query(struct Mailbox *m, bool writable)
441 {
442  struct NmMboxData *mdata = nm_mdata_get(m);
443  if (!mdata)
444  return NULL;
445 
446  notmuch_database_t *db = nm_db_get(m, writable);
447  const char *str = get_query_string(mdata, true);
448 
449  if (!db || !str)
450  goto err;
451 
452  notmuch_query_t *q = notmuch_query_create(db, str);
453  if (!q)
454  goto err;
455 
457  notmuch_query_set_sort(q, NOTMUCH_SORT_NEWEST_FIRST);
458  mutt_debug(LL_DEBUG2, "nm: query successfully initialized (%s)\n", str);
459  return q;
460 err:
461  nm_db_release(m);
462  return NULL;
463 }
464 
472 static int update_email_tags(struct Email *e, notmuch_message_t *msg)
473 {
474  struct NmEmailData *edata = nm_edata_get(e);
475  char *new_tags = NULL;
476  char *old_tags = NULL;
477 
478  mutt_debug(LL_DEBUG2, "nm: tags update requested (%s)\n", edata->virtual_id);
479 
480  for (notmuch_tags_t *tags = notmuch_message_get_tags(msg);
481  tags && notmuch_tags_valid(tags); notmuch_tags_move_to_next(tags))
482  {
483  const char *t = notmuch_tags_get(tags);
484  if (!t || (*t == '\0'))
485  continue;
486 
487  mutt_str_append_item(&new_tags, t, ' ');
488  }
489 
490  old_tags = driver_tags_get(&e->tags);
491 
492  if (new_tags && old_tags && (strcmp(old_tags, new_tags) == 0))
493  {
494  FREE(&old_tags);
495  FREE(&new_tags);
496  mutt_debug(LL_DEBUG2, "nm: tags unchanged\n");
497  return 1;
498  }
499 
500  /* new version */
501  driver_tags_replace(&e->tags, new_tags);
502  FREE(&new_tags);
503 
504  new_tags = driver_tags_get_transformed(&e->tags);
505  mutt_debug(LL_DEBUG2, "nm: new tags: '%s'\n", new_tags);
506  FREE(&new_tags);
507 
508  new_tags = driver_tags_get(&e->tags);
509  mutt_debug(LL_DEBUG2, "nm: new tag transforms: '%s'\n", new_tags);
510  FREE(&new_tags);
511 
512  return 0;
513 }
514 
522 static int update_message_path(struct Email *e, const char *path)
523 {
524  struct NmEmailData *edata = nm_edata_get(e);
525 
526  mutt_debug(LL_DEBUG2, "nm: path update requested path=%s, (%s)\n", path, edata->virtual_id);
527 
528  char *p = strrchr(path, '/');
529  if (p && ((p - path) > 3) &&
530  (mutt_strn_equal(p - 3, "cur", 3) || mutt_strn_equal(p - 3, "new", 3) ||
531  mutt_strn_equal(p - 3, "tmp", 3)))
532  {
533  edata->type = MUTT_MAILDIR;
534 
535  FREE(&e->path);
536  FREE(&edata->folder);
537 
538  p -= 3; /* skip subfolder (e.g. "new") */
539  e->old = mutt_str_startswith(p, "cur");
540  e->path = mutt_str_dup(p);
541 
542  for (; (p > path) && (*(p - 1) == '/'); p--)
543  ; // do nothing
544 
545  edata->folder = mutt_strn_dup(path, p - path);
546 
547  mutt_debug(LL_DEBUG2, "nm: folder='%s', file='%s'\n", edata->folder, e->path);
548  return 0;
549  }
550 
551  return 1;
552 }
553 
560 static char *get_folder_from_path(const char *path)
561 {
562  char *p = strrchr(path, '/');
563 
564  if (p && ((p - path) > 3) &&
565  (mutt_strn_equal(p - 3, "cur", 3) || mutt_strn_equal(p - 3, "new", 3) ||
566  mutt_strn_equal(p - 3, "tmp", 3)))
567  {
568  p -= 3;
569  for (; (p > path) && (*(p - 1) == '/'); p--)
570  ; // do nothing
571 
572  return mutt_strn_dup(path, p - path);
573  }
574 
575  return NULL;
576 }
577 
585 static char *nm2mutt_message_id(const char *id)
586 {
587  size_t sz;
588  char *mid = NULL;
589 
590  if (!id)
591  return NULL;
592  sz = strlen(id) + 3;
593  mid = mutt_mem_malloc(sz);
594 
595  snprintf(mid, sz, "<%s>", id);
596  return mid;
597 }
598 
607 static int init_email(struct Email *e, const char *path, notmuch_message_t *msg)
608 {
609  if (nm_edata_get(e))
610  return 0;
611 
612  struct NmEmailData *edata = nm_edata_new();
613  e->nm_edata = edata;
614 
615  /* Notmuch ensures that message Id exists (if not notmuch Notmuch will
616  * generate an ID), so it's more safe than use neomutt Email->env->id */
617  const char *id = notmuch_message_get_message_id(msg);
618  edata->virtual_id = mutt_str_dup(id);
619 
620  mutt_debug(LL_DEBUG2, "nm: [e=%p, edata=%p] (%s)\n", (void *) e, (void *) edata, id);
621 
622  char *nm_msg_id = nm2mutt_message_id(id);
623  if (!e->env->message_id)
624  {
625  e->env->message_id = nm_msg_id;
626  }
627  else if (!mutt_str_equal(e->env->message_id, nm_msg_id))
628  {
629  FREE(&e->env->message_id);
630  e->env->message_id = nm_msg_id;
631  }
632  else
633  {
634  FREE(&nm_msg_id);
635  }
636 
637  if (update_message_path(e, path) != 0)
638  return -1;
639 
640  update_email_tags(e, msg);
641 
642  return 0;
643 }
644 
651 static const char *get_message_last_filename(notmuch_message_t *msg)
652 {
653  const char *name = NULL;
654 
655  for (notmuch_filenames_t *ls = notmuch_message_get_filenames(msg);
656  ls && notmuch_filenames_valid(ls); notmuch_filenames_move_to_next(ls))
657  {
658  name = notmuch_filenames_get(ls);
659  }
660 
661  return name;
662 }
663 
668 static void progress_setup(struct Mailbox *m)
669 {
670  if (!m->verbose)
671  return;
672 
673  struct NmMboxData *mdata = nm_mdata_get(m);
674  if (!mdata)
675  return;
676 
677  mdata->oldmsgcount = m->msg_count;
678  mdata->ignmsgcount = 0;
679  mdata->progress =
680  progress_new(_("Reading messages..."), MUTT_PROGRESS_READ, mdata->oldmsgcount);
681 }
682 
687 static void nm_progress_update(struct Mailbox *m)
688 {
689  struct NmMboxData *mdata = nm_mdata_get(m);
690 
691  if (!m->verbose || !mdata || !mdata->progress)
692  return;
693 
694  progress_update(mdata->progress, m->msg_count + mdata->ignmsgcount, -1);
695 }
696 
704 static struct Email *get_mutt_email(struct Mailbox *m, notmuch_message_t *msg)
705 {
706  if (!m || !msg)
707  return NULL;
708 
709  const char *id = notmuch_message_get_message_id(msg);
710  if (!id)
711  return NULL;
712 
713  mutt_debug(LL_DEBUG2, "nm: neomutt email, id='%s'\n", id);
714 
715  if (!m->id_hash)
716  {
717  mutt_debug(LL_DEBUG2, "nm: init hash\n");
718  m->id_hash = mutt_make_id_hash(m);
719  if (!m->id_hash)
720  return NULL;
721  }
722 
723  char *mid = nm2mutt_message_id(id);
724  mutt_debug(LL_DEBUG2, "nm: neomutt id='%s'\n", mid);
725 
726  struct Email *e = mutt_hash_find(m->id_hash, mid);
727  FREE(&mid);
728  return e;
729 }
730 
739 static void append_message(struct HeaderCache *h, struct Mailbox *m,
740  notmuch_query_t *q, notmuch_message_t *msg, bool dedup)
741 {
742  struct NmMboxData *mdata = nm_mdata_get(m);
743  if (!mdata)
744  return;
745 
746  char *newpath = NULL;
747  struct Email *e = NULL;
748 
749  /* deduplicate */
750  if (dedup && get_mutt_email(m, msg))
751  {
752  mdata->ignmsgcount++;
754  mutt_debug(LL_DEBUG2, "nm: ignore id=%s, already in the m\n",
755  notmuch_message_get_message_id(msg));
756  return;
757  }
758 
759  const char *path = get_message_last_filename(msg);
760  if (!path)
761  return;
762 
763  mutt_debug(LL_DEBUG2, "nm: appending message, i=%d, id=%s, path=%s\n",
764  m->msg_count, notmuch_message_get_message_id(msg), path);
765 
766  if (m->msg_count >= m->email_max)
767  {
768  mutt_debug(LL_DEBUG2, "nm: allocate mx memory\n");
769  mx_alloc_memory(m);
770  }
771 
772 #ifdef USE_HCACHE
773  e = mutt_hcache_fetch(h, path, mutt_str_len(path), 0).email;
774  if (!e)
775 #endif
776  {
777  if (access(path, F_OK) == 0)
778  {
779  /* We pass is_old=false as argument here, but e->old will be updated later
780  * by update_message_path() (called by init_email() below). */
781  e = maildir_parse_message(MUTT_MAILDIR, path, false, NULL);
782  }
783  else
784  {
785  /* maybe moved try find it... */
786  char *folder = get_folder_from_path(path);
787 
788  if (folder)
789  {
790  FILE *fp = maildir_open_find_message(folder, path, &newpath);
791  if (fp)
792  {
793  e = maildir_parse_stream(MUTT_MAILDIR, fp, newpath, false, NULL);
794  mutt_file_fclose(&fp);
795 
796  mutt_debug(LL_DEBUG1, "nm: not up-to-date: %s -> %s\n", path, newpath);
797  }
798  }
799  FREE(&folder);
800  }
801 
802  if (!e)
803  {
804  mutt_debug(LL_DEBUG1, "nm: failed to parse message: %s\n", path);
805  goto done;
806  }
807 
808 #ifdef USE_HCACHE
809  mutt_hcache_store(h, newpath ? newpath : path,
810  mutt_str_len(newpath ? newpath : path), e, 0);
811 #endif
812  }
813 
814  if (init_email(e, newpath ? newpath : path, msg) != 0)
815  {
816  email_free(&e);
817  mutt_debug(LL_DEBUG1, "nm: failed to append email!\n");
818  goto done;
819  }
820 
821  e->active = true;
822  e->index = m->msg_count;
823  mailbox_size_add(m, e);
824  m->emails[m->msg_count] = e;
825  m->msg_count++;
826 
827  if (newpath)
828  {
829  /* remember that file has been moved -- nm_mbox_sync() will update the DB */
830  struct NmEmailData *edata = nm_edata_get(e);
831  if (edata)
832  {
833  mutt_debug(LL_DEBUG1, "nm: remember obsolete path: %s\n", path);
834  edata->oldpath = mutt_str_dup(path);
835  }
836  }
838 done:
839  FREE(&newpath);
840 }
841 
852 static void append_replies(struct HeaderCache *h, struct Mailbox *m,
853  notmuch_query_t *q, notmuch_message_t *top, bool dedup)
854 {
855  notmuch_messages_t *msgs = NULL;
856 
857  for (msgs = notmuch_message_get_replies(top); notmuch_messages_valid(msgs);
858  notmuch_messages_move_to_next(msgs))
859  {
860  notmuch_message_t *nm = notmuch_messages_get(msgs);
861  append_message(h, m, q, nm, dedup);
862  /* recurse through all the replies to this message too */
863  append_replies(h, m, q, nm, dedup);
864  notmuch_message_destroy(nm);
865  }
866 }
867 
879 static void append_thread(struct HeaderCache *h, struct Mailbox *m,
880  notmuch_query_t *q, notmuch_thread_t *thread, bool dedup)
881 {
882  notmuch_messages_t *msgs = NULL;
883 
884  for (msgs = notmuch_thread_get_toplevel_messages(thread);
885  notmuch_messages_valid(msgs); notmuch_messages_move_to_next(msgs))
886  {
887  notmuch_message_t *nm = notmuch_messages_get(msgs);
888  append_message(h, m, q, nm, dedup);
889  append_replies(h, m, q, nm, dedup);
890  notmuch_message_destroy(nm);
891  }
892 }
893 
903 static notmuch_messages_t *get_messages(notmuch_query_t *query)
904 {
905  if (!query)
906  return NULL;
907 
908  notmuch_messages_t *msgs = NULL;
909 
910 #if LIBNOTMUCH_CHECK_VERSION(5, 0, 0)
911  if (notmuch_query_search_messages(query, &msgs) != NOTMUCH_STATUS_SUCCESS)
912  return NULL;
913 #elif LIBNOTMUCH_CHECK_VERSION(4, 3, 0)
914  if (notmuch_query_search_messages_st(query, &msgs) != NOTMUCH_STATUS_SUCCESS)
915  return NULL;
916 #else
917  msgs = notmuch_query_search_messages(query);
918 #endif
919 
920  return msgs;
921 }
922 
931 static bool read_mesgs_query(struct Mailbox *m, notmuch_query_t *q, bool dedup)
932 {
933  struct NmMboxData *mdata = nm_mdata_get(m);
934  if (!mdata)
935  return false;
936 
937  int limit = get_limit(mdata);
938 
939  notmuch_messages_t *msgs = get_messages(q);
940 
941  if (!msgs)
942  return false;
943 
944  struct HeaderCache *h = nm_hcache_open(m);
945 
946  for (; notmuch_messages_valid(msgs) && ((limit == 0) || (m->msg_count < limit));
947  notmuch_messages_move_to_next(msgs))
948  {
949  if (SigInt)
950  {
951  nm_hcache_close(h);
952  SigInt = false;
953  return false;
954  }
955  notmuch_message_t *nm = notmuch_messages_get(msgs);
956  append_message(h, m, q, nm, dedup);
957  notmuch_message_destroy(nm);
958  }
959 
960  nm_hcache_close(h);
961  return true;
962 }
963 
973 static notmuch_threads_t *get_threads(notmuch_query_t *query)
974 {
975  if (!query)
976  return NULL;
977 
978  notmuch_threads_t *threads = NULL;
979 #if LIBNOTMUCH_CHECK_VERSION(5, 0, 0)
980  if (notmuch_query_search_threads(query, &threads) != NOTMUCH_STATUS_SUCCESS)
981  return false;
982 #elif LIBNOTMUCH_CHECK_VERSION(4, 3, 0)
983  if (notmuch_query_search_threads_st(query, &threads) != NOTMUCH_STATUS_SUCCESS)
984  return false;
985 #else
986  threads = notmuch_query_search_threads(query);
987 #endif
988 
989  return threads;
990 }
991 
1001 static bool read_threads_query(struct Mailbox *m, notmuch_query_t *q, bool dedup, int limit)
1002 {
1003  struct NmMboxData *mdata = nm_mdata_get(m);
1004  if (!mdata)
1005  return false;
1006 
1007  notmuch_threads_t *threads = get_threads(q);
1008  if (!threads)
1009  return false;
1010 
1011  struct HeaderCache *h = nm_hcache_open(m);
1012 
1013  for (; notmuch_threads_valid(threads) && ((limit == 0) || (m->msg_count < limit));
1014  notmuch_threads_move_to_next(threads))
1015  {
1016  if (SigInt)
1017  {
1018  nm_hcache_close(h);
1019  SigInt = false;
1020  return false;
1021  }
1022  notmuch_thread_t *thread = notmuch_threads_get(threads);
1023  append_thread(h, m, q, thread, dedup);
1024  notmuch_thread_destroy(thread);
1025  }
1026 
1027  nm_hcache_close(h);
1028  return true;
1029 }
1030 
1037 static notmuch_message_t *get_nm_message(notmuch_database_t *db, struct Email *e)
1038 {
1039  notmuch_message_t *msg = NULL;
1040  char *id = email_get_id(e);
1041 
1042  mutt_debug(LL_DEBUG2, "nm: find message (%s)\n", id);
1043 
1044  if (id && db)
1045  notmuch_database_find_message(db, id, &msg);
1046 
1047  return msg;
1048 }
1049 
1056 static bool nm_message_has_tag(notmuch_message_t *msg, char *tag)
1057 {
1058  const char *possible_match_tag = NULL;
1059  notmuch_tags_t *tags = NULL;
1060 
1061  for (tags = notmuch_message_get_tags(msg); notmuch_tags_valid(tags);
1062  notmuch_tags_move_to_next(tags))
1063  {
1064  possible_match_tag = notmuch_tags_get(tags);
1065  if (mutt_str_equal(possible_match_tag, tag))
1066  {
1067  return true;
1068  }
1069  }
1070  return false;
1071 }
1072 
1078 static void sync_email_path_with_nm(struct Email *e, notmuch_message_t *msg)
1079 {
1080  const char *new_file = get_message_last_filename(msg);
1081  char old_file[PATH_MAX];
1082  email_get_fullpath(e, old_file, sizeof(old_file));
1083 
1084  if (!mutt_str_equal(old_file, new_file))
1085  update_message_path(e, new_file);
1086 }
1087 
1095 static int update_tags(notmuch_message_t *msg, const char *tag_str)
1096 {
1097  if (!tag_str)
1098  return -1;
1099 
1100  notmuch_message_freeze(msg);
1101 
1102  struct TagArray tags = nm_tag_str_to_tags(tag_str);
1103  char **tag_elem = NULL;
1104  ARRAY_FOREACH(tag_elem, &tags.tags)
1105  {
1106  char *tag = *tag_elem;
1107 
1108  if (tag[0] == '-')
1109  {
1110  mutt_debug(LL_DEBUG1, "nm: remove tag: '%s'\n", tag + 1);
1111  notmuch_message_remove_tag(msg, tag + 1);
1112  }
1113  else if (tag[0] == '!')
1114  {
1115  mutt_debug(LL_DEBUG1, "nm: toggle tag: '%s'\n", tag + 1);
1116  if (nm_message_has_tag(msg, tag + 1))
1117  {
1118  notmuch_message_remove_tag(msg, tag + 1);
1119  }
1120  else
1121  {
1122  notmuch_message_add_tag(msg, tag + 1);
1123  }
1124  }
1125  else
1126  {
1127  mutt_debug(LL_DEBUG1, "nm: add tag: '%s'\n", (tag[0] == '+') ? tag + 1 : tag);
1128  notmuch_message_add_tag(msg, (tag[0] == '+') ? tag + 1 : tag);
1129  }
1130  }
1131 
1132  notmuch_message_thaw(msg);
1133  nm_tag_array_free(&tags);
1134 
1135  return 0;
1136 }
1137 
1149 static int update_email_flags(struct Mailbox *m, struct Email *e, const char *tag_str)
1150 {
1151  if (!tag_str)
1152  return -1;
1153 
1154  const char *const c_nm_unread_tag =
1155  cs_subset_string(NeoMutt->sub, "nm_unread_tag");
1156  const char *const c_nm_replied_tag =
1157  cs_subset_string(NeoMutt->sub, "nm_replied_tag");
1158  const char *const c_nm_flagged_tag =
1159  cs_subset_string(NeoMutt->sub, "nm_flagged_tag");
1160 
1161  struct TagArray tags = nm_tag_str_to_tags(tag_str);
1162  char **tag_elem = NULL;
1163  ARRAY_FOREACH(tag_elem, &tags.tags)
1164  {
1165  char *tag = *tag_elem;
1166 
1167  if (tag[0] == '-')
1168  {
1169  tag++;
1170  if (strcmp(tag, c_nm_unread_tag) == 0)
1171  mutt_set_flag(m, e, MUTT_READ, true);
1172  else if (strcmp(tag, c_nm_replied_tag) == 0)
1173  mutt_set_flag(m, e, MUTT_REPLIED, false);
1174  else if (strcmp(tag, c_nm_flagged_tag) == 0)
1175  mutt_set_flag(m, e, MUTT_FLAG, false);
1176  }
1177  else
1178  {
1179  tag = (tag[0] == '+') ? tag + 1 : tag;
1180  if (strcmp(tag, c_nm_unread_tag) == 0)
1181  mutt_set_flag(m, e, MUTT_READ, false);
1182  else if (strcmp(tag, c_nm_replied_tag) == 0)
1183  mutt_set_flag(m, e, MUTT_REPLIED, true);
1184  else if (strcmp(tag, c_nm_flagged_tag) == 0)
1185  mutt_set_flag(m, e, MUTT_FLAG, true);
1186  }
1187  }
1188 
1189  nm_tag_array_free(&tags);
1190 
1191  return 0;
1192 }
1193 
1204 static int rename_maildir_filename(const char *old, char *buf, size_t buflen, struct Email *e)
1205 {
1206  char filename[PATH_MAX];
1207  char suffix[PATH_MAX];
1208  char folder[PATH_MAX];
1209 
1210  mutt_str_copy(folder, old, sizeof(folder));
1211  char *p = strrchr(folder, '/');
1212  if (p)
1213  {
1214  *p = '\0';
1215  p++;
1216  }
1217  else
1218  p = folder;
1219 
1220  mutt_str_copy(filename, p, sizeof(filename));
1221 
1222  /* remove (new,cur,...) from folder path */
1223  p = strrchr(folder, '/');
1224  if (p)
1225  *p = '\0';
1226 
1227  /* remove old flags from filename */
1228  p = strchr(filename, ':');
1229  if (p)
1230  *p = '\0';
1231 
1232  /* compose new flags */
1233  maildir_gen_flags(suffix, sizeof(suffix), e);
1234 
1235  snprintf(buf, buflen, "%s/%s/%s%s", folder,
1236  (e->read || e->old) ? "cur" : "new", filename, suffix);
1237 
1238  if (strcmp(old, buf) == 0)
1239  return 1;
1240 
1241  if (rename(old, buf) != 0)
1242  {
1243  mutt_debug(LL_DEBUG1, "nm: rename(2) failed %s -> %s\n", old, buf);
1244  return -1;
1245  }
1246 
1247  return 0;
1248 }
1249 
1257 static int remove_filename(struct Mailbox *m, const char *path)
1258 {
1259  struct NmMboxData *mdata = nm_mdata_get(m);
1260  if (!mdata)
1261  return -1;
1262 
1263  mutt_debug(LL_DEBUG2, "nm: remove filename '%s'\n", path);
1264 
1265  notmuch_database_t *db = nm_db_get(m, true);
1266  if (!db)
1267  return -1;
1268 
1269  notmuch_message_t *msg = NULL;
1270  notmuch_status_t st = notmuch_database_find_message_by_filename(db, path, &msg);
1271  if (st || !msg)
1272  return -1;
1273 
1274  int trans = nm_db_trans_begin(m);
1275  if (trans < 0)
1276  return -1;
1277 
1278  /* note that unlink() is probably unnecessary here, it's already removed
1279  * by mh_sync_mailbox_message(), but for sure... */
1280  notmuch_filenames_t *ls = NULL;
1281  st = notmuch_database_remove_message(db, path);
1282  switch (st)
1283  {
1284  case NOTMUCH_STATUS_SUCCESS:
1285  mutt_debug(LL_DEBUG2, "nm: remove success, call unlink\n");
1286  unlink(path);
1287  break;
1288  case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:
1289  mutt_debug(LL_DEBUG2, "nm: remove success (duplicate), call unlink\n");
1290  unlink(path);
1291  for (ls = notmuch_message_get_filenames(msg);
1292  ls && notmuch_filenames_valid(ls); notmuch_filenames_move_to_next(ls))
1293  {
1294  path = notmuch_filenames_get(ls);
1295 
1296  mutt_debug(LL_DEBUG2, "nm: remove duplicate: '%s'\n", path);
1297  unlink(path);
1298  notmuch_database_remove_message(db, path);
1299  }
1300  break;
1301  default:
1302  mutt_debug(LL_DEBUG1, "nm: failed to remove '%s' [st=%d]\n", path, (int) st);
1303  break;
1304  }
1305 
1306  notmuch_message_destroy(msg);
1307  if (trans)
1308  nm_db_trans_end(m);
1309  return 0;
1310 }
1311 
1321 static int rename_filename(struct Mailbox *m, const char *old_file,
1322  const char *new_file, struct Email *e)
1323 {
1324  struct NmMboxData *mdata = nm_mdata_get(m);
1325  if (!mdata)
1326  return -1;
1327 
1328  notmuch_database_t *db = nm_db_get(m, true);
1329  if (!db || !new_file || !old_file || (access(new_file, F_OK) != 0))
1330  return -1;
1331 
1332  int rc = -1;
1333  notmuch_status_t st;
1334  notmuch_filenames_t *ls = NULL;
1335  notmuch_message_t *msg = NULL;
1336 
1337  mutt_debug(LL_DEBUG1, "nm: rename filename, %s -> %s\n", old_file, new_file);
1338  int trans = nm_db_trans_begin(m);
1339  if (trans < 0)
1340  return -1;
1341 
1342  mutt_debug(LL_DEBUG2, "nm: rename: add '%s'\n", new_file);
1343 #ifdef HAVE_NOTMUCH_DATABASE_INDEX_FILE
1344  st = notmuch_database_index_file(db, new_file, NULL, &msg);
1345 #else
1346  st = notmuch_database_add_message(db, new_file, &msg);
1347 #endif
1348 
1349  if ((st != NOTMUCH_STATUS_SUCCESS) && (st != NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID))
1350  {
1351  mutt_debug(LL_DEBUG1, "nm: failed to add '%s' [st=%d]\n", new_file, (int) st);
1352  goto done;
1353  }
1354 
1355  mutt_debug(LL_DEBUG2, "nm: rename: rem '%s'\n", old_file);
1356  st = notmuch_database_remove_message(db, old_file);
1357  switch (st)
1358  {
1359  case NOTMUCH_STATUS_SUCCESS:
1360  break;
1361  case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:
1362  mutt_debug(LL_DEBUG2, "nm: rename: syncing duplicate filename\n");
1363  notmuch_message_destroy(msg);
1364  msg = NULL;
1365  notmuch_database_find_message_by_filename(db, new_file, &msg);
1366 
1367  for (ls = notmuch_message_get_filenames(msg);
1368  msg && ls && notmuch_filenames_valid(ls); notmuch_filenames_move_to_next(ls))
1369  {
1370  const char *path = notmuch_filenames_get(ls);
1371  char newpath[PATH_MAX];
1372 
1373  if (strcmp(new_file, path) == 0)
1374  continue;
1375 
1376  mutt_debug(LL_DEBUG2, "nm: rename: syncing duplicate: %s\n", path);
1377 
1378  if (rename_maildir_filename(path, newpath, sizeof(newpath), e) == 0)
1379  {
1380  mutt_debug(LL_DEBUG2, "nm: rename dup %s -> %s\n", path, newpath);
1381  notmuch_database_remove_message(db, path);
1382 #ifdef HAVE_NOTMUCH_DATABASE_INDEX_FILE
1383  notmuch_database_index_file(db, newpath, NULL, NULL);
1384 #else
1385  notmuch_database_add_message(db, newpath, NULL);
1386 #endif
1387  }
1388  }
1389  notmuch_message_destroy(msg);
1390  msg = NULL;
1391  notmuch_database_find_message_by_filename(db, new_file, &msg);
1392  st = NOTMUCH_STATUS_SUCCESS;
1393  break;
1394  default:
1395  mutt_debug(LL_DEBUG1, "nm: failed to remove '%s' [st=%d]\n", old_file, (int) st);
1396  break;
1397  }
1398 
1399  if ((st == NOTMUCH_STATUS_SUCCESS) && e && msg)
1400  {
1401  notmuch_message_maildir_flags_to_tags(msg);
1402  update_email_tags(e, msg);
1403 
1404  char *tags = driver_tags_get(&e->tags);
1405  update_tags(msg, tags);
1406  FREE(&tags);
1407  }
1408 
1409  rc = 0;
1410 done:
1411  if (msg)
1412  notmuch_message_destroy(msg);
1413  if (trans)
1414  nm_db_trans_end(m);
1415  return rc;
1416 }
1417 
1425 static unsigned int count_query(notmuch_database_t *db, const char *qstr, int limit)
1426 {
1427  notmuch_query_t *q = notmuch_query_create(db, qstr);
1428  if (!q)
1429  return 0;
1430 
1431  unsigned int res = 0;
1432 
1433  apply_exclude_tags(q);
1434 #if LIBNOTMUCH_CHECK_VERSION(5, 0, 0)
1435  if (notmuch_query_count_messages(q, &res) != NOTMUCH_STATUS_SUCCESS)
1436  res = 0; /* may not be defined on error */
1437 #elif LIBNOTMUCH_CHECK_VERSION(4, 3, 0)
1438  if (notmuch_query_count_messages_st(q, &res) != NOTMUCH_STATUS_SUCCESS)
1439  res = 0; /* may not be defined on error */
1440 #else
1441  res = notmuch_query_count_messages(q);
1442 #endif
1443  notmuch_query_destroy(q);
1444  mutt_debug(LL_DEBUG1, "nm: count '%s', result=%d\n", qstr, res);
1445 
1446  if ((limit > 0) && (res > limit))
1447  res = limit;
1448 
1449  return res;
1450 }
1451 
1458 char *nm_email_get_folder(struct Email *e)
1459 {
1460  struct NmEmailData *edata = nm_edata_get(e);
1461  if (!edata)
1462  return NULL;
1463 
1464  return edata->folder;
1465 }
1466 
1477 char *nm_email_get_folder_rel_db(struct Mailbox *m, struct Email *e)
1478 {
1479  char *full_folder = nm_email_get_folder(e);
1480  if (!full_folder)
1481  return NULL;
1482 
1483  const char *db_path = nm_db_get_filename(m);
1484  if (!db_path)
1485  return NULL;
1486 
1487  return full_folder + strlen(db_path);
1488 }
1489 
1497 int nm_read_entire_thread(struct Mailbox *m, struct Email *e)
1498 {
1499  if (!m)
1500  return -1;
1501 
1502  struct NmMboxData *mdata = nm_mdata_get(m);
1503  if (!mdata)
1504  return -1;
1505 
1506  notmuch_query_t *q = NULL;
1507  notmuch_database_t *db = NULL;
1508  notmuch_message_t *msg = NULL;
1509  int rc = -1;
1510 
1511  if (!(db = nm_db_get(m, false)) || !(msg = get_nm_message(db, e)))
1512  goto done;
1513 
1514  mutt_debug(LL_DEBUG1, "nm: reading entire-thread messages...[current count=%d]\n",
1515  m->msg_count);
1516 
1517  progress_setup(m);
1518  const char *id = notmuch_message_get_thread_id(msg);
1519  if (!id)
1520  goto done;
1521 
1522  char *qstr = NULL;
1523  mutt_str_append_item(&qstr, "thread:", '\0');
1524  mutt_str_append_item(&qstr, id, '\0');
1525 
1526  q = notmuch_query_create(db, qstr);
1527  FREE(&qstr);
1528  if (!q)
1529  goto done;
1530  apply_exclude_tags(q);
1531  notmuch_query_set_sort(q, NOTMUCH_SORT_NEWEST_FIRST);
1532 
1533  read_threads_query(m, q, true, 0);
1534  m->mtime.tv_sec = mutt_date_epoch();
1535  m->mtime.tv_nsec = 0;
1536  rc = 0;
1537 
1538  if (m->msg_count > mdata->oldmsgcount)
1540 done:
1541  if (q)
1542  notmuch_query_destroy(q);
1543 
1544  nm_db_release(m);
1545 
1546  if (m->msg_count == mdata->oldmsgcount)
1547  mutt_message(_("No more messages in the thread"));
1548 
1549  mdata->oldmsgcount = 0;
1550  mutt_debug(LL_DEBUG1, "nm: reading entire-thread messages... done [rc=%d, count=%d]\n",
1551  rc, m->msg_count);
1552  progress_free(&mdata->progress);
1553  return rc;
1554 }
1555 
1564 char *nm_url_from_query(struct Mailbox *m, char *buf, size_t buflen)
1565 {
1566  mutt_debug(LL_DEBUG2, "(%s)\n", buf);
1567  struct NmMboxData *mdata = nm_mdata_get(m);
1568  char url[PATH_MAX + 1024 + 32]; /* path to DB + query + URL "decoration" */
1569  int added;
1570  bool using_default_data = false;
1571 
1572  // No existing data. Try to get a default NmMboxData.
1573  if (!mdata)
1574  {
1575  mdata = nm_get_default_data();
1576 
1577  // Failed to get default data.
1578  if (!mdata)
1579  return NULL;
1580 
1581  using_default_data = true;
1582  }
1583 
1584  mdata->query_type = nm_parse_type_from_query(buf);
1585 
1586  const short c_nm_db_limit = cs_subset_number(NeoMutt->sub, "nm_db_limit");
1587  if (get_limit(mdata) == c_nm_db_limit)
1588  {
1589  added = snprintf(url, sizeof(url), "%s%s?type=%s&query=", NmUrlProtocol,
1591  }
1592  else
1593  {
1594  added = snprintf(url, sizeof(url), "%s%s?type=%s&limit=%d&query=", NmUrlProtocol,
1595  nm_db_get_filename(m),
1596  nm_query_type_to_string(mdata->query_type), get_limit(mdata));
1597  }
1598 
1599  if (added >= sizeof(url))
1600  {
1601  // snprintf output was truncated, so can't create URL
1602  return NULL;
1603  }
1604 
1605  url_pct_encode(&url[added], sizeof(url) - added, buf);
1606 
1607  mutt_str_copy(buf, url, buflen);
1608  buf[buflen - 1] = '\0';
1609 
1610  if (using_default_data)
1611  nm_mdata_free((void **) &mdata);
1612 
1613  mutt_debug(LL_DEBUG1, "nm: url from query '%s'\n", buf);
1614  return buf;
1615 }
1616 
1622 {
1623  const short c_nm_query_window_duration =
1624  cs_subset_number(NeoMutt->sub, "nm_query_window_duration");
1625  const bool c_nm_query_window_enable =
1626  cs_subset_bool(NeoMutt->sub, "nm_query_window_enable");
1627 
1628  return c_nm_query_window_enable || (c_nm_query_window_duration > 0);
1629 }
1630 
1641 {
1642  const short c_nm_query_window_current_position =
1643  cs_subset_number(NeoMutt->sub, "nm_query_window_current_position");
1644  if (c_nm_query_window_current_position != 0)
1645  {
1646  cs_subset_str_native_set(NeoMutt->sub, "nm_query_window_current_position",
1647  c_nm_query_window_current_position - 1, NULL);
1648  }
1649 
1650  mutt_debug(LL_DEBUG2, "(%d)\n", c_nm_query_window_current_position - 1);
1651 }
1652 
1662 {
1663  const short c_nm_query_window_current_position =
1664  cs_subset_number(NeoMutt->sub, "nm_query_window_current_position");
1665  cs_subset_str_native_set(NeoMutt->sub, "nm_query_window_current_position",
1666  c_nm_query_window_current_position + 1, NULL);
1667  mutt_debug(LL_DEBUG2, "(%d)\n", c_nm_query_window_current_position + 1);
1668 }
1669 
1674 {
1675  cs_subset_str_native_set(NeoMutt->sub, "nm_query_window_current_position", 0, NULL);
1676  mutt_debug(LL_DEBUG2, "Reset nm_query_window_current_position to 0\n");
1677 }
1678 
1685 bool nm_message_is_still_queried(struct Mailbox *m, struct Email *e)
1686 {
1687  struct NmMboxData *mdata = nm_mdata_get(m);
1688  notmuch_database_t *db = nm_db_get(m, false);
1689  char *orig_str = get_query_string(mdata, true);
1690 
1691  if (!db || !orig_str)
1692  return false;
1693 
1694  char *new_str = NULL;
1695  bool rc = false;
1696  if (mutt_str_asprintf(&new_str, "id:%s and (%s)", email_get_id(e), orig_str) < 0)
1697  return false;
1698 
1699  mutt_debug(LL_DEBUG2, "nm: checking if message is still queried: %s\n", new_str);
1700 
1701  notmuch_query_t *q = notmuch_query_create(db, new_str);
1702 
1703  switch (mdata->query_type)
1704  {
1705  case NM_QUERY_TYPE_UNKNOWN: // UNKNOWN should never occur, but MESGS is default
1706  case NM_QUERY_TYPE_MESGS:
1707  {
1708  notmuch_messages_t *messages = get_messages(q);
1709 
1710  if (!messages)
1711  return false;
1712 
1713  rc = notmuch_messages_valid(messages);
1714  notmuch_messages_destroy(messages);
1715  break;
1716  }
1717  case NM_QUERY_TYPE_THREADS:
1718  {
1719  notmuch_threads_t *threads = get_threads(q);
1720 
1721  if (!threads)
1722  return false;
1723 
1724  rc = notmuch_threads_valid(threads);
1725  notmuch_threads_destroy(threads);
1726  break;
1727  }
1728  }
1729 
1730  notmuch_query_destroy(q);
1731 
1732  mutt_debug(LL_DEBUG2, "nm: checking if message is still queried: %s = %s\n",
1733  new_str, rc ? "true" : "false");
1734 
1735  return rc;
1736 }
1737 
1747 int nm_update_filename(struct Mailbox *m, const char *old_file,
1748  const char *new_file, struct Email *e)
1749 {
1750  char buf[PATH_MAX];
1751  struct NmMboxData *mdata = nm_mdata_get(m);
1752  if (!mdata || !new_file)
1753  return -1;
1754 
1755  if (!old_file && nm_edata_get(e))
1756  {
1757  email_get_fullpath(e, buf, sizeof(buf));
1758  old_file = buf;
1759  }
1760 
1761  int rc = rename_filename(m, old_file, new_file, e);
1762 
1763  nm_db_release(m);
1764  m->mtime.tv_sec = mutt_date_epoch();
1765  m->mtime.tv_nsec = 0;
1766  return rc;
1767 }
1768 
1772 static enum MxStatus nm_mbox_check_stats(struct Mailbox *m, uint8_t flags)
1773 {
1774  struct UrlQuery *item = NULL;
1775  struct Url *url = NULL;
1776  const char *db_filename = NULL;
1777  char *db_query = NULL;
1778  notmuch_database_t *db = NULL;
1779  enum MxStatus rc = MX_STATUS_ERROR;
1780  const short c_nm_db_limit = cs_subset_number(NeoMutt->sub, "nm_db_limit");
1781  int limit = c_nm_db_limit;
1782  mutt_debug(LL_DEBUG1, "nm: count\n");
1783 
1784  url = url_parse(mailbox_path(m));
1785  if (!url)
1786  {
1787  mutt_error(_("failed to parse notmuch url: %s"), mailbox_path(m));
1788  goto done;
1789  }
1790 
1791  STAILQ_FOREACH(item, &url->query_strings, entries)
1792  {
1793  if (item->value && (strcmp(item->name, "query") == 0))
1794  db_query = item->value;
1795  else if (item->value && (strcmp(item->name, "limit") == 0))
1796  {
1797  // Try to parse the limit
1798  if (mutt_str_atoi(item->value, &limit) != 0)
1799  {
1800  mutt_error(_("failed to parse limit: %s"), item->value);
1801  goto done;
1802  }
1803  }
1804  }
1805 
1806  if (!db_query)
1807  goto done;
1808 
1809  db_filename = url->path;
1810  if (!db_filename)
1811  {
1812  const char *const c_nm_default_url =
1813  cs_subset_string(NeoMutt->sub, "nm_default_url");
1814  const char *const c_folder = cs_subset_string(NeoMutt->sub, "folder");
1815  if (c_nm_default_url)
1816  {
1817  if (nm_path_probe(c_nm_default_url, NULL) == MUTT_NOTMUCH)
1818  db_filename = c_nm_default_url + NmUrlProtocolLen;
1819  else
1820  db_filename = c_nm_default_url;
1821  }
1822  else if (c_folder)
1823  db_filename = c_folder;
1824  }
1825 
1826  /* don't be verbose about connection, as we're called from
1827  * sidebar/mailbox very often */
1828  db = nm_db_do_open(db_filename, false, false);
1829  if (!db)
1830  goto done;
1831 
1832  /* all emails */
1833  m->msg_count = count_query(db, db_query, limit);
1834  while (m->email_max < m->msg_count)
1835  mx_alloc_memory(m);
1836 
1837  // holder variable for extending query to unread/flagged
1838  char *qstr = NULL;
1839 
1840  // unread messages
1841  const char *const c_nm_unread_tag =
1842  cs_subset_string(NeoMutt->sub, "nm_unread_tag");
1843  mutt_str_asprintf(&qstr, "( %s ) tag:%s", db_query, c_nm_unread_tag);
1844  m->msg_unread = count_query(db, qstr, limit);
1845  FREE(&qstr);
1846 
1847  // flagged messages
1848  const char *const c_nm_flagged_tag =
1849  cs_subset_string(NeoMutt->sub, "nm_flagged_tag");
1850  mutt_str_asprintf(&qstr, "( %s ) tag:%s", db_query, c_nm_flagged_tag);
1851  m->msg_flagged = count_query(db, qstr, limit);
1852  FREE(&qstr);
1853 
1854  rc = (m->msg_new > 0) ? MX_STATUS_NEW_MAIL : MX_STATUS_OK;
1855 done:
1856  if (db)
1857  {
1858  nm_db_free(db);
1859  mutt_debug(LL_DEBUG1, "nm: count close DB\n");
1860  }
1861  url_free(&url);
1862 
1863  mutt_debug(LL_DEBUG1, "nm: count done [rc=%d]\n", rc);
1864  return rc;
1865 }
1866 
1871 static struct Mailbox *get_default_mailbox(void)
1872 {
1873  // Create a new notmuch mailbox from scratch and add plumbing for DB access.
1874  char *default_url = nm_get_default_url();
1875  struct Mailbox *m = mx_path_resolve(default_url);
1876 
1877  FREE(&default_url);
1878 
1879  // These are no-ops for an initialized mailbox.
1880  init_mailbox(m);
1881  mx_mbox_ac_link(m);
1882 
1883  return m;
1884 }
1885 
1894 int nm_record_message(struct Mailbox *m, char *path, struct Email *e)
1895 {
1896  notmuch_database_t *db = NULL;
1897  notmuch_status_t st;
1898  notmuch_message_t *msg = NULL;
1899  int rc = -1;
1900 
1901  struct NmMboxData *mdata = nm_mdata_get(m);
1902 
1903  // If no notmuch data, fall back to the default mailbox.
1904  //
1905  // IMPORTANT: DO NOT FREE THIS MAILBOX. Two reasons:
1906  // 1) If user has default mailbox in config, we'll be removing it. That's not
1907  // good program behavior!
1908  // 2) If not in user's config, keep mailbox around for future nm_record calls.
1909  // It saves NeoMutt from allocating/deallocating repeatedly.
1910  if (!mdata)
1911  {
1912  mutt_debug(LL_DEBUG1, "nm: non-nm mailbox. trying the default nm mailbox.");
1913  m = get_default_mailbox();
1914  mdata = nm_mdata_get(m);
1915  }
1916 
1917  if (!path || !mdata || (access(path, F_OK) != 0))
1918  return 0;
1919  db = nm_db_get(m, true);
1920  if (!db)
1921  return -1;
1922 
1923  mutt_debug(LL_DEBUG1, "nm: record message: %s\n", path);
1924  int trans = nm_db_trans_begin(m);
1925  if (trans < 0)
1926  goto done;
1927 
1928 #ifdef HAVE_NOTMUCH_DATABASE_INDEX_FILE
1929  st = notmuch_database_index_file(db, path, NULL, &msg);
1930 #else
1931  st = notmuch_database_add_message(db, path, &msg);
1932 #endif
1933 
1934  if ((st != NOTMUCH_STATUS_SUCCESS) && (st != NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID))
1935  {
1936  mutt_debug(LL_DEBUG1, "nm: failed to add '%s' [st=%d]\n", path, (int) st);
1937  goto done;
1938  }
1939 
1940  if ((st == NOTMUCH_STATUS_SUCCESS) && msg)
1941  {
1942  notmuch_message_maildir_flags_to_tags(msg);
1943  if (e)
1944  {
1945  char *tags = driver_tags_get(&e->tags);
1946  update_tags(msg, tags);
1947  FREE(&tags);
1948  }
1949  const char *const c_nm_record_tags =
1950  cs_subset_string(NeoMutt->sub, "nm_record_tags");
1951  if (c_nm_record_tags)
1952  update_tags(msg, c_nm_record_tags);
1953  }
1954 
1955  rc = 0;
1956 done:
1957  if (msg)
1958  notmuch_message_destroy(msg);
1959  if (trans == 1)
1960  nm_db_trans_end(m);
1961 
1962  nm_db_release(m);
1963 
1964  return rc;
1965 }
1966 
1977 int nm_get_all_tags(struct Mailbox *m, char **tag_list, int *tag_count)
1978 {
1979  struct NmMboxData *mdata = nm_mdata_get(m);
1980  if (!mdata)
1981  return -1;
1982 
1983  notmuch_database_t *db = NULL;
1984  notmuch_tags_t *tags = NULL;
1985  const char *tag = NULL;
1986  int rc = -1;
1987 
1988  if (!(db = nm_db_get(m, false)) || !(tags = notmuch_database_get_all_tags(db)))
1989  goto done;
1990 
1991  *tag_count = 0;
1992  mutt_debug(LL_DEBUG1, "nm: get all tags\n");
1993 
1994  while (notmuch_tags_valid(tags))
1995  {
1996  tag = notmuch_tags_get(tags);
1997  /* Skip empty string */
1998  if (*tag)
1999  {
2000  if (tag_list)
2001  tag_list[*tag_count] = mutt_str_dup(tag);
2002  (*tag_count)++;
2003  }
2004  notmuch_tags_move_to_next(tags);
2005  }
2006 
2007  rc = 0;
2008 done:
2009  if (tags)
2010  notmuch_tags_destroy(tags);
2011 
2012  nm_db_release(m);
2013 
2014  mutt_debug(LL_DEBUG1, "nm: get all tags done [rc=%d tag_count=%u]\n", rc, *tag_count);
2015  return rc;
2016 }
2017 
2021 static bool nm_ac_owns_path(struct Account *a, const char *path)
2022 {
2023  return true;
2024 }
2025 
2029 static bool nm_ac_add(struct Account *a, struct Mailbox *m)
2030 {
2031  if (a->adata)
2032  return true;
2033 
2034  struct NmAccountData *adata = nm_adata_new();
2035  a->adata = adata;
2037 
2038  return true;
2039 }
2040 
2044 static enum MxOpenReturns nm_mbox_open(struct Mailbox *m)
2045 {
2046  if (init_mailbox(m) != 0)
2047  return MX_OPEN_ERROR;
2048 
2049  struct NmMboxData *mdata = nm_mdata_get(m);
2050  if (!mdata)
2051  return MX_OPEN_ERROR;
2052 
2053  mutt_debug(LL_DEBUG1, "nm: reading messages...[current count=%d]\n", m->msg_count);
2054 
2055  progress_setup(m);
2056  enum MxOpenReturns rc = MX_OPEN_ERROR;
2057 
2058  notmuch_query_t *q = get_query(m, false);
2059  if (q)
2060  {
2061  rc = MX_OPEN_OK;
2062  switch (mdata->query_type)
2063  {
2064  case NM_QUERY_TYPE_UNKNOWN: // UNKNOWN should never occur, but MESGS is default
2065  case NM_QUERY_TYPE_MESGS:
2066  if (!read_mesgs_query(m, q, false))
2067  rc = MX_OPEN_ABORT;
2068  break;
2069  case NM_QUERY_TYPE_THREADS:
2070  if (!read_threads_query(m, q, false, get_limit(mdata)))
2071  rc = MX_OPEN_ABORT;
2072  break;
2073  }
2074  notmuch_query_destroy(q);
2075  }
2076 
2077  nm_db_release(m);
2078 
2079  m->mtime.tv_sec = mutt_date_epoch();
2080  m->mtime.tv_nsec = 0;
2081 
2082  mdata->oldmsgcount = 0;
2083 
2084  mutt_debug(LL_DEBUG1, "nm: reading messages... done [rc=%d, count=%d]\n", rc, m->msg_count);
2085  progress_free(&mdata->progress);
2086  return rc;
2087 }
2088 
2094 static enum MxStatus nm_mbox_check(struct Mailbox *m)
2095 {
2096  struct NmMboxData *mdata = nm_mdata_get(m);
2097  time_t mtime = 0;
2098  if (!mdata || (nm_db_get_mtime(m, &mtime) != 0))
2099  return MX_STATUS_ERROR;
2100 
2101  int new_flags = 0;
2102  bool occult = false;
2103 
2104  if (m->mtime.tv_sec >= mtime)
2105  {
2106  mutt_debug(LL_DEBUG2, "nm: check unnecessary (db=%lu mailbox=%lu)\n", mtime,
2107  m->mtime.tv_sec);
2108  return MX_STATUS_OK;
2109  }
2110 
2111  mutt_debug(LL_DEBUG1, "nm: checking (db=%lu mailbox=%lu)\n", mtime, m->mtime.tv_sec);
2112 
2113  notmuch_query_t *q = get_query(m, false);
2114  if (!q)
2115  goto done;
2116 
2117  mutt_debug(LL_DEBUG1, "nm: start checking (count=%d)\n", m->msg_count);
2118  mdata->oldmsgcount = m->msg_count;
2119 
2120  for (int i = 0; i < m->msg_count; i++)
2121  {
2122  struct Email *e = m->emails[i];
2123  if (!e)
2124  break;
2125 
2126  e->active = false;
2127  }
2128 
2129  int limit = get_limit(mdata);
2130 
2131  notmuch_messages_t *msgs = get_messages(q);
2132 
2133  // TODO: Analyze impact of removing this version guard.
2134 #if LIBNOTMUCH_CHECK_VERSION(5, 0, 0)
2135  if (!msgs)
2136  return MX_STATUS_OK;
2137 #elif LIBNOTMUCH_CHECK_VERSION(4, 3, 0)
2138  if (!msgs)
2139  goto done;
2140 #endif
2141 
2142  struct HeaderCache *h = nm_hcache_open(m);
2143 
2144  for (int i = 0; notmuch_messages_valid(msgs) && ((limit == 0) || (i < limit));
2145  notmuch_messages_move_to_next(msgs), i++)
2146  {
2147  notmuch_message_t *msg = notmuch_messages_get(msgs);
2148  struct Email *e = get_mutt_email(m, msg);
2149 
2150  if (!e)
2151  {
2152  /* new email */
2153  append_message(h, m, NULL, msg, false);
2154  notmuch_message_destroy(msg);
2155  continue;
2156  }
2157 
2158  /* message already exists, merge flags */
2159  e->active = true;
2160 
2161  /* Check to see if the message has moved to a different subdirectory.
2162  * If so, update the associated filename. */
2163  const char *new_file = get_message_last_filename(msg);
2164  char old_file[PATH_MAX];
2165  email_get_fullpath(e, old_file, sizeof(old_file));
2166 
2167  if (!mutt_str_equal(old_file, new_file))
2168  update_message_path(e, new_file);
2169 
2170  if (!e->changed)
2171  {
2172  /* if the user hasn't modified the flags on this message, update the
2173  * flags we just detected. */
2174  struct Email e_tmp = { 0 };
2175  e_tmp.edata = maildir_edata_new();
2176  maildir_parse_flags(&e_tmp, new_file);
2177  maildir_update_flags(m, e, &e_tmp);
2178  maildir_edata_free(&e_tmp.edata);
2179  }
2180 
2181  if (update_email_tags(e, msg) == 0)
2182  new_flags++;
2183 
2184  notmuch_message_destroy(msg);
2185  }
2186 
2187  nm_hcache_close(h);
2188 
2189  for (int i = 0; i < m->msg_count; i++)
2190  {
2191  struct Email *e = m->emails[i];
2192  if (!e)
2193  break;
2194 
2195  if (!e->active)
2196  {
2197  occult = true;
2198  break;
2199  }
2200  }
2201 
2202  if (m->msg_count > mdata->oldmsgcount)
2204 done:
2205  if (q)
2206  notmuch_query_destroy(q);
2207 
2208  nm_db_release(m);
2209 
2210  m->mtime.tv_sec = mutt_date_epoch();
2211  m->mtime.tv_nsec = 0;
2212 
2213  mutt_debug(LL_DEBUG1, "nm: ... check done [count=%d, new_flags=%d, occult=%d]\n",
2214  m->msg_count, new_flags, occult);
2215 
2216  if (occult)
2217  return MX_STATUS_REOPENED;
2218  if (m->msg_count > mdata->oldmsgcount)
2219  return MX_STATUS_NEW_MAIL;
2220  if (new_flags)
2221  return MX_STATUS_FLAGS;
2222  return MX_STATUS_OK;
2223 }
2224 
2228 static enum MxStatus nm_mbox_sync(struct Mailbox *m)
2229 {
2230  struct NmMboxData *mdata = nm_mdata_get(m);
2231  if (!mdata)
2232  return MX_STATUS_ERROR;
2233 
2234  enum MxStatus rc = MX_STATUS_OK;
2235  struct Progress *progress = NULL;
2236  char *url = mutt_str_dup(mailbox_path(m));
2237  bool changed = false;
2238 
2239  mutt_debug(LL_DEBUG1, "nm: sync start\n");
2240 
2241  if (m->verbose)
2242  {
2243  /* all is in this function so we don't use data->progress here */
2244  char msg[PATH_MAX];
2245  snprintf(msg, sizeof(msg), _("Writing %s..."), mailbox_path(m));
2246  progress = progress_new(msg, MUTT_PROGRESS_WRITE, m->msg_count);
2247  }
2248 
2249  struct HeaderCache *h = nm_hcache_open(m);
2250 
2251  int mh_sync_errors = 0;
2252  for (int i = 0; i < m->msg_count; i++)
2253  {
2254  char old_file[PATH_MAX], new_file[PATH_MAX];
2255  struct Email *e = m->emails[i];
2256  if (!e)
2257  break;
2258 
2259  struct NmEmailData *edata = nm_edata_get(e);
2260 
2261  if (m->verbose)
2262  progress_update(progress, i, -1);
2263 
2264  *old_file = '\0';
2265  *new_file = '\0';
2266 
2267  if (edata->oldpath)
2268  {
2269  mutt_str_copy(old_file, edata->oldpath, sizeof(old_file));
2270  old_file[sizeof(old_file) - 1] = '\0';
2271  mutt_debug(LL_DEBUG2, "nm: fixing obsolete path '%s'\n", old_file);
2272  }
2273  else
2274  email_get_fullpath(e, old_file, sizeof(old_file));
2275 
2276  mutt_buffer_strcpy(&m->pathbuf, edata->folder);
2277  m->type = edata->type;
2278 
2279  bool ok = maildir_sync_mailbox_message(m, i, h);
2280  if (!ok)
2281  {
2282  // Syncing file failed, query notmuch for new filepath.
2283  m->type = MUTT_NOTMUCH;
2284  notmuch_database_t *db = nm_db_get(m, true);
2285  if (db)
2286  {
2287  notmuch_message_t *msg = get_nm_message(db, e);
2288 
2289  sync_email_path_with_nm(e, msg);
2290 
2291  mutt_buffer_strcpy(&m->pathbuf, edata->folder);
2292  m->type = edata->type;
2293  ok = maildir_sync_mailbox_message(m, i, h);
2294  m->type = MUTT_NOTMUCH;
2295  }
2296  nm_db_release(m);
2297  m->type = edata->type;
2298  }
2299 
2300  mutt_buffer_strcpy(&m->pathbuf, url);
2301  m->type = MUTT_NOTMUCH;
2302 
2303  if (!ok)
2304  {
2305  mh_sync_errors += 1;
2306  continue;
2307  }
2308 
2309  if (!e->deleted)
2310  email_get_fullpath(e, new_file, sizeof(new_file));
2311 
2312  if (e->deleted || (strcmp(old_file, new_file) != 0))
2313  {
2314  if (e->deleted && (remove_filename(m, old_file) == 0))
2315  changed = true;
2316  else if (*new_file && *old_file && (rename_filename(m, old_file, new_file, e) == 0))
2317  changed = true;
2318  }
2319 
2320  FREE(&edata->oldpath);
2321  }
2322 
2323  if (mh_sync_errors > 0)
2324  {
2325  mutt_error(
2326  ngettext(
2327  "Unable to sync %d message due to external mailbox modification",
2328  "Unable to sync %d messages due to external mailbox modification", mh_sync_errors),
2329  mh_sync_errors);
2330  }
2331 
2332  mutt_buffer_strcpy(&m->pathbuf, url);
2333  m->type = MUTT_NOTMUCH;
2334 
2335  nm_db_release(m);
2336 
2337  if (changed)
2338  {
2339  m->mtime.tv_sec = mutt_date_epoch();
2340  m->mtime.tv_nsec = 0;
2341  }
2342 
2343  nm_hcache_close(h);
2344 
2345  progress_free(&progress);
2346  FREE(&url);
2347  mutt_debug(LL_DEBUG1, "nm: .... sync done [rc=%d]\n", rc);
2348  return rc;
2349 }
2350 
2356 static enum MxStatus nm_mbox_close(struct Mailbox *m)
2357 {
2358  return MX_STATUS_OK;
2359 }
2360 
2364 static bool nm_msg_open(struct Mailbox *m, struct Message *msg, int msgno)
2365 {
2366  struct Email *e = m->emails[msgno];
2367  if (!e)
2368  return false;
2369 
2370  char path[PATH_MAX];
2371  char *folder = nm_email_get_folder(e);
2372 
2373  snprintf(path, sizeof(path), "%s/%s", folder, e->path);
2374 
2375  msg->fp = fopen(path, "r");
2376  if (!msg->fp && (errno == ENOENT) && ((m->type == MUTT_MAILDIR) || (m->type == MUTT_NOTMUCH)))
2377  {
2378  msg->fp = maildir_open_find_message(folder, e->path, NULL);
2379  }
2380 
2381  return msg->fp != NULL;
2382 }
2383 
2388 static int nm_msg_commit(struct Mailbox *m, struct Message *msg)
2389 {
2390  mutt_error(_("Can't write to virtual folder"));
2391  return -1;
2392 }
2393 
2397 static int nm_msg_close(struct Mailbox *m, struct Message *msg)
2398 {
2399  mutt_file_fclose(&(msg->fp));
2400  return 0;
2401 }
2402 
2406 static int nm_tags_edit(struct Mailbox *m, const char *tags, char *buf, size_t buflen)
2407 {
2408  *buf = '\0';
2409  if (mutt_get_field("Add/remove labels: ", buf, buflen, MUTT_NM_TAG, false, NULL, NULL) != 0)
2410  {
2411  return -1;
2412  }
2413  return 1;
2414 }
2415 
2419 static int nm_tags_commit(struct Mailbox *m, struct Email *e, char *buf)
2420 {
2421  if (*buf == '\0')
2422  return 0; /* no tag change, so nothing to do */
2423 
2424  struct NmMboxData *mdata = nm_mdata_get(m);
2425  if (!mdata)
2426  return -1;
2427 
2428  notmuch_database_t *db = NULL;
2429  notmuch_message_t *msg = NULL;
2430  int rc = -1;
2431 
2432  if (!(db = nm_db_get(m, true)) || !(msg = get_nm_message(db, e)))
2433  goto done;
2434 
2435  mutt_debug(LL_DEBUG1, "nm: tags modify: '%s'\n", buf);
2436 
2437  update_tags(msg, buf);
2438  update_email_flags(m, e, buf);
2439  update_email_tags(e, msg);
2440  mutt_set_header_color(m, e);
2441 
2442  rc = 0;
2443  e->changed = true;
2444 done:
2445  nm_db_release(m);
2446  if (e->changed)
2447  {
2448  m->mtime.tv_sec = mutt_date_epoch();
2449  m->mtime.tv_nsec = 0;
2450  }
2451  mutt_debug(LL_DEBUG1, "nm: tags modify done [rc=%d]\n", rc);
2452  return rc;
2453 }
2454 
2458 enum MailboxType nm_path_probe(const char *path, const struct stat *st)
2459 {
2460  if (!mutt_istr_startswith(path, NmUrlProtocol))
2461  return MUTT_UNKNOWN;
2462 
2463  return MUTT_NOTMUCH;
2464 }
2465 
2469 static int nm_path_canon(char *buf, size_t buflen)
2470 {
2471  return 0;
2472 }
2473 
2477 static int nm_path_pretty(char *buf, size_t buflen, const char *folder)
2478 {
2479  /* Succeed, but don't do anything, for now */
2480  return 0;
2481 }
2482 
2486 static int nm_path_parent(char *buf, size_t buflen)
2487 {
2488  /* Succeed, but don't do anything, for now */
2489  return 0;
2490 }
2491 
2492 // clang-format off
2496 struct MxOps MxNotmuchOps = {
2497  .type = MUTT_NOTMUCH,
2498  .name = "notmuch",
2499  .is_local = false,
2500  .ac_owns_path = nm_ac_owns_path,
2501  .ac_add = nm_ac_add,
2502  .mbox_open = nm_mbox_open,
2503  .mbox_open_append = NULL,
2504  .mbox_check = nm_mbox_check,
2505  .mbox_check_stats = nm_mbox_check_stats,
2506  .mbox_sync = nm_mbox_sync,
2507  .mbox_close = nm_mbox_close,
2508  .msg_open = nm_msg_open,
2509  .msg_open_new = maildir_msg_open_new,
2510  .msg_commit = nm_msg_commit,
2511  .msg_close = nm_msg_close,
2512  .msg_padding_size = NULL,
2513  .msg_save_hcache = NULL,
2514  .tags_edit = nm_tags_edit,
2515  .tags_commit = nm_tags_commit,
2516  .path_probe = nm_path_probe,
2517  .path_canon = nm_path_canon,
2518  .path_pretty = nm_path_pretty,
2519  .path_parent = nm_path_parent,
2520  .path_is_empty = NULL,
2521 };
2522 // clang-format on
notmuch_database_t * nm_db_do_open(const char *filename, bool writable, bool verbose)
Open a Notmuch database.
Definition: db.c:90
void * mutt_hash_find(const struct HashTable *table, const char *strkey)
Find the HashElem data in a Hash Table element using a key.
Definition: hash.c:354
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition: string.c:904
struct Email ** emails
Array of Emails.
Definition: mailbox.h:99
Convenience wrapper for the gui headers.
time_t mutt_date_epoch(void)
Return the number of seconds since the Unix epoch.
Definition: date.c:427
static notmuch_query_t * get_query(struct Mailbox *m, bool writable)
Create a new query.
Definition: notmuch.c:440
char * name
Query name.
Definition: url.h:59
static const char * mailbox_path(const struct Mailbox *m)
Get the Mailbox&#39;s path string.
Definition: mailbox.h:215
Notmuch tag functions.
struct NmMboxData * nm_mdata_new(const char *url)
Create a new NmMboxData object from a query.
Definition: mdata.c:69
enum MailboxType type
Mailbox type.
Definition: mailbox.h:105
int msg_count
Total number of messages.
Definition: mailbox.h:91
#define mutt_set_flag(m, e, flag, bf)
Definition: protos.h:66
bool cs_subset_bool(const struct ConfigSubset *sub, const char *name)
Get a boolean config item by name.
Definition: helpers.c:73
int mutt_str_atoi(const char *str, int *dst)
Convert ASCII string to an integer.
Definition: string.c:252
The envelope/body of an email.
Definition: email.h:37
int nm_db_get_mtime(struct Mailbox *m, time_t *mtime)
Get the database modification time.
Definition: db.c:278
enum NmWindowQueryRc nm_windowed_query_from_query(char *buf, size_t buflen, const bool force_enable, const short duration, const short cur_pos, const char *cur_search, const char *timebase, const char *or_terms)
Windows buf with notmuch date: search term.
Definition: query.c:206
void email_free(struct Email **ptr)
Free an Email.
Definition: email.c:44
bool maildir_update_flags(struct Mailbox *m, struct Email *e_old, struct Email *e_new)
Update the mailbox flags.
Definition: shared.c:122
enum MailboxType type
Type of Mailbox the Email is in.
Definition: edata.h:38
int msg_unread
Number of unread messages.
Definition: mailbox.h:92
void mailbox_size_add(struct Mailbox *m, const struct Email *e)
Add an email&#39;s size to the total size of a Mailbox.
Definition: mailbox.c:223
Structs that make up an email.
#define mutt_error(...)
Definition: logging.h:88
struct Email * maildir_parse_message(enum MailboxType type, const char *fname, bool is_old, struct Email *e)
Actually parse a maildir message.
Definition: maildir.c:926
char * nm_email_get_folder_rel_db(struct Mailbox *m, struct Email *e)
Get the folder for a Email from the same level as the notmuch database.
Definition: notmuch.c:1477
static notmuch_threads_t * get_threads(notmuch_query_t *query)
load threads for a query
Definition: notmuch.c:973
static int nm_path_parent(char *buf, size_t buflen)
Find the parent of a Mailbox path - Implements MxOps::path_parent() -.
Definition: notmuch.c:2486
int msg_flagged
Number of flagged messages.
Definition: mailbox.h:93
A parsed URL proto://user:password@host:port/path?a=1&b=2
Definition: url.h:68
WHERE SIG_ATOMIC_VOLATILE_T SigInt
true after SIGINT is received
Definition: mutt_globals.h:67
char * tag_str
Source string.
Definition: tag.h:37
int mutt_get_field(const char *field, char *buf, size_t buflen, CompletionFlags complete, bool multiple, char ***files, int *numfiles)
Ask the user for a string.
Definition: curs_lib.c:335
struct MuttThread * thread
Thread of Emails.
Definition: email.h:95
Notmuch-specific Account data.
struct HashTable * mutt_make_id_hash(struct Mailbox *m)
Create a Hash Table for message-ids.
Definition: mutt_thread.c:1658
static int nm_tags_commit(struct Mailbox *m, struct Email *e, char *buf)
Save the tags to a message - Implements MxOps::tags_commit() -.
Definition: notmuch.c:2419
void mutt_set_header_color(struct Mailbox *m, struct Email *e)
Select a colour for a message.
Definition: dlg_index.c:1382
Invalid duration.
Definition: query.h:48
const char * nm_query_type_to_string(enum NmQueryType query_type)
Turn a query type into a string.
Definition: query.c:95
#define ARRAY_FOREACH(elem, head)
Iterate over all elements of the array.
Definition: array.h:206
static void progress_setup(struct Mailbox *m)
Set up the Progress Bar.
Definition: notmuch.c:668
char * oldpath
Definition: edata.h:36
static enum MxStatus nm_mbox_check_stats(struct Mailbox *m, uint8_t flags)
Check the Mailbox statistics - Implements MxOps::mbox_check_stats() -.
Definition: notmuch.c:1772
bool maildir_sync_mailbox_message(struct Mailbox *m, int msgno, struct HeaderCache *hc)
Save changes to the mailbox.
Definition: maildir.c:946
static bool windowed_query_from_query(const char *query, char *buf, size_t buflen)
transforms a vfolder search query into a windowed one
Definition: notmuch.c:271
A group of associated Mailboxes.
Definition: account.h:36
int nm_read_entire_thread(struct Mailbox *m, struct Email *e)
Get the entire thread of an email.
Definition: notmuch.c:1497
header cache structure
Definition: lib.h:84
struct timespec mtime
Time Mailbox was last changed.
Definition: mailbox.h:107
Nondestructive flags change (IMAP)
Definition: mxapi.h:82
New mail received in Mailbox.
Definition: mxapi.h:79
static int update_email_flags(struct Mailbox *m, struct Email *e, const char *tag_str)
Update the Email&#39;s flags.
Definition: notmuch.c:1149
Parsed Query String.
Definition: url.h:57
Flagged messages.
Definition: mutt.h:98
void mx_alloc_memory(struct Mailbox *m)
Create storage for the emails.
Definition: mx.c:1212
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:370
static int nm_msg_close(struct Mailbox *m, struct Message *msg)
Close an email - Implements MxOps::msg_close() -.
Definition: notmuch.c:2397
#define _(a)
Definition: message.h:28
Mailbox wasn&#39;t recognised.
Definition: mailbox.h:47
bool maildir_msg_open_new(struct Mailbox *m, struct Message *msg, const struct Email *e)
Open a new message in a Mailbox - Implements MxOps::msg_open_new() -Open a new (temporary) message in...
Definition: maildir.c:1488
Progress tracks elements, according to $read_inc
Definition: lib.h:46
bool changed
Email has been edited.
Definition: email.h:48
static int nm_path_canon(char *buf, size_t buflen)
Canonicalise a Mailbox path - Implements MxOps::path_canon() -.
Definition: notmuch.c:2469
#define MUTT_NAMED
Definition: mutt_commands.h:79
static bool nm_ac_add(struct Account *a, struct Mailbox *m)
Add a Mailbox to an Account - Implements MxOps::ac_add() -.
Definition: notmuch.c:2029
static int init_email(struct Email *e, const char *path, notmuch_message_t *msg)
Set up an email&#39;s Notmuch data.
Definition: notmuch.c:607
char * value
Query value.
Definition: url.h:60
Email list was changed.
Definition: mailbox.h:180
void url_free(struct Url **ptr)
Free the contents of a URL.
Definition: url.c:123
void nm_query_window_reset(void)
Resets the vfolder window position to the present.
Definition: notmuch.c:1673
MxOpenReturns
Return values for mbox_open()
Definition: mxapi.h:88
static struct Mailbox * get_default_mailbox(void)
Get Mailbox for notmuch without any parameters.
Definition: notmuch.c:1871
static int update_message_path(struct Email *e, const char *path)
Set the path for a message.
Definition: notmuch.c:522
struct HeaderCache * mutt_hcache_open(const char *path, const char *folder, hcache_namer_t namer)
Multiplexor for StoreOps::open.
Definition: hcache.c:332
int nm_db_trans_begin(struct Mailbox *m)
Start a Notmuch database transaction.
Definition: db.c:229
short cs_subset_number(const struct ConfigSubset *sub, const char *name)
Get a number config item by name.
Definition: helpers.c:169
Container for Accounts, Notifications.
Definition: neomutt.h:36
A Progress Bar.
Definition: progress.c:47
notmuch_database_t * nm_db_get(struct Mailbox *m, bool writable)
Get the Notmuch database.
Definition: db.c:172
#define COMMANDS_REGISTER(cmds)
Definition: mutt_commands.h:82
Open succeeded.
Definition: mxapi.h:90
Messages that have been replied to.
Definition: mutt.h:91
Mailbox was reopened.
Definition: mxapi.h:81
Invalid timebase.
Definition: query.h:47
Convenience wrapper for the config headers.
struct NmAccountData * nm_adata_new(void)
Allocate and initialise a new NmAccountData structure.
Definition: adata.c:55
void nm_mdata_free(void **ptr)
Free the private Mailbox data - Implements Mailbox::mdata_free()
Definition: mdata.c:46
time_t tv_sec
Definition: file.h:50
int oldmsgcount
Definition: mdata.h:41
static const char * get_message_last_filename(notmuch_message_t *msg)
Get a message&#39;s last filename.
Definition: notmuch.c:651
Notmuch query functions.
bool nm_query_window_available(void)
Are windowed queries enabled for use?
Definition: notmuch.c:1621
struct NmEmailData * nm_edata_get(struct Email *e)
Get the Notmuch Email data.
Definition: edata.c:72
void(* mdata_free)(void **ptr)
Free the private data attached to the Mailbox.
Definition: mailbox.h:146
int cs_subset_str_native_set(const struct ConfigSubset *sub, const char *name, intptr_t value, struct Buffer *err)
Natively set the value of a string config item.
Definition: subset.c:305
Progress bar.
void maildir_parse_flags(struct Email *e, const char *path)
Parse Maildir file flags.
Definition: maildir.c:811
bool nm_message_is_still_queried(struct Mailbox *m, struct Email *e)
Is a message still visible in the query?
Definition: notmuch.c:1685
int nm_get_all_tags(struct Mailbox *m, char **tag_list, int *tag_count)
Fill a list with all notmuch tags.
Definition: notmuch.c:1977
int nm_record_message(struct Mailbox *m, char *path, struct Email *e)
Add a message to the Notmuch database.
Definition: notmuch.c:1894
bool read
Email is read.
Definition: email.h:51
int cs_subset_str_string_set(const struct ConfigSubset *sub, const char *name, const char *value, struct Buffer *err)
Set a config item by string.
Definition: subset.c:408
Header cache multiplexor.
struct HashTable * id_hash
Hash Table by msg id.
Definition: mailbox.h:127
Many unsorted constants and some structs.
Log at debug level 2.
Definition: logging.h:41
static int get_limit(struct NmMboxData *mdata)
Get the database limit.
Definition: notmuch.c:404
API for mailboxes.
Notmuch-specific Mailbox data -.
Definition: mdata.h:33
void nm_init(void)
Setup feature commands.
Definition: notmuch.c:99
bool old
Email is seen, but unread.
Definition: email.h:50
static char * email_get_id(struct Email *e)
Get the unique Notmuch Id.
Definition: notmuch.c:210
static enum MxStatus nm_mbox_close(struct Mailbox *m)
Close a Mailbox - Implements MxOps::mbox_close() -Nothing to do.
Definition: notmuch.c:2356
static enum MxStatus nm_mbox_check(struct Mailbox *m)
Check for new mail - Implements MxOps::mbox_check() -.
Definition: notmuch.c:2094
enum NmQueryType nm_parse_type_from_query(char *buf)
Parse a query type out of a query.
Definition: query.c:48
char * mutt_strn_dup(const char *begin, size_t len)
Duplicate a sub-string.
Definition: string.c:548
FILE * maildir_open_find_message(const char *folder, const char *msg, char **newname)
Find a new.
Definition: maildir.c:993
enum NmQueryType nm_string_to_query_type(const char *str)
Lookup a query type.
Definition: query.c:109
int mutt_file_fclose(FILE **fp)
Close a FILE handle (and NULL the pointer)
Definition: file.c:153
char * virtual_id
Unique Notmuch Id.
Definition: edata.h:37
#define MUTT_NM_TAG
Notmuch tag +/- mode.
Definition: mutt.h:63
char * folder
Location of the Email.
Definition: edata.h:35
Convenience wrapper for the core headers.
void nm_tag_array_free(struct TagArray *tags)
Free all memory of a TagArray.
Definition: tag.c:39
static notmuch_messages_t * get_messages(notmuch_query_t *query)
load messages for a query
Definition: notmuch.c:903
void nm_query_window_backward(void)
Function to move the current search window backward in time.
Definition: notmuch.c:1661
static int update_tags(notmuch_message_t *msg, const char *tag_str)
Update the tags on a message.
Definition: notmuch.c:1095
void(* adata_free)(void **ptr)
Free the private data attached to the Account.
Definition: account.h:53
long tv_nsec
Definition: file.h:51
struct Progress * progress
A progress bar.
Definition: mdata.h:40
void * mdata
Driver specific data.
Definition: mailbox.h:136
struct TagList tags
For drivers that support server tagging.
Definition: email.h:109
&#39;Maildir&#39; Mailbox type
Definition: mailbox.h:51
enum MailboxType type
Mailbox type, e.g. MUTT_IMAP.
Definition: mxapi.h:105
static int rename_filename(struct Mailbox *m, const char *old_file, const char *new_file, struct Email *e)
Rename the file.
Definition: notmuch.c:1321
NmWindowQueryRc
Return codes for nm_windowed_query_from_query()
Definition: query.h:44
static notmuch_message_t * get_nm_message(notmuch_database_t *db, struct Email *e)
Find a Notmuch message.
Definition: notmuch.c:1037
struct NmMboxData * nm_mdata_get(struct Mailbox *m)
Get the Notmuch Mailbox data.
Definition: mdata.c:98
static void nm_progress_update(struct Mailbox *m)
Update the progress counter.
Definition: notmuch.c:687
Prototypes for many functions.
void nm_query_window_forward(void)
Function to move the current search window forward in time.
Definition: notmuch.c:1640
bool active
Message is not to be removed.
Definition: email.h:59
char * nm_url_from_query(struct Mailbox *m, char *buf, size_t buflen)
Turn a query into a URL.
Definition: notmuch.c:1564
void url_pct_encode(char *buf, size_t buflen, const char *src)
Percent-encode a string.
Definition: url.c:151
struct UrlQueryList query_strings
List of query strings.
Definition: url.h:76
static int nm_msg_commit(struct Mailbox *m, struct Message *msg)
Save changes to an email - Implements MxOps::msg_commit() -.
Definition: notmuch.c:2388
struct Email * maildir_parse_stream(enum MailboxType type, FILE *fp, const char *fname, bool is_old, struct Email *e)
Parse a Maildir message.
Definition: maildir.c:882
size_t mutt_str_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix.
Definition: string.c:160
int nm_db_trans_end(struct Mailbox *m)
End a database transaction.
Definition: db.c:251
A local copy of an email.
Definition: mxapi.h:41
int email_max
Number of pointers in emails.
Definition: mailbox.h:100
static int nm_tags_edit(struct Mailbox *m, const char *tags, char *buf, size_t buflen)
Prompt and validate new messages tags - Implements MxOps::tags_edit() -.
Definition: notmuch.c:2406
Create/manipulate threading in emails.
A mailbox.
Definition: mailbox.h:81
#define PATH_MAX
Definition: mutt.h:40
Functions to parse commands in a config file.
Notmuch-specific Email data -.
Definition: edata.h:33
char * nm_email_get_folder(struct Email *e)
Get the folder for a Email.
Definition: notmuch.c:1458
void * mutt_mem_malloc(size_t size)
Allocate memory on the heap.
Definition: memory.c:90
static struct HeaderCache * nm_hcache_open(struct Mailbox *m)
Open a header cache.
Definition: notmuch.c:109
void * adata
Private data (for Mailbox backends)
Definition: account.h:43
static int init_mailbox(struct Mailbox *m)
Add Notmuch data to the Mailbox.
Definition: notmuch.c:188
char * driver_tags_get(struct TagList *list)
Get tags.
Definition: tags.c:145
struct NmEmailData * nm_edata_new(void)
Create a new NmEmailData for an email.
Definition: edata.c:61
No changes.
Definition: mxapi.h:78
static char * get_query_string(struct NmMboxData *mdata, bool window)
builds the notmuch vfolder search string
Definition: notmuch.c:343
struct MaildirEmailData * maildir_edata_new(void)
Create a new MaildirEmailData object.
Definition: edata.c:53
struct TagArray nm_tag_str_to_tags(const char *tag_str)
Converts a comma and/or space-delimited string of tags into an array.
Definition: tag.c:50
char * db_query
Previous query.
Definition: mdata.h:36
Query was successful.
Definition: query.h:46
Open was aborted.
Definition: mxapi.h:92
void nm_db_free(notmuch_database_t *db)
decoupled way to close a Notmuch database
Definition: db.c:213
static void append_thread(struct HeaderCache *h, struct Mailbox *m, notmuch_query_t *q, notmuch_thread_t *thread, bool dedup)
add each top level reply in the thread
Definition: notmuch.c:879
Messages that have been read.
Definition: mutt.h:92
Definitions of NeoMutt commands.
const char * cs_subset_string(const struct ConfigSubset *sub, const char *name)
Get a string config item by name.
Definition: helpers.c:317
char msg[1024]
Message to display.
Definition: progress.c:50
static unsigned int count_query(notmuch_database_t *db, const char *qstr, int limit)
Count the results of a query.
Definition: notmuch.c:1425
bool verbose
Display status messages?
Definition: mailbox.h:118
int mutt_hcache_store(struct HeaderCache *hc, const char *key, size_t keylen, struct Email *e, uint32_t uidvalidity)
Multiplexor for StoreOps::store.
Definition: hcache.c:556
Default: Messages only.
Definition: query.h:36
size_t mutt_istr_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix, ignoring case.
Definition: string.c:172
static void sync_email_path_with_nm(struct Email *e, notmuch_message_t *msg)
Synchronize Neomutt&#39;s Email path with notmuch.
Definition: notmuch.c:1078
#define STAILQ_FOREACH(var, head, field)
Definition: queue.h:352
const char * cs_subset_path(const struct ConfigSubset *sub, const char *name)
Get a path config item by name.
Definition: helpers.c:194
#define mutt_debug(LEVEL,...)
Definition: logging.h:85
Unknown query type. Error in notmuch query.
Definition: query.h:38
static char * nm_get_default_url(void)
Create a Mailbox with default Notmuch settings.
Definition: notmuch.c:136
Maildir local mailbox type.
void progress_free(struct Progress **ptr)
Free a Progress Bar.
Definition: progress.c:228
void progress_update(struct Progress *progress, size_t pos, int percent)
Update the state of the progress bar.
Definition: progress.c:175
struct Url * db_url
Parsed view url of the Notmuch database.
Definition: mdata.h:35
static void nm_hcache_close(struct HeaderCache *h)
Close the header cache.
Definition: notmuch.c:124
size_t mutt_buffer_strcpy(struct Buffer *buf, const char *s)
Copy a string into a Buffer.
Definition: buffer.c:312
char * path
Path.
Definition: url.h:75
&#39;Notmuch&#39; (virtual) Mailbox type
Definition: mailbox.h:54
void mutt_hcache_close(struct HeaderCache *hc)
Multiplexor for StoreOps::close.
Definition: hcache.c:435
const char NmUrlProtocol[]
Definition: notmuch.c:93
uint8_t flags
e.g. MB_NORMAL
Definition: mailbox.h:134
void maildir_gen_flags(char *dest, size_t destlen, struct Email *e)
Generate the Maildir flags for an email.
Definition: maildir.c:186
size_t mutt_str_len(const char *a)
Calculate the length of a string, safely.
Definition: string.c:664
void nm_adata_free(void **ptr)
Free the private Account data - Implements Account::adata_free()
Definition: adata.c:39
enum CommandResult parse_mailboxes(struct Buffer *buf, struct Buffer *s, intptr_t data, struct Buffer *err)
Parse the &#39;mailboxes&#39; command - Implements Command::parse() -This is also used by &#39;virtual-mailboxes&#39;...
enum MailboxType nm_path_probe(const char *path, const struct stat *st)
Is this a Notmuch Mailbox? - Implements MxOps::path_probe() -.
Definition: notmuch.c:2458
MailboxType
Supported mailbox formats.
Definition: mailbox.h:43
Maildir-specific Email data.
Log at debug level 1.
Definition: logging.h:40
static enum MxOpenReturns nm_mbox_open(struct Mailbox *m)
Open a Mailbox - Implements MxOps::mbox_open() -.
Definition: notmuch.c:2044
size_t mutt_str_copy(char *dest, const char *src, size_t dsize)
Copy a string into a buffer (guaranteeing NUL-termination)
Definition: string.c:749
Array of Notmuch tags.
Definition: tag.h:34
int msg_new
Number of new messages.
Definition: mailbox.h:95
bool mutt_strn_equal(const char *a, const char *b, size_t num)
Check for equality of two strings (to a maximum), safely.
Definition: string.c:593
static enum MxStatus nm_mbox_sync(struct Mailbox *m)
Save changes to the Mailbox - Implements MxOps::mbox_sync() -.
Definition: notmuch.c:2228
char * driver_tags_get_transformed(struct TagList *list)
Get transformed tags.
Definition: tags.c:133
Progress tracks elements, according to $write_inc
Definition: lib.h:47
static bool read_mesgs_query(struct Mailbox *m, notmuch_query_t *q, bool dedup)
Search for matching messages.
Definition: notmuch.c:931
bool deleted
Email is deleted.
Definition: email.h:45
int nm_db_release(struct Mailbox *m)
Close the Notmuch database.
Definition: db.c:196
void * edata
Driver-specific data.
Definition: email.h:111
struct Progress * progress_new(const char *msg, enum ProgressType type, size_t size)
Create a new Progress Bar.
Definition: progress.c:246
static bool nm_message_has_tag(notmuch_message_t *msg, char *tag)
Does a message have this tag?
Definition: notmuch.c:1056
Whole threads.
Definition: query.h:37
static char * email_get_fullpath(struct Email *e, char *buf, size_t buflen)
Get the full path of an email.
Definition: notmuch.c:226
char * path
Path of Email (for local Mailboxes)
Definition: email.h:92
static char * get_folder_from_path(const char *path)
Find an email&#39;s folder from its path.
Definition: notmuch.c:560
FILE * fp
pointer to the message data
Definition: mxapi.h:43
int ignmsgcount
Ignored messages.
Definition: mdata.h:42
int index
The absolute (unsorted) message number.
Definition: email.h:86
static void append_message(struct HeaderCache *h, struct Mailbox *m, notmuch_query_t *q, notmuch_message_t *msg, bool dedup)
Associate a message.
Definition: notmuch.c:739
#define mutt_message(...)
Definition: logging.h:87
#define FREE(x)
Definition: memory.h:40
static int update_email_tags(struct Email *e, notmuch_message_t *msg)
Update the Email&#39;s tags from Notmuch.
Definition: notmuch.c:472
struct Mailbox * mx_path_resolve(const char *path)
Get a Mailbox for a path.
Definition: mx.c:1668
static struct Email * get_mutt_email(struct Mailbox *m, notmuch_message_t *msg)
Get the Email of a Notmuch message.
Definition: notmuch.c:704
enum CommandResult parse_unmailboxes(struct Buffer *buf, struct Buffer *s, intptr_t data, struct Buffer *err)
Parse the &#39;unmailboxes&#39; command - Implements Command::parse() -This is also used by &#39;unvirtual-mailbo...
bool mx_mbox_ac_link(struct Mailbox *m)
Link a Mailbox to an existing or new Account.
Definition: mx.c:267
static char * nm2mutt_message_id(const char *id)
converts notmuch message Id to neomutt message Id
Definition: notmuch.c:585
struct HCacheEntry mutt_hcache_fetch(struct HeaderCache *hc, const char *key, size_t keylen, uint32_t uidvalidity)
Multiplexor for StoreOps::fetch.
Definition: hcache.c:461
static void append_replies(struct HeaderCache *h, struct Mailbox *m, notmuch_query_t *q, notmuch_message_t *top, bool dedup)
add all the replies to a given messages into the display
Definition: notmuch.c:852
const int NmUrlProtocolLen
Definition: notmuch.c:94
Hundreds of global variables to back the user variables.
void maildir_edata_free(void **ptr)
Free the private Email data - Implements Email::edata_free()
Definition: edata.c:38
struct ConfigSubset * sub
Inherited config items.
Definition: neomutt.h:39
bool driver_tags_replace(struct TagList *head, char *tags)
Replace all tags.
Definition: tags.c:186
struct Email * email
Retrieved email.
Definition: lib.h:99
int mutt_str_asprintf(char **strp, const char *fmt,...)
Definition: string.c:1128
static int remove_filename(struct Mailbox *m, const char *path)
Delete a file.
Definition: notmuch.c:1257
static void query_window_reset(void)
Restore vfolder&#39;s search window to its original position.
Definition: notmuch.c:241
struct Buffer pathbuf
Definition: mailbox.h:83
Convenience wrapper for the library headers.
Open failed with an error.
Definition: mxapi.h:91
static struct NmMboxData * nm_get_default_data(void)
Create a Mailbox with default Notmuch settings.
Definition: notmuch.c:165
void mutt_str_append_item(char **str, const char *item, char sep)
Add string to another separated by sep.
Definition: string.c:466
GUI manage the main index (list of emails)
Notmuch private types.
static int nm_path_pretty(char *buf, size_t buflen, const char *folder)
Abbreviate a Mailbox path - Implements MxOps::path_pretty() -.
Definition: notmuch.c:2477
static void apply_exclude_tags(notmuch_query_t *query)
Exclude the configured tags.
Definition: notmuch.c:413
int nm_update_filename(struct Mailbox *m, const char *old_file, const char *new_file, struct Email *e)
Change the filename.
Definition: notmuch.c:1747
enum NmQueryType query_type
Messages or Threads.
Definition: mdata.h:38
void mailbox_changed(struct Mailbox *m, enum NotifyMailbox action)
Notify observers of a change to a Mailbox.
Definition: mailbox.c:208
int db_limit
Maximum number of results to return.
Definition: mdata.h:37
static bool nm_ac_owns_path(struct Account *a, const char *path)
Check whether an Account owns a Mailbox path - Implements MxOps::ac_owns_path() -.
Definition: notmuch.c:2021
MxStatus
Return values from mbox_check(), mbox_check_stats(), mbox_snc(), and mbox_close() ...
Definition: mxapi.h:75
static bool nm_msg_open(struct Mailbox *m, struct Message *msg, int msgno)
Open an email message in a Mailbox - Implements MxOps::msg_open() -.
Definition: notmuch.c:2364
static bool read_threads_query(struct Mailbox *m, notmuch_query_t *q, bool dedup, int limit)
Perform a query with threads.
Definition: notmuch.c:1001
Notmuch-specific Mailbox data.
static int rename_maildir_filename(const char *old, char *buf, size_t buflen, struct Email *e)
Rename a Maildir file.
Definition: notmuch.c:1204
Definition: mxapi.h:103
int msgno
Number displayed to the user.
Definition: email.h:87
void * nm_edata
Notmuch private data.
Definition: email.h:106
Notmuch-specific Account data -.
Definition: adata.h:34
An error occurred.
Definition: mxapi.h:77
struct Url * url_parse(const char *src)
Fill in Url.
Definition: url.c:234
const char * nm_db_get_filename(struct Mailbox *m)
Get the filename of the Notmuch database.
Definition: db.c:57