NeoMutt  2018-07-16 +2481-68dcde
Teaching an old dog new tricks
DOXYGEN
mutt_notmuch.c
Go to the documentation of this file.
1 
44 #include "config.h"
45 #include <ctype.h>
46 #include <errno.h>
47 #include <limits.h>
48 #include <notmuch.h>
49 #include <stdbool.h>
50 #include <stdio.h>
51 #include <string.h>
52 #include <unistd.h>
53 #include "notmuch_private.h"
54 #include "mutt/mutt.h"
55 #include "config/lib.h"
56 #include "email/lib.h"
57 #include "core/lib.h"
58 #include "mutt.h"
59 #include "mutt_notmuch.h"
60 #include "curs_lib.h"
61 #include "globals.h"
62 #include "hcache/hcache.h"
63 #include "index.h"
64 #include "maildir/lib.h"
65 #include "mutt_thread.h"
66 #include "mx.h"
67 #include "progress.h"
68 #include "protos.h"
69 
70 const char NmUriProtocol[] = "notmuch://";
71 const int NmUriProtocolLen = sizeof(NmUriProtocol) - 1;
72 
73 /* These Config Variables are only used in notmuch/mutt_notmuch.c */
85 
92 {
93 #ifdef USE_HCACHE
95 #else
96  return NULL;
97 #endif
98 }
99 
105 {
106 #ifdef USE_HCACHE
108 #endif
109 }
110 
116 static enum NmQueryType string_to_query_type(const char *str)
117 {
118  if (mutt_str_strcmp(str, "threads") == 0)
119  return NM_QUERY_TYPE_THREADS;
120  else if (mutt_str_strcmp(str, "messages") == 0)
121  return NM_QUERY_TYPE_MESGS;
122 
123  mutt_error(_("failed to parse notmuch query type: %s"), NONULL(str));
124  return NM_QUERY_TYPE_MESGS;
125 }
126 
131 void nm_adata_free(void **ptr)
132 {
133  if (!ptr || !*ptr)
134  return;
135 
136  struct NmAccountData *adata = *ptr;
137  if (adata->db)
138  {
139  nm_db_free(adata->db);
140  adata->db = NULL;
141  }
142 
143  FREE(ptr);
144 }
145 
151 {
152  struct NmAccountData *adata = mutt_mem_calloc(1, sizeof(struct NmAccountData));
153 
154  return adata;
155 }
156 
164 {
165  if (!m || (m->magic != MUTT_NOTMUCH))
166  return NULL;
167 
168  struct Account *a = m->account;
169  if (!a)
170  return NULL;
171 
172  return a->adata;
173 }
174 
183 void nm_mdata_free(void **ptr)
184 {
185  if (!ptr || !*ptr)
186  return;
187 
188  struct NmMboxData *mdata = *ptr;
189 
190  mutt_debug(LL_DEBUG1, "nm: freeing context data %p\n", mdata);
191 
192  url_free(&mdata->db_url);
193  FREE(&mdata->db_query);
194  FREE(ptr);
195 }
196 
205 struct NmMboxData *nm_mdata_new(const char *uri)
206 {
207  if (!uri)
208  return NULL;
209 
210  struct NmMboxData *mdata = mutt_mem_calloc(1, sizeof(struct NmMboxData));
211  mutt_debug(LL_DEBUG1, "nm: initialize mailbox mdata %p\n", (void *) mdata);
212 
213  mdata->db_limit = C_NmDbLimit;
214  mdata->query_type = string_to_query_type(C_NmQueryType);
215  mdata->db_url = url_parse(uri);
216  if (!mdata->db_url)
217  {
218  mutt_error(_("failed to parse notmuch uri: %s"), uri);
219  FREE(&mdata);
220  return NULL;
221  }
222  return mdata;
223 }
224 
231 struct NmMboxData *nm_mdata_get(struct Mailbox *m)
232 {
233  if (!m || (m->magic != MUTT_NOTMUCH))
234  return NULL;
235 
236  return m->mdata;
237 }
238 
246 void nm_edata_free(void **ptr)
247 {
248  if (!ptr || !*ptr)
249  return;
250 
251  struct NmEmailData *edata = *ptr;
252  mutt_debug(LL_DEBUG2, "nm: freeing email %p\n", (void *) edata);
253  FREE(&edata->folder);
254  FREE(&edata->oldpath);
255  FREE(&edata->virtual_id);
256  FREE(ptr);
257 }
258 
264 {
265  return mutt_mem_calloc(1, sizeof(struct NmEmailData));
266 }
267 
274 {
275  // path to DB + query + URI "decoration"
276  char uri[PATH_MAX + 1024 + 32];
277 
278  // Try to use C_NmDefaultUri or C_Folder.
279  // If neither are set, it is impossible to create a Notmuch URI.
280  if (C_NmDefaultUri)
281  snprintf(uri, sizeof(uri), "%s", C_NmDefaultUri);
282  else if (C_Folder)
283  snprintf(uri, sizeof(uri), "notmuch://%s", C_Folder);
284  else
285  return NULL;
286 
287  return nm_mdata_new(uri);
288 }
289 
300 static int init_mailbox(struct Mailbox *m)
301 {
302  if (!m || (m->magic != MUTT_NOTMUCH))
303  return -1;
304 
305  if (m->mdata)
306  return 0;
307 
309  if (!m->mdata)
310  return -1;
311 
313  return 0;
314 }
315 
322 static char *email_get_id(struct Email *e)
323 {
324  return (e && e->edata) ? ((struct NmEmailData *) e->edata)->virtual_id : NULL;
325 }
326 
334 static char *email_get_fullpath(struct Email *e, char *buf, size_t buflen)
335 {
336  snprintf(buf, buflen, "%s/%s", nm_email_get_folder(e), e->path);
337  return buf;
338 }
339 
347 static const char *query_type_to_string(enum NmQueryType query_type)
348 {
349  if (query_type == NM_QUERY_TYPE_THREADS)
350  return "threads";
351  else
352  return "messages";
353 }
354 
365 static bool query_window_check_timebase(const char *timebase)
366 {
367  if ((strcmp(timebase, "hour") == 0) || (strcmp(timebase, "day") == 0) ||
368  (strcmp(timebase, "week") == 0) || (strcmp(timebase, "month") == 0) ||
369  (strcmp(timebase, "year") == 0))
370  {
371  return true;
372  }
373  return false;
374 }
375 
385 static void query_window_reset(void)
386 {
387  mutt_debug(LL_DEBUG2, "entering\n");
388  cs_str_native_set(Config, "nm_query_window_current_position", 0, NULL);
389 }
390 
429 static bool windowed_query_from_query(const char *query, char *buf, size_t buflen)
430 {
431  mutt_debug(LL_DEBUG2, "nm: %s\n", query);
432 
435 
436  /* if the duration is a non positive integer, disable the window */
437  if (C_NmQueryWindowDuration <= 0)
438  {
440  return false;
441  }
442 
443  /* if the query has changed, reset the window position */
444  if (!C_NmQueryWindowCurrentSearch || (strcmp(query, C_NmQueryWindowCurrentSearch) != 0))
446 
448  {
449  mutt_message(_("Invalid nm_query_window_timebase value (valid values are: "
450  "hour, day, week, month or year)"));
451  mutt_debug(LL_DEBUG2, "Invalid nm_query_window_timebase value\n");
452  return false;
453  }
454 
455  if (end == 0)
456  {
457  // Open-ended date allows mail from the future.
458  // This may occur is the sender's time settings are off.
459  snprintf(buf, buflen, "date:%d%s.. and %s", beg, C_NmQueryWindowTimebase,
461  }
462  else
463  {
464  snprintf(buf, buflen, "date:%d%s..%d%s and %s", beg, C_NmQueryWindowTimebase,
466  }
467 
468  mutt_debug(LL_DEBUG2, "nm: %s -> %s\n", query, buf);
469 
470  return true;
471 }
472 
489 static char *get_query_string(struct NmMboxData *mdata, bool window)
490 {
491  mutt_debug(LL_DEBUG2, "nm: %s\n", window ? "true" : "false");
492 
493  if (!mdata)
494  return NULL;
495  if (mdata->db_query)
496  return mdata->db_query;
497 
498  mdata->query_type = string_to_query_type(C_NmQueryType); /* user's default */
499 
500  struct UrlQuery *item = NULL;
501  STAILQ_FOREACH(item, &mdata->db_url->query_strings, entries)
502  {
503  if (!item->value || !item->name)
504  continue;
505 
506  if (strcmp(item->name, "limit") == 0)
507  {
508  if (mutt_str_atoi(item->value, &mdata->db_limit))
509  mutt_error(_("failed to parse notmuch limit: %s"), item->value);
510  }
511  else if (strcmp(item->name, "type") == 0)
512  mdata->query_type = string_to_query_type(item->value);
513 
514  else if (strcmp(item->name, "query") == 0)
515  mdata->db_query = mutt_str_strdup(item->value);
516  }
517 
518  if (!mdata->db_query)
519  return NULL;
520 
521  if (window)
522  {
523  char buf[1024];
525 
526  /* if a date part is defined, do not apply windows (to avoid the risk of
527  * having a non-intersected date frame). A good improvement would be to
528  * accept if they intersect */
529  if (!strstr(mdata->db_query, "date:") &&
530  windowed_query_from_query(mdata->db_query, buf, sizeof(buf)))
531  {
532  mdata->db_query = mutt_str_strdup(buf);
533  }
534 
535  mutt_debug(LL_DEBUG2, "nm: query (windowed) '%s'\n", mdata->db_query);
536  }
537  else
538  mutt_debug(LL_DEBUG2, "nm: query '%s'\n", mdata->db_query);
539 
540  return mdata->db_query;
541 }
542 
548 static int get_limit(struct NmMboxData *mdata)
549 {
550  return mdata ? mdata->db_limit : 0;
551 }
552 
557 static void apply_exclude_tags(notmuch_query_t *query)
558 {
559  if (!C_NmExcludeTags || !query)
560  return;
561 
562  char *end = NULL, *tag = NULL;
563 
564  char *buf = mutt_str_strdup(C_NmExcludeTags);
565 
566  for (char *p = buf; p && (p[0] != '\0'); p++)
567  {
568  if (!tag && isspace(*p))
569  continue;
570  if (!tag)
571  tag = p; /* begin of the tag */
572  if ((p[0] == ',') || (p[0] == ' '))
573  end = p; /* terminate the tag */
574  else if (p[1] == '\0')
575  end = p + 1; /* end of optstr */
576  if (!tag || !end)
577  continue;
578  if (tag >= end)
579  break;
580  *end = '\0';
581 
582  mutt_debug(LL_DEBUG2, "nm: query exclude tag '%s'\n", tag);
583  notmuch_query_add_tag_exclude(query, tag);
584  end = NULL;
585  tag = NULL;
586  }
587  notmuch_query_set_omit_excluded(query, 1);
588  FREE(&buf);
589 }
590 
598 static notmuch_query_t *get_query(struct Mailbox *m, bool writable)
599 {
600  struct NmMboxData *mdata = nm_mdata_get(m);
601  if (!mdata)
602  return NULL;
603 
604  notmuch_database_t *db = nm_db_get(m, writable);
605  const char *str = get_query_string(mdata, true);
606 
607  if (!db || !str)
608  goto err;
609 
610  notmuch_query_t *q = notmuch_query_create(db, str);
611  if (!q)
612  goto err;
613 
615  notmuch_query_set_sort(q, NOTMUCH_SORT_NEWEST_FIRST);
616  mutt_debug(LL_DEBUG2, "nm: query successfully initialized (%s)\n", str);
617  return q;
618 err:
619  nm_db_release(m);
620  return NULL;
621 }
622 
630 static int update_email_tags(struct Email *e, notmuch_message_t *msg)
631 {
632  struct NmEmailData *edata = e->edata;
633  char *new_tags = NULL;
634  char *old_tags = NULL;
635 
636  mutt_debug(LL_DEBUG2, "nm: tags update requested (%s)\n", edata->virtual_id);
637 
638  for (notmuch_tags_t *tags = notmuch_message_get_tags(msg);
639  tags && notmuch_tags_valid(tags); notmuch_tags_move_to_next(tags))
640  {
641  const char *t = notmuch_tags_get(tags);
642  if (!t || !*t)
643  continue;
644 
645  mutt_str_append_item(&new_tags, t, ' ');
646  }
647 
648  old_tags = driver_tags_get(&e->tags);
649 
650  if (new_tags && old_tags && (strcmp(old_tags, new_tags) == 0))
651  {
652  FREE(&old_tags);
653  FREE(&new_tags);
654  mutt_debug(LL_DEBUG2, "nm: tags unchanged\n");
655  return 1;
656  }
657 
658  /* new version */
659  driver_tags_replace(&e->tags, new_tags);
660  FREE(&new_tags);
661 
662  new_tags = driver_tags_get_transformed(&e->tags);
663  mutt_debug(LL_DEBUG2, "nm: new tags: '%s'\n", new_tags);
664  FREE(&new_tags);
665 
666  new_tags = driver_tags_get(&e->tags);
667  mutt_debug(LL_DEBUG2, "nm: new tag transforms: '%s'\n", new_tags);
668  FREE(&new_tags);
669 
670  return 0;
671 }
672 
680 static int update_message_path(struct Email *e, const char *path)
681 {
682  struct NmEmailData *edata = e->edata;
683 
684  mutt_debug(LL_DEBUG2, "nm: path update requested path=%s, (%s)\n", path, edata->virtual_id);
685 
686  char *p = strrchr(path, '/');
687  if (p && ((p - path) > 3) &&
688  ((strncmp(p - 3, "cur", 3) == 0) || (strncmp(p - 3, "new", 3) == 0) ||
689  (strncmp(p - 3, "tmp", 3) == 0)))
690  {
691  edata->magic = MUTT_MAILDIR;
692 
693  FREE(&e->path);
694  FREE(&edata->folder);
695 
696  p -= 3; /* skip subfolder (e.g. "new") */
697  e->path = mutt_str_strdup(p);
698 
699  for (; (p > path) && (*(p - 1) == '/'); p--)
700  ;
701 
702  edata->folder = mutt_str_substr_dup(path, p);
703 
704  mutt_debug(LL_DEBUG2, "nm: folder='%s', file='%s'\n", edata->folder, e->path);
705  return 0;
706  }
707 
708  return 1;
709 }
710 
717 static char *get_folder_from_path(const char *path)
718 {
719  char *p = strrchr(path, '/');
720 
721  if (p && ((p - path) > 3) &&
722  ((strncmp(p - 3, "cur", 3) == 0) || (strncmp(p - 3, "new", 3) == 0) ||
723  (strncmp(p - 3, "tmp", 3) == 0)))
724  {
725  p -= 3;
726  for (; (p > path) && (*(p - 1) == '/'); p--)
727  ;
728 
729  return mutt_str_substr_dup(path, p);
730  }
731 
732  return NULL;
733 }
734 
742 static char *nm2mutt_message_id(const char *id)
743 {
744  size_t sz;
745  char *mid = NULL;
746 
747  if (!id)
748  return NULL;
749  sz = strlen(id) + 3;
750  mid = mutt_mem_malloc(sz);
751 
752  snprintf(mid, sz, "<%s>", id);
753  return mid;
754 }
755 
764 static int init_email(struct Email *e, const char *path, notmuch_message_t *msg)
765 {
766  if (e->edata)
767  return 0;
768 
769  struct NmEmailData *edata = nm_edata_new();
770  e->edata = edata;
772 
773  /* Notmuch ensures that message Id exists (if not notmuch Notmuch will
774  * generate an ID), so it's more safe than use neomutt Email->env->id */
775  const char *id = notmuch_message_get_message_id(msg);
776  edata->virtual_id = mutt_str_strdup(id);
777 
778  mutt_debug(LL_DEBUG2, "nm: [e=%p, edata=%p] (%s)\n", (void *) e, (void *) e->edata, id);
779 
780  char *nm_msg_id = nm2mutt_message_id(id);
781  if (!e->env->message_id)
782  {
783  e->env->message_id = nm_msg_id;
784  }
785  else if (mutt_str_strcmp(e->env->message_id, nm_msg_id) != 0)
786  {
787  FREE(&e->env->message_id);
788  e->env->message_id = nm_msg_id;
789  }
790  else
791  {
792  FREE(&nm_msg_id);
793  }
794 
795  if (update_message_path(e, path) != 0)
796  return -1;
797 
798  update_email_tags(e, msg);
799 
800  return 0;
801 }
802 
809 static const char *get_message_last_filename(notmuch_message_t *msg)
810 {
811  const char *name = NULL;
812 
813  for (notmuch_filenames_t *ls = notmuch_message_get_filenames(msg);
814  ls && notmuch_filenames_valid(ls); notmuch_filenames_move_to_next(ls))
815  {
816  name = notmuch_filenames_get(ls);
817  }
818 
819  return name;
820 }
821 
826 static void progress_reset(struct Mailbox *m)
827 {
828  if (m->quiet)
829  return;
830 
831  struct NmMboxData *mdata = nm_mdata_get(m);
832  if (!mdata)
833  return;
834 
835  memset(&mdata->progress, 0, sizeof(mdata->progress));
836  mdata->oldmsgcount = m->msg_count;
837  mdata->ignmsgcount = 0;
838  mdata->noprogress = false;
839  mdata->progress_ready = false;
840 }
841 
847 static void progress_update(struct Mailbox *m, notmuch_query_t *q)
848 {
849  struct NmMboxData *mdata = nm_mdata_get(m);
850 
851  if (m->quiet || !mdata || mdata->noprogress)
852  return;
853 
854  if (!mdata->progress_ready && q)
855  {
856  // The total mail count is in oldmsgcount, so use that instead of recounting.
857  mutt_progress_init(&mdata->progress, _("Reading messages..."),
859  mdata->progress_ready = true;
860  }
861 
862  if (mdata->progress_ready)
863  {
864  mutt_progress_update(&mdata->progress, m->msg_count + mdata->ignmsgcount, -1);
865  }
866 }
867 
875 static struct Email *get_mutt_email(struct Mailbox *m, notmuch_message_t *msg)
876 {
877  if (!m || !msg)
878  return NULL;
879 
880  const char *id = notmuch_message_get_message_id(msg);
881  if (!id)
882  return NULL;
883 
884  mutt_debug(LL_DEBUG2, "nm: neomutt email, id='%s'\n", id);
885 
886  if (!m->id_hash)
887  {
888  mutt_debug(LL_DEBUG2, "nm: init hash\n");
889  m->id_hash = mutt_make_id_hash(m);
890  if (!m->id_hash)
891  return NULL;
892  }
893 
894  char *mid = nm2mutt_message_id(id);
895  mutt_debug(LL_DEBUG2, "nm: neomutt id='%s'\n", mid);
896 
897  struct Email *e = mutt_hash_find(m->id_hash, mid);
898  FREE(&mid);
899  return e;
900 }
901 
910 static void append_message(header_cache_t *h, struct Mailbox *m,
911  notmuch_query_t *q, notmuch_message_t *msg, bool dedup)
912 {
913  struct NmMboxData *mdata = nm_mdata_get(m);
914  if (!mdata)
915  return;
916 
917  char *newpath = NULL;
918  struct Email *e = NULL;
919 
920  /* deduplicate */
921  if (dedup && get_mutt_email(m, msg))
922  {
923  mdata->ignmsgcount++;
924  progress_update(m, q);
925  mutt_debug(LL_DEBUG2, "nm: ignore id=%s, already in the m\n",
926  notmuch_message_get_message_id(msg));
927  return;
928  }
929 
930  const char *path = get_message_last_filename(msg);
931  if (!path)
932  return;
933 
934  mutt_debug(LL_DEBUG2, "nm: appending message, i=%d, id=%s, path=%s\n",
935  m->msg_count, notmuch_message_get_message_id(msg), path);
936 
937  if (m->msg_count >= m->email_max)
938  {
939  mutt_debug(LL_DEBUG2, "nm: allocate mx memory\n");
940  mx_alloc_memory(m);
941  }
942 
943 #ifdef USE_HCACHE
944  void *from_cache = mutt_hcache_fetch(h, path, mutt_str_strlen(path));
945  if (from_cache)
946  {
947  e = mutt_hcache_restore(from_cache);
948  }
949  else
950 #endif
951  {
952  if (access(path, F_OK) == 0)
953  e = maildir_parse_message(MUTT_MAILDIR, path, false, NULL);
954  else
955  {
956  /* maybe moved try find it... */
957  char *folder = get_folder_from_path(path);
958 
959  if (folder)
960  {
961  FILE *fp = maildir_open_find_message(folder, path, &newpath);
962  if (fp)
963  {
964  e = maildir_parse_stream(MUTT_MAILDIR, fp, newpath, false, NULL);
965  fclose(fp);
966 
967  mutt_debug(LL_DEBUG1, "nm: not up-to-date: %s -> %s\n", path, newpath);
968  }
969  }
970  FREE(&folder);
971  }
972  }
973 
974  if (!e)
975  {
976  mutt_debug(LL_DEBUG1, "nm: failed to parse message: %s\n", path);
977  goto done;
978  }
979 
980 #ifdef USE_HCACHE
981 
982  if (from_cache)
983  {
984  mutt_hcache_free(h, &from_cache);
985  }
986  else
987  {
988  mutt_hcache_store(h, newpath ? newpath : path,
989  mutt_str_strlen(newpath ? newpath : path), e, 0);
990  }
991 #endif
992  if (init_email(e, newpath ? newpath : path, msg) != 0)
993  {
994  email_free(&e);
995  mutt_debug(LL_DEBUG1, "nm: failed to append email!\n");
996  goto done;
997  }
998 
999  e->active = true;
1000  e->index = m->msg_count;
1001  mailbox_size_add(m, e);
1002  m->emails[m->msg_count] = e;
1003  m->msg_count++;
1004 
1005  if (newpath)
1006  {
1007  /* remember that file has been moved -- nm_mbox_sync() will update the DB */
1008  struct NmEmailData *edata = e->edata;
1009 
1010  if (edata)
1011  {
1012  mutt_debug(LL_DEBUG1, "nm: remember obsolete path: %s\n", path);
1013  edata->oldpath = mutt_str_strdup(path);
1014  }
1015  }
1016  progress_update(m, q);
1017 done:
1018  FREE(&newpath);
1019 }
1020 
1031 static void append_replies(header_cache_t *h, struct Mailbox *m,
1032  notmuch_query_t *q, notmuch_message_t *top, bool dedup)
1033 {
1034  notmuch_messages_t *msgs = NULL;
1035 
1036  for (msgs = notmuch_message_get_replies(top); notmuch_messages_valid(msgs);
1037  notmuch_messages_move_to_next(msgs))
1038  {
1039  notmuch_message_t *nm = notmuch_messages_get(msgs);
1040  append_message(h, m, q, nm, dedup);
1041  /* recurse through all the replies to this message too */
1042  append_replies(h, m, q, nm, dedup);
1043  notmuch_message_destroy(nm);
1044  }
1045 }
1046 
1058 static void append_thread(header_cache_t *h, struct Mailbox *m,
1059  notmuch_query_t *q, notmuch_thread_t *thread, bool dedup)
1060 {
1061  notmuch_messages_t *msgs = NULL;
1062 
1063  for (msgs = notmuch_thread_get_toplevel_messages(thread);
1064  notmuch_messages_valid(msgs); notmuch_messages_move_to_next(msgs))
1065  {
1066  notmuch_message_t *nm = notmuch_messages_get(msgs);
1067  append_message(h, m, q, nm, dedup);
1068  append_replies(h, m, q, nm, dedup);
1069  notmuch_message_destroy(nm);
1070  }
1071 }
1072 
1082 static notmuch_messages_t *get_messages(notmuch_query_t *query)
1083 {
1084  if (!query)
1085  return NULL;
1086 
1087  notmuch_messages_t *msgs = NULL;
1088 
1089 #if LIBNOTMUCH_CHECK_VERSION(5, 0, 0)
1090  if (notmuch_query_search_messages(query, &msgs) != NOTMUCH_STATUS_SUCCESS)
1091  return NULL;
1092 #elif LIBNOTMUCH_CHECK_VERSION(4, 3, 0)
1093  if (notmuch_query_search_messages_st(query, &msgs) != NOTMUCH_STATUS_SUCCESS)
1094  return NULL;
1095 #else
1096  msgs = notmuch_query_search_messages(query);
1097 #endif
1098 
1099  return msgs;
1100 }
1101 
1110 static bool read_mesgs_query(struct Mailbox *m, notmuch_query_t *q, bool dedup)
1111 {
1112  struct NmMboxData *mdata = nm_mdata_get(m);
1113  if (!mdata)
1114  return false;
1115 
1116  int limit = get_limit(mdata);
1117 
1118  notmuch_messages_t *msgs = get_messages(q);
1119 
1120  if (!msgs)
1121  return false;
1122 
1124 
1125  for (; notmuch_messages_valid(msgs) && ((limit == 0) || (m->msg_count < limit));
1126  notmuch_messages_move_to_next(msgs))
1127  {
1128  if (SigInt == 1)
1129  {
1130  nm_hcache_close(h);
1131  SigInt = 0;
1132  return false;
1133  }
1134  notmuch_message_t *nm = notmuch_messages_get(msgs);
1135  append_message(h, m, q, nm, dedup);
1136  notmuch_message_destroy(nm);
1137  }
1138 
1139  nm_hcache_close(h);
1140  return true;
1141 }
1142 
1152 static notmuch_threads_t *get_threads(notmuch_query_t *query)
1153 {
1154  if (!query)
1155  return NULL;
1156 
1157  notmuch_threads_t *threads = NULL;
1158 #if LIBNOTMUCH_CHECK_VERSION(5, 0, 0)
1159  if (notmuch_query_search_threads(query, &threads) != NOTMUCH_STATUS_SUCCESS)
1160  return false;
1161 #elif LIBNOTMUCH_CHECK_VERSION(4, 3, 0)
1162  if (notmuch_query_search_threads_st(query, &threads) != NOTMUCH_STATUS_SUCCESS)
1163  return false;
1164 #else
1165  threads = notmuch_query_search_threads(query);
1166 #endif
1167 
1168  return threads;
1169 }
1170 
1180 static bool read_threads_query(struct Mailbox *m, notmuch_query_t *q, bool dedup, int limit)
1181 {
1182  struct NmMboxData *mdata = nm_mdata_get(m);
1183  if (!mdata)
1184  return false;
1185 
1186  notmuch_threads_t *threads = get_threads(q);
1187  if (!threads)
1188  return false;
1189 
1191 
1192  for (; notmuch_threads_valid(threads) && ((limit == 0) || (m->msg_count < limit));
1193  notmuch_threads_move_to_next(threads))
1194  {
1195  if (SigInt == 1)
1196  {
1197  nm_hcache_close(h);
1198  SigInt = 0;
1199  return false;
1200  }
1201  notmuch_thread_t *thread = notmuch_threads_get(threads);
1202  append_thread(h, m, q, thread, dedup);
1203  notmuch_thread_destroy(thread);
1204  }
1205 
1206  nm_hcache_close(h);
1207  return true;
1208 }
1209 
1216 static notmuch_message_t *get_nm_message(notmuch_database_t *db, struct Email *e)
1217 {
1218  notmuch_message_t *msg = NULL;
1219  char *id = email_get_id(e);
1220 
1221  mutt_debug(LL_DEBUG2, "nm: find message (%s)\n", id);
1222 
1223  if (id && db)
1224  notmuch_database_find_message(db, id, &msg);
1225 
1226  return msg;
1227 }
1228 
1235 static bool nm_message_has_tag(notmuch_message_t *msg, char *tag)
1236 {
1237  const char *possible_match_tag = NULL;
1238  notmuch_tags_t *tags = NULL;
1239 
1240  for (tags = notmuch_message_get_tags(msg); notmuch_tags_valid(tags);
1241  notmuch_tags_move_to_next(tags))
1242  {
1243  possible_match_tag = notmuch_tags_get(tags);
1244  if (mutt_str_strcmp(possible_match_tag, tag) == 0)
1245  {
1246  return true;
1247  }
1248  }
1249  return false;
1250 }
1251 
1259 static int update_tags(notmuch_message_t *msg, const char *tags)
1260 {
1261  char *buf = mutt_str_strdup(tags);
1262  if (!buf)
1263  return -1;
1264 
1265  notmuch_message_freeze(msg);
1266 
1267  char *tag = NULL, *end = NULL;
1268  for (char *p = buf; p && *p; p++)
1269  {
1270  if (!tag && isspace(*p))
1271  continue;
1272  if (!tag)
1273  tag = p; /* begin of the tag */
1274  if ((p[0] == ',') || (p[0] == ' '))
1275  end = p; /* terminate the tag */
1276  else if (p[1] == '\0')
1277  end = p + 1; /* end of optstr */
1278  if (!tag || !end)
1279  continue;
1280  if (tag >= end)
1281  break;
1282 
1283  end[0] = '\0';
1284 
1285  if (tag[0] == '-')
1286  {
1287  mutt_debug(LL_DEBUG1, "nm: remove tag: '%s'\n", tag + 1);
1288  notmuch_message_remove_tag(msg, tag + 1);
1289  }
1290  else if (tag[0] == '!')
1291  {
1292  mutt_debug(LL_DEBUG1, "nm: toggle tag: '%s'\n", tag + 1);
1293  if (nm_message_has_tag(msg, tag + 1))
1294  {
1295  notmuch_message_remove_tag(msg, tag + 1);
1296  }
1297  else
1298  {
1299  notmuch_message_add_tag(msg, tag + 1);
1300  }
1301  }
1302  else
1303  {
1304  mutt_debug(LL_DEBUG1, "nm: add tag: '%s'\n", (tag[0] == '+') ? tag + 1 : tag);
1305  notmuch_message_add_tag(msg, (tag[0] == '+') ? tag + 1 : tag);
1306  }
1307  end = NULL;
1308  tag = NULL;
1309  }
1310 
1311  notmuch_message_thaw(msg);
1312  FREE(&buf);
1313  return 0;
1314 }
1315 
1328 static int update_email_flags(struct Mailbox *m, struct Email *e, const char *tags)
1329 {
1330  char *buf = mutt_str_strdup(tags);
1331  if (!buf)
1332  return -1;
1333 
1334  char *tag = NULL, *end = NULL;
1335  for (char *p = buf; p && *p; p++)
1336  {
1337  if (!tag && isspace(*p))
1338  continue;
1339  if (!tag)
1340  tag = p; /* begin of the tag */
1341  if ((p[0] == ',') || (p[0] == ' '))
1342  end = p; /* terminate the tag */
1343  else if (p[1] == '\0')
1344  end = p + 1; /* end of optstr */
1345  if (!tag || !end)
1346  continue;
1347  if (tag >= end)
1348  break;
1349 
1350  end[0] = '\0';
1351 
1352  if (tag[0] == '-')
1353  {
1354  tag++;
1355  if (strcmp(tag, C_NmUnreadTag) == 0)
1356  mutt_set_flag(m, e, MUTT_READ, true);
1357  else if (strcmp(tag, C_NmRepliedTag) == 0)
1358  mutt_set_flag(m, e, MUTT_REPLIED, false);
1359  else if (strcmp(tag, C_NmFlaggedTag) == 0)
1360  mutt_set_flag(m, e, MUTT_FLAG, false);
1361  }
1362  else
1363  {
1364  tag = (tag[0] == '+') ? tag + 1 : tag;
1365  if (strcmp(tag, C_NmUnreadTag) == 0)
1366  mutt_set_flag(m, e, MUTT_READ, false);
1367  else if (strcmp(tag, C_NmRepliedTag) == 0)
1368  mutt_set_flag(m, e, MUTT_REPLIED, true);
1369  else if (strcmp(tag, C_NmFlaggedTag) == 0)
1370  mutt_set_flag(m, e, MUTT_FLAG, true);
1371  }
1372  end = NULL;
1373  tag = NULL;
1374  }
1375 
1376  FREE(&buf);
1377  return 0;
1378 }
1379 
1390 static int rename_maildir_filename(const char *old, char *buf, size_t buflen, struct Email *e)
1391 {
1392  char filename[PATH_MAX];
1393  char suffix[PATH_MAX];
1394  char folder[PATH_MAX];
1395 
1396  mutt_str_strfcpy(folder, old, sizeof(folder));
1397  char *p = strrchr(folder, '/');
1398  if (p)
1399  {
1400  *p = '\0';
1401  p++;
1402  }
1403  else
1404  p = folder;
1405 
1406  mutt_str_strfcpy(filename, p, sizeof(filename));
1407 
1408  /* remove (new,cur,...) from folder path */
1409  p = strrchr(folder, '/');
1410  if (p)
1411  *p = '\0';
1412 
1413  /* remove old flags from filename */
1414  p = strchr(filename, ':');
1415  if (p)
1416  *p = '\0';
1417 
1418  /* compose new flags */
1419  maildir_gen_flags(suffix, sizeof(suffix), e);
1420 
1421  snprintf(buf, buflen, "%s/%s/%s%s", folder,
1422  (e->read || e->old) ? "cur" : "new", filename, suffix);
1423 
1424  if (strcmp(old, buf) == 0)
1425  return 1;
1426 
1427  if (rename(old, buf) != 0)
1428  {
1429  mutt_debug(LL_DEBUG1, "nm: rename(2) failed %s -> %s\n", old, buf);
1430  return -1;
1431  }
1432 
1433  return 0;
1434 }
1435 
1443 static int remove_filename(struct Mailbox *m, const char *path)
1444 {
1445  struct NmMboxData *mdata = nm_mdata_get(m);
1446  if (!mdata)
1447  return -1;
1448 
1449  mutt_debug(LL_DEBUG2, "nm: remove filename '%s'\n", path);
1450 
1451  notmuch_database_t *db = nm_db_get(m, true);
1452  if (!db)
1453  return -1;
1454 
1455  notmuch_message_t *msg = NULL;
1456  notmuch_status_t st = notmuch_database_find_message_by_filename(db, path, &msg);
1457  if (st || !msg)
1458  return -1;
1459 
1460  int trans = nm_db_trans_begin(m);
1461  if (trans < 0)
1462  return -1;
1463 
1464  /* note that unlink() is probably unnecessary here, it's already removed
1465  * by mh_sync_mailbox_message(), but for sure... */
1466  notmuch_filenames_t *ls = NULL;
1467  st = notmuch_database_remove_message(db, path);
1468  switch (st)
1469  {
1470  case NOTMUCH_STATUS_SUCCESS:
1471  mutt_debug(LL_DEBUG2, "nm: remove success, call unlink\n");
1472  unlink(path);
1473  break;
1474  case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:
1475  mutt_debug(LL_DEBUG2, "nm: remove success (duplicate), call unlink\n");
1476  unlink(path);
1477  for (ls = notmuch_message_get_filenames(msg);
1478  ls && notmuch_filenames_valid(ls); notmuch_filenames_move_to_next(ls))
1479  {
1480  path = notmuch_filenames_get(ls);
1481 
1482  mutt_debug(LL_DEBUG2, "nm: remove duplicate: '%s'\n", path);
1483  unlink(path);
1484  notmuch_database_remove_message(db, path);
1485  }
1486  break;
1487  default:
1488  mutt_debug(LL_DEBUG1, "nm: failed to remove '%s' [st=%d]\n", path, (int) st);
1489  break;
1490  }
1491 
1492  notmuch_message_destroy(msg);
1493  if (trans)
1494  nm_db_trans_end(m);
1495  return 0;
1496 }
1497 
1507 static int rename_filename(struct Mailbox *m, const char *old_file,
1508  const char *new_file, struct Email *e)
1509 {
1510  struct NmMboxData *mdata = nm_mdata_get(m);
1511  if (!mdata)
1512  return -1;
1513 
1514  notmuch_database_t *db = nm_db_get(m, true);
1515  if (!db || !new_file || !old_file || (access(new_file, F_OK) != 0))
1516  return -1;
1517 
1518  int rc = -1;
1519  notmuch_status_t st;
1520  notmuch_filenames_t *ls = NULL;
1521  notmuch_message_t *msg = NULL;
1522 
1523  mutt_debug(LL_DEBUG1, "nm: rename filename, %s -> %s\n", old_file, new_file);
1524  int trans = nm_db_trans_begin(m);
1525  if (trans < 0)
1526  return -1;
1527 
1528  mutt_debug(LL_DEBUG2, "nm: rename: add '%s'\n", new_file);
1529 #ifdef HAVE_NOTMUCH_DATABASE_INDEX_FILE
1530  st = notmuch_database_index_file(db, new_file, NULL, &msg);
1531 #else
1532  st = notmuch_database_add_message(db, new_file, &msg);
1533 #endif
1534 
1535  if ((st != NOTMUCH_STATUS_SUCCESS) && (st != NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID))
1536  {
1537  mutt_debug(LL_DEBUG1, "nm: failed to add '%s' [st=%d]\n", new_file, (int) st);
1538  goto done;
1539  }
1540 
1541  mutt_debug(LL_DEBUG2, "nm: rename: rem '%s'\n", old_file);
1542  st = notmuch_database_remove_message(db, old_file);
1543  switch (st)
1544  {
1545  case NOTMUCH_STATUS_SUCCESS:
1546  break;
1547  case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:
1548  mutt_debug(LL_DEBUG2, "nm: rename: syncing duplicate filename\n");
1549  notmuch_message_destroy(msg);
1550  msg = NULL;
1551  notmuch_database_find_message_by_filename(db, new_file, &msg);
1552 
1553  for (ls = notmuch_message_get_filenames(msg);
1554  msg && ls && notmuch_filenames_valid(ls); notmuch_filenames_move_to_next(ls))
1555  {
1556  const char *path = notmuch_filenames_get(ls);
1557  char newpath[PATH_MAX];
1558 
1559  if (strcmp(new_file, path) == 0)
1560  continue;
1561 
1562  mutt_debug(LL_DEBUG2, "nm: rename: syncing duplicate: %s\n", path);
1563 
1564  if (rename_maildir_filename(path, newpath, sizeof(newpath), e) == 0)
1565  {
1566  mutt_debug(LL_DEBUG2, "nm: rename dup %s -> %s\n", path, newpath);
1567  notmuch_database_remove_message(db, path);
1568 #ifdef HAVE_NOTMUCH_DATABASE_INDEX_FILE
1569  notmuch_database_index_file(db, newpath, NULL, NULL);
1570 #else
1571  notmuch_database_add_message(db, newpath, NULL);
1572 #endif
1573  }
1574  }
1575  notmuch_message_destroy(msg);
1576  msg = NULL;
1577  notmuch_database_find_message_by_filename(db, new_file, &msg);
1578  st = NOTMUCH_STATUS_SUCCESS;
1579  break;
1580  default:
1581  mutt_debug(LL_DEBUG1, "nm: failed to remove '%s' [st=%d]\n", old_file, (int) st);
1582  break;
1583  }
1584 
1585  if ((st == NOTMUCH_STATUS_SUCCESS) && e && msg)
1586  {
1587  notmuch_message_maildir_flags_to_tags(msg);
1588  update_email_tags(e, msg);
1589 
1590  char *tags = driver_tags_get(&e->tags);
1591  update_tags(msg, tags);
1592  FREE(&tags);
1593  }
1594 
1595  rc = 0;
1596 done:
1597  if (msg)
1598  notmuch_message_destroy(msg);
1599  if (trans)
1600  nm_db_trans_end(m);
1601  return rc;
1602 }
1603 
1611 static unsigned int count_query(notmuch_database_t *db, const char *qstr, int limit)
1612 {
1613  notmuch_query_t *q = notmuch_query_create(db, qstr);
1614  if (!q)
1615  return 0;
1616 
1617  unsigned int res = 0;
1618 
1619  apply_exclude_tags(q);
1620 #if LIBNOTMUCH_CHECK_VERSION(5, 0, 0)
1621  if (notmuch_query_count_messages(q, &res) != NOTMUCH_STATUS_SUCCESS)
1622  res = 0; /* may not be defined on error */
1623 #elif LIBNOTMUCH_CHECK_VERSION(4, 3, 0)
1624  if (notmuch_query_count_messages_st(q, &res) != NOTMUCH_STATUS_SUCCESS)
1625  res = 0; /* may not be defined on error */
1626 #else
1627  res = notmuch_query_count_messages(q);
1628 #endif
1629  notmuch_query_destroy(q);
1630  mutt_debug(LL_DEBUG1, "nm: count '%s', result=%d\n", qstr, res);
1631 
1632  if ((limit > 0) && (res > limit))
1633  res = limit;
1634 
1635  return res;
1636 }
1637 
1644 char *nm_email_get_folder(struct Email *e)
1645 {
1646  return (e && e->edata) ? ((struct NmEmailData *) e->edata)->folder : NULL;
1647 }
1648 
1656 int nm_read_entire_thread(struct Mailbox *m, struct Email *e)
1657 {
1658  if (!m)
1659  return -1;
1660 
1661  struct NmMboxData *mdata = nm_mdata_get(m);
1662  if (!mdata)
1663  return -1;
1664 
1665  notmuch_query_t *q = NULL;
1666  notmuch_database_t *db = NULL;
1667  notmuch_message_t *msg = NULL;
1668  int rc = -1;
1669 
1670  if (!(db = nm_db_get(m, false)) || !(msg = get_nm_message(db, e)))
1671  goto done;
1672 
1673  mutt_debug(LL_DEBUG1, "nm: reading entire-thread messages...[current count=%d]\n",
1674  m->msg_count);
1675 
1676  progress_reset(m);
1677  const char *id = notmuch_message_get_thread_id(msg);
1678  if (!id)
1679  goto done;
1680 
1681  char *qstr = NULL;
1682  mutt_str_append_item(&qstr, "thread:", '\0');
1683  mutt_str_append_item(&qstr, id, '\0');
1684 
1685  q = notmuch_query_create(db, qstr);
1686  FREE(&qstr);
1687  if (!q)
1688  goto done;
1689  apply_exclude_tags(q);
1690  notmuch_query_set_sort(q, NOTMUCH_SORT_NEWEST_FIRST);
1691 
1692  read_threads_query(m, q, true, 0);
1693  m->mtime.tv_sec = mutt_date_epoch();
1694  m->mtime.tv_nsec = 0;
1695  rc = 0;
1696 
1697  if (m->msg_count > mdata->oldmsgcount)
1699 done:
1700  if (q)
1701  notmuch_query_destroy(q);
1702 
1703  nm_db_release(m);
1704 
1705  if (m->msg_count == mdata->oldmsgcount)
1706  mutt_message(_("No more messages in the thread"));
1707 
1708  mdata->oldmsgcount = 0;
1709  mutt_debug(LL_DEBUG1, "nm: reading entire-thread messages... done [rc=%d, count=%d]\n",
1710  rc, m->msg_count);
1711  return rc;
1712 }
1713 
1725 {
1726  // The six variations of how type= could appear.
1727  const char *variants[6] = { "&type=threads", "&type=messages",
1728  "type=threads&", "type=messages&",
1729  "type=threads", "type=messages" };
1730 
1731  int variants_size = mutt_array_size(variants);
1732  for (int i = 0; i < variants_size; i++)
1733  {
1734  if (mutt_str_strcasestr(buf, variants[i]) != NULL)
1735  {
1736  // variants[] is setup such that type can be determined via modulo 2.
1737  mdata->query_type = ((i % 2) == 0) ? NM_QUERY_TYPE_THREADS : NM_QUERY_TYPE_MESGS;
1738 
1739  mutt_str_remall_strcasestr(buf, variants[i]);
1740  }
1741  }
1742 }
1743 
1752 char *nm_uri_from_query(struct Mailbox *m, char *buf, size_t buflen)
1753 {
1754  mutt_debug(LL_DEBUG2, "(%s)\n", buf);
1755  struct NmMboxData *mdata = nm_mdata_get(m);
1756  char uri[PATH_MAX + 1024 + 32]; /* path to DB + query + URI "decoration" */
1757  int added;
1758  bool using_default_data = false;
1759 
1760  // No existing data. Try to get a default NmMboxData.
1761  if (!mdata)
1762  {
1763  mdata = nm_get_default_data();
1764 
1765  // Failed to get default data.
1766  if (!mdata)
1767  return NULL;
1768 
1769  using_default_data = true;
1770  }
1771 
1772  nm_parse_type_from_query(mdata, buf);
1773 
1774  if (get_limit(mdata) != C_NmDbLimit)
1775  {
1776  added = snprintf(uri, sizeof(uri), "%s%s?type=%s&limit=%d&query=", NmUriProtocol,
1777  nm_db_get_filename(m),
1778  query_type_to_string(mdata->query_type), get_limit(mdata));
1779  }
1780  else
1781  {
1782  added = snprintf(uri, sizeof(uri), "%s%s?type=%s&query=", NmUriProtocol,
1784  }
1785 
1786  if (added >= sizeof(uri))
1787  {
1788  // snprintf output was truncated, so can't create URI
1789  return NULL;
1790  }
1791 
1792  url_pct_encode(&uri[added], sizeof(uri) - added, buf);
1793 
1794  mutt_str_strfcpy(buf, uri, buflen);
1795  buf[buflen - 1] = '\0';
1796 
1797  if (using_default_data)
1798  nm_mdata_free((void **) &mdata);
1799 
1800  mutt_debug(LL_DEBUG1, "nm: uri from query '%s'\n", buf);
1801  return buf;
1802 }
1803 
1814 {
1817 
1819 }
1820 
1830 {
1833 }
1834 
1841 bool nm_message_is_still_queried(struct Mailbox *m, struct Email *e)
1842 {
1843  struct NmMboxData *mdata = nm_mdata_get(m);
1844  notmuch_database_t *db = nm_db_get(m, false);
1845  char *orig_str = get_query_string(mdata, true);
1846 
1847  if (!db || !orig_str)
1848  return false;
1849 
1850  char *new_str = NULL;
1851  bool rc = false;
1852  if (mutt_str_asprintf(&new_str, "id:%s and (%s)", email_get_id(e), orig_str) < 0)
1853  return false;
1854 
1855  mutt_debug(LL_DEBUG2, "nm: checking if message is still queried: %s\n", new_str);
1856 
1857  notmuch_query_t *q = notmuch_query_create(db, new_str);
1858 
1859  switch (mdata->query_type)
1860  {
1861  case NM_QUERY_TYPE_MESGS:
1862  {
1863  notmuch_messages_t *messages = get_messages(q);
1864 
1865  if (!messages)
1866  return false;
1867 
1868  rc = notmuch_messages_valid(messages);
1869  notmuch_messages_destroy(messages);
1870  break;
1871  }
1872  case NM_QUERY_TYPE_THREADS:
1873  {
1874  notmuch_threads_t *threads = get_threads(q);
1875 
1876  if (!threads)
1877  return false;
1878 
1879  rc = notmuch_threads_valid(threads);
1880  notmuch_threads_destroy(threads);
1881  break;
1882  }
1883  }
1884 
1885  notmuch_query_destroy(q);
1886 
1887  mutt_debug(LL_DEBUG2, "nm: checking if message is still queried: %s = %s\n",
1888  new_str, rc ? "true" : "false");
1889 
1890  return rc;
1891 }
1892 
1902 int nm_update_filename(struct Mailbox *m, const char *old_file,
1903  const char *new_file, struct Email *e)
1904 {
1905  char buf[PATH_MAX];
1906  struct NmMboxData *mdata = nm_mdata_get(m);
1907  if (!mdata || !new_file)
1908  return -1;
1909 
1910  if (!old_file && e && e->edata)
1911  {
1912  email_get_fullpath(e, buf, sizeof(buf));
1913  old_file = buf;
1914  }
1915 
1916  int rc = rename_filename(m, old_file, new_file, e);
1917 
1918  nm_db_release(m);
1919  m->mtime.tv_sec = mutt_date_epoch();
1920  m->mtime.tv_nsec = 0;
1921  return rc;
1922 }
1923 
1927 static int nm_mbox_check_stats(struct Mailbox *m, int flags)
1928 {
1929  struct UrlQuery *item = NULL;
1930  struct Url *url = NULL;
1931  char *db_filename = NULL, *db_query = NULL;
1932  notmuch_database_t *db = NULL;
1933  int rc = -1;
1934  int limit = C_NmDbLimit;
1935  mutt_debug(LL_DEBUG1, "nm: count\n");
1936 
1937  url = url_parse(mailbox_path(m));
1938  if (!url)
1939  {
1940  mutt_error(_("failed to parse notmuch uri: %s"), mailbox_path(m));
1941  goto done;
1942  }
1943 
1944  STAILQ_FOREACH(item, &url->query_strings, entries)
1945  {
1946  if (item->value && (strcmp(item->name, "query") == 0))
1947  db_query = item->value;
1948  else if (item->value && (strcmp(item->name, "limit") == 0))
1949  {
1950  // Try to parse the limit
1951  if (mutt_str_atoi(item->value, &limit) != 0)
1952  {
1953  mutt_error(_("failed to parse limit: %s"), item->value);
1954  goto done;
1955  }
1956  }
1957  }
1958 
1959  if (!db_query)
1960  goto done;
1961 
1962  db_filename = url->path;
1963  if (!db_filename)
1964  {
1965  if (C_NmDefaultUri)
1966  {
1968  db_filename = C_NmDefaultUri + NmUriProtocolLen;
1969  else
1970  db_filename = C_NmDefaultUri;
1971  }
1972  else if (C_Folder)
1973  db_filename = C_Folder;
1974  }
1975 
1976  /* don't be verbose about connection, as we're called from
1977  * sidebar/mailbox very often */
1978  db = nm_db_do_open(db_filename, false, false);
1979  if (!db)
1980  goto done;
1981 
1982  /* all emails */
1983  m->msg_count = count_query(db, db_query, limit);
1984 
1985  // holder variable for extending query to unread/flagged
1986  char *qstr = NULL;
1987 
1988  // unread messages
1989  mutt_str_asprintf(&qstr, "( %s ) tag:%s", db_query, C_NmUnreadTag);
1990  m->msg_unread = count_query(db, qstr, limit);
1991  FREE(&qstr);
1992 
1993  // flagged messages
1994  mutt_str_asprintf(&qstr, "( %s ) tag:%s", db_query, C_NmFlaggedTag);
1995  m->msg_flagged = count_query(db, qstr, limit);
1996  FREE(&qstr);
1997 
1998  rc = 0;
1999 done:
2000  if (db)
2001  {
2002  nm_db_free(db);
2003  mutt_debug(LL_DEBUG1, "nm: count close DB\n");
2004  }
2005  url_free(&url);
2006 
2007  mutt_debug(LL_DEBUG1, "nm: count done [rc=%d]\n", rc);
2008  return rc;
2009 }
2010 
2019 int nm_record_message(struct Mailbox *m, char *path, struct Email *e)
2020 {
2021  notmuch_database_t *db = NULL;
2022  notmuch_status_t st;
2023  notmuch_message_t *msg = NULL;
2024  int rc = -1;
2025  struct NmMboxData *mdata = nm_mdata_get(m);
2026 
2027  if (!path || !mdata || (access(path, F_OK) != 0))
2028  return 0;
2029  db = nm_db_get(m, true);
2030  if (!db)
2031  return -1;
2032 
2033  mutt_debug(LL_DEBUG1, "nm: record message: %s\n", path);
2034  int trans = nm_db_trans_begin(m);
2035  if (trans < 0)
2036  goto done;
2037 
2038 #ifdef HAVE_NOTMUCH_DATABASE_INDEX_FILE
2039  st = notmuch_database_index_file(db, path, NULL, &msg);
2040 #else
2041  st = notmuch_database_add_message(db, path, &msg);
2042 #endif
2043 
2044  if ((st != NOTMUCH_STATUS_SUCCESS) && (st != NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID))
2045  {
2046  mutt_debug(LL_DEBUG1, "nm: failed to add '%s' [st=%d]\n", path, (int) st);
2047  goto done;
2048  }
2049 
2050  if ((st == NOTMUCH_STATUS_SUCCESS) && msg)
2051  {
2052  notmuch_message_maildir_flags_to_tags(msg);
2053  if (e)
2054  {
2055  char *tags = driver_tags_get(&e->tags);
2056  update_tags(msg, tags);
2057  FREE(&tags);
2058  }
2059  if (C_NmRecordTags)
2061  }
2062 
2063  rc = 0;
2064 done:
2065  if (msg)
2066  notmuch_message_destroy(msg);
2067  if (trans == 1)
2068  nm_db_trans_end(m);
2069 
2070  nm_db_release(m);
2071  return rc;
2072 }
2073 
2084 int nm_get_all_tags(struct Mailbox *m, char **tag_list, int *tag_count)
2085 {
2086  struct NmMboxData *mdata = nm_mdata_get(m);
2087  if (!mdata)
2088  return -1;
2089 
2090  notmuch_database_t *db = NULL;
2091  notmuch_tags_t *tags = NULL;
2092  const char *tag = NULL;
2093  int rc = -1;
2094 
2095  if (!(db = nm_db_get(m, false)) || !(tags = notmuch_database_get_all_tags(db)))
2096  goto done;
2097 
2098  *tag_count = 0;
2099  mutt_debug(LL_DEBUG1, "nm: get all tags\n");
2100 
2101  while (notmuch_tags_valid(tags))
2102  {
2103  tag = notmuch_tags_get(tags);
2104  /* Skip empty string */
2105  if (*tag)
2106  {
2107  if (tag_list)
2108  tag_list[*tag_count] = mutt_str_strdup(tag);
2109  (*tag_count)++;
2110  }
2111  notmuch_tags_move_to_next(tags);
2112  }
2113 
2114  rc = 0;
2115 done:
2116  if (tags)
2117  notmuch_tags_destroy(tags);
2118 
2119  nm_db_release(m);
2120 
2121  mutt_debug(LL_DEBUG1, "nm: get all tags done [rc=%d tag_count=%u]\n", rc, *tag_count);
2122  return rc;
2123 }
2124 
2128 struct Account *nm_ac_find(struct Account *a, const char *path)
2129 {
2130  if (!a || (a->magic != MUTT_NOTMUCH) || !path)
2131  return NULL;
2132 
2133  return a;
2134 }
2135 
2139 int nm_ac_add(struct Account *a, struct Mailbox *m)
2140 {
2141  if (!a || !m || (m->magic != MUTT_NOTMUCH))
2142  return -1;
2143 
2144  if (a->adata)
2145  return 0;
2146 
2147  struct NmAccountData *adata = nm_adata_new();
2148  a->adata = adata;
2150 
2151  return 0;
2152 }
2153 
2157 static int nm_mbox_open(struct Mailbox *m)
2158 {
2159  if (init_mailbox(m) != 0)
2160  return -1;
2161 
2162  struct NmMboxData *mdata = nm_mdata_get(m);
2163  if (!mdata)
2164  return -1;
2165 
2166  mutt_debug(LL_DEBUG1, "nm: reading messages...[current count=%d]\n", m->msg_count);
2167 
2168  progress_reset(m);
2169 
2170  if (!m->emails)
2171  {
2172  /* Allocate some memory to get started */
2173  m->email_max = m->msg_count;
2174  m->msg_count = 0;
2175  m->vcount = 0;
2176  m->size = 0;
2177  mx_alloc_memory(m);
2178  }
2179 
2180  int rc = -1;
2181 
2182  notmuch_query_t *q = get_query(m, false);
2183  if (q)
2184  {
2185  rc = 0;
2186  switch (mdata->query_type)
2187  {
2188  case NM_QUERY_TYPE_MESGS:
2189  if (!read_mesgs_query(m, q, false))
2190  rc = -2;
2191  break;
2192  case NM_QUERY_TYPE_THREADS:
2193  if (!read_threads_query(m, q, false, get_limit(mdata)))
2194  rc = -2;
2195  break;
2196  }
2197  notmuch_query_destroy(q);
2198  }
2199 
2200  nm_db_release(m);
2201 
2202  m->mtime.tv_sec = mutt_date_epoch();
2203  m->mtime.tv_nsec = 0;
2204 
2205  mdata->oldmsgcount = 0;
2206 
2207  mutt_debug(LL_DEBUG1, "nm: reading messages... done [rc=%d, count=%d]\n", rc, m->msg_count);
2208  return rc;
2209 }
2210 
2221 static int nm_mbox_check(struct Mailbox *m, int *index_hint)
2222 {
2223  if (!m)
2224  return -1;
2225 
2226  struct NmMboxData *mdata = nm_mdata_get(m);
2227  time_t mtime = 0;
2228  if (!mdata || (nm_db_get_mtime(m, &mtime) != 0))
2229  return -1;
2230 
2231  int new_flags = 0;
2232  bool occult = false;
2233 
2234  if (m->mtime.tv_sec >= mtime)
2235  {
2236  mutt_debug(LL_DEBUG2, "nm: check unnecessary (db=%lu mailbox=%lu)\n", mtime,
2237  m->mtime.tv_sec);
2238  return 0;
2239  }
2240 
2241  mutt_debug(LL_DEBUG1, "nm: checking (db=%lu mailbox=%lu)\n", mtime, m->mtime.tv_sec);
2242 
2243  notmuch_query_t *q = get_query(m, false);
2244  if (!q)
2245  goto done;
2246 
2247  mutt_debug(LL_DEBUG1, "nm: start checking (count=%d)\n", m->msg_count);
2248  mdata->oldmsgcount = m->msg_count;
2249  mdata->noprogress = true;
2250 
2251  for (int i = 0; i < m->msg_count; i++)
2252  m->emails[i]->active = false;
2253 
2254  int limit = get_limit(mdata);
2255 
2256  notmuch_messages_t *msgs = get_messages(q);
2257 
2258  // TODO: Analyze impact of removing this version guard.
2259 #if LIBNOTMUCH_CHECK_VERSION(5, 0, 0)
2260  if (!msgs)
2261  return false;
2262 #elif LIBNOTMUCH_CHECK_VERSION(4, 3, 0)
2263  if (!msgs)
2264  goto done;
2265 #endif
2266 
2268 
2269  for (int i = 0; notmuch_messages_valid(msgs) && ((limit == 0) || (i < limit));
2270  notmuch_messages_move_to_next(msgs), i++)
2271  {
2272  notmuch_message_t *msg = notmuch_messages_get(msgs);
2273  struct Email *e = get_mutt_email(m, msg);
2274 
2275  if (!e)
2276  {
2277  /* new email */
2278  append_message(h, m, NULL, msg, false);
2279  notmuch_message_destroy(msg);
2280  continue;
2281  }
2282 
2283  /* message already exists, merge flags */
2284  e->active = true;
2285 
2286  /* Check to see if the message has moved to a different subdirectory.
2287  * If so, update the associated filename. */
2288  const char *new_file = get_message_last_filename(msg);
2289  char old_file[PATH_MAX];
2290  email_get_fullpath(e, old_file, sizeof(old_file));
2291 
2292  if (mutt_str_strcmp(old_file, new_file) != 0)
2293  update_message_path(e, new_file);
2294 
2295  if (!e->changed)
2296  {
2297  /* if the user hasn't modified the flags on this message, update the
2298  * flags we just detected. */
2299  struct Email e_tmp = { 0 };
2300  maildir_parse_flags(&e_tmp, new_file);
2301  maildir_update_flags(m, e, &e_tmp);
2302  }
2303 
2304  if (update_email_tags(e, msg) == 0)
2305  new_flags++;
2306 
2307  notmuch_message_destroy(msg);
2308  }
2309 
2310  nm_hcache_close(h);
2311 
2312  for (int i = 0; i < m->msg_count; i++)
2313  {
2314  if (!m->emails[i]->active)
2315  {
2316  occult = true;
2317  break;
2318  }
2319  }
2320 
2321  if (m->msg_count > mdata->oldmsgcount)
2323 done:
2324  if (q)
2325  notmuch_query_destroy(q);
2326 
2327  nm_db_release(m);
2328 
2329  m->mtime.tv_sec = mutt_date_epoch();
2330  m->mtime.tv_nsec = 0;
2331 
2332  mutt_debug(LL_DEBUG1, "nm: ... check done [count=%d, new_flags=%d, occult=%d]\n",
2333  m->msg_count, new_flags, occult);
2334 
2335  return occult ? MUTT_REOPENED :
2336  (m->msg_count > mdata->oldmsgcount) ? MUTT_NEW_MAIL :
2337  new_flags ? MUTT_FLAGS : 0;
2338 }
2339 
2343 static int nm_mbox_sync(struct Mailbox *m, int *index_hint)
2344 {
2345  if (!m)
2346  return -1;
2347 
2348  struct NmMboxData *mdata = nm_mdata_get(m);
2349  if (!mdata)
2350  return -1;
2351 
2352  int rc = 0;
2353  struct Progress progress;
2354  char *uri = mutt_str_strdup(mailbox_path(m));
2355  bool changed = false;
2356 
2357  mutt_debug(LL_DEBUG1, "nm: sync start\n");
2358 
2359  if (!m->quiet)
2360  {
2361  /* all is in this function so we don't use data->progress here */
2362  char msg[PATH_MAX];
2363  snprintf(msg, sizeof(msg), _("Writing %s..."), mailbox_path(m));
2364  mutt_progress_init(&progress, msg, MUTT_PROGRESS_WRITE, m->msg_count);
2365  }
2366 
2368 
2369  for (int i = 0; i < m->msg_count; i++)
2370  {
2371  char old_file[PATH_MAX], new_file[PATH_MAX];
2372  struct Email *e = m->emails[i];
2373  struct NmEmailData *edata = e->edata;
2374 
2375  if (!m->quiet)
2376  mutt_progress_update(&progress, i, -1);
2377 
2378  *old_file = '\0';
2379  *new_file = '\0';
2380 
2381  if (edata->oldpath)
2382  {
2383  mutt_str_strfcpy(old_file, edata->oldpath, sizeof(old_file));
2384  old_file[sizeof(old_file) - 1] = '\0';
2385  mutt_debug(LL_DEBUG2, "nm: fixing obsolete path '%s'\n", old_file);
2386  }
2387  else
2388  email_get_fullpath(e, old_file, sizeof(old_file));
2389 
2390  mutt_buffer_strcpy(&m->pathbuf, edata->folder);
2391  m->magic = edata->magic;
2392  rc = mh_sync_mailbox_message(m, i, h);
2393  mutt_buffer_strcpy(&m->pathbuf, uri);
2394  m->magic = MUTT_NOTMUCH;
2395 
2396  if (rc)
2397  break;
2398 
2399  if (!e->deleted)
2400  email_get_fullpath(e, new_file, sizeof(new_file));
2401 
2402  if (e->deleted || (strcmp(old_file, new_file) != 0))
2403  {
2404  if (e->deleted && (remove_filename(m, old_file) == 0))
2405  changed = true;
2406  else if (*new_file && *old_file && (rename_filename(m, old_file, new_file, e) == 0))
2407  changed = true;
2408  }
2409 
2410  FREE(&edata->oldpath);
2411  }
2412 
2413  mutt_buffer_strcpy(&m->pathbuf, uri);
2414  m->magic = MUTT_NOTMUCH;
2415 
2416  nm_db_release(m);
2417 
2418  if (changed)
2419  {
2420  m->mtime.tv_sec = mutt_date_epoch();
2421  m->mtime.tv_nsec = 0;
2422  }
2423 
2424  nm_hcache_close(h);
2425 
2426  FREE(&uri);
2427  mutt_debug(LL_DEBUG1, "nm: .... sync done [rc=%d]\n", rc);
2428  return rc;
2429 }
2430 
2436 static int nm_mbox_close(struct Mailbox *m)
2437 {
2438  return 0;
2439 }
2440 
2444 static int nm_msg_open(struct Mailbox *m, struct Message *msg, int msgno)
2445 {
2446  if (!m || !m->emails || !msg)
2447  return -1;
2448 
2449  struct Email *e = m->emails[msgno];
2450  char path[PATH_MAX];
2451  char *folder = nm_email_get_folder(e);
2452 
2453  snprintf(path, sizeof(path), "%s/%s", folder, e->path);
2454 
2455  msg->fp = fopen(path, "r");
2456  if (!msg->fp && (errno == ENOENT) &&
2457  ((m->magic == MUTT_MAILDIR) || (m->magic == MUTT_NOTMUCH)))
2458  {
2459  msg->fp = maildir_open_find_message(folder, e->path, NULL);
2460  }
2461 
2462  if (!msg->fp)
2463  return -1;
2464 
2465  return 0;
2466 }
2467 
2472 static int nm_msg_commit(struct Mailbox *m, struct Message *msg)
2473 {
2474  mutt_error(_("Can't write to virtual folder"));
2475  return -1;
2476 }
2477 
2481 static int nm_msg_close(struct Mailbox *m, struct Message *msg)
2482 {
2483  if (!msg)
2484  return -1;
2485  mutt_file_fclose(&(msg->fp));
2486  return 0;
2487 }
2488 
2492 static int nm_tags_edit(struct Mailbox *m, const char *tags, char *buf, size_t buflen)
2493 {
2494  *buf = '\0';
2495  if (mutt_get_field("Add/remove labels: ", buf, buflen, MUTT_NM_TAG) != 0)
2496  return -1;
2497  return 1;
2498 }
2499 
2503 static int nm_tags_commit(struct Mailbox *m, struct Email *e, char *buf)
2504 {
2505  if (!m)
2506  return -1;
2507 
2508  struct NmMboxData *mdata = nm_mdata_get(m);
2509  if (!buf || (*buf == '\0') || !mdata)
2510  return -1;
2511 
2512  notmuch_database_t *db = NULL;
2513  notmuch_message_t *msg = NULL;
2514  int rc = -1;
2515 
2516  if (!(db = nm_db_get(m, true)) || !(msg = get_nm_message(db, e)))
2517  goto done;
2518 
2519  mutt_debug(LL_DEBUG1, "nm: tags modify: '%s'\n", buf);
2520 
2521  update_tags(msg, buf);
2522  update_email_flags(m, e, buf);
2523  update_email_tags(e, msg);
2524  mutt_set_header_color(m, e);
2525 
2526  rc = 0;
2527  e->changed = true;
2528 done:
2529  nm_db_release(m);
2530  if (e->changed)
2531  {
2532  m->mtime.tv_sec = mutt_date_epoch();
2533  m->mtime.tv_nsec = 0;
2534  }
2535  mutt_debug(LL_DEBUG1, "nm: tags modify done [rc=%d]\n", rc);
2536  return rc;
2537 }
2538 
2542 enum MailboxType nm_path_probe(const char *path, const struct stat *st)
2543 {
2544  if (!path || !mutt_str_startswith(path, NmUriProtocol, CASE_IGNORE))
2545  return MUTT_UNKNOWN;
2546 
2547  return MUTT_NOTMUCH;
2548 }
2549 
2553 int nm_path_canon(char *buf, size_t buflen)
2554 {
2555  if (!buf)
2556  return -1;
2557 
2558  return 0;
2559 }
2560 
2564 int nm_path_pretty(char *buf, size_t buflen, const char *folder)
2565 {
2566  /* Succeed, but don't do anything, for now */
2567  return 0;
2568 }
2569 
2573 int nm_path_parent(char *buf, size_t buflen)
2574 {
2575  /* Succeed, but don't do anything, for now */
2576  return 0;
2577 }
2578 
2579 // clang-format off
2583 struct MxOps MxNotmuchOps = {
2584  .magic = MUTT_NOTMUCH,
2585  .name = "notmuch",
2586  .ac_find = nm_ac_find,
2587  .ac_add = nm_ac_add,
2588  .mbox_open = nm_mbox_open,
2589  .mbox_open_append = NULL,
2590  .mbox_check = nm_mbox_check,
2591  .mbox_check_stats = nm_mbox_check_stats,
2592  .mbox_sync = nm_mbox_sync,
2593  .mbox_close = nm_mbox_close,
2594  .msg_open = nm_msg_open,
2595  .msg_open_new = maildir_msg_open_new,
2596  .msg_commit = nm_msg_commit,
2597  .msg_close = nm_msg_close,
2598  .msg_padding_size = NULL,
2599  .msg_save_hcache = NULL,
2600  .tags_edit = nm_tags_edit,
2601  .tags_commit = nm_tags_commit,
2602  .path_probe = nm_path_probe,
2603  .path_canon = nm_path_canon,
2604  .path_pretty = nm_path_pretty,
2605  .path_parent = nm_path_parent,
2606 };
2607 // clang-format on
void * mutt_hcache_fetch(header_cache_t *hc, const char *key, size_t keylen)
Multiplexor for HcacheOps::fetch.
Definition: hcache.c:347
struct Email ** emails
Array of Emails.
Definition: mailbox.h:110
time_t mutt_date_epoch(void)
Return the number of seconds since the Unix epoch.
Definition: date.c:410
int maildir_msg_open_new(struct Mailbox *m, struct Message *msg, struct Email *e)
Open a new message in a Mailbox - Implements MxOps::msg_open_new()
Definition: maildir.c:575
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()
char * name
Query name.
Definition: url.h:57
static const char * mailbox_path(const struct Mailbox *m)
Get the Mailbox&#39;s path string.
Definition: mailbox.h:194
void(* free_edata)(void **)
Driver-specific data free function.
Definition: email.h:113
Notmuch virtual mailbox type.
int nm_db_trans_begin(struct Mailbox *m)
Start a Notmuch database transaction.
Definition: nm_db.c:200
#define NONULL(x)
Definition: string2.h:37
void * mutt_mem_calloc(size_t nmemb, size_t size)
Allocate zeroed memory on the heap.
Definition: memory.c:50
int msg_count
Total number of messages.
Definition: mailbox.h:102
char * C_NmQueryWindowTimebase
Config: (notmuch) Units for the time duration.
Definition: mutt_notmuch.c:80
#define mutt_set_flag(m, e, flag, bf)
Definition: protos.h:69
off_t size
Size of the Mailbox.
Definition: mailbox.h:98
static notmuch_query_t * get_query(struct Mailbox *m, bool writable)
Create a new query.
Definition: mutt_notmuch.c:598
Notmuch private types.
int mutt_str_atoi(const char *str, int *dst)
Convert ASCII string to an integer.
Definition: string.c:262
The envelope/body of an email.
Definition: email.h:39
bool maildir_update_flags(struct Mailbox *m, struct Email *e_old, struct Email *e_new)
Update the mailbox flags.
Definition: shared.c:1441
static void append_message(header_cache_t *h, struct Mailbox *m, notmuch_query_t *q, notmuch_message_t *msg, bool dedup)
Associate a message.
Definition: mutt_notmuch.c:910
int C_NmDbLimit
Config: (notmuch) Default limit for Notmuch queries.
Definition: mutt_notmuch.c:74
notmuch_database_t * nm_db_do_open(const char *filename, bool writable, bool verbose)
Open a Notmuch database.
Definition: nm_db.c:79
enum MailboxType nm_path_probe(const char *path, const struct stat *st)
Is this a Notmuch Mailbox? - Implements MxOps::path_probe()
notmuch_database_t * nm_db_get(struct Mailbox *m, bool writable)
Get the Notmuch database.
Definition: nm_db.c:143
int msg_unread
Number of unread messages.
Definition: mailbox.h:103
int nm_path_canon(char *buf, size_t buflen)
Canonicalise a Mailbox path - Implements MxOps::path_canon()
GUI miscellaneous curses (window drawing) routines.
static void query_window_reset(void)
Restore vfolder&#39;s search window to its original position.
Definition: mutt_notmuch.c:385
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:173
Structs that make up an email.
void nm_query_window_backward(void)
Function to move the current search window backward in time.
int nm_db_trans_end(struct Mailbox *m)
End a database transaction.
Definition: nm_db.c:222
void mailbox_changed(struct Mailbox *m, enum MailboxNotification action)
Notify observers of a change to a Mailbox.
Definition: mailbox.c:160
static int rename_filename(struct Mailbox *m, const char *old_file, const char *new_file, struct Email *e)
Rename the file.
void nm_adata_free(void **ptr)
Release and clear storage in an NmAccountData structure.
Definition: mutt_notmuch.c:131
static const char * get_message_last_filename(notmuch_message_t *msg)
Get a message&#39;s last filename.
Definition: mutt_notmuch.c:809
GUI manage the main index (list of emails)
#define mutt_message(...)
Definition: logging.h:83
void nm_db_free(notmuch_database_t *db)
decoupled way to close a Notmuch database
Definition: nm_db.c:184
int msg_flagged
Number of flagged messages.
Definition: mailbox.h:104
A parsed URL proto://user:password@host:port/path?a=1&b=2
Definition: url.h:66
int nm_path_parent(char *buf, size_t buflen)
Find the parent of a Mailbox path - Implements MxOps::path_parent()
struct MuttThread * thread
Thread of Emails.
Definition: email.h:96
WHERE SIG_ATOMIC_VOLATILE_T SigInt
true after SIGINT is received
Definition: globals.h:84
char * C_NmExcludeTags
Config: (notmuch) Exclude messages with these tags.
Definition: mutt_notmuch.c:76
static void progress_reset(struct Mailbox *m)
Reset the progress counter.
Definition: mutt_notmuch.c:826
Progress tracks elements, according to C_WriteInc.
Definition: progress.h:41
struct Account * nm_ac_find(struct Account *a, const char *path)
Find an Account that matches a Mailbox path - Implements MxOps::ac_find()
int nm_db_get_mtime(struct Mailbox *m, time_t *mtime)
Get the database modification time.
Definition: nm_db.c:249
static int update_email_flags(struct Mailbox *m, struct Email *e, const char *tags)
Update the Email&#39;s flags.
NmQueryType
Notmuch Query Types.
static unsigned int count_query(notmuch_database_t *db, const char *qstr, int limit)
Count the results of a query.
void * mutt_hash_find(const struct Hash *table, const char *strkey)
Find the HashElem data in a Hash table element using a key.
Definition: hash.c:379
static int update_email_tags(struct Email *e, notmuch_message_t *msg)
Update the Email&#39;s tags from Notmuch.
Definition: mutt_notmuch.c:630
WHERE char * C_NmQueryWindowCurrentSearch
Config: (notmuch) Current search parameters.
Definition: globals.h:177
A group of associated Mailboxes.
Definition: account.h:36
struct timespec mtime
Time Mailbox was last changed.
Definition: mailbox.h:118
Parsed Query String.
Definition: url.h:55
Flagged messages.
Definition: mutt.h:108
void mx_alloc_memory(struct Mailbox *m)
Create storage for the emails.
Definition: mx.c:1113
#define _(a)
Definition: message.h:28
Mailbox wasn&#39;t recognised.
Definition: mailbox.h:46
WHERE struct ConfigSet * Config
Wrapper around the user&#39;s config settings.
Definition: globals.h:40
bool changed
Email has been edited.
Definition: email.h:50
int mutt_str_remall_strcasestr(char *str, const char *target)
Remove all occurrences of substring, ignoring case.
Definition: string.c:1095
static notmuch_messages_t * get_messages(notmuch_query_t *query)
load messages for a query
static char * email_get_fullpath(struct Email *e, char *buf, size_t buflen)
Get the full path of an email.
Definition: mutt_notmuch.c:334
int nm_path_pretty(char *buf, size_t buflen, const char *folder)
Abbreviate a Mailbox path - Implements MxOps::path_pretty()
size_t mutt_str_strlen(const char *a)
Calculate the length of a string, safely.
Definition: string.c:666
const char * mutt_str_strcasestr(const char *haystack, const char *needle)
Find a substring within a string without worrying about case.
Definition: string.c:1120
char * value
Query value.
Definition: url.h:58
char * C_NmUnreadTag
Config: (notmuch) Tag to use for unread messages.
Definition: mutt_notmuch.c:82
WHERE char * C_HeaderCache
Config: (hcache) Directory/file for the header cache database.
Definition: globals.h:126
struct Email * mutt_hcache_restore(const unsigned char *d)
restore an Email from data retrieved from the cache
Definition: serialize.c:634
static bool read_mesgs_query(struct Mailbox *m, notmuch_query_t *q, bool dedup)
Search for matching messages.
A progress bar.
Definition: progress.h:48
void(* free_mdata)(void **)
Driver-specific data free function.
Definition: mailbox.h:148
int C_NmOpenTimeout
Config: (notmuch) Database timeout.
Definition: mutt_notmuch.c:77
#define mutt_get_field(field, buf, buflen, complete)
Definition: curs_lib.h:86
Messages that have been replied to.
Definition: mutt.h:101
int vcount
The number of virtual messages.
Definition: mailbox.h:113
Convenience wrapper for the config headers.
void mutt_hcache_close(header_cache_t *hc)
Multiplexor for HcacheOps::close.
Definition: hcache.c:333
static int nm_mbox_close(struct Mailbox *m)
Close a Mailbox - Implements MxOps::mbox_close()
Hundreds of global variables to back the user variables.
time_t tv_sec
Definition: file.h:47
static char * email_get_id(struct Email *e)
Get the unique Notmuch Id.
Definition: mutt_notmuch.c:322
int nm_record_message(struct Mailbox *m, char *path, struct Email *e)
Add a message to the Notmuch database.
#define mutt_array_size(x)
Definition: memory.h:33
notmuch_database_t * db
void maildir_parse_flags(struct Email *e, const char *path)
Parse Maildir file flags.
Definition: shared.c:1224
bool read
Email is read.
Definition: email.h:53
void mutt_progress_update(struct Progress *progress, size_t pos, int percent)
Update the state of the progress bar.
Definition: progress.c:212
struct NmMboxData * nm_get_default_data(void)
Create a Mailbox with default Notmuch settings.
Definition: mutt_notmuch.c:273
char * nm_email_get_folder(struct Email *e)
Get the folder for a Email.
Log at debug level 2.
Definition: logging.h:57
API for mailboxes.
Notmuch-specific Mailbox data -.
bool old
Email is seen, but unread.
Definition: email.h:52
const char NmUriProtocol[]
Definition: mutt_notmuch.c:70
static char * nm2mutt_message_id(const char *id)
converts notmuch message Id to neomutt message Id
Definition: mutt_notmuch.c:742
enum MailboxType magic
Mailbox type.
Definition: mailbox.h:116
FILE * maildir_open_find_message(const char *folder, const char *msg, char **newname)
Find a new.
Definition: shared.c:1487
static int nm_msg_commit(struct Mailbox *m, struct Message *msg)
Save changes to an email - Implements MxOps::msg_commit()
int mutt_file_fclose(FILE **fp)
Close a FILE handle (and NULL the pointer)
Definition: file.c:150
const char * name
Definition: pgpmicalg.c:45
char * virtual_id
Unique Notmuch Id.
#define MUTT_NM_TAG
Notmuch tag +/- mode.
Definition: mutt.h:75
int nm_db_release(struct Mailbox *m)
Close the Notmuch database.
Definition: nm_db.c:167
enum MailboxType magic
Mailbox type, e.g. MUTT_IMAP.
Definition: mx.h:105
char * folder
Location of the Email.
Convenience wrapper for the core headers.
Progress tracks elements, according to C_ReadInc.
Definition: progress.h:40
bool nm_message_is_still_queried(struct Mailbox *m, struct Email *e)
Is a message still visible in the query?
WHERE int C_NmQueryWindowDuration
Config: (notmuch) Time duration of the current search window.
Definition: globals.h:176
Default: Messages only.
static void progress_update(struct Mailbox *m, notmuch_query_t *q)
Update the progress counter.
Definition: mutt_notmuch.c:847
Progress bar.
long tv_nsec
Definition: file.h:48
void * mdata
Driver specific data.
Definition: mailbox.h:147
static int get_limit(struct NmMboxData *mdata)
Get the database limit.
Definition: mutt_notmuch.c:548
header_cache_t * mutt_hcache_open(const char *path, const char *folder, hcache_namer_t namer)
Multiplexor for HcacheOps::open.
Definition: hcache.c:255
struct TagList tags
For drivers that support server tagging.
Definition: email.h:108
char * nm_uri_from_query(struct Mailbox *m, char *buf, size_t buflen)
Turn a query into a URI.
&#39;Maildir&#39; Mailbox type
Definition: mailbox.h:50
char * C_NmQueryType
Config: (notmuch) Default query type: &#39;threads&#39; or &#39;messages&#39;.
Definition: mutt_notmuch.c:78
int flags
e.g. MB_NORMAL
Definition: mailbox.h:145
static int nm_msg_open(struct Mailbox *m, struct Message *msg, int msgno)
Open an email message in a Mailbox - Implements MxOps::msg_open()
void nm_edata_free(void **ptr)
Free data attached to an Email.
Definition: mutt_notmuch.c:246
Prototypes for many functions.
bool active
Message is not to be removed.
Definition: email.h:61
void nm_mdata_free(void **ptr)
Free data attached to the Mailbox.
Definition: mutt_notmuch.c:183
void mutt_buffer_strcpy(struct Buffer *buf, const char *s)
Copy a string into a Buffer.
Definition: buffer.c:310
void url_pct_encode(char *buf, size_t buflen, const char *src)
Percent-encode a string.
Definition: url.c:315
struct Email * maildir_parse_message(enum MailboxType magic, const char *fname, bool is_old, struct Email *e)
Actually parse a maildir message.
Definition: shared.c:1330
const int NmUriProtocolLen
Definition: mutt_notmuch.c:71
struct UrlQueryList query_strings
List of query strings.
Definition: url.h:74
A local copy of an email.
Definition: mx.h:81
int email_max
Number of pointers in emails.
Definition: mailbox.h:111
char * C_NmRepliedTag
Config: (notmuch) Tag to use for replied messages.
Definition: mutt_notmuch.c:84
WHERE char * C_Folder
Config: Base folder for a set of mailboxes.
Definition: globals.h:124
Create/manipulate threading in emails.
A mailbox.
Definition: mailbox.h:92
#define PATH_MAX
Definition: mutt.h:52
enum MailboxType magic
Type of Mailboxes this Account contains.
Definition: account.h:38
static bool nm_message_has_tag(notmuch_message_t *msg, char *tag)
Does a message have this tag?
Notmuch-specific Email data -.
const char * nm_db_get_filename(struct Mailbox *m)
Get the filename of the Notmuch database.
Definition: nm_db.c:52
static bool query_window_check_timebase(const char *timebase)
Checks if a given timebase string is valid.
Definition: mutt_notmuch.c:365
static int init_email(struct Email *e, const char *path, notmuch_message_t *msg)
Set up an email&#39;s Notmuch data.
Definition: mutt_notmuch.c:764
void * mutt_mem_malloc(size_t size)
Allocate memory on the heap.
Definition: memory.c:90
int C_NmQueryWindowCurrentPosition
Config: (notmuch) Position of current search window.
Definition: mutt_notmuch.c:79
Nondestructive flags change (IMAP)
Definition: mx.h:75
void * adata
Private data (for Mailbox backends)
Definition: account.h:43
char * driver_tags_get(struct TagList *list)
Get tags.
Definition: tags.c:141
void nm_query_window_forward(void)
Function to move the current search window forward in time.
static char * get_query_string(struct NmMboxData *mdata, bool window)
builds the notmuch vfolder search string
Definition: mutt_notmuch.c:489
struct NmMboxData * nm_mdata_get(struct Mailbox *m)
Get the Notmuch Mailbox data.
Definition: mutt_notmuch.c:231
int mutt_hcache_store(header_cache_t *hc, const char *key, size_t keylen, struct Email *e, unsigned int uidvalidity)
Multiplexor for HcacheOps::store.
Definition: hcache.c:406
struct Progress progress
A progress bar.
char * db_query
Previous query.
size_t mutt_str_strfcpy(char *dest, const char *src, size_t dsize)
Copy a string into a buffer (guaranteeing NUL-termination)
Definition: string.c:750
Messages that have been read.
Definition: mutt.h:102
static int nm_mbox_check(struct Mailbox *m, int *index_hint)
Check for new mail - Implements MxOps::mbox_check()
char msg[1024]
Definition: progress.h:50
static notmuch_message_t * get_nm_message(notmuch_database_t *db, struct Email *e)
Find a Notmuch message.
bool quiet
Inhibit status messages?
Definition: mailbox.h:129
Ignore case when comparing strings.
Definition: string2.h:68
char * C_NmDefaultUri
Config: (notmuch) Path to the Notmuch database.
Definition: mutt_notmuch.c:75
static int nm_tags_commit(struct Mailbox *m, struct Email *e, char *buf)
Save the tags to a message - Implements MxOps::tags_commit()
void nm_parse_type_from_query(struct NmMboxData *mdata, char *buf)
Parse a query type out of a query.
#define STAILQ_FOREACH(var, head, field)
Definition: queue.h:350
static int remove_filename(struct Mailbox *m, const char *path)
Delete a file.
void mutt_progress_init(struct Progress *progress, const char *msg, enum ProgressType type, size_t size)
Set up a progress bar.
Definition: progress.c:153
static int nm_mbox_sync(struct Mailbox *m, int *index_hint)
Save changes to the Mailbox - Implements MxOps::mbox_sync()
struct Hash * mutt_make_id_hash(struct Mailbox *m)
Create a Hash Table for message-ids.
Definition: mutt_thread.c:1437
void mutt_str_replace(char **p, const char *s)
Replace one string with another.
Definition: string.c:453
Maildir local mailbox type.
struct Url * db_url
Parsed view url of the Notmuch database.
static void apply_exclude_tags(notmuch_query_t *query)
Exclude the configured tags.
Definition: mutt_notmuch.c:557
int nm_get_all_tags(struct Mailbox *m, char **tag_list, int *tag_count)
Fill a list with all notmuch tags.
static int update_message_path(struct Email *e, const char *path)
Set the path for a message.
Definition: mutt_notmuch.c:680
char * path
Path.
Definition: url.h:73
static bool read_threads_query(struct Mailbox *m, notmuch_query_t *q, bool dedup, int limit)
Perform a query with threads.
Header cache multiplexor.
&#39;Notmuch&#39; (virtual) Mailbox type
Definition: mailbox.h:53
static int nm_mbox_open(struct Mailbox *m)
Open a Mailbox - Implements MxOps::mbox_open()
size_t mutt_str_startswith(const char *str, const char *prefix, enum CaseSensitivity cs)
Check whether a string starts with a prefix.
Definition: string.c:168
struct NmAccountData * nm_adata_get(struct Mailbox *m)
Get the Notmuch Account data.
Definition: mutt_notmuch.c:163
void mutt_set_header_color(struct Mailbox *m, struct Email *e)
Select a colour for a message.
Definition: index.c:3672
static int init_mailbox(struct Mailbox *m)
Add Notmuch data to the Mailbox.
Definition: mutt_notmuch.c:300
void maildir_gen_flags(char *dest, size_t destlen, struct Email *e)
Generate the Maildir flags for an email.
Definition: maildir.c:172
char * C_NmFlaggedTag
Config: (notmuch) Tag to use for flagged messages.
Definition: mutt_notmuch.c:83
struct NmMboxData * nm_mdata_new(const char *uri)
Create a new NmMboxData object from a query.
Definition: mutt_notmuch.c:205
Email list was changed.
Definition: mailbox.h:64
MailboxType
Supported mailbox formats.
Definition: mailbox.h:42
struct Account * account
Account that owns this Mailbox.
Definition: mailbox.h:142
Log at debug level 1.
Definition: logging.h:56
static int update_tags(notmuch_message_t *msg, const char *tags)
Update the tags on a message.
char * mutt_str_strdup(const char *str)
Copy a string, safely.
Definition: string.c:380
char * driver_tags_get_transformed(struct TagList *list)
Get transformed tags.
Definition: tags.c:129
bool deleted
Email is deleted.
Definition: email.h:47
void * edata
Driver-specific data.
Definition: email.h:112
static struct Email * get_mutt_email(struct Mailbox *m, notmuch_message_t *msg)
Get the Email of a Notmuch message.
Definition: mutt_notmuch.c:875
#define mutt_error(...)
Definition: logging.h:84
static char * get_folder_from_path(const char *path)
Find an email&#39;s folder from its path.
Definition: mutt_notmuch.c:717
int nm_ac_add(struct Account *a, struct Mailbox *m)
Add a Mailbox to an Account - Implements MxOps::ac_add()
static void append_thread(header_cache_t *h, struct Mailbox *m, notmuch_query_t *q, notmuch_thread_t *thread, bool dedup)
add each top level reply in the thread
char * path
Path of Email (for local Mailboxes)
Definition: email.h:93
static int nm_mbox_check_stats(struct Mailbox *m, int flags)
Check the Mailbox statistics - Implements MxOps::check_stats()
static header_cache_t * nm_hcache_open(struct Mailbox *m)
Open a header cache.
Definition: mutt_notmuch.c:91
FILE * fp
pointer to the message data
Definition: mx.h:83
void url_free(struct Url **u)
Free the contents of a URL.
Definition: url.c:288
int ignmsgcount
Ignored messages.
int index
The absolute (unsorted) message number.
Definition: email.h:87
#define FREE(x)
Definition: memory.h:40
static enum NmQueryType string_to_query_type(const char *str)
Lookup a query type.
Definition: mutt_notmuch.c:116
static notmuch_threads_t * get_threads(notmuch_query_t *query)
load threads for a query
bool noprogress
Don&#39;t show the progress bar.
void mutt_hcache_free(header_cache_t *hc, void **data)
Multiplexor for HcacheOps::free.
Definition: hcache.c:393
int nm_read_entire_thread(struct Mailbox *m, struct Email *e)
Get the entire thread of an email.
#define mutt_debug(LEVEL,...)
Definition: logging.h:81
bool driver_tags_replace(struct TagList *head, char *tags)
Replace all tags.
Definition: tags.c:182
bool progress_ready
A progress bar has been initialised.
New mail received in Mailbox.
Definition: mx.h:72
struct NmAccountData * nm_adata_new(void)
Allocate and initialise a new NmAccountData structure.
Definition: mutt_notmuch.c:150
static const char * query_type_to_string(enum NmQueryType query_type)
Turn a query type into a string.
Definition: mutt_notmuch.c:347
int mutt_str_asprintf(char **strp, const char *fmt,...)
Definition: string.c:1195
struct Email * maildir_parse_stream(enum MailboxType magic, FILE *fp, const char *fname, bool is_old, struct Email *e)
Parse a Maildir message.
Definition: shared.c:1290
struct NmEmailData * nm_edata_new(void)
Create a new NmEmailData for an email.
Definition: mutt_notmuch.c:263
static void append_replies(header_cache_t *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
header cache structure
Definition: hcache.h:67
struct Buffer pathbuf
Definition: mailbox.h:94
void mutt_str_append_item(char **str, const char *item, char sep)
Add string to another separated by sep.
Definition: string.c:471
void(* free_adata)(void **)
Callback function to free private data.
Definition: account.h:44
void email_free(struct Email **ptr)
Free an Email.
Definition: email.c:41
static bool windowed_query_from_query(const char *query, char *buf, size_t buflen)
transforms a vfolder search query into a windowed one
Definition: mutt_notmuch.c:429
Mailbox was reopened.
Definition: mx.h:74
int nm_update_filename(struct Mailbox *m, const char *old_file, const char *new_file, struct Email *e)
Change the filename.
char * mutt_str_substr_dup(const char *begin, const char *end)
Duplicate a sub-string.
Definition: string.c:579
enum NmQueryType query_type
Messages or Threads.
static int nm_msg_close(struct Mailbox *m, struct Message *msg)
Close an email - Implements MxOps::msg_close()
int db_limit
Maximum number of results to return.
int mh_sync_mailbox_message(struct Mailbox *m, int msgno, header_cache_t *hc)
Save changes to the mailbox.
Definition: shared.c:1350
int mutt_str_strcmp(const char *a, const char *b)
Compare two strings, safely.
Definition: string.c:615
char * C_NmRecordTags
Config: (notmuch) Tags to apply to the &#39;record&#39; mailbox (sent mail)
Definition: mutt_notmuch.c:81
struct Hash * id_hash
Hash table by msg id.
Definition: mailbox.h:138
void nm_hcache_close(header_cache_t *h)
Close the header cache.
Definition: mutt_notmuch.c:104
static int rename_maildir_filename(const char *old, char *buf, size_t buflen, struct Email *e)
Rename a Maildir file.
enum MailboxType magic
Type of Mailbox the Email is in.
The Mailbox API.
Definition: mx.h:103
int msgno
Number displayed to the user.
Definition: email.h:88
Notmuch-specific Account data -.
int cs_str_native_set(const struct ConfigSet *cs, const char *name, intptr_t value, struct Buffer *err)
Natively set the value of a string config item.
Definition: set.c:772
struct Url * url_parse(const char *src)
Fill in Url.
Definition: url.c:161