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