NeoMutt  2020-08-21-74-g346364
Teaching an old dog new tricks
DOXYGEN
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 "private.h"
55 #include "mutt/lib.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 "lib.h"
62 #include "hcache/lib.h"
63 #include "maildir/lib.h"
64 #include "index.h"
65 #include "mutt_globals.h"
66 #include "mutt_thread.h"
67 #include "mx.h"
68 #include "progress.h"
69 #include "protos.h"
70 
71 struct stat;
72 
73 const char NmUrlProtocol[] = "notmuch://";
74 const int NmUrlProtocolLen = sizeof(NmUrlProtocol) - 1;
75 
81 static struct HeaderCache *nm_hcache_open(struct Mailbox *m)
82 {
83 #ifdef USE_HCACHE
85 #else
86  return NULL;
87 #endif
88 }
89 
94 static void nm_hcache_close(struct HeaderCache *h)
95 {
96 #ifdef USE_HCACHE
98 #endif
99 }
100 
106 static enum NmQueryType string_to_query_type(const char *str)
107 {
108  if (mutt_str_equal(str, "threads"))
109  return NM_QUERY_TYPE_THREADS;
110  if (mutt_str_equal(str, "messages"))
111  return NM_QUERY_TYPE_MESGS;
112 
113  mutt_error(_("failed to parse notmuch query type: %s"), NONULL(str));
114  return NM_QUERY_TYPE_MESGS;
115 }
116 
120 void nm_adata_free(void **ptr)
121 {
122  if (!ptr || !*ptr)
123  return;
124 
125  struct NmAccountData *adata = *ptr;
126  if (adata->db)
127  {
128  nm_db_free(adata->db);
129  adata->db = NULL;
130  }
131 
132  FREE(ptr);
133 }
134 
140 {
141  struct NmAccountData *adata = mutt_mem_calloc(1, sizeof(struct NmAccountData));
142 
143  return adata;
144 }
145 
153 {
154  if (!m || (m->type != MUTT_NOTMUCH))
155  return NULL;
156 
157  struct Account *a = m->account;
158  if (!a)
159  return NULL;
160 
161  return a->adata;
162 }
163 
171 void nm_mdata_free(void **ptr)
172 {
173  if (!ptr || !*ptr)
174  return;
175 
176  struct NmMboxData *mdata = *ptr;
177 
178  mutt_debug(LL_DEBUG1, "nm: freeing context data %p\n", mdata);
179 
180  url_free(&mdata->db_url);
181  FREE(&mdata->db_query);
182  FREE(ptr);
183 }
184 
193 struct NmMboxData *nm_mdata_new(const char *url)
194 {
195  if (!url)
196  return NULL;
197 
198  struct NmMboxData *mdata = mutt_mem_calloc(1, sizeof(struct NmMboxData));
199  mutt_debug(LL_DEBUG1, "nm: initialize mailbox mdata %p\n", (void *) mdata);
200 
201  mdata->db_limit = C_NmDbLimit;
202  mdata->query_type = string_to_query_type(C_NmQueryType);
203  mdata->db_url = url_parse(url);
204  if (!mdata->db_url)
205  {
206  mutt_error(_("failed to parse notmuch url: %s"), url);
207  FREE(&mdata);
208  return NULL;
209  }
210  return mdata;
211 }
212 
219 struct NmMboxData *nm_mdata_get(struct Mailbox *m)
220 {
221  if (!m || (m->type != MUTT_NOTMUCH))
222  return NULL;
223 
224  return m->mdata;
225 }
226 
234 void nm_edata_free(void **ptr)
235 {
236  if (!ptr || !*ptr)
237  return;
238 
239  struct NmEmailData *edata = *ptr;
240 
241  mutt_debug(LL_DEBUG2, "nm: freeing email %p\n", (void *) edata);
242  FREE(&edata->folder);
243  FREE(&edata->oldpath);
244  FREE(&edata->virtual_id);
245 
246  FREE(ptr);
247 }
248 
254 {
255  return mutt_mem_calloc(1, sizeof(struct NmEmailData));
256 }
257 
264 struct NmEmailData *nm_edata_get(struct Email *e)
265 {
266  if (!e)
267  return NULL;
268 
269  return e->nm_edata;
270 }
271 
277 static struct NmMboxData *nm_get_default_data(void)
278 {
279  // path to DB + query + url "decoration"
280  char url[PATH_MAX + 1024 + 32];
281 
282  // Try to use C_NmDefaultUrl or C_Folder.
283  // If neither are set, it is impossible to create a Notmuch URL.
284  if (C_NmDefaultUrl)
285  snprintf(url, sizeof(url), "%s", C_NmDefaultUrl);
286  else if (C_Folder)
287  snprintf(url, sizeof(url), "notmuch://%s", C_Folder);
288  else
289  return NULL;
290 
291  return nm_mdata_new(url);
292 }
293 
304 static int init_mailbox(struct Mailbox *m)
305 {
306  if (!m || (m->type != MUTT_NOTMUCH))
307  return -1;
308 
309  if (m->mdata)
310  return 0;
311 
313  if (!m->mdata)
314  return -1;
315 
317  return 0;
318 }
319 
326 static char *email_get_id(struct Email *e)
327 {
328  struct NmEmailData *edata = nm_edata_get(e);
329  if (!edata)
330  return NULL;
331 
332  return edata->virtual_id;
333 }
334 
342 static char *email_get_fullpath(struct Email *e, char *buf, size_t buflen)
343 {
344  snprintf(buf, buflen, "%s/%s", nm_email_get_folder(e), e->path);
345  return buf;
346 }
347 
355 static const char *query_type_to_string(enum NmQueryType query_type)
356 {
357  if (query_type == NM_QUERY_TYPE_THREADS)
358  return "threads";
359  return "messages";
360 }
361 
372 static bool query_window_check_timebase(const char *timebase)
373 {
374  if ((strcmp(timebase, "hour") == 0) || (strcmp(timebase, "day") == 0) ||
375  (strcmp(timebase, "week") == 0) || (strcmp(timebase, "month") == 0) ||
376  (strcmp(timebase, "year") == 0))
377  {
378  return true;
379  }
380  return false;
381 }
382 
392 static void query_window_reset(void)
393 {
394  mutt_debug(LL_DEBUG2, "entering\n");
395  cs_subset_str_native_set(NeoMutt->sub, "nm_query_window_current_position", 0, NULL);
396 }
397 
436 static bool windowed_query_from_query(const char *query, char *buf, size_t buflen)
437 {
438  mutt_debug(LL_DEBUG2, "nm: %s\n", query);
439 
442 
443  /* if the duration is a non positive integer, disable the window */
444  if (C_NmQueryWindowDuration <= 0)
445  {
447  return false;
448  }
449 
450  /* if the query has changed, reset the window position */
451  if (!C_NmQueryWindowCurrentSearch || (strcmp(query, C_NmQueryWindowCurrentSearch) != 0))
453 
455  {
456  mutt_message(_("Invalid nm_query_window_timebase value (valid values are: "
457  "hour, day, week, month or year)"));
458  mutt_debug(LL_DEBUG2, "Invalid nm_query_window_timebase value\n");
459  return false;
460  }
461 
462  if (end == 0)
463  {
464  // Open-ended date allows mail from the future.
465  // This may occur is the sender's time settings are off.
466  snprintf(buf, buflen, "date:%d%s.. and %s", beg, C_NmQueryWindowTimebase,
468  }
469  else
470  {
471  snprintf(buf, buflen, "date:%d%s..%d%s and %s", beg, C_NmQueryWindowTimebase,
473  }
474 
475  mutt_debug(LL_DEBUG2, "nm: %s -> %s\n", query, buf);
476 
477  return true;
478 }
479 
496 static char *get_query_string(struct NmMboxData *mdata, bool window)
497 {
498  mutt_debug(LL_DEBUG2, "nm: %s\n", window ? "true" : "false");
499 
500  if (!mdata)
501  return NULL;
502  if (mdata->db_query && !window)
503  return mdata->db_query;
504 
505  mdata->query_type = string_to_query_type(C_NmQueryType); /* user's default */
506 
507  struct UrlQuery *item = NULL;
508  STAILQ_FOREACH(item, &mdata->db_url->query_strings, entries)
509  {
510  if (!item->value || !item->name)
511  continue;
512 
513  if (strcmp(item->name, "limit") == 0)
514  {
515  if (mutt_str_atoi(item->value, &mdata->db_limit))
516  mutt_error(_("failed to parse notmuch limit: %s"), item->value);
517  }
518  else if (strcmp(item->name, "type") == 0)
519  mdata->query_type = string_to_query_type(item->value);
520  else if (strcmp(item->name, "query") == 0)
521  mdata->db_query = mutt_str_dup(item->value);
522  }
523 
524  if (!mdata->db_query)
525  return NULL;
526 
527  if (window)
528  {
529  char buf[1024];
531 
532  /* if a date part is defined, do not apply windows (to avoid the risk of
533  * having a non-intersected date frame). A good improvement would be to
534  * accept if they intersect */
535  if (!strstr(mdata->db_query, "date:") &&
536  windowed_query_from_query(mdata->db_query, buf, sizeof(buf)))
537  {
538  mdata->db_query = mutt_str_dup(buf);
539  }
540 
541  mutt_debug(LL_DEBUG2, "nm: query (windowed) '%s'\n", mdata->db_query);
542  }
543  else
544  mutt_debug(LL_DEBUG2, "nm: query '%s'\n", mdata->db_query);
545 
546  return mdata->db_query;
547 }
548 
554 static int get_limit(struct NmMboxData *mdata)
555 {
556  return mdata ? mdata->db_limit : 0;
557 }
558 
563 static void apply_exclude_tags(notmuch_query_t *query)
564 {
565  if (!C_NmExcludeTags || !query)
566  return;
567 
568  char *end = NULL, *tag = NULL;
569 
570  char *buf = mutt_str_dup(C_NmExcludeTags);
571 
572  for (char *p = buf; p && (p[0] != '\0'); p++)
573  {
574  if (!tag && isspace(*p))
575  continue;
576  if (!tag)
577  tag = p; /* begin of the tag */
578  if ((p[0] == ',') || (p[0] == ' '))
579  end = p; /* terminate the tag */
580  else if (p[1] == '\0')
581  end = p + 1; /* end of optstr */
582  if (!tag || !end)
583  continue;
584  if (tag >= end)
585  break;
586  *end = '\0';
587 
588  mutt_debug(LL_DEBUG2, "nm: query exclude tag '%s'\n", tag);
589  notmuch_query_add_tag_exclude(query, tag);
590  end = NULL;
591  tag = NULL;
592  }
593  notmuch_query_set_omit_excluded(query, 1);
594  FREE(&buf);
595 }
596 
604 static notmuch_query_t *get_query(struct Mailbox *m, bool writable)
605 {
606  struct NmMboxData *mdata = nm_mdata_get(m);
607  if (!mdata)
608  return NULL;
609 
610  notmuch_database_t *db = nm_db_get(m, writable);
611  const char *str = get_query_string(mdata, true);
612 
613  if (!db || !str)
614  goto err;
615 
616  notmuch_query_t *q = notmuch_query_create(db, str);
617  if (!q)
618  goto err;
619 
621  notmuch_query_set_sort(q, NOTMUCH_SORT_NEWEST_FIRST);
622  mutt_debug(LL_DEBUG2, "nm: query successfully initialized (%s)\n", str);
623  return q;
624 err:
625  nm_db_release(m);
626  return NULL;
627 }
628 
636 static int update_email_tags(struct Email *e, notmuch_message_t *msg)
637 {
638  struct NmEmailData *edata = nm_edata_get(e);
639  char *new_tags = NULL;
640  char *old_tags = NULL;
641 
642  mutt_debug(LL_DEBUG2, "nm: tags update requested (%s)\n", edata->virtual_id);
643 
644  for (notmuch_tags_t *tags = notmuch_message_get_tags(msg);
645  tags && notmuch_tags_valid(tags); notmuch_tags_move_to_next(tags))
646  {
647  const char *t = notmuch_tags_get(tags);
648  if (!t || (*t == '\0'))
649  continue;
650 
651  mutt_str_append_item(&new_tags, t, ' ');
652  }
653 
654  old_tags = driver_tags_get(&e->tags);
655 
656  if (new_tags && old_tags && (strcmp(old_tags, new_tags) == 0))
657  {
658  FREE(&old_tags);
659  FREE(&new_tags);
660  mutt_debug(LL_DEBUG2, "nm: tags unchanged\n");
661  return 1;
662  }
663 
664  /* new version */
665  driver_tags_replace(&e->tags, new_tags);
666  FREE(&new_tags);
667 
668  new_tags = driver_tags_get_transformed(&e->tags);
669  mutt_debug(LL_DEBUG2, "nm: new tags: '%s'\n", new_tags);
670  FREE(&new_tags);
671 
672  new_tags = driver_tags_get(&e->tags);
673  mutt_debug(LL_DEBUG2, "nm: new tag transforms: '%s'\n", new_tags);
674  FREE(&new_tags);
675 
676  return 0;
677 }
678 
686 static int update_message_path(struct Email *e, const char *path)
687 {
688  struct NmEmailData *edata = nm_edata_get(e);
689 
690  mutt_debug(LL_DEBUG2, "nm: path update requested path=%s, (%s)\n", path, edata->virtual_id);
691 
692  char *p = strrchr(path, '/');
693  if (p && ((p - path) > 3) &&
694  (mutt_strn_equal(p - 3, "cur", 3) || mutt_strn_equal(p - 3, "new", 3) ||
695  mutt_strn_equal(p - 3, "tmp", 3)))
696  {
697  edata->type = MUTT_MAILDIR;
698 
699  FREE(&e->path);
700  FREE(&edata->folder);
701 
702  p -= 3; /* skip subfolder (e.g. "new") */
703  e->path = mutt_str_dup(p);
704 
705  for (; (p > path) && (*(p - 1) == '/'); p--)
706  ; // do nothing
707 
708  edata->folder = mutt_strn_dup(path, p - path);
709 
710  mutt_debug(LL_DEBUG2, "nm: folder='%s', file='%s'\n", edata->folder, e->path);
711  return 0;
712  }
713 
714  return 1;
715 }
716 
723 static char *get_folder_from_path(const char *path)
724 {
725  char *p = strrchr(path, '/');
726 
727  if (p && ((p - path) > 3) &&
728  (mutt_strn_equal(p - 3, "cur", 3) || mutt_strn_equal(p - 3, "new", 3) ||
729  mutt_strn_equal(p - 3, "tmp", 3)))
730  {
731  p -= 3;
732  for (; (p > path) && (*(p - 1) == '/'); p--)
733  ; // do nothing
734 
735  return mutt_strn_dup(path, p - path);
736  }
737 
738  return NULL;
739 }
740 
748 static char *nm2mutt_message_id(const char *id)
749 {
750  size_t sz;
751  char *mid = NULL;
752 
753  if (!id)
754  return NULL;
755  sz = strlen(id) + 3;
756  mid = mutt_mem_malloc(sz);
757 
758  snprintf(mid, sz, "<%s>", id);
759  return mid;
760 }
761 
770 static int init_email(struct Email *e, const char *path, notmuch_message_t *msg)
771 {
772  if (nm_edata_get(e))
773  return 0;
774 
775  struct NmEmailData *edata = nm_edata_new();
776  e->nm_edata = edata;
777 
778  /* Notmuch ensures that message Id exists (if not notmuch Notmuch will
779  * generate an ID), so it's more safe than use neomutt Email->env->id */
780  const char *id = notmuch_message_get_message_id(msg);
781  edata->virtual_id = mutt_str_dup(id);
782 
783  mutt_debug(LL_DEBUG2, "nm: [e=%p, edata=%p] (%s)\n", (void *) e, (void *) edata, id);
784 
785  char *nm_msg_id = nm2mutt_message_id(id);
786  if (!e->env->message_id)
787  {
788  e->env->message_id = nm_msg_id;
789  }
790  else if (!mutt_str_equal(e->env->message_id, nm_msg_id))
791  {
792  FREE(&e->env->message_id);
793  e->env->message_id = nm_msg_id;
794  }
795  else
796  {
797  FREE(&nm_msg_id);
798  }
799 
800  if (update_message_path(e, path) != 0)
801  return -1;
802 
803  update_email_tags(e, msg);
804 
805  return 0;
806 }
807 
814 static const char *get_message_last_filename(notmuch_message_t *msg)
815 {
816  const char *name = NULL;
817 
818  for (notmuch_filenames_t *ls = notmuch_message_get_filenames(msg);
819  ls && notmuch_filenames_valid(ls); notmuch_filenames_move_to_next(ls))
820  {
821  name = notmuch_filenames_get(ls);
822  }
823 
824  return name;
825 }
826 
831 static void progress_reset(struct Mailbox *m)
832 {
833  if (!m->verbose)
834  return;
835 
836  struct NmMboxData *mdata = nm_mdata_get(m);
837  if (!mdata)
838  return;
839 
840  memset(&mdata->progress, 0, sizeof(mdata->progress));
841  mdata->oldmsgcount = m->msg_count;
842  mdata->ignmsgcount = 0;
843  mdata->noprogress = false;
844  mdata->progress_ready = false;
845 }
846 
852 static void progress_update(struct Mailbox *m, notmuch_query_t *q)
853 {
854  struct NmMboxData *mdata = nm_mdata_get(m);
855 
856  if (!m->verbose || !mdata || mdata->noprogress)
857  return;
858 
859  if (!mdata->progress_ready && q)
860  {
861  // The total mail count is in oldmsgcount, so use that instead of recounting.
862  mutt_progress_init(&mdata->progress, _("Reading messages..."),
864  mdata->progress_ready = true;
865  }
866 
867  if (mdata->progress_ready)
868  {
869  mutt_progress_update(&mdata->progress, m->msg_count + mdata->ignmsgcount, -1);
870  }
871 }
872 
880 static struct Email *get_mutt_email(struct Mailbox *m, notmuch_message_t *msg)
881 {
882  if (!m || !msg)
883  return NULL;
884 
885  const char *id = notmuch_message_get_message_id(msg);
886  if (!id)
887  return NULL;
888 
889  mutt_debug(LL_DEBUG2, "nm: neomutt email, id='%s'\n", id);
890 
891  if (!m->id_hash)
892  {
893  mutt_debug(LL_DEBUG2, "nm: init hash\n");
894  m->id_hash = mutt_make_id_hash(m);
895  if (!m->id_hash)
896  return NULL;
897  }
898 
899  char *mid = nm2mutt_message_id(id);
900  mutt_debug(LL_DEBUG2, "nm: neomutt id='%s'\n", mid);
901 
902  struct Email *e = mutt_hash_find(m->id_hash, mid);
903  FREE(&mid);
904  return e;
905 }
906 
915 static void append_message(struct HeaderCache *h, struct Mailbox *m,
916  notmuch_query_t *q, notmuch_message_t *msg, bool dedup)
917 {
918  struct NmMboxData *mdata = nm_mdata_get(m);
919  if (!mdata)
920  return;
921 
922  char *newpath = NULL;
923  struct Email *e = NULL;
924 
925  /* deduplicate */
926  if (dedup && get_mutt_email(m, msg))
927  {
928  mdata->ignmsgcount++;
929  progress_update(m, q);
930  mutt_debug(LL_DEBUG2, "nm: ignore id=%s, already in the m\n",
931  notmuch_message_get_message_id(msg));
932  return;
933  }
934 
935  const char *path = get_message_last_filename(msg);
936  if (!path)
937  return;
938 
939  mutt_debug(LL_DEBUG2, "nm: appending message, i=%d, id=%s, path=%s\n",
940  m->msg_count, notmuch_message_get_message_id(msg), path);
941 
942  if (m->msg_count >= m->email_max)
943  {
944  mutt_debug(LL_DEBUG2, "nm: allocate mx memory\n");
945  mx_alloc_memory(m);
946  }
947 
948 #ifdef USE_HCACHE
949  e = mutt_hcache_fetch(h, path, mutt_str_len(path), 0).email;
950  if (!e)
951 #endif
952  {
953  if (access(path, F_OK) == 0)
954  e = maildir_parse_message(MUTT_MAILDIR, path, false, NULL);
955  else
956  {
957  /* maybe moved try find it... */
958  char *folder = get_folder_from_path(path);
959 
960  if (folder)
961  {
962  FILE *fp = maildir_open_find_message(folder, path, &newpath);
963  if (fp)
964  {
965  e = maildir_parse_stream(MUTT_MAILDIR, fp, newpath, false, NULL);
966  mutt_file_fclose(&fp);
967 
968  mutt_debug(LL_DEBUG1, "nm: not up-to-date: %s -> %s\n", path, newpath);
969  }
970  }
971  FREE(&folder);
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  mutt_hcache_store(h, newpath ? newpath : path,
982  mutt_str_len(newpath ? newpath : path), e, 0);
983 #endif
984  }
985 
986  if (init_email(e, newpath ? newpath : path, msg) != 0)
987  {
988  email_free(&e);
989  mutt_debug(LL_DEBUG1, "nm: failed to append email!\n");
990  goto done;
991  }
992 
993  e->active = true;
994  e->index = m->msg_count;
995  mailbox_size_add(m, e);
996  m->emails[m->msg_count] = e;
997  m->msg_count++;
998 
999  if (newpath)
1000  {
1001  /* remember that file has been moved -- nm_mbox_sync() will update the DB */
1002  struct NmEmailData *edata = nm_edata_get(e);
1003  if (edata)
1004  {
1005  mutt_debug(LL_DEBUG1, "nm: remember obsolete path: %s\n", path);
1006  edata->oldpath = mutt_str_dup(path);
1007  }
1008  }
1009  progress_update(m, q);
1010 done:
1011  FREE(&newpath);
1012 }
1013 
1024 static void append_replies(struct HeaderCache *h, struct Mailbox *m,
1025  notmuch_query_t *q, notmuch_message_t *top, bool dedup)
1026 {
1027  notmuch_messages_t *msgs = NULL;
1028 
1029  for (msgs = notmuch_message_get_replies(top); notmuch_messages_valid(msgs);
1030  notmuch_messages_move_to_next(msgs))
1031  {
1032  notmuch_message_t *nm = notmuch_messages_get(msgs);
1033  append_message(h, m, q, nm, dedup);
1034  /* recurse through all the replies to this message too */
1035  append_replies(h, m, q, nm, dedup);
1036  notmuch_message_destroy(nm);
1037  }
1038 }
1039 
1051 static void append_thread(struct HeaderCache *h, struct Mailbox *m,
1052  notmuch_query_t *q, notmuch_thread_t *thread, bool dedup)
1053 {
1054  notmuch_messages_t *msgs = NULL;
1055 
1056  for (msgs = notmuch_thread_get_toplevel_messages(thread);
1057  notmuch_messages_valid(msgs); notmuch_messages_move_to_next(msgs))
1058  {
1059  notmuch_message_t *nm = notmuch_messages_get(msgs);
1060  append_message(h, m, q, nm, dedup);
1061  append_replies(h, m, q, nm, dedup);
1062  notmuch_message_destroy(nm);
1063  }
1064 }
1065 
1075 static notmuch_messages_t *get_messages(notmuch_query_t *query)
1076 {
1077  if (!query)
1078  return NULL;
1079 
1080  notmuch_messages_t *msgs = NULL;
1081 
1082 #if LIBNOTMUCH_CHECK_VERSION(5, 0, 0)
1083  if (notmuch_query_search_messages(query, &msgs) != NOTMUCH_STATUS_SUCCESS)
1084  return NULL;
1085 #elif LIBNOTMUCH_CHECK_VERSION(4, 3, 0)
1086  if (notmuch_query_search_messages_st(query, &msgs) != NOTMUCH_STATUS_SUCCESS)
1087  return NULL;
1088 #else
1089  msgs = notmuch_query_search_messages(query);
1090 #endif
1091 
1092  return msgs;
1093 }
1094 
1103 static bool read_mesgs_query(struct Mailbox *m, notmuch_query_t *q, bool dedup)
1104 {
1105  struct NmMboxData *mdata = nm_mdata_get(m);
1106  if (!mdata)
1107  return false;
1108 
1109  int limit = get_limit(mdata);
1110 
1111  notmuch_messages_t *msgs = get_messages(q);
1112 
1113  if (!msgs)
1114  return false;
1115 
1116  struct HeaderCache *h = nm_hcache_open(m);
1117 
1118  for (; notmuch_messages_valid(msgs) && ((limit == 0) || (m->msg_count < limit));
1119  notmuch_messages_move_to_next(msgs))
1120  {
1121  if (SigInt == 1)
1122  {
1123  nm_hcache_close(h);
1124  SigInt = 0;
1125  return false;
1126  }
1127  notmuch_message_t *nm = notmuch_messages_get(msgs);
1128  append_message(h, m, q, nm, dedup);
1129  notmuch_message_destroy(nm);
1130  }
1131 
1132  nm_hcache_close(h);
1133  return true;
1134 }
1135 
1145 static notmuch_threads_t *get_threads(notmuch_query_t *query)
1146 {
1147  if (!query)
1148  return NULL;
1149 
1150  notmuch_threads_t *threads = NULL;
1151 #if LIBNOTMUCH_CHECK_VERSION(5, 0, 0)
1152  if (notmuch_query_search_threads(query, &threads) != NOTMUCH_STATUS_SUCCESS)
1153  return false;
1154 #elif LIBNOTMUCH_CHECK_VERSION(4, 3, 0)
1155  if (notmuch_query_search_threads_st(query, &threads) != NOTMUCH_STATUS_SUCCESS)
1156  return false;
1157 #else
1158  threads = notmuch_query_search_threads(query);
1159 #endif
1160 
1161  return threads;
1162 }
1163 
1173 static bool read_threads_query(struct Mailbox *m, notmuch_query_t *q, bool dedup, int limit)
1174 {
1175  struct NmMboxData *mdata = nm_mdata_get(m);
1176  if (!mdata)
1177  return false;
1178 
1179  notmuch_threads_t *threads = get_threads(q);
1180  if (!threads)
1181  return false;
1182 
1183  struct HeaderCache *h = nm_hcache_open(m);
1184 
1185  for (; notmuch_threads_valid(threads) && ((limit == 0) || (m->msg_count < limit));
1186  notmuch_threads_move_to_next(threads))
1187  {
1188  if (SigInt == 1)
1189  {
1190  nm_hcache_close(h);
1191  SigInt = 0;
1192  return false;
1193  }
1194  notmuch_thread_t *thread = notmuch_threads_get(threads);
1195  append_thread(h, m, q, thread, dedup);
1196  notmuch_thread_destroy(thread);
1197  }
1198 
1199  nm_hcache_close(h);
1200  return true;
1201 }
1202 
1209 static notmuch_message_t *get_nm_message(notmuch_database_t *db, struct Email *e)
1210 {
1211  notmuch_message_t *msg = NULL;
1212  char *id = email_get_id(e);
1213 
1214  mutt_debug(LL_DEBUG2, "nm: find message (%s)\n", id);
1215 
1216  if (id && db)
1217  notmuch_database_find_message(db, id, &msg);
1218 
1219  return msg;
1220 }
1221 
1228 static bool nm_message_has_tag(notmuch_message_t *msg, char *tag)
1229 {
1230  const char *possible_match_tag = NULL;
1231  notmuch_tags_t *tags = NULL;
1232 
1233  for (tags = notmuch_message_get_tags(msg); notmuch_tags_valid(tags);
1234  notmuch_tags_move_to_next(tags))
1235  {
1236  possible_match_tag = notmuch_tags_get(tags);
1237  if (mutt_str_equal(possible_match_tag, tag))
1238  {
1239  return true;
1240  }
1241  }
1242  return false;
1243 }
1244 
1250 static void sync_email_path_with_nm(struct Email *e, notmuch_message_t *msg)
1251 {
1252  const char *new_file = get_message_last_filename(msg);
1253  char old_file[PATH_MAX];
1254  email_get_fullpath(e, old_file, sizeof(old_file));
1255 
1256  if (!mutt_str_equal(old_file, new_file))
1257  update_message_path(e, new_file);
1258 }
1259 
1267 static int update_tags(notmuch_message_t *msg, const char *tags)
1268 {
1269  char *buf = mutt_str_dup(tags);
1270  if (!buf)
1271  return -1;
1272 
1273  notmuch_message_freeze(msg);
1274 
1275  char *tag = NULL, *end = NULL;
1276  for (char *p = buf; p && *p; p++)
1277  {
1278  if (!tag && isspace(*p))
1279  continue;
1280  if (!tag)
1281  tag = p; /* begin of the tag */
1282  if ((p[0] == ',') || (p[0] == ' '))
1283  end = p; /* terminate the tag */
1284  else if (p[1] == '\0')
1285  end = p + 1; /* end of optstr */
1286  if (!tag || !end)
1287  continue;
1288  if (tag >= end)
1289  break;
1290 
1291  end[0] = '\0';
1292 
1293  if (tag[0] == '-')
1294  {
1295  mutt_debug(LL_DEBUG1, "nm: remove tag: '%s'\n", tag + 1);
1296  notmuch_message_remove_tag(msg, tag + 1);
1297  }
1298  else if (tag[0] == '!')
1299  {
1300  mutt_debug(LL_DEBUG1, "nm: toggle tag: '%s'\n", tag + 1);
1301  if (nm_message_has_tag(msg, tag + 1))
1302  {
1303  notmuch_message_remove_tag(msg, tag + 1);
1304  }
1305  else
1306  {
1307  notmuch_message_add_tag(msg, tag + 1);
1308  }
1309  }
1310  else
1311  {
1312  mutt_debug(LL_DEBUG1, "nm: add tag: '%s'\n", (tag[0] == '+') ? tag + 1 : tag);
1313  notmuch_message_add_tag(msg, (tag[0] == '+') ? tag + 1 : tag);
1314  }
1315  end = NULL;
1316  tag = NULL;
1317  }
1318 
1319  notmuch_message_thaw(msg);
1320  FREE(&buf);
1321  return 0;
1322 }
1323 
1336 static int update_email_flags(struct Mailbox *m, struct Email *e, const char *tags)
1337 {
1338  char *buf = mutt_str_dup(tags);
1339  if (!buf)
1340  return -1;
1341 
1342  char *tag = NULL, *end = NULL;
1343  for (char *p = buf; p && *p; p++)
1344  {
1345  if (!tag && isspace(*p))
1346  continue;
1347  if (!tag)
1348  tag = p; /* begin of the tag */
1349  if ((p[0] == ',') || (p[0] == ' '))
1350  end = p; /* terminate the tag */
1351  else if (p[1] == '\0')
1352  end = p + 1; /* end of optstr */
1353  if (!tag || !end)
1354  continue;
1355  if (tag >= end)
1356  break;
1357 
1358  end[0] = '\0';
1359 
1360  if (tag[0] == '-')
1361  {
1362  tag++;
1363  if (strcmp(tag, C_NmUnreadTag) == 0)
1364  mutt_set_flag(m, e, MUTT_READ, true);
1365  else if (strcmp(tag, C_NmRepliedTag) == 0)
1366  mutt_set_flag(m, e, MUTT_REPLIED, false);
1367  else if (strcmp(tag, C_NmFlaggedTag) == 0)
1368  mutt_set_flag(m, e, MUTT_FLAG, false);
1369  }
1370  else
1371  {
1372  tag = (tag[0] == '+') ? tag + 1 : tag;
1373  if (strcmp(tag, C_NmUnreadTag) == 0)
1374  mutt_set_flag(m, e, MUTT_READ, false);
1375  else if (strcmp(tag, C_NmRepliedTag) == 0)
1376  mutt_set_flag(m, e, MUTT_REPLIED, true);
1377  else if (strcmp(tag, C_NmFlaggedTag) == 0)
1378  mutt_set_flag(m, e, MUTT_FLAG, true);
1379  }
1380  end = NULL;
1381  tag = NULL;
1382  }
1383 
1384  FREE(&buf);
1385  return 0;
1386 }
1387 
1398 static int rename_maildir_filename(const char *old, char *buf, size_t buflen, struct Email *e)
1399 {
1400  char filename[PATH_MAX];
1401  char suffix[PATH_MAX];
1402  char folder[PATH_MAX];
1403 
1404  mutt_str_copy(folder, old, sizeof(folder));
1405  char *p = strrchr(folder, '/');
1406  if (p)
1407  {
1408  *p = '\0';
1409  p++;
1410  }
1411  else
1412  p = folder;
1413 
1414  mutt_str_copy(filename, p, sizeof(filename));
1415 
1416  /* remove (new,cur,...) from folder path */
1417  p = strrchr(folder, '/');
1418  if (p)
1419  *p = '\0';
1420 
1421  /* remove old flags from filename */
1422  p = strchr(filename, ':');
1423  if (p)
1424  *p = '\0';
1425 
1426  /* compose new flags */
1427  maildir_gen_flags(suffix, sizeof(suffix), e);
1428 
1429  snprintf(buf, buflen, "%s/%s/%s%s", folder,
1430  (e->read || e->old) ? "cur" : "new", filename, suffix);
1431 
1432  if (strcmp(old, buf) == 0)
1433  return 1;
1434 
1435  if (rename(old, buf) != 0)
1436  {
1437  mutt_debug(LL_DEBUG1, "nm: rename(2) failed %s -> %s\n", old, buf);
1438  return -1;
1439  }
1440 
1441  return 0;
1442 }
1443 
1451 static int remove_filename(struct Mailbox *m, const char *path)
1452 {
1453  struct NmMboxData *mdata = nm_mdata_get(m);
1454  if (!mdata)
1455  return -1;
1456 
1457  mutt_debug(LL_DEBUG2, "nm: remove filename '%s'\n", path);
1458 
1459  notmuch_database_t *db = nm_db_get(m, true);
1460  if (!db)
1461  return -1;
1462 
1463  notmuch_message_t *msg = NULL;
1464  notmuch_status_t st = notmuch_database_find_message_by_filename(db, path, &msg);
1465  if (st || !msg)
1466  return -1;
1467 
1468  int trans = nm_db_trans_begin(m);
1469  if (trans < 0)
1470  return -1;
1471 
1472  /* note that unlink() is probably unnecessary here, it's already removed
1473  * by mh_sync_mailbox_message(), but for sure... */
1474  notmuch_filenames_t *ls = NULL;
1475  st = notmuch_database_remove_message(db, path);
1476  switch (st)
1477  {
1478  case NOTMUCH_STATUS_SUCCESS:
1479  mutt_debug(LL_DEBUG2, "nm: remove success, call unlink\n");
1480  unlink(path);
1481  break;
1482  case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:
1483  mutt_debug(LL_DEBUG2, "nm: remove success (duplicate), call unlink\n");
1484  unlink(path);
1485  for (ls = notmuch_message_get_filenames(msg);
1486  ls && notmuch_filenames_valid(ls); notmuch_filenames_move_to_next(ls))
1487  {
1488  path = notmuch_filenames_get(ls);
1489 
1490  mutt_debug(LL_DEBUG2, "nm: remove duplicate: '%s'\n", path);
1491  unlink(path);
1492  notmuch_database_remove_message(db, path);
1493  }
1494  break;
1495  default:
1496  mutt_debug(LL_DEBUG1, "nm: failed to remove '%s' [st=%d]\n", path, (int) st);
1497  break;
1498  }
1499 
1500  notmuch_message_destroy(msg);
1501  if (trans)
1502  nm_db_trans_end(m);
1503  return 0;
1504 }
1505 
1515 static int rename_filename(struct Mailbox *m, const char *old_file,
1516  const char *new_file, struct Email *e)
1517 {
1518  struct NmMboxData *mdata = nm_mdata_get(m);
1519  if (!mdata)
1520  return -1;
1521 
1522  notmuch_database_t *db = nm_db_get(m, true);
1523  if (!db || !new_file || !old_file || (access(new_file, F_OK) != 0))
1524  return -1;
1525 
1526  int rc = -1;
1527  notmuch_status_t st;
1528  notmuch_filenames_t *ls = NULL;
1529  notmuch_message_t *msg = NULL;
1530 
1531  mutt_debug(LL_DEBUG1, "nm: rename filename, %s -> %s\n", old_file, new_file);
1532  int trans = nm_db_trans_begin(m);
1533  if (trans < 0)
1534  return -1;
1535 
1536  mutt_debug(LL_DEBUG2, "nm: rename: add '%s'\n", new_file);
1537 #ifdef HAVE_NOTMUCH_DATABASE_INDEX_FILE
1538  st = notmuch_database_index_file(db, new_file, NULL, &msg);
1539 #else
1540  st = notmuch_database_add_message(db, new_file, &msg);
1541 #endif
1542 
1543  if ((st != NOTMUCH_STATUS_SUCCESS) && (st != NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID))
1544  {
1545  mutt_debug(LL_DEBUG1, "nm: failed to add '%s' [st=%d]\n", new_file, (int) st);
1546  goto done;
1547  }
1548 
1549  mutt_debug(LL_DEBUG2, "nm: rename: rem '%s'\n", old_file);
1550  st = notmuch_database_remove_message(db, old_file);
1551  switch (st)
1552  {
1553  case NOTMUCH_STATUS_SUCCESS:
1554  break;
1555  case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:
1556  mutt_debug(LL_DEBUG2, "nm: rename: syncing duplicate filename\n");
1557  notmuch_message_destroy(msg);
1558  msg = NULL;
1559  notmuch_database_find_message_by_filename(db, new_file, &msg);
1560 
1561  for (ls = notmuch_message_get_filenames(msg);
1562  msg && ls && notmuch_filenames_valid(ls); notmuch_filenames_move_to_next(ls))
1563  {
1564  const char *path = notmuch_filenames_get(ls);
1565  char newpath[PATH_MAX];
1566 
1567  if (strcmp(new_file, path) == 0)
1568  continue;
1569 
1570  mutt_debug(LL_DEBUG2, "nm: rename: syncing duplicate: %s\n", path);
1571 
1572  if (rename_maildir_filename(path, newpath, sizeof(newpath), e) == 0)
1573  {
1574  mutt_debug(LL_DEBUG2, "nm: rename dup %s -> %s\n", path, newpath);
1575  notmuch_database_remove_message(db, path);
1576 #ifdef HAVE_NOTMUCH_DATABASE_INDEX_FILE
1577  notmuch_database_index_file(db, newpath, NULL, NULL);
1578 #else
1579  notmuch_database_add_message(db, newpath, NULL);
1580 #endif
1581  }
1582  }
1583  notmuch_message_destroy(msg);
1584  msg = NULL;
1585  notmuch_database_find_message_by_filename(db, new_file, &msg);
1586  st = NOTMUCH_STATUS_SUCCESS;
1587  break;
1588  default:
1589  mutt_debug(LL_DEBUG1, "nm: failed to remove '%s' [st=%d]\n", old_file, (int) st);
1590  break;
1591  }
1592 
1593  if ((st == NOTMUCH_STATUS_SUCCESS) && e && msg)
1594  {
1595  notmuch_message_maildir_flags_to_tags(msg);
1596  update_email_tags(e, msg);
1597 
1598  char *tags = driver_tags_get(&e->tags);
1599  update_tags(msg, tags);
1600  FREE(&tags);
1601  }
1602 
1603  rc = 0;
1604 done:
1605  if (msg)
1606  notmuch_message_destroy(msg);
1607  if (trans)
1608  nm_db_trans_end(m);
1609  return rc;
1610 }
1611 
1619 static unsigned int count_query(notmuch_database_t *db, const char *qstr, int limit)
1620 {
1621  notmuch_query_t *q = notmuch_query_create(db, qstr);
1622  if (!q)
1623  return 0;
1624 
1625  unsigned int res = 0;
1626 
1627  apply_exclude_tags(q);
1628 #if LIBNOTMUCH_CHECK_VERSION(5, 0, 0)
1629  if (notmuch_query_count_messages(q, &res) != NOTMUCH_STATUS_SUCCESS)
1630  res = 0; /* may not be defined on error */
1631 #elif LIBNOTMUCH_CHECK_VERSION(4, 3, 0)
1632  if (notmuch_query_count_messages_st(q, &res) != NOTMUCH_STATUS_SUCCESS)
1633  res = 0; /* may not be defined on error */
1634 #else
1635  res = notmuch_query_count_messages(q);
1636 #endif
1637  notmuch_query_destroy(q);
1638  mutt_debug(LL_DEBUG1, "nm: count '%s', result=%d\n", qstr, res);
1639 
1640  if ((limit > 0) && (res > limit))
1641  res = limit;
1642 
1643  return res;
1644 }
1645 
1652 char *nm_email_get_folder(struct Email *e)
1653 {
1654  struct NmEmailData *edata = nm_edata_get(e);
1655  if (!edata)
1656  return NULL;
1657 
1658  return edata->folder;
1659 }
1660 
1668 int nm_read_entire_thread(struct Mailbox *m, struct Email *e)
1669 {
1670  if (!m)
1671  return -1;
1672 
1673  struct NmMboxData *mdata = nm_mdata_get(m);
1674  if (!mdata)
1675  return -1;
1676 
1677  notmuch_query_t *q = NULL;
1678  notmuch_database_t *db = NULL;
1679  notmuch_message_t *msg = NULL;
1680  int rc = -1;
1681 
1682  if (!(db = nm_db_get(m, false)) || !(msg = get_nm_message(db, e)))
1683  goto done;
1684 
1685  mutt_debug(LL_DEBUG1, "nm: reading entire-thread messages...[current count=%d]\n",
1686  m->msg_count);
1687 
1688  progress_reset(m);
1689  const char *id = notmuch_message_get_thread_id(msg);
1690  if (!id)
1691  goto done;
1692 
1693  char *qstr = NULL;
1694  mutt_str_append_item(&qstr, "thread:", '\0');
1695  mutt_str_append_item(&qstr, id, '\0');
1696 
1697  q = notmuch_query_create(db, qstr);
1698  FREE(&qstr);
1699  if (!q)
1700  goto done;
1701  apply_exclude_tags(q);
1702  notmuch_query_set_sort(q, NOTMUCH_SORT_NEWEST_FIRST);
1703 
1704  read_threads_query(m, q, true, 0);
1705  m->mtime.tv_sec = mutt_date_epoch();
1706  m->mtime.tv_nsec = 0;
1707  rc = 0;
1708 
1709  if (m->msg_count > mdata->oldmsgcount)
1711 done:
1712  if (q)
1713  notmuch_query_destroy(q);
1714 
1715  nm_db_release(m);
1716 
1717  if (m->msg_count == mdata->oldmsgcount)
1718  mutt_message(_("No more messages in the thread"));
1719 
1720  mdata->oldmsgcount = 0;
1721  mutt_debug(LL_DEBUG1, "nm: reading entire-thread messages... done [rc=%d, count=%d]\n",
1722  rc, m->msg_count);
1723  return rc;
1724 }
1725 
1737 {
1738  if (!buf)
1739  return;
1740 
1741  // The six variations of how type= could appear.
1742  const char *variants[6] = { "&type=threads", "&type=messages",
1743  "type=threads&", "type=messages&",
1744  "type=threads", "type=messages" };
1745 
1746  int variants_size = mutt_array_size(variants);
1747  for (int i = 0; i < variants_size; i++)
1748  {
1749  if (strcasestr(buf, variants[i]) != NULL)
1750  {
1751  // variants[] is setup such that type can be determined via modulo 2.
1752  mdata->query_type = ((i % 2) == 0) ? NM_QUERY_TYPE_THREADS : NM_QUERY_TYPE_MESGS;
1753 
1754  mutt_istr_remall(buf, variants[i]);
1755  }
1756  }
1757 }
1758 
1767 char *nm_url_from_query(struct Mailbox *m, char *buf, size_t buflen)
1768 {
1769  mutt_debug(LL_DEBUG2, "(%s)\n", buf);
1770  struct NmMboxData *mdata = nm_mdata_get(m);
1771  char url[PATH_MAX + 1024 + 32]; /* path to DB + query + URL "decoration" */
1772  int added;
1773  bool using_default_data = false;
1774 
1775  // No existing data. Try to get a default NmMboxData.
1776  if (!mdata)
1777  {
1778  mdata = nm_get_default_data();
1779 
1780  // Failed to get default data.
1781  if (!mdata)
1782  return NULL;
1783 
1784  using_default_data = true;
1785  }
1786 
1787  nm_parse_type_from_query(mdata, buf);
1788 
1789  if (get_limit(mdata) == C_NmDbLimit)
1790  {
1791  added = snprintf(url, sizeof(url), "%s%s?type=%s&query=", NmUrlProtocol,
1793  }
1794  else
1795  {
1796  added = snprintf(url, sizeof(url), "%s%s?type=%s&limit=%d&query=", NmUrlProtocol,
1797  nm_db_get_filename(m),
1798  query_type_to_string(mdata->query_type), get_limit(mdata));
1799  }
1800 
1801  if (added >= sizeof(url))
1802  {
1803  // snprintf output was truncated, so can't create URL
1804  return NULL;
1805  }
1806 
1807  url_pct_encode(&url[added], sizeof(url) - added, buf);
1808 
1809  mutt_str_copy(buf, url, buflen);
1810  buf[buflen - 1] = '\0';
1811 
1812  if (using_default_data)
1813  nm_mdata_free((void **) &mdata);
1814 
1815  mutt_debug(LL_DEBUG1, "nm: url from query '%s'\n", buf);
1816  return buf;
1817 }
1818 
1829 {
1832 
1834 }
1835 
1845 {
1848 }
1849 
1856 bool nm_message_is_still_queried(struct Mailbox *m, struct Email *e)
1857 {
1858  struct NmMboxData *mdata = nm_mdata_get(m);
1859  notmuch_database_t *db = nm_db_get(m, false);
1860  char *orig_str = get_query_string(mdata, true);
1861 
1862  if (!db || !orig_str)
1863  return false;
1864 
1865  char *new_str = NULL;
1866  bool rc = false;
1867  if (mutt_str_asprintf(&new_str, "id:%s and (%s)", email_get_id(e), orig_str) < 0)
1868  return false;
1869 
1870  mutt_debug(LL_DEBUG2, "nm: checking if message is still queried: %s\n", new_str);
1871 
1872  notmuch_query_t *q = notmuch_query_create(db, new_str);
1873 
1874  switch (mdata->query_type)
1875  {
1876  case NM_QUERY_TYPE_MESGS:
1877  {
1878  notmuch_messages_t *messages = get_messages(q);
1879 
1880  if (!messages)
1881  return false;
1882 
1883  rc = notmuch_messages_valid(messages);
1884  notmuch_messages_destroy(messages);
1885  break;
1886  }
1887  case NM_QUERY_TYPE_THREADS:
1888  {
1889  notmuch_threads_t *threads = get_threads(q);
1890 
1891  if (!threads)
1892  return false;
1893 
1894  rc = notmuch_threads_valid(threads);
1895  notmuch_threads_destroy(threads);
1896  break;
1897  }
1898  }
1899 
1900  notmuch_query_destroy(q);
1901 
1902  mutt_debug(LL_DEBUG2, "nm: checking if message is still queried: %s = %s\n",
1903  new_str, rc ? "true" : "false");
1904 
1905  return rc;
1906 }
1907 
1917 int nm_update_filename(struct Mailbox *m, const char *old_file,
1918  const char *new_file, struct Email *e)
1919 {
1920  char buf[PATH_MAX];
1921  struct NmMboxData *mdata = nm_mdata_get(m);
1922  if (!mdata || !new_file)
1923  return -1;
1924 
1925  if (!old_file && nm_edata_get(e))
1926  {
1927  email_get_fullpath(e, buf, sizeof(buf));
1928  old_file = buf;
1929  }
1930 
1931  int rc = rename_filename(m, old_file, new_file, e);
1932 
1933  nm_db_release(m);
1934  m->mtime.tv_sec = mutt_date_epoch();
1935  m->mtime.tv_nsec = 0;
1936  return rc;
1937 }
1938 
1942 static int nm_mbox_check_stats(struct Mailbox *m, int flags)
1943 {
1944  struct UrlQuery *item = NULL;
1945  struct Url *url = NULL;
1946  char *db_filename = NULL, *db_query = NULL;
1947  notmuch_database_t *db = NULL;
1948  int rc = -1;
1949  int limit = C_NmDbLimit;
1950  mutt_debug(LL_DEBUG1, "nm: count\n");
1951 
1952  url = url_parse(mailbox_path(m));
1953  if (!url)
1954  {
1955  mutt_error(_("failed to parse notmuch url: %s"), mailbox_path(m));
1956  goto done;
1957  }
1958 
1959  STAILQ_FOREACH(item, &url->query_strings, entries)
1960  {
1961  if (item->value && (strcmp(item->name, "query") == 0))
1962  db_query = item->value;
1963  else if (item->value && (strcmp(item->name, "limit") == 0))
1964  {
1965  // Try to parse the limit
1966  if (mutt_str_atoi(item->value, &limit) != 0)
1967  {
1968  mutt_error(_("failed to parse limit: %s"), item->value);
1969  goto done;
1970  }
1971  }
1972  }
1973 
1974  if (!db_query)
1975  goto done;
1976 
1977  db_filename = url->path;
1978  if (!db_filename)
1979  {
1980  if (C_NmDefaultUrl)
1981  {
1983  db_filename = C_NmDefaultUrl + NmUrlProtocolLen;
1984  else
1985  db_filename = C_NmDefaultUrl;
1986  }
1987  else if (C_Folder)
1988  db_filename = C_Folder;
1989  }
1990 
1991  /* don't be verbose about connection, as we're called from
1992  * sidebar/mailbox very often */
1993  db = nm_db_do_open(db_filename, false, false);
1994  if (!db)
1995  goto done;
1996 
1997  /* all emails */
1998  m->msg_count = count_query(db, db_query, limit);
1999  while (m->email_max < m->msg_count)
2000  mx_alloc_memory(m);
2001 
2002  // holder variable for extending query to unread/flagged
2003  char *qstr = NULL;
2004 
2005  // unread messages
2006  mutt_str_asprintf(&qstr, "( %s ) tag:%s", db_query, C_NmUnreadTag);
2007  m->msg_unread = count_query(db, qstr, limit);
2008  FREE(&qstr);
2009 
2010  // flagged messages
2011  mutt_str_asprintf(&qstr, "( %s ) tag:%s", db_query, C_NmFlaggedTag);
2012  m->msg_flagged = count_query(db, qstr, limit);
2013  FREE(&qstr);
2014 
2015  rc = (m->msg_new > 0);
2016 done:
2017  if (db)
2018  {
2019  nm_db_free(db);
2020  mutt_debug(LL_DEBUG1, "nm: count close DB\n");
2021  }
2022  url_free(&url);
2023 
2024  mutt_debug(LL_DEBUG1, "nm: count done [rc=%d]\n", rc);
2025  return rc;
2026 }
2027 
2036 int nm_record_message(struct Mailbox *m, char *path, struct Email *e)
2037 {
2038  notmuch_database_t *db = NULL;
2039  notmuch_status_t st;
2040  notmuch_message_t *msg = NULL;
2041  int rc = -1;
2042  struct NmMboxData *mdata = nm_mdata_get(m);
2043 
2044  if (!path || !mdata || (access(path, F_OK) != 0))
2045  return 0;
2046  db = nm_db_get(m, true);
2047  if (!db)
2048  return -1;
2049 
2050  mutt_debug(LL_DEBUG1, "nm: record message: %s\n", path);
2051  int trans = nm_db_trans_begin(m);
2052  if (trans < 0)
2053  goto done;
2054 
2055 #ifdef HAVE_NOTMUCH_DATABASE_INDEX_FILE
2056  st = notmuch_database_index_file(db, path, NULL, &msg);
2057 #else
2058  st = notmuch_database_add_message(db, path, &msg);
2059 #endif
2060 
2061  if ((st != NOTMUCH_STATUS_SUCCESS) && (st != NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID))
2062  {
2063  mutt_debug(LL_DEBUG1, "nm: failed to add '%s' [st=%d]\n", path, (int) st);
2064  goto done;
2065  }
2066 
2067  if ((st == NOTMUCH_STATUS_SUCCESS) && msg)
2068  {
2069  notmuch_message_maildir_flags_to_tags(msg);
2070  if (e)
2071  {
2072  char *tags = driver_tags_get(&e->tags);
2073  update_tags(msg, tags);
2074  FREE(&tags);
2075  }
2076  if (C_NmRecordTags)
2078  }
2079 
2080  rc = 0;
2081 done:
2082  if (msg)
2083  notmuch_message_destroy(msg);
2084  if (trans == 1)
2085  nm_db_trans_end(m);
2086 
2087  nm_db_release(m);
2088  return rc;
2089 }
2090 
2101 int nm_get_all_tags(struct Mailbox *m, char **tag_list, int *tag_count)
2102 {
2103  struct NmMboxData *mdata = nm_mdata_get(m);
2104  if (!mdata)
2105  return -1;
2106 
2107  notmuch_database_t *db = NULL;
2108  notmuch_tags_t *tags = NULL;
2109  const char *tag = NULL;
2110  int rc = -1;
2111 
2112  if (!(db = nm_db_get(m, false)) || !(tags = notmuch_database_get_all_tags(db)))
2113  goto done;
2114 
2115  *tag_count = 0;
2116  mutt_debug(LL_DEBUG1, "nm: get all tags\n");
2117 
2118  while (notmuch_tags_valid(tags))
2119  {
2120  tag = notmuch_tags_get(tags);
2121  /* Skip empty string */
2122  if (*tag)
2123  {
2124  if (tag_list)
2125  tag_list[*tag_count] = mutt_str_dup(tag);
2126  (*tag_count)++;
2127  }
2128  notmuch_tags_move_to_next(tags);
2129  }
2130 
2131  rc = 0;
2132 done:
2133  if (tags)
2134  notmuch_tags_destroy(tags);
2135 
2136  nm_db_release(m);
2137 
2138  mutt_debug(LL_DEBUG1, "nm: get all tags done [rc=%d tag_count=%u]\n", rc, *tag_count);
2139  return rc;
2140 }
2141 
2145 static struct Account *nm_ac_find(struct Account *a, const char *path)
2146 {
2147  if (!a || (a->type != MUTT_NOTMUCH) || !path)
2148  return NULL;
2149 
2150  return a;
2151 }
2152 
2156 static int nm_ac_add(struct Account *a, struct Mailbox *m)
2157 {
2158  if (!a || !m || (m->type != MUTT_NOTMUCH))
2159  return -1;
2160 
2161  if (a->adata)
2162  return 0;
2163 
2164  struct NmAccountData *adata = nm_adata_new();
2165  a->adata = adata;
2167 
2168  return 0;
2169 }
2170 
2174 static int nm_mbox_open(struct Mailbox *m)
2175 {
2176  if (init_mailbox(m) != 0)
2177  return -1;
2178 
2179  struct NmMboxData *mdata = nm_mdata_get(m);
2180  if (!mdata)
2181  return -1;
2182 
2183  mutt_debug(LL_DEBUG1, "nm: reading messages...[current count=%d]\n", m->msg_count);
2184 
2185  progress_reset(m);
2186 
2187  int rc = -1;
2188 
2189  notmuch_query_t *q = get_query(m, false);
2190  if (q)
2191  {
2192  rc = 0;
2193  switch (mdata->query_type)
2194  {
2195  case NM_QUERY_TYPE_MESGS:
2196  if (!read_mesgs_query(m, q, false))
2197  rc = -2;
2198  break;
2199  case NM_QUERY_TYPE_THREADS:
2200  if (!read_threads_query(m, q, false, get_limit(mdata)))
2201  rc = -2;
2202  break;
2203  }
2204  notmuch_query_destroy(q);
2205  }
2206 
2207  nm_db_release(m);
2208 
2209  m->mtime.tv_sec = mutt_date_epoch();
2210  m->mtime.tv_nsec = 0;
2211 
2212  mdata->oldmsgcount = 0;
2213 
2214  mutt_debug(LL_DEBUG1, "nm: reading messages... done [rc=%d, count=%d]\n", rc, m->msg_count);
2215  return rc;
2216 }
2217 
2227 static int nm_mbox_check(struct Mailbox *m)
2228 {
2229  if (!m)
2230  return -1;
2231 
2232  struct NmMboxData *mdata = nm_mdata_get(m);
2233  time_t mtime = 0;
2234  if (!mdata || (nm_db_get_mtime(m, &mtime) != 0))
2235  return -1;
2236 
2237  int new_flags = 0;
2238  bool occult = false;
2239 
2240  if (m->mtime.tv_sec >= mtime)
2241  {
2242  mutt_debug(LL_DEBUG2, "nm: check unnecessary (db=%lu mailbox=%lu)\n", mtime,
2243  m->mtime.tv_sec);
2244  return 0;
2245  }
2246 
2247  mutt_debug(LL_DEBUG1, "nm: checking (db=%lu mailbox=%lu)\n", mtime, m->mtime.tv_sec);
2248 
2249  notmuch_query_t *q = get_query(m, false);
2250  if (!q)
2251  goto done;
2252 
2253  mutt_debug(LL_DEBUG1, "nm: start checking (count=%d)\n", m->msg_count);
2254  mdata->oldmsgcount = m->msg_count;
2255  mdata->noprogress = true;
2256 
2257  for (int i = 0; i < m->msg_count; i++)
2258  {
2259  struct Email *e = m->emails[i];
2260  if (!e)
2261  break;
2262 
2263  e->active = false;
2264  }
2265 
2266  int limit = get_limit(mdata);
2267 
2268  notmuch_messages_t *msgs = get_messages(q);
2269 
2270  // TODO: Analyze impact of removing this version guard.
2271 #if LIBNOTMUCH_CHECK_VERSION(5, 0, 0)
2272  if (!msgs)
2273  return false;
2274 #elif LIBNOTMUCH_CHECK_VERSION(4, 3, 0)
2275  if (!msgs)
2276  goto done;
2277 #endif
2278 
2279  struct HeaderCache *h = nm_hcache_open(m);
2280 
2281  for (int i = 0; notmuch_messages_valid(msgs) && ((limit == 0) || (i < limit));
2282  notmuch_messages_move_to_next(msgs), i++)
2283  {
2284  notmuch_message_t *msg = notmuch_messages_get(msgs);
2285  struct Email *e = get_mutt_email(m, msg);
2286 
2287  if (!e)
2288  {
2289  /* new email */
2290  append_message(h, m, NULL, msg, false);
2291  notmuch_message_destroy(msg);
2292  continue;
2293  }
2294 
2295  /* message already exists, merge flags */
2296  e->active = true;
2297 
2298  /* Check to see if the message has moved to a different subdirectory.
2299  * If so, update the associated filename. */
2300  const char *new_file = get_message_last_filename(msg);
2301  char old_file[PATH_MAX];
2302  email_get_fullpath(e, old_file, sizeof(old_file));
2303 
2304  if (!mutt_str_equal(old_file, new_file))
2305  update_message_path(e, new_file);
2306 
2307  if (!e->changed)
2308  {
2309  /* if the user hasn't modified the flags on this message, update the
2310  * flags we just detected. */
2311  struct Email e_tmp = { 0 };
2312  e_tmp.edata = maildir_edata_new();
2313  maildir_parse_flags(&e_tmp, new_file);
2314  maildir_update_flags(m, e, &e_tmp);
2315  maildir_edata_free(&e_tmp.edata);
2316  }
2317 
2318  if (update_email_tags(e, msg) == 0)
2319  new_flags++;
2320 
2321  notmuch_message_destroy(msg);
2322  }
2323 
2324  nm_hcache_close(h);
2325 
2326  for (int i = 0; i < m->msg_count; i++)
2327  {
2328  struct Email *e = m->emails[i];
2329  if (!e)
2330  break;
2331 
2332  if (!e->active)
2333  {
2334  occult = true;
2335  break;
2336  }
2337  }
2338 
2339  if (m->msg_count > mdata->oldmsgcount)
2341 done:
2342  if (q)
2343  notmuch_query_destroy(q);
2344 
2345  nm_db_release(m);
2346 
2347  m->mtime.tv_sec = mutt_date_epoch();
2348  m->mtime.tv_nsec = 0;
2349 
2350  mutt_debug(LL_DEBUG1, "nm: ... check done [count=%d, new_flags=%d, occult=%d]\n",
2351  m->msg_count, new_flags, occult);
2352 
2353  return occult ? MUTT_REOPENED :
2354  (m->msg_count > mdata->oldmsgcount) ? MUTT_NEW_MAIL :
2355  new_flags ? MUTT_FLAGS : 0;
2356 }
2357 
2361 static int nm_mbox_sync(struct Mailbox *m)
2362 {
2363  if (!m)
2364  return -1;
2365 
2366  struct NmMboxData *mdata = nm_mdata_get(m);
2367  if (!mdata)
2368  return -1;
2369 
2370  int rc = 0;
2371  struct Progress progress;
2372  char *url = mutt_str_dup(mailbox_path(m));
2373  bool changed = false;
2374 
2375  mutt_debug(LL_DEBUG1, "nm: sync start\n");
2376 
2377  if (m->verbose)
2378  {
2379  /* all is in this function so we don't use data->progress here */
2380  char msg[PATH_MAX];
2381  snprintf(msg, sizeof(msg), _("Writing %s..."), mailbox_path(m));
2382  mutt_progress_init(&progress, msg, MUTT_PROGRESS_WRITE, m->msg_count);
2383  }
2384 
2385  struct HeaderCache *h = nm_hcache_open(m);
2386 
2387  int mh_sync_errors = 0;
2388  for (int i = 0; i < m->msg_count; i++)
2389  {
2390  char old_file[PATH_MAX], new_file[PATH_MAX];
2391  struct Email *e = m->emails[i];
2392  if (!e)
2393  break;
2394 
2395  struct NmEmailData *edata = nm_edata_get(e);
2396 
2397  if (m->verbose)
2398  mutt_progress_update(&progress, i, -1);
2399 
2400  *old_file = '\0';
2401  *new_file = '\0';
2402 
2403  if (edata->oldpath)
2404  {
2405  mutt_str_copy(old_file, edata->oldpath, sizeof(old_file));
2406  old_file[sizeof(old_file) - 1] = '\0';
2407  mutt_debug(LL_DEBUG2, "nm: fixing obsolete path '%s'\n", old_file);
2408  }
2409  else
2410  email_get_fullpath(e, old_file, sizeof(old_file));
2411 
2412  mutt_buffer_strcpy(&m->pathbuf, edata->folder);
2413  m->type = edata->type;
2414  rc = mh_sync_mailbox_message(m, i, h);
2415 
2416  // Syncing file failed, query notmuch for new filepath.
2417  if (rc)
2418  {
2419  notmuch_database_t *db = nm_db_get(m, true);
2420  if (db)
2421  {
2422  notmuch_message_t *msg = get_nm_message(db, e);
2423 
2424  sync_email_path_with_nm(e, msg);
2425 
2426  rc = mh_sync_mailbox_message(m, i, h);
2427  }
2428  nm_db_release(m);
2429  }
2430 
2431  mutt_buffer_strcpy(&m->pathbuf, url);
2432  m->type = MUTT_NOTMUCH;
2433 
2434  if (rc)
2435  {
2436  mh_sync_errors += 1;
2437  continue;
2438  }
2439 
2440  if (!e->deleted)
2441  email_get_fullpath(e, new_file, sizeof(new_file));
2442 
2443  if (e->deleted || (strcmp(old_file, new_file) != 0))
2444  {
2445  if (e->deleted && (remove_filename(m, old_file) == 0))
2446  changed = true;
2447  else if (*new_file && *old_file && (rename_filename(m, old_file, new_file, e) == 0))
2448  changed = true;
2449  }
2450 
2451  FREE(&edata->oldpath);
2452  }
2453 
2454  if (mh_sync_errors > 0)
2455  {
2456  mutt_error(
2457  ngettext(
2458  "Unable to sync %d message due to external mailbox modification",
2459  "Unable to sync %d messages due to external mailbox modification", mh_sync_errors),
2460  mh_sync_errors);
2461  }
2462 
2463  mutt_buffer_strcpy(&m->pathbuf, url);
2464  m->type = MUTT_NOTMUCH;
2465 
2466  nm_db_release(m);
2467 
2468  if (changed)
2469  {
2470  m->mtime.tv_sec = mutt_date_epoch();
2471  m->mtime.tv_nsec = 0;
2472  }
2473 
2474  nm_hcache_close(h);
2475 
2476  FREE(&url);
2477  mutt_debug(LL_DEBUG1, "nm: .... sync done [rc=%d]\n", rc);
2478  return rc;
2479 }
2480 
2486 static int nm_mbox_close(struct Mailbox *m)
2487 {
2488  return 0;
2489 }
2490 
2494 static int nm_msg_open(struct Mailbox *m, struct Message *msg, int msgno)
2495 {
2496  if (!m || !m->emails || (msgno >= m->msg_count) || !msg)
2497  return -1;
2498 
2499  struct Email *e = m->emails[msgno];
2500  if (!e)
2501  return -1;
2502 
2503  char path[PATH_MAX];
2504  char *folder = nm_email_get_folder(e);
2505 
2506  snprintf(path, sizeof(path), "%s/%s", folder, e->path);
2507 
2508  msg->fp = fopen(path, "r");
2509  if (!msg->fp && (errno == ENOENT) && ((m->type == MUTT_MAILDIR) || (m->type == MUTT_NOTMUCH)))
2510  {
2511  msg->fp = maildir_open_find_message(folder, e->path, NULL);
2512  }
2513 
2514  if (!msg->fp)
2515  return -1;
2516 
2517  return 0;
2518 }
2519 
2524 static int nm_msg_commit(struct Mailbox *m, struct Message *msg)
2525 {
2526  mutt_error(_("Can't write to virtual folder"));
2527  return -1;
2528 }
2529 
2533 static int nm_msg_close(struct Mailbox *m, struct Message *msg)
2534 {
2535  if (!msg)
2536  return -1;
2537  mutt_file_fclose(&(msg->fp));
2538  return 0;
2539 }
2540 
2544 static int nm_tags_edit(struct Mailbox *m, const char *tags, char *buf, size_t buflen)
2545 {
2546  *buf = '\0';
2547  if (mutt_get_field("Add/remove labels: ", buf, buflen, MUTT_NM_TAG) != 0)
2548  return -1;
2549  return 1;
2550 }
2551 
2555 static int nm_tags_commit(struct Mailbox *m, struct Email *e, char *buf)
2556 {
2557  if (!m)
2558  return -1;
2559 
2560  struct NmMboxData *mdata = nm_mdata_get(m);
2561  if (!buf || (*buf == '\0') || !mdata)
2562  return -1;
2563 
2564  notmuch_database_t *db = NULL;
2565  notmuch_message_t *msg = NULL;
2566  int rc = -1;
2567 
2568  if (!(db = nm_db_get(m, true)) || !(msg = get_nm_message(db, e)))
2569  goto done;
2570 
2571  mutt_debug(LL_DEBUG1, "nm: tags modify: '%s'\n", buf);
2572 
2573  update_tags(msg, buf);
2574  update_email_flags(m, e, buf);
2575  update_email_tags(e, msg);
2576  mutt_set_header_color(m, e);
2577 
2578  rc = 0;
2579  e->changed = true;
2580 done:
2581  nm_db_release(m);
2582  if (e->changed)
2583  {
2584  m->mtime.tv_sec = mutt_date_epoch();
2585  m->mtime.tv_nsec = 0;
2586  }
2587  mutt_debug(LL_DEBUG1, "nm: tags modify done [rc=%d]\n", rc);
2588  return rc;
2589 }
2590 
2594 enum MailboxType nm_path_probe(const char *path, const struct stat *st)
2595 {
2596  if (!path || !mutt_istr_startswith(path, NmUrlProtocol))
2597  return MUTT_UNKNOWN;
2598 
2599  return MUTT_NOTMUCH;
2600 }
2601 
2605 static int nm_path_canon(char *buf, size_t buflen)
2606 {
2607  if (!buf)
2608  return -1;
2609 
2610  return 0;
2611 }
2612 
2616 static int nm_path_pretty(char *buf, size_t buflen, const char *folder)
2617 {
2618  /* Succeed, but don't do anything, for now */
2619  return 0;
2620 }
2621 
2625 static int nm_path_parent(char *buf, size_t buflen)
2626 {
2627  /* Succeed, but don't do anything, for now */
2628  return 0;
2629 }
2630 
2631 // clang-format off
2635 struct MxOps MxNotmuchOps = {
2636  .type = MUTT_NOTMUCH,
2637  .name = "notmuch",
2638  .is_local = false,
2639  .ac_find = nm_ac_find,
2640  .ac_add = nm_ac_add,
2641  .mbox_open = nm_mbox_open,
2642  .mbox_open_append = NULL,
2643  .mbox_check = nm_mbox_check,
2644  .mbox_check_stats = nm_mbox_check_stats,
2645  .mbox_sync = nm_mbox_sync,
2646  .mbox_close = nm_mbox_close,
2647  .msg_open = nm_msg_open,
2648  .msg_open_new = maildir_msg_open_new,
2649  .msg_commit = nm_msg_commit,
2650  .msg_close = nm_msg_close,
2651  .msg_padding_size = NULL,
2652  .msg_save_hcache = NULL,
2653  .tags_edit = nm_tags_edit,
2654  .tags_commit = nm_tags_commit,
2655  .path_probe = nm_path_probe,
2656  .path_canon = nm_path_canon,
2657  .path_pretty = nm_path_pretty,
2658  .path_parent = nm_path_parent,
2659 };
2660 // clang-format on
notmuch_database_t * nm_db_do_open(const char *filename, bool writable, bool verbose)
Open a Notmuch database.
Definition: db.c:83
void * mutt_hash_find(const struct HashTable *table, const char *strkey)
Find the HashElem data in a Hash Table element using a key.
Definition: hash.c:354
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition: string.c:871
struct Email ** emails
Array of Emails.
Definition: mailbox.h:99
Convenience wrapper for the gui headers.
time_t mutt_date_epoch(void)
Return the number of seconds since the Unix epoch.
Definition: date.c:416
static notmuch_query_t * get_query(struct Mailbox *m, bool writable)
Create a new query.
Definition: notmuch.c:604
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:201
void maildir_edata_free(void **ptr)
Free the private Email data - Implements Email::edata_free()
Definition: shared.c:69
int maildir_msg_open_new(struct Mailbox *m, struct Message *msg, const struct Email *e)
Open a new message in a Mailbox - Implements MxOps::msg_open_new()
Definition: maildir.c:593
char * C_NmQueryWindowTimebase
Config: (notmuch) Units for the time duration.
Definition: config.c:46
enum MailboxType type
Mailbox type.
Definition: mailbox.h:105
#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
static enum NmQueryType string_to_query_type(const char *str)
Lookup a query type.
Definition: notmuch.c:106
int msg_count
Total number of messages.
Definition: mailbox.h:91
#define mutt_set_flag(m, e, flag, bf)
Definition: protos.h:67
int mutt_str_atoi(const char *str, int *dst)
Convert ASCII string to an integer.
Definition: string.c:252
The envelope/body of an email.
Definition: email.h:37
int nm_db_get_mtime(struct Mailbox *m, time_t *mtime)
Get the database modification time.
Definition: db.c:253
bool maildir_update_flags(struct Mailbox *m, struct Email *e_old, struct Email *e_new)
Update the mailbox flags.
Definition: shared.c:1475
enum MailboxType type
Type of Mailbox the Email is in.
Definition: private.h:92
int msg_unread
Number of unread messages.
Definition: mailbox.h:92
void mailbox_size_add(struct Mailbox *m, const struct Email *e)
Add an email&#39;s size to the total size of a Mailbox.
Definition: mailbox.c:188
Structs that make up an email.
struct Email * maildir_parse_message(enum MailboxType type, const char *fname, bool is_old, struct Email *e)
Actually parse a maildir message.
Definition: shared.c:1362
static void progress_reset(struct Mailbox *m)
Reset the progress counter.
Definition: notmuch.c:831
static notmuch_threads_t * get_threads(notmuch_query_t *query)
load threads for a query
Definition: notmuch.c:1145
static int nm_path_parent(char *buf, size_t buflen)
Find the parent of a Mailbox path - Implements MxOps::path_parent()
Definition: notmuch.c:2625
GUI manage the main index (list of emails)
#define mutt_message(...)
Definition: logging.h:83
int msg_flagged
Number of flagged messages.
Definition: mailbox.h:93
A parsed URL proto://user:password@host:port/path?a=1&b=2
Definition: url.h:66
WHERE SIG_ATOMIC_VOLATILE_T SigInt
true after SIGINT is received
Definition: mutt_globals.h:74
struct MuttThread * thread
Thread of Emails.
Definition: email.h:95
Progress tracks elements, according to C_WriteInc.
Definition: progress.h:43
struct HashTable * mutt_make_id_hash(struct Mailbox *m)
Create a Hash Table for message-ids.
Definition: mutt_thread.c:1521
char * C_HeaderCache
Config: (hcache) Directory/file for the header cache database.
Definition: config.c:40
char * oldpath
Definition: private.h:90
static int update_tags(notmuch_message_t *msg, const char *tags)
Update the tags on a message.
Definition: notmuch.c:1267
static bool windowed_query_from_query(const char *query, char *buf, size_t buflen)
transforms a vfolder search query into a windowed one
Definition: notmuch.c:436
A group of associated Mailboxes.
Definition: account.h:36
int nm_read_entire_thread(struct Mailbox *m, struct Email *e)
Get the entire thread of an email.
Definition: notmuch.c:1668
header cache structure
Definition: lib.h:85
struct timespec mtime
Time Mailbox was last changed.
Definition: mailbox.h:107
int mh_sync_mailbox_message(struct Mailbox *m, int msgno, struct HeaderCache *hc)
Save changes to the mailbox.
Definition: shared.c:1382
Parsed Query String.
Definition: url.h:55
Flagged messages.
Definition: mutt.h:102
void mx_alloc_memory(struct Mailbox *m)
Create storage for the emails.
Definition: mx.c:1232
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:370
#define _(a)
Definition: message.h:28
Mailbox wasn&#39;t recognised.
Definition: mailbox.h:47
bool changed
Email has been edited.
Definition: email.h:48
static int nm_mbox_sync(struct Mailbox *m)
Save changes to the Mailbox - Implements MxOps::mbox_sync()
Definition: notmuch.c:2361
NmQueryType
Notmuch Query Types.
Definition: private.h:60
static int init_email(struct Email *e, const char *path, notmuch_message_t *msg)
Set up an email&#39;s Notmuch data.
Definition: notmuch.c:770
struct NmAccountData * nm_adata_new(void)
Allocate and initialise a new NmAccountData structure.
Definition: notmuch.c:139
char * value
Query value.
Definition: url.h:58
Email list was changed.
Definition: mailbox.h:171
void url_free(struct Url **ptr)
Free the contents of a URL.
Definition: url.c:123
enum MailboxType type
Type of Mailboxes this Account contains.
Definition: account.h:38
static int update_message_path(struct Email *e, const char *path)
Set the path for a message.
Definition: notmuch.c:686
static bool query_window_check_timebase(const char *timebase)
Checks if a given timebase string is valid.
Definition: notmuch.c:372
struct HeaderCache * mutt_hcache_open(const char *path, const char *folder, hcache_namer_t namer)
Multiplexor for StoreOps::open.
Definition: hcache.c:320
static int nm_mbox_close(struct Mailbox *m)
Close a Mailbox - Implements MxOps::mbox_close()
Definition: notmuch.c:2486
int nm_db_trans_begin(struct Mailbox *m)
Start a Notmuch database transaction.
Definition: db.c:204
int C_NmQueryWindowDuration
Config: (notmuch) Time duration of the current search window.
Definition: config.c:45
Container for Accounts, Notifications.
Definition: neomutt.h:36
A progress bar.
Definition: progress.h:50
notmuch_database_t * nm_db_get(struct Mailbox *m, bool writable)
Get the Notmuch database.
Definition: db.c:147
#define mutt_get_field(field, buf, buflen, complete)
Definition: curs_lib.h:91
Messages that have been replied to.
Definition: mutt.h:95
Convenience wrapper for the config headers.
static int nm_msg_close(struct Mailbox *m, struct Message *msg)
Close an email - Implements MxOps::msg_close()
Definition: notmuch.c:2533
time_t tv_sec
Definition: file.h:48
int oldmsgcount
Definition: private.h:77
static int nm_msg_open(struct Mailbox *m, struct Message *msg, int msgno)
Open an email message in a Mailbox - Implements MxOps::msg_open()
Definition: notmuch.c:2494
static const char * get_message_last_filename(notmuch_message_t *msg)
Get a message&#39;s last filename.
Definition: notmuch.c:814
void(* mdata_free)(void **ptr)
Free the private data attached to the Mailbox.
Definition: mailbox.h:142
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:292
#define mutt_array_size(x)
Definition: memory.h:33
notmuch_database_t * db
Definition: private.h:50
void maildir_parse_flags(struct Email *e, const char *path)
Parse Maildir file flags.
Definition: shared.c:1250
bool nm_message_is_still_queried(struct Mailbox *m, struct Email *e)
Is a message still visible in the query?
Definition: notmuch.c:1856
int nm_get_all_tags(struct Mailbox *m, char **tag_list, int *tag_count)
Fill a list with all notmuch tags.
Definition: notmuch.c:2101
struct NmEmailData * nm_edata_get(struct Email *e)
Get the Notmuch Email data.
Definition: notmuch.c:264
int nm_record_message(struct Mailbox *m, char *path, struct Email *e)
Add a message to the Notmuch database.
Definition: notmuch.c:2036
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
Header cache multiplexor.
struct HashTable * id_hash
Hash Table by msg id.
Definition: mailbox.h:127
struct NmAccountData * nm_adata_get(struct Mailbox *m)
Get the Notmuch Account data.
Definition: notmuch.c:152
Many unsorted constants and some structs.
Log at debug level 2.
Definition: logging.h:41
static int get_limit(struct NmMboxData *mdata)
Get the database limit.
Definition: notmuch.c:554
API for mailboxes.
Notmuch-specific Mailbox data -.
Definition: private.h:69
bool old
Email is seen, but unread.
Definition: email.h:50
static char * email_get_id(struct Email *e)
Get the unique Notmuch Id.
Definition: notmuch.c:326
char * C_NmFlaggedTag
Config: (notmuch) Tag to use for flagged messages.
Definition: config.c:40
char * C_NmUnreadTag
Config: (notmuch) Tag to use for unread messages.
Definition: config.c:49
static int update_email_flags(struct Mailbox *m, struct Email *e, const char *tags)
Update the Email&#39;s flags.
Definition: notmuch.c:1336
char * mutt_strn_dup(const char *begin, size_t len)
Duplicate a sub-string.
Definition: string.c:548
char * C_NmDefaultUrl
Config: (notmuch) Path to the Notmuch database.
Definition: config.c:38
FILE * maildir_open_find_message(const char *folder, const char *msg, char **newname)
Find a new.
Definition: shared.c:1521
int mutt_file_fclose(FILE **fp)
Close a FILE handle (and NULL the pointer)
Definition: file.c:153
char * virtual_id
Unique Notmuch Id.
Definition: private.h:91
#define MUTT_NM_TAG
Notmuch tag +/- mode.
Definition: mutt.h:67
char * folder
Location of the Email.
Definition: private.h:89
Convenience wrapper for the core headers.
Progress tracks elements, according to C_ReadInc.
Definition: progress.h:42
static int nm_mbox_check_stats(struct Mailbox *m, int flags)
Check the Mailbox statistics - Implements MxOps::mbox_check_stats()
Definition: notmuch.c:1942
static notmuch_messages_t * get_messages(notmuch_query_t *query)
load messages for a query
Definition: notmuch.c:1075
void nm_query_window_backward(void)
Function to move the current search window backward in time.
Definition: notmuch.c:1844
Progress bar.
static const char * query_type_to_string(enum NmQueryType query_type)
Turn a query type into a string.
Definition: notmuch.c:355
void(* adata_free)(void **ptr)
Free the private data attached to the Account.
Definition: account.h:49
long tv_nsec
Definition: file.h:49
void * mdata
Driver specific data.
Definition: mailbox.h:136
struct TagList tags
For drivers that support server tagging.
Definition: email.h:109
&#39;Maildir&#39; Mailbox type
Definition: mailbox.h:51
int flags
e.g. MB_NORMAL
Definition: mailbox.h:134
enum MailboxType type
Mailbox type, e.g. MUTT_IMAP.
Definition: mx.h:106
void nm_edata_free(void **ptr)
Free data attached to an Email.
Definition: notmuch.c:234
static int rename_filename(struct Mailbox *m, const char *old_file, const char *new_file, struct Email *e)
Rename the file.
Definition: notmuch.c:1515
static notmuch_message_t * get_nm_message(notmuch_database_t *db, struct Email *e)
Find a Notmuch message.
Definition: notmuch.c:1209
void nm_parse_type_from_query(struct NmMboxData *mdata, char *buf)
Parse a query type out of a query.
Definition: notmuch.c:1736
Prototypes for many functions.
void nm_query_window_forward(void)
Function to move the current search window forward in time.
Definition: notmuch.c:1828
bool active
Message is not to be removed.
Definition: email.h:59
char * nm_url_from_query(struct Mailbox *m, char *buf, size_t buflen)
Turn a query into a URL.
Definition: notmuch.c:1767
void url_pct_encode(char *buf, size_t buflen, const char *src)
Percent-encode a string.
Definition: url.c:151
struct UrlQueryList query_strings
List of query strings.
Definition: url.h:74
int mutt_istr_remall(char *str, const char *target)
Remove all occurrences of substring, ignoring case.
Definition: string.c:1037
struct Email * maildir_parse_stream(enum MailboxType type, FILE *fp, const char *fname, bool is_old, struct Email *e)
Parse a Maildir message.
Definition: shared.c:1318
int nm_db_trans_end(struct Mailbox *m)
End a database transaction.
Definition: db.c:226
A local copy of an email.
Definition: mx.h:82
int email_max
Number of pointers in emails.
Definition: mailbox.h:100
Create/manipulate threading in emails.
int C_NmQueryWindowCurrentPosition
Config: (notmuch) Position of current search window.
Definition: config.c:43
A mailbox.
Definition: mailbox.h:81
#define PATH_MAX
Definition: mutt.h:44
static int nm_ac_add(struct Account *a, struct Mailbox *m)
Add a Mailbox to an Account - Implements MxOps::ac_add()
Definition: notmuch.c:2156
Notmuch-specific Email data -.
Definition: private.h:87
char * nm_email_get_folder(struct Email *e)
Get the folder for a Email.
Definition: notmuch.c:1652
void * mutt_mem_malloc(size_t size)
Allocate memory on the heap.
Definition: memory.c:90
Nondestructive flags change (IMAP)
Definition: mx.h:76
static struct HeaderCache * nm_hcache_open(struct Mailbox *m)
Open a header cache.
Definition: notmuch.c:81
void * adata
Private data (for Mailbox backends)
Definition: account.h:43
static int init_mailbox(struct Mailbox *m)
Add Notmuch data to the Mailbox.
Definition: notmuch.c:304
char * driver_tags_get(struct TagList *list)
Get tags.
Definition: tags.c:142
static int nm_path_canon(char *buf, size_t buflen)
Canonicalise a Mailbox path - Implements MxOps::path_canon()
Definition: notmuch.c:2605
static char * get_query_string(struct NmMboxData *mdata, bool window)
builds the notmuch vfolder search string
Definition: notmuch.c:496
struct Progress progress
A progress bar.
Definition: private.h:76
char * C_NmRepliedTag
Config: (notmuch) Tag to use for replied messages.
Definition: config.c:48
WHERE char * C_Folder
Config: Base folder for a set of mailboxes.
Definition: mutt_globals.h:96
char * db_query
Previous query.
Definition: private.h:72
void nm_db_free(notmuch_database_t *db)
decoupled way to close a Notmuch database
Definition: db.c:188
static void append_thread(struct HeaderCache *h, struct Mailbox *m, notmuch_query_t *q, notmuch_thread_t *thread, bool dedup)
add each top level reply in the thread
Definition: notmuch.c:1051
Messages that have been read.
Definition: mutt.h:96
Default: Messages only.
Definition: private.h:62
char msg[1024]
Definition: progress.h:52
static unsigned int count_query(notmuch_database_t *db, const char *qstr, int limit)
Count the results of a query.
Definition: notmuch.c:1619
bool verbose
Display status messages?
Definition: mailbox.h:118
int mutt_hcache_store(struct HeaderCache *hc, const char *key, size_t keylen, struct Email *e, uint32_t uidvalidity)
Multiplexor for StoreOps::store.
Definition: hcache.c:525
static int nm_mbox_open(struct Mailbox *m)
Open a Mailbox - Implements MxOps::mbox_open()
Definition: notmuch.c:2174
static struct Account * nm_ac_find(struct Account *a, const char *path)
Find an Account that matches a Mailbox path - Implements MxOps::ac_find()
Definition: notmuch.c:2145
size_t mutt_istr_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix, ignoring case.
Definition: string.c:172
static void sync_email_path_with_nm(struct Email *e, notmuch_message_t *msg)
Synchronize Neomutt&#39;s Email path with notmuch.
Definition: notmuch.c:1250
#define STAILQ_FOREACH(var, head, field)
Definition: queue.h:349
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
Maildir local mailbox type.
struct Url * db_url
Parsed view url of the Notmuch database.
Definition: private.h:71
static void nm_hcache_close(struct HeaderCache *h)
Close the header cache.
Definition: notmuch.c:94
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
&#39;Notmuch&#39; (virtual) Mailbox type
Definition: mailbox.h:54
int C_NmDbLimit
Config: (notmuch) Default limit for Notmuch queries.
Definition: config.c:37
bool mutt_strn_equal(const char *a, const char *b, size_t l)
Check for equality of two strings (to a maximum), safely.
Definition: string.c:593
char * C_NmQueryWindowCurrentSearch
Config: (notmuch) Current search parameters.
Definition: config.c:44
void mutt_hcache_close(struct HeaderCache *hc)
Multiplexor for StoreOps::close.
Definition: hcache.c:417
static int nm_msg_commit(struct Mailbox *m, struct Message *msg)
Save changes to an email - Implements MxOps::msg_commit()
Definition: notmuch.c:2524
const char NmUrlProtocol[]
Definition: notmuch.c:73
void mutt_set_header_color(struct Mailbox *m, struct Email *e)
Select a colour for a message.
Definition: index.c:3950
void maildir_gen_flags(char *dest, size_t destlen, struct Email *e)
Generate the Maildir flags for an email.
Definition: maildir.c:170
size_t mutt_str_len(const char *a)
Calculate the length of a string, safely.
Definition: string.c:631
struct NmMboxData * nm_mdata_get(struct Mailbox *m)
Get the Notmuch Mailbox data.
Definition: notmuch.c:219
MailboxType
Supported mailbox formats.
Definition: mailbox.h:43
struct Account * account
Account that owns this Mailbox.
Definition: mailbox.h:131
Log at debug level 1.
Definition: logging.h:40
size_t mutt_str_copy(char *dest, const char *src, size_t dsize)
Copy a string into a buffer (guaranteeing NUL-termination)
Definition: string.c:716
int msg_new
Number of new messages.
Definition: mailbox.h:95
char * mutt_str_replace(char **p, const char *s)
Replace one string with another.
Definition: string.c:446
char * driver_tags_get_transformed(struct TagList *list)
Get transformed tags.
Definition: tags.c:130
static bool read_mesgs_query(struct Mailbox *m, notmuch_query_t *q, bool dedup)
Search for matching messages.
Definition: notmuch.c:1103
struct NmMboxData * nm_mdata_new(const char *url)
Create a new NmMboxData object from a query.
Definition: notmuch.c:193
bool deleted
Email is deleted.
Definition: email.h:45
int nm_db_release(struct Mailbox *m)
Close the Notmuch database.
Definition: db.c:171
void * edata
Driver-specific data.
Definition: email.h:111
static int nm_tags_commit(struct Mailbox *m, struct Email *e, char *buf)
Save the tags to a message - Implements MxOps::tags_commit()
Definition: notmuch.c:2555
#define mutt_error(...)
Definition: logging.h:84
static bool nm_message_has_tag(notmuch_message_t *msg, char *tag)
Does a message have this tag?
Definition: notmuch.c:1228
static char * email_get_fullpath(struct Email *e, char *buf, size_t buflen)
Get the full path of an email.
Definition: notmuch.c:342
char * path
Path of Email (for local Mailboxes)
Definition: email.h:92
struct NmEmailData * nm_edata_new(void)
Create a new NmEmailData for an email.
Definition: notmuch.c:253
static char * get_folder_from_path(const char *path)
Find an email&#39;s folder from its path.
Definition: notmuch.c:723
FILE * fp
pointer to the message data
Definition: mx.h:84
int ignmsgcount
Ignored messages.
Definition: private.h:78
int index
The absolute (unsorted) message number.
Definition: email.h:86
static void append_message(struct HeaderCache *h, struct Mailbox *m, notmuch_query_t *q, notmuch_message_t *msg, bool dedup)
Associate a message.
Definition: notmuch.c:915
static int nm_path_pretty(char *buf, size_t buflen, const char *folder)
Abbreviate a Mailbox path - Implements MxOps::path_pretty()
Definition: notmuch.c:2616
#define FREE(x)
Definition: memory.h:40
static int update_email_tags(struct Email *e, notmuch_message_t *msg)
Update the Email&#39;s tags from Notmuch.
Definition: notmuch.c:636
static struct Email * get_mutt_email(struct Mailbox *m, notmuch_message_t *msg)
Get the Email of a Notmuch message.
Definition: notmuch.c:880
char * folder
Definition: lib.h:87
char * C_NmExcludeTags
Config: (notmuch) Exclude messages with these tags.
Definition: config.c:39
Whole threads.
Definition: private.h:63
static char * nm2mutt_message_id(const char *id)
converts notmuch message Id to neomutt message Id
Definition: notmuch.c:748
bool noprogress
Don&#39;t show the progress bar.
Definition: private.h:80
struct HCacheEntry mutt_hcache_fetch(struct HeaderCache *hc, const char *key, size_t keylen, uint32_t uidvalidity)
Multiplexor for StoreOps::fetch.
Definition: hcache.c:436
static int nm_tags_edit(struct Mailbox *m, const char *tags, char *buf, size_t buflen)
Prompt and validate new messages tags - Implements MxOps::tags_edit()
Definition: notmuch.c:2544
static void append_replies(struct HeaderCache *h, struct Mailbox *m, notmuch_query_t *q, notmuch_message_t *top, bool dedup)
add all the replies to a given messages into the display
Definition: notmuch.c:1024
char * C_NmQueryType
Config: (notmuch) Default query type: &#39;threads&#39; or &#39;messages&#39;.
Definition: config.c:42
const int NmUrlProtocolLen
Definition: notmuch.c:74
Hundreds of global variables to back the user variables.
#define mutt_debug(LEVEL,...)
Definition: logging.h:81
struct ConfigSubset * sub
Inherited config items.
Definition: neomutt.h:39
bool driver_tags_replace(struct TagList *head, char *tags)
Replace all tags.
Definition: tags.c:183
bool progress_ready
A progress bar has been initialised.
Definition: private.h:81
New mail received in Mailbox.
Definition: mx.h:73
struct Email * email
Retrieved email.
Definition: lib.h:100
int mutt_str_asprintf(char **strp, const char *fmt,...)
Definition: string.c:1095
static int remove_filename(struct Mailbox *m, const char *path)
Delete a file.
Definition: notmuch.c:1451
void nm_mdata_free(void **ptr)
Free the private Mailbox data - Implements Mailbox::mdata_free()
Definition: notmuch.c:171
static void query_window_reset(void)
Restore vfolder&#39;s search window to its original position.
Definition: notmuch.c:392
struct Buffer pathbuf
Definition: mailbox.h:83
Convenience wrapper for the library headers.
static struct NmMboxData * nm_get_default_data(void)
Create a Mailbox with default Notmuch settings.
Definition: notmuch.c:277
void mutt_str_append_item(char **str, const char *item, char sep)
Add string to another separated by sep.
Definition: string.c:466
void email_free(struct Email **ptr)
Free an Email.
Definition: email.c:43
void nm_adata_free(void **ptr)
Free the private Account data - Implements Account::adata_free()
Definition: notmuch.c:120
Notmuch private types.
Mailbox was reopened.
Definition: mx.h:75
static void apply_exclude_tags(notmuch_query_t *query)
Exclude the configured tags.
Definition: notmuch.c:563
char * C_NmRecordTags
Config: (notmuch) Tags to apply to the &#39;record&#39; mailbox (sent mail)
Definition: config.c:47
int nm_update_filename(struct Mailbox *m, const char *old_file, const char *new_file, struct Email *e)
Change the filename.
Definition: notmuch.c:1917
enum NmQueryType query_type
Messages or Threads.
Definition: private.h:74
void mailbox_changed(struct Mailbox *m, enum NotifyMailbox action)
Notify observers of a change to a Mailbox.
Definition: mailbox.c:174
int db_limit
Maximum number of results to return.
Definition: private.h:73
enum MailboxType nm_path_probe(const char *path, const struct stat *st)
Is this a Notmuch Mailbox? - Implements MxOps::path_probe()
Definition: notmuch.c:2594
static bool read_threads_query(struct Mailbox *m, notmuch_query_t *q, bool dedup, int limit)
Perform a query with threads.
Definition: notmuch.c:1173
struct MaildirEmailData * maildir_edata_new(void)
Create a new MaildirEmailData object.
Definition: shared.c:84
static int rename_maildir_filename(const char *old, char *buf, size_t buflen, struct Email *e)
Rename a Maildir file.
Definition: notmuch.c:1398
static void progress_update(struct Mailbox *m, notmuch_query_t *q)
Update the progress counter.
Definition: notmuch.c:852
The Mailbox API.
Definition: mx.h:104
int msgno
Number displayed to the user.
Definition: email.h:87
static int nm_mbox_check(struct Mailbox *m)
Check for new mail - Implements MxOps::mbox_check()
Definition: notmuch.c:2227
void * nm_edata
Notmuch private data.
Definition: email.h:106
Notmuch-specific Account data -.
Definition: private.h:48
struct Url * url_parse(const char *src)
Fill in Url.
Definition: url.c:234
const char * nm_db_get_filename(struct Mailbox *m)
Get the filename of the Notmuch database.
Definition: db.c:53