NeoMutt  2020-11-20
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 "command_parse.h"
65 #include "index.h"
66 #include "mutt_commands.h"
67 #include "mutt_globals.h"
68 #include "mutt_thread.h"
69 #include "mx.h"
70 #include "progress.h"
71 #include "protos.h"
72 
73 struct stat;
74 
75 static const struct Command nm_commands[] = {
76  // clang-format off
77  { "unvirtual-mailboxes", parse_unmailboxes, 0 },
78  { "virtual-mailboxes", parse_mailboxes, MUTT_NAMED },
79  // clang-format on
80 };
81 
82 const char NmUrlProtocol[] = "notmuch://";
83 const int NmUrlProtocolLen = sizeof(NmUrlProtocol) - 1;
84 
88 void nm_init(void)
89 {
90  COMMANDS_REGISTER(nm_commands);
91 }
92 
98 static struct HeaderCache *nm_hcache_open(struct Mailbox *m)
99 {
100 #ifdef USE_HCACHE
101  return mutt_hcache_open(C_HeaderCache, mailbox_path(m), NULL);
102 #else
103  return NULL;
104 #endif
105 }
106 
111 static void nm_hcache_close(struct HeaderCache *h)
112 {
113 #ifdef USE_HCACHE
115 #endif
116 }
117 
123 static enum NmQueryType string_to_query_type(const char *str)
124 {
125  if (mutt_str_equal(str, "threads"))
126  return NM_QUERY_TYPE_THREADS;
127  if (mutt_str_equal(str, "messages"))
128  return NM_QUERY_TYPE_MESGS;
129 
130  mutt_error(_("failed to parse notmuch query type: %s"), NONULL(str));
131  return NM_QUERY_TYPE_MESGS;
132 }
133 
137 void nm_adata_free(void **ptr)
138 {
139  if (!ptr || !*ptr)
140  return;
141 
142  struct NmAccountData *adata = *ptr;
143  if (adata->db)
144  {
145  nm_db_free(adata->db);
146  adata->db = NULL;
147  }
148 
149  FREE(ptr);
150 }
151 
157 {
158  struct NmAccountData *adata = mutt_mem_calloc(1, sizeof(struct NmAccountData));
159 
160  return adata;
161 }
162 
170 {
171  if (!m || (m->type != MUTT_NOTMUCH))
172  return NULL;
173 
174  struct Account *a = m->account;
175  if (!a)
176  return NULL;
177 
178  return a->adata;
179 }
180 
188 void nm_mdata_free(void **ptr)
189 {
190  if (!ptr || !*ptr)
191  return;
192 
193  struct NmMboxData *mdata = *ptr;
194 
195  mutt_debug(LL_DEBUG1, "nm: freeing context data %p\n", mdata);
196 
197  url_free(&mdata->db_url);
198  FREE(&mdata->db_query);
199  FREE(ptr);
200 }
201 
210 struct NmMboxData *nm_mdata_new(const char *url)
211 {
212  if (!url)
213  return NULL;
214 
215  struct NmMboxData *mdata = mutt_mem_calloc(1, sizeof(struct NmMboxData));
216  mutt_debug(LL_DEBUG1, "nm: initialize mailbox mdata %p\n", (void *) mdata);
217 
218  mdata->db_limit = C_NmDbLimit;
219  mdata->query_type = string_to_query_type(C_NmQueryType);
220  mdata->db_url = url_parse(url);
221  if (!mdata->db_url)
222  {
223  mutt_error(_("failed to parse notmuch url: %s"), url);
224  FREE(&mdata);
225  return NULL;
226  }
227  return mdata;
228 }
229 
236 struct NmMboxData *nm_mdata_get(struct Mailbox *m)
237 {
238  if (!m || (m->type != MUTT_NOTMUCH))
239  return NULL;
240 
241  return m->mdata;
242 }
243 
251 void nm_edata_free(void **ptr)
252 {
253  if (!ptr || !*ptr)
254  return;
255 
256  struct NmEmailData *edata = *ptr;
257 
258  mutt_debug(LL_DEBUG2, "nm: freeing email %p\n", (void *) edata);
259  FREE(&edata->folder);
260  FREE(&edata->oldpath);
261  FREE(&edata->virtual_id);
262 
263  FREE(ptr);
264 }
265 
271 {
272  return mutt_mem_calloc(1, sizeof(struct NmEmailData));
273 }
274 
281 struct NmEmailData *nm_edata_get(struct Email *e)
282 {
283  if (!e)
284  return NULL;
285 
286  return e->nm_edata;
287 }
288 
294 static struct NmMboxData *nm_get_default_data(void)
295 {
296  // path to DB + query + url "decoration"
297  char url[PATH_MAX + 1024 + 32];
298 
299  // Try to use `$nm_default_url` or `$folder`.
300  // If neither are set, it is impossible to create a Notmuch URL.
301  if (C_NmDefaultUrl)
302  snprintf(url, sizeof(url), "%s", C_NmDefaultUrl);
303  else if (C_Folder)
304  snprintf(url, sizeof(url), "notmuch://%s", C_Folder);
305  else
306  return NULL;
307 
308  return nm_mdata_new(url);
309 }
310 
321 static int init_mailbox(struct Mailbox *m)
322 {
323  if (!m || (m->type != MUTT_NOTMUCH))
324  return -1;
325 
326  if (m->mdata)
327  return 0;
328 
330  if (!m->mdata)
331  return -1;
332 
334  return 0;
335 }
336 
343 static char *email_get_id(struct Email *e)
344 {
345  struct NmEmailData *edata = nm_edata_get(e);
346  if (!edata)
347  return NULL;
348 
349  return edata->virtual_id;
350 }
351 
359 static char *email_get_fullpath(struct Email *e, char *buf, size_t buflen)
360 {
361  snprintf(buf, buflen, "%s/%s", nm_email_get_folder(e), e->path);
362  return buf;
363 }
364 
372 static const char *query_type_to_string(enum NmQueryType query_type)
373 {
374  if (query_type == NM_QUERY_TYPE_THREADS)
375  return "threads";
376  return "messages";
377 }
378 
389 static bool query_window_check_timebase(const char *timebase)
390 {
391  if ((strcmp(timebase, "hour") == 0) || (strcmp(timebase, "day") == 0) ||
392  (strcmp(timebase, "week") == 0) || (strcmp(timebase, "month") == 0) ||
393  (strcmp(timebase, "year") == 0))
394  {
395  return true;
396  }
397  return false;
398 }
399 
409 static void query_window_reset(void)
410 {
411  mutt_debug(LL_DEBUG2, "entering\n");
412  cs_subset_str_native_set(NeoMutt->sub, "nm_query_window_current_position", 0, NULL);
413 }
414 
453 static bool windowed_query_from_query(const char *query, char *buf, size_t buflen)
454 {
455  mutt_debug(LL_DEBUG2, "nm: %s\n", query);
456 
459 
460  /* if the duration is a non positive integer, disable the window */
461  if (C_NmQueryWindowDuration <= 0)
462  {
464  return false;
465  }
466 
467  /* if the query has changed, reset the window position */
468  if (!C_NmQueryWindowCurrentSearch || (strcmp(query, C_NmQueryWindowCurrentSearch) != 0))
470 
472  {
473  mutt_message(_("Invalid nm_query_window_timebase value (valid values are: "
474  "hour, day, week, month or year)"));
475  mutt_debug(LL_DEBUG2, "Invalid nm_query_window_timebase value\n");
476  return false;
477  }
478 
479  if (end == 0)
480  {
481  // Open-ended date allows mail from the future.
482  // This may occur is the sender's time settings are off.
483  snprintf(buf, buflen, "date:%d%s.. and %s", beg, C_NmQueryWindowTimebase,
485  }
486  else
487  {
488  snprintf(buf, buflen, "date:%d%s..%d%s and %s", beg, C_NmQueryWindowTimebase,
490  }
491 
492  mutt_debug(LL_DEBUG2, "nm: %s -> %s\n", query, buf);
493 
494  return true;
495 }
496 
513 static char *get_query_string(struct NmMboxData *mdata, bool window)
514 {
515  mutt_debug(LL_DEBUG2, "nm: %s\n", window ? "true" : "false");
516 
517  if (!mdata)
518  return NULL;
519  if (mdata->db_query && !window)
520  return mdata->db_query;
521 
522  mdata->query_type = string_to_query_type(C_NmQueryType); /* user's default */
523 
524  struct UrlQuery *item = NULL;
525  STAILQ_FOREACH(item, &mdata->db_url->query_strings, entries)
526  {
527  if (!item->value || !item->name)
528  continue;
529 
530  if (strcmp(item->name, "limit") == 0)
531  {
532  if (mutt_str_atoi(item->value, &mdata->db_limit))
533  mutt_error(_("failed to parse notmuch limit: %s"), item->value);
534  }
535  else if (strcmp(item->name, "type") == 0)
536  mdata->query_type = string_to_query_type(item->value);
537  else if (strcmp(item->name, "query") == 0)
538  mdata->db_query = mutt_str_dup(item->value);
539  }
540 
541  if (!mdata->db_query)
542  return NULL;
543 
544  if (window)
545  {
546  char buf[1024];
548 
549  /* if a date part is defined, do not apply windows (to avoid the risk of
550  * having a non-intersected date frame). A good improvement would be to
551  * accept if they intersect */
552  if (!strstr(mdata->db_query, "date:") &&
553  windowed_query_from_query(mdata->db_query, buf, sizeof(buf)))
554  {
555  mdata->db_query = mutt_str_dup(buf);
556  }
557 
558  mutt_debug(LL_DEBUG2, "nm: query (windowed) '%s'\n", mdata->db_query);
559  }
560  else
561  mutt_debug(LL_DEBUG2, "nm: query '%s'\n", mdata->db_query);
562 
563  return mdata->db_query;
564 }
565 
571 static int get_limit(struct NmMboxData *mdata)
572 {
573  return mdata ? mdata->db_limit : 0;
574 }
575 
580 static void apply_exclude_tags(notmuch_query_t *query)
581 {
582  if (!C_NmExcludeTags || !query)
583  return;
584 
585  char *end = NULL, *tag = NULL;
586 
587  char *buf = mutt_str_dup(C_NmExcludeTags);
588 
589  for (char *p = buf; p && (p[0] != '\0'); p++)
590  {
591  if (!tag && isspace(*p))
592  continue;
593  if (!tag)
594  tag = p; /* begin of the tag */
595  if ((p[0] == ',') || (p[0] == ' '))
596  end = p; /* terminate the tag */
597  else if (p[1] == '\0')
598  end = p + 1; /* end of optstr */
599  if (!tag || !end)
600  continue;
601  if (tag >= end)
602  break;
603  *end = '\0';
604 
605  mutt_debug(LL_DEBUG2, "nm: query exclude tag '%s'\n", tag);
606  notmuch_query_add_tag_exclude(query, tag);
607  end = NULL;
608  tag = NULL;
609  }
610  notmuch_query_set_omit_excluded(query, 1);
611  FREE(&buf);
612 }
613 
621 static notmuch_query_t *get_query(struct Mailbox *m, bool writable)
622 {
623  struct NmMboxData *mdata = nm_mdata_get(m);
624  if (!mdata)
625  return NULL;
626 
627  notmuch_database_t *db = nm_db_get(m, writable);
628  const char *str = get_query_string(mdata, true);
629 
630  if (!db || !str)
631  goto err;
632 
633  notmuch_query_t *q = notmuch_query_create(db, str);
634  if (!q)
635  goto err;
636 
638  notmuch_query_set_sort(q, NOTMUCH_SORT_NEWEST_FIRST);
639  mutt_debug(LL_DEBUG2, "nm: query successfully initialized (%s)\n", str);
640  return q;
641 err:
642  nm_db_release(m);
643  return NULL;
644 }
645 
653 static int update_email_tags(struct Email *e, notmuch_message_t *msg)
654 {
655  struct NmEmailData *edata = nm_edata_get(e);
656  char *new_tags = NULL;
657  char *old_tags = NULL;
658 
659  mutt_debug(LL_DEBUG2, "nm: tags update requested (%s)\n", edata->virtual_id);
660 
661  for (notmuch_tags_t *tags = notmuch_message_get_tags(msg);
662  tags && notmuch_tags_valid(tags); notmuch_tags_move_to_next(tags))
663  {
664  const char *t = notmuch_tags_get(tags);
665  if (!t || (*t == '\0'))
666  continue;
667 
668  mutt_str_append_item(&new_tags, t, ' ');
669  }
670 
671  old_tags = driver_tags_get(&e->tags);
672 
673  if (new_tags && old_tags && (strcmp(old_tags, new_tags) == 0))
674  {
675  FREE(&old_tags);
676  FREE(&new_tags);
677  mutt_debug(LL_DEBUG2, "nm: tags unchanged\n");
678  return 1;
679  }
680 
681  /* new version */
682  driver_tags_replace(&e->tags, new_tags);
683  FREE(&new_tags);
684 
685  new_tags = driver_tags_get_transformed(&e->tags);
686  mutt_debug(LL_DEBUG2, "nm: new tags: '%s'\n", new_tags);
687  FREE(&new_tags);
688 
689  new_tags = driver_tags_get(&e->tags);
690  mutt_debug(LL_DEBUG2, "nm: new tag transforms: '%s'\n", new_tags);
691  FREE(&new_tags);
692 
693  return 0;
694 }
695 
703 static int update_message_path(struct Email *e, const char *path)
704 {
705  struct NmEmailData *edata = nm_edata_get(e);
706 
707  mutt_debug(LL_DEBUG2, "nm: path update requested path=%s, (%s)\n", path, edata->virtual_id);
708 
709  char *p = strrchr(path, '/');
710  if (p && ((p - path) > 3) &&
711  (mutt_strn_equal(p - 3, "cur", 3) || mutt_strn_equal(p - 3, "new", 3) ||
712  mutt_strn_equal(p - 3, "tmp", 3)))
713  {
714  edata->type = MUTT_MAILDIR;
715 
716  FREE(&e->path);
717  FREE(&edata->folder);
718 
719  p -= 3; /* skip subfolder (e.g. "new") */
720  e->path = mutt_str_dup(p);
721 
722  for (; (p > path) && (*(p - 1) == '/'); p--)
723  ; // do nothing
724 
725  edata->folder = mutt_strn_dup(path, p - path);
726 
727  mutt_debug(LL_DEBUG2, "nm: folder='%s', file='%s'\n", edata->folder, e->path);
728  return 0;
729  }
730 
731  return 1;
732 }
733 
740 static char *get_folder_from_path(const char *path)
741 {
742  char *p = strrchr(path, '/');
743 
744  if (p && ((p - path) > 3) &&
745  (mutt_strn_equal(p - 3, "cur", 3) || mutt_strn_equal(p - 3, "new", 3) ||
746  mutt_strn_equal(p - 3, "tmp", 3)))
747  {
748  p -= 3;
749  for (; (p > path) && (*(p - 1) == '/'); p--)
750  ; // do nothing
751 
752  return mutt_strn_dup(path, p - path);
753  }
754 
755  return NULL;
756 }
757 
765 static char *nm2mutt_message_id(const char *id)
766 {
767  size_t sz;
768  char *mid = NULL;
769 
770  if (!id)
771  return NULL;
772  sz = strlen(id) + 3;
773  mid = mutt_mem_malloc(sz);
774 
775  snprintf(mid, sz, "<%s>", id);
776  return mid;
777 }
778 
787 static int init_email(struct Email *e, const char *path, notmuch_message_t *msg)
788 {
789  if (nm_edata_get(e))
790  return 0;
791 
792  struct NmEmailData *edata = nm_edata_new();
793  e->nm_edata = edata;
794 
795  /* Notmuch ensures that message Id exists (if not notmuch Notmuch will
796  * generate an ID), so it's more safe than use neomutt Email->env->id */
797  const char *id = notmuch_message_get_message_id(msg);
798  edata->virtual_id = mutt_str_dup(id);
799 
800  mutt_debug(LL_DEBUG2, "nm: [e=%p, edata=%p] (%s)\n", (void *) e, (void *) edata, id);
801 
802  char *nm_msg_id = nm2mutt_message_id(id);
803  if (!e->env->message_id)
804  {
805  e->env->message_id = nm_msg_id;
806  }
807  else if (!mutt_str_equal(e->env->message_id, nm_msg_id))
808  {
809  FREE(&e->env->message_id);
810  e->env->message_id = nm_msg_id;
811  }
812  else
813  {
814  FREE(&nm_msg_id);
815  }
816 
817  if (update_message_path(e, path) != 0)
818  return -1;
819 
820  update_email_tags(e, msg);
821 
822  return 0;
823 }
824 
831 static const char *get_message_last_filename(notmuch_message_t *msg)
832 {
833  const char *name = NULL;
834 
835  for (notmuch_filenames_t *ls = notmuch_message_get_filenames(msg);
836  ls && notmuch_filenames_valid(ls); notmuch_filenames_move_to_next(ls))
837  {
838  name = notmuch_filenames_get(ls);
839  }
840 
841  return name;
842 }
843 
848 static void progress_reset(struct Mailbox *m)
849 {
850  if (!m->verbose)
851  return;
852 
853  struct NmMboxData *mdata = nm_mdata_get(m);
854  if (!mdata)
855  return;
856 
857  memset(&mdata->progress, 0, sizeof(mdata->progress));
858  mdata->oldmsgcount = m->msg_count;
859  mdata->ignmsgcount = 0;
860  mdata->noprogress = false;
861  mdata->progress_ready = false;
862 }
863 
869 static void progress_update(struct Mailbox *m, notmuch_query_t *q)
870 {
871  struct NmMboxData *mdata = nm_mdata_get(m);
872 
873  if (!m->verbose || !mdata || mdata->noprogress)
874  return;
875 
876  if (!mdata->progress_ready && q)
877  {
878  // The total mail count is in oldmsgcount, so use that instead of recounting.
879  mutt_progress_init(&mdata->progress, _("Reading messages..."),
881  mdata->progress_ready = true;
882  }
883 
884  if (mdata->progress_ready)
885  {
886  mutt_progress_update(&mdata->progress, m->msg_count + mdata->ignmsgcount, -1);
887  }
888 }
889 
897 static struct Email *get_mutt_email(struct Mailbox *m, notmuch_message_t *msg)
898 {
899  if (!m || !msg)
900  return NULL;
901 
902  const char *id = notmuch_message_get_message_id(msg);
903  if (!id)
904  return NULL;
905 
906  mutt_debug(LL_DEBUG2, "nm: neomutt email, id='%s'\n", id);
907 
908  if (!m->id_hash)
909  {
910  mutt_debug(LL_DEBUG2, "nm: init hash\n");
911  m->id_hash = mutt_make_id_hash(m);
912  if (!m->id_hash)
913  return NULL;
914  }
915 
916  char *mid = nm2mutt_message_id(id);
917  mutt_debug(LL_DEBUG2, "nm: neomutt id='%s'\n", mid);
918 
919  struct Email *e = mutt_hash_find(m->id_hash, mid);
920  FREE(&mid);
921  return e;
922 }
923 
932 static void append_message(struct HeaderCache *h, struct Mailbox *m,
933  notmuch_query_t *q, notmuch_message_t *msg, bool dedup)
934 {
935  struct NmMboxData *mdata = nm_mdata_get(m);
936  if (!mdata)
937  return;
938 
939  char *newpath = NULL;
940  struct Email *e = NULL;
941 
942  /* deduplicate */
943  if (dedup && get_mutt_email(m, msg))
944  {
945  mdata->ignmsgcount++;
946  progress_update(m, q);
947  mutt_debug(LL_DEBUG2, "nm: ignore id=%s, already in the m\n",
948  notmuch_message_get_message_id(msg));
949  return;
950  }
951 
952  const char *path = get_message_last_filename(msg);
953  if (!path)
954  return;
955 
956  mutt_debug(LL_DEBUG2, "nm: appending message, i=%d, id=%s, path=%s\n",
957  m->msg_count, notmuch_message_get_message_id(msg), path);
958 
959  if (m->msg_count >= m->email_max)
960  {
961  mutt_debug(LL_DEBUG2, "nm: allocate mx memory\n");
962  mx_alloc_memory(m);
963  }
964 
965 #ifdef USE_HCACHE
966  e = mutt_hcache_fetch(h, path, mutt_str_len(path), 0).email;
967  if (!e)
968 #endif
969  {
970  if (access(path, F_OK) == 0)
971  e = maildir_parse_message(MUTT_MAILDIR, path, false, NULL);
972  else
973  {
974  /* maybe moved try find it... */
975  char *folder = get_folder_from_path(path);
976 
977  if (folder)
978  {
979  FILE *fp = maildir_open_find_message(folder, path, &newpath);
980  if (fp)
981  {
982  e = maildir_parse_stream(MUTT_MAILDIR, fp, newpath, false, NULL);
983  mutt_file_fclose(&fp);
984 
985  mutt_debug(LL_DEBUG1, "nm: not up-to-date: %s -> %s\n", path, newpath);
986  }
987  }
988  FREE(&folder);
989  }
990 
991  if (!e)
992  {
993  mutt_debug(LL_DEBUG1, "nm: failed to parse message: %s\n", path);
994  goto done;
995  }
996 
997 #ifdef USE_HCACHE
998  mutt_hcache_store(h, newpath ? newpath : path,
999  mutt_str_len(newpath ? newpath : path), e, 0);
1000 #endif
1001  }
1002 
1003  if (init_email(e, newpath ? newpath : path, msg) != 0)
1004  {
1005  email_free(&e);
1006  mutt_debug(LL_DEBUG1, "nm: failed to append email!\n");
1007  goto done;
1008  }
1009 
1010  e->active = true;
1011  e->index = m->msg_count;
1012  mailbox_size_add(m, e);
1013  m->emails[m->msg_count] = e;
1014  m->msg_count++;
1015 
1016  if (newpath)
1017  {
1018  /* remember that file has been moved -- nm_mbox_sync() will update the DB */
1019  struct NmEmailData *edata = nm_edata_get(e);
1020  if (edata)
1021  {
1022  mutt_debug(LL_DEBUG1, "nm: remember obsolete path: %s\n", path);
1023  edata->oldpath = mutt_str_dup(path);
1024  }
1025  }
1026  progress_update(m, q);
1027 done:
1028  FREE(&newpath);
1029 }
1030 
1041 static void append_replies(struct HeaderCache *h, struct Mailbox *m,
1042  notmuch_query_t *q, notmuch_message_t *top, bool dedup)
1043 {
1044  notmuch_messages_t *msgs = NULL;
1045 
1046  for (msgs = notmuch_message_get_replies(top); notmuch_messages_valid(msgs);
1047  notmuch_messages_move_to_next(msgs))
1048  {
1049  notmuch_message_t *nm = notmuch_messages_get(msgs);
1050  append_message(h, m, q, nm, dedup);
1051  /* recurse through all the replies to this message too */
1052  append_replies(h, m, q, nm, dedup);
1053  notmuch_message_destroy(nm);
1054  }
1055 }
1056 
1068 static void append_thread(struct HeaderCache *h, struct Mailbox *m,
1069  notmuch_query_t *q, notmuch_thread_t *thread, bool dedup)
1070 {
1071  notmuch_messages_t *msgs = NULL;
1072 
1073  for (msgs = notmuch_thread_get_toplevel_messages(thread);
1074  notmuch_messages_valid(msgs); notmuch_messages_move_to_next(msgs))
1075  {
1076  notmuch_message_t *nm = notmuch_messages_get(msgs);
1077  append_message(h, m, q, nm, dedup);
1078  append_replies(h, m, q, nm, dedup);
1079  notmuch_message_destroy(nm);
1080  }
1081 }
1082 
1092 static notmuch_messages_t *get_messages(notmuch_query_t *query)
1093 {
1094  if (!query)
1095  return NULL;
1096 
1097  notmuch_messages_t *msgs = NULL;
1098 
1099 #if LIBNOTMUCH_CHECK_VERSION(5, 0, 0)
1100  if (notmuch_query_search_messages(query, &msgs) != NOTMUCH_STATUS_SUCCESS)
1101  return NULL;
1102 #elif LIBNOTMUCH_CHECK_VERSION(4, 3, 0)
1103  if (notmuch_query_search_messages_st(query, &msgs) != NOTMUCH_STATUS_SUCCESS)
1104  return NULL;
1105 #else
1106  msgs = notmuch_query_search_messages(query);
1107 #endif
1108 
1109  return msgs;
1110 }
1111 
1120 static bool read_mesgs_query(struct Mailbox *m, notmuch_query_t *q, bool dedup)
1121 {
1122  struct NmMboxData *mdata = nm_mdata_get(m);
1123  if (!mdata)
1124  return false;
1125 
1126  int limit = get_limit(mdata);
1127 
1128  notmuch_messages_t *msgs = get_messages(q);
1129 
1130  if (!msgs)
1131  return false;
1132 
1133  struct HeaderCache *h = nm_hcache_open(m);
1134 
1135  for (; notmuch_messages_valid(msgs) && ((limit == 0) || (m->msg_count < limit));
1136  notmuch_messages_move_to_next(msgs))
1137  {
1138  if (SigInt == 1)
1139  {
1140  nm_hcache_close(h);
1141  SigInt = 0;
1142  return false;
1143  }
1144  notmuch_message_t *nm = notmuch_messages_get(msgs);
1145  append_message(h, m, q, nm, dedup);
1146  notmuch_message_destroy(nm);
1147  }
1148 
1149  nm_hcache_close(h);
1150  return true;
1151 }
1152 
1162 static notmuch_threads_t *get_threads(notmuch_query_t *query)
1163 {
1164  if (!query)
1165  return NULL;
1166 
1167  notmuch_threads_t *threads = NULL;
1168 #if LIBNOTMUCH_CHECK_VERSION(5, 0, 0)
1169  if (notmuch_query_search_threads(query, &threads) != NOTMUCH_STATUS_SUCCESS)
1170  return false;
1171 #elif LIBNOTMUCH_CHECK_VERSION(4, 3, 0)
1172  if (notmuch_query_search_threads_st(query, &threads) != NOTMUCH_STATUS_SUCCESS)
1173  return false;
1174 #else
1175  threads = notmuch_query_search_threads(query);
1176 #endif
1177 
1178  return threads;
1179 }
1180 
1190 static bool read_threads_query(struct Mailbox *m, notmuch_query_t *q, bool dedup, int limit)
1191 {
1192  struct NmMboxData *mdata = nm_mdata_get(m);
1193  if (!mdata)
1194  return false;
1195 
1196  notmuch_threads_t *threads = get_threads(q);
1197  if (!threads)
1198  return false;
1199 
1200  struct HeaderCache *h = nm_hcache_open(m);
1201 
1202  for (; notmuch_threads_valid(threads) && ((limit == 0) || (m->msg_count < limit));
1203  notmuch_threads_move_to_next(threads))
1204  {
1205  if (SigInt == 1)
1206  {
1207  nm_hcache_close(h);
1208  SigInt = 0;
1209  return false;
1210  }
1211  notmuch_thread_t *thread = notmuch_threads_get(threads);
1212  append_thread(h, m, q, thread, dedup);
1213  notmuch_thread_destroy(thread);
1214  }
1215 
1216  nm_hcache_close(h);
1217  return true;
1218 }
1219 
1226 static notmuch_message_t *get_nm_message(notmuch_database_t *db, struct Email *e)
1227 {
1228  notmuch_message_t *msg = NULL;
1229  char *id = email_get_id(e);
1230 
1231  mutt_debug(LL_DEBUG2, "nm: find message (%s)\n", id);
1232 
1233  if (id && db)
1234  notmuch_database_find_message(db, id, &msg);
1235 
1236  return msg;
1237 }
1238 
1245 static bool nm_message_has_tag(notmuch_message_t *msg, char *tag)
1246 {
1247  const char *possible_match_tag = NULL;
1248  notmuch_tags_t *tags = NULL;
1249 
1250  for (tags = notmuch_message_get_tags(msg); notmuch_tags_valid(tags);
1251  notmuch_tags_move_to_next(tags))
1252  {
1253  possible_match_tag = notmuch_tags_get(tags);
1254  if (mutt_str_equal(possible_match_tag, tag))
1255  {
1256  return true;
1257  }
1258  }
1259  return false;
1260 }
1261 
1267 static void sync_email_path_with_nm(struct Email *e, notmuch_message_t *msg)
1268 {
1269  const char *new_file = get_message_last_filename(msg);
1270  char old_file[PATH_MAX];
1271  email_get_fullpath(e, old_file, sizeof(old_file));
1272 
1273  if (!mutt_str_equal(old_file, new_file))
1274  update_message_path(e, new_file);
1275 }
1276 
1284 static int update_tags(notmuch_message_t *msg, const char *tags)
1285 {
1286  char *buf = mutt_str_dup(tags);
1287  if (!buf)
1288  return -1;
1289 
1290  notmuch_message_freeze(msg);
1291 
1292  char *tag = NULL, *end = NULL;
1293  for (char *p = buf; p && *p; p++)
1294  {
1295  if (!tag && isspace(*p))
1296  continue;
1297  if (!tag)
1298  tag = p; /* begin of the tag */
1299  if ((p[0] == ',') || (p[0] == ' '))
1300  end = p; /* terminate the tag */
1301  else if (p[1] == '\0')
1302  end = p + 1; /* end of optstr */
1303  if (!tag || !end)
1304  continue;
1305  if (tag >= end)
1306  break;
1307 
1308  end[0] = '\0';
1309 
1310  if (tag[0] == '-')
1311  {
1312  mutt_debug(LL_DEBUG1, "nm: remove tag: '%s'\n", tag + 1);
1313  notmuch_message_remove_tag(msg, tag + 1);
1314  }
1315  else if (tag[0] == '!')
1316  {
1317  mutt_debug(LL_DEBUG1, "nm: toggle tag: '%s'\n", tag + 1);
1318  if (nm_message_has_tag(msg, tag + 1))
1319  {
1320  notmuch_message_remove_tag(msg, tag + 1);
1321  }
1322  else
1323  {
1324  notmuch_message_add_tag(msg, tag + 1);
1325  }
1326  }
1327  else
1328  {
1329  mutt_debug(LL_DEBUG1, "nm: add tag: '%s'\n", (tag[0] == '+') ? tag + 1 : tag);
1330  notmuch_message_add_tag(msg, (tag[0] == '+') ? tag + 1 : tag);
1331  }
1332  end = NULL;
1333  tag = NULL;
1334  }
1335 
1336  notmuch_message_thaw(msg);
1337  FREE(&buf);
1338  return 0;
1339 }
1340 
1353 static int update_email_flags(struct Mailbox *m, struct Email *e, const char *tags)
1354 {
1355  char *buf = mutt_str_dup(tags);
1356  if (!buf)
1357  return -1;
1358 
1359  char *tag = NULL, *end = NULL;
1360  for (char *p = buf; p && *p; p++)
1361  {
1362  if (!tag && isspace(*p))
1363  continue;
1364  if (!tag)
1365  tag = p; /* begin of the tag */
1366  if ((p[0] == ',') || (p[0] == ' '))
1367  end = p; /* terminate the tag */
1368  else if (p[1] == '\0')
1369  end = p + 1; /* end of optstr */
1370  if (!tag || !end)
1371  continue;
1372  if (tag >= end)
1373  break;
1374 
1375  end[0] = '\0';
1376 
1377  if (tag[0] == '-')
1378  {
1379  tag++;
1380  if (strcmp(tag, C_NmUnreadTag) == 0)
1381  mutt_set_flag(m, e, MUTT_READ, true);
1382  else if (strcmp(tag, C_NmRepliedTag) == 0)
1383  mutt_set_flag(m, e, MUTT_REPLIED, false);
1384  else if (strcmp(tag, C_NmFlaggedTag) == 0)
1385  mutt_set_flag(m, e, MUTT_FLAG, false);
1386  }
1387  else
1388  {
1389  tag = (tag[0] == '+') ? tag + 1 : tag;
1390  if (strcmp(tag, C_NmUnreadTag) == 0)
1391  mutt_set_flag(m, e, MUTT_READ, false);
1392  else if (strcmp(tag, C_NmRepliedTag) == 0)
1393  mutt_set_flag(m, e, MUTT_REPLIED, true);
1394  else if (strcmp(tag, C_NmFlaggedTag) == 0)
1395  mutt_set_flag(m, e, MUTT_FLAG, true);
1396  }
1397  end = NULL;
1398  tag = NULL;
1399  }
1400 
1401  FREE(&buf);
1402  return 0;
1403 }
1404 
1415 static int rename_maildir_filename(const char *old, char *buf, size_t buflen, struct Email *e)
1416 {
1417  char filename[PATH_MAX];
1418  char suffix[PATH_MAX];
1419  char folder[PATH_MAX];
1420 
1421  mutt_str_copy(folder, old, sizeof(folder));
1422  char *p = strrchr(folder, '/');
1423  if (p)
1424  {
1425  *p = '\0';
1426  p++;
1427  }
1428  else
1429  p = folder;
1430 
1431  mutt_str_copy(filename, p, sizeof(filename));
1432 
1433  /* remove (new,cur,...) from folder path */
1434  p = strrchr(folder, '/');
1435  if (p)
1436  *p = '\0';
1437 
1438  /* remove old flags from filename */
1439  p = strchr(filename, ':');
1440  if (p)
1441  *p = '\0';
1442 
1443  /* compose new flags */
1444  maildir_gen_flags(suffix, sizeof(suffix), e);
1445 
1446  snprintf(buf, buflen, "%s/%s/%s%s", folder,
1447  (e->read || e->old) ? "cur" : "new", filename, suffix);
1448 
1449  if (strcmp(old, buf) == 0)
1450  return 1;
1451 
1452  if (rename(old, buf) != 0)
1453  {
1454  mutt_debug(LL_DEBUG1, "nm: rename(2) failed %s -> %s\n", old, buf);
1455  return -1;
1456  }
1457 
1458  return 0;
1459 }
1460 
1468 static int remove_filename(struct Mailbox *m, const char *path)
1469 {
1470  struct NmMboxData *mdata = nm_mdata_get(m);
1471  if (!mdata)
1472  return -1;
1473 
1474  mutt_debug(LL_DEBUG2, "nm: remove filename '%s'\n", path);
1475 
1476  notmuch_database_t *db = nm_db_get(m, true);
1477  if (!db)
1478  return -1;
1479 
1480  notmuch_message_t *msg = NULL;
1481  notmuch_status_t st = notmuch_database_find_message_by_filename(db, path, &msg);
1482  if (st || !msg)
1483  return -1;
1484 
1485  int trans = nm_db_trans_begin(m);
1486  if (trans < 0)
1487  return -1;
1488 
1489  /* note that unlink() is probably unnecessary here, it's already removed
1490  * by mh_sync_mailbox_message(), but for sure... */
1491  notmuch_filenames_t *ls = NULL;
1492  st = notmuch_database_remove_message(db, path);
1493  switch (st)
1494  {
1495  case NOTMUCH_STATUS_SUCCESS:
1496  mutt_debug(LL_DEBUG2, "nm: remove success, call unlink\n");
1497  unlink(path);
1498  break;
1499  case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:
1500  mutt_debug(LL_DEBUG2, "nm: remove success (duplicate), call unlink\n");
1501  unlink(path);
1502  for (ls = notmuch_message_get_filenames(msg);
1503  ls && notmuch_filenames_valid(ls); notmuch_filenames_move_to_next(ls))
1504  {
1505  path = notmuch_filenames_get(ls);
1506 
1507  mutt_debug(LL_DEBUG2, "nm: remove duplicate: '%s'\n", path);
1508  unlink(path);
1509  notmuch_database_remove_message(db, path);
1510  }
1511  break;
1512  default:
1513  mutt_debug(LL_DEBUG1, "nm: failed to remove '%s' [st=%d]\n", path, (int) st);
1514  break;
1515  }
1516 
1517  notmuch_message_destroy(msg);
1518  if (trans)
1519  nm_db_trans_end(m);
1520  return 0;
1521 }
1522 
1532 static int rename_filename(struct Mailbox *m, const char *old_file,
1533  const char *new_file, struct Email *e)
1534 {
1535  struct NmMboxData *mdata = nm_mdata_get(m);
1536  if (!mdata)
1537  return -1;
1538 
1539  notmuch_database_t *db = nm_db_get(m, true);
1540  if (!db || !new_file || !old_file || (access(new_file, F_OK) != 0))
1541  return -1;
1542 
1543  int rc = -1;
1544  notmuch_status_t st;
1545  notmuch_filenames_t *ls = NULL;
1546  notmuch_message_t *msg = NULL;
1547 
1548  mutt_debug(LL_DEBUG1, "nm: rename filename, %s -> %s\n", old_file, new_file);
1549  int trans = nm_db_trans_begin(m);
1550  if (trans < 0)
1551  return -1;
1552 
1553  mutt_debug(LL_DEBUG2, "nm: rename: add '%s'\n", new_file);
1554 #ifdef HAVE_NOTMUCH_DATABASE_INDEX_FILE
1555  st = notmuch_database_index_file(db, new_file, NULL, &msg);
1556 #else
1557  st = notmuch_database_add_message(db, new_file, &msg);
1558 #endif
1559 
1560  if ((st != NOTMUCH_STATUS_SUCCESS) && (st != NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID))
1561  {
1562  mutt_debug(LL_DEBUG1, "nm: failed to add '%s' [st=%d]\n", new_file, (int) st);
1563  goto done;
1564  }
1565 
1566  mutt_debug(LL_DEBUG2, "nm: rename: rem '%s'\n", old_file);
1567  st = notmuch_database_remove_message(db, old_file);
1568  switch (st)
1569  {
1570  case NOTMUCH_STATUS_SUCCESS:
1571  break;
1572  case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:
1573  mutt_debug(LL_DEBUG2, "nm: rename: syncing duplicate filename\n");
1574  notmuch_message_destroy(msg);
1575  msg = NULL;
1576  notmuch_database_find_message_by_filename(db, new_file, &msg);
1577 
1578  for (ls = notmuch_message_get_filenames(msg);
1579  msg && ls && notmuch_filenames_valid(ls); notmuch_filenames_move_to_next(ls))
1580  {
1581  const char *path = notmuch_filenames_get(ls);
1582  char newpath[PATH_MAX];
1583 
1584  if (strcmp(new_file, path) == 0)
1585  continue;
1586 
1587  mutt_debug(LL_DEBUG2, "nm: rename: syncing duplicate: %s\n", path);
1588 
1589  if (rename_maildir_filename(path, newpath, sizeof(newpath), e) == 0)
1590  {
1591  mutt_debug(LL_DEBUG2, "nm: rename dup %s -> %s\n", path, newpath);
1592  notmuch_database_remove_message(db, path);
1593 #ifdef HAVE_NOTMUCH_DATABASE_INDEX_FILE
1594  notmuch_database_index_file(db, newpath, NULL, NULL);
1595 #else
1596  notmuch_database_add_message(db, newpath, NULL);
1597 #endif
1598  }
1599  }
1600  notmuch_message_destroy(msg);
1601  msg = NULL;
1602  notmuch_database_find_message_by_filename(db, new_file, &msg);
1603  st = NOTMUCH_STATUS_SUCCESS;
1604  break;
1605  default:
1606  mutt_debug(LL_DEBUG1, "nm: failed to remove '%s' [st=%d]\n", old_file, (int) st);
1607  break;
1608  }
1609 
1610  if ((st == NOTMUCH_STATUS_SUCCESS) && e && msg)
1611  {
1612  notmuch_message_maildir_flags_to_tags(msg);
1613  update_email_tags(e, msg);
1614 
1615  char *tags = driver_tags_get(&e->tags);
1616  update_tags(msg, tags);
1617  FREE(&tags);
1618  }
1619 
1620  rc = 0;
1621 done:
1622  if (msg)
1623  notmuch_message_destroy(msg);
1624  if (trans)
1625  nm_db_trans_end(m);
1626  return rc;
1627 }
1628 
1636 static unsigned int count_query(notmuch_database_t *db, const char *qstr, int limit)
1637 {
1638  notmuch_query_t *q = notmuch_query_create(db, qstr);
1639  if (!q)
1640  return 0;
1641 
1642  unsigned int res = 0;
1643 
1644  apply_exclude_tags(q);
1645 #if LIBNOTMUCH_CHECK_VERSION(5, 0, 0)
1646  if (notmuch_query_count_messages(q, &res) != NOTMUCH_STATUS_SUCCESS)
1647  res = 0; /* may not be defined on error */
1648 #elif LIBNOTMUCH_CHECK_VERSION(4, 3, 0)
1649  if (notmuch_query_count_messages_st(q, &res) != NOTMUCH_STATUS_SUCCESS)
1650  res = 0; /* may not be defined on error */
1651 #else
1652  res = notmuch_query_count_messages(q);
1653 #endif
1654  notmuch_query_destroy(q);
1655  mutt_debug(LL_DEBUG1, "nm: count '%s', result=%d\n", qstr, res);
1656 
1657  if ((limit > 0) && (res > limit))
1658  res = limit;
1659 
1660  return res;
1661 }
1662 
1669 char *nm_email_get_folder(struct Email *e)
1670 {
1671  struct NmEmailData *edata = nm_edata_get(e);
1672  if (!edata)
1673  return NULL;
1674 
1675  return edata->folder;
1676 }
1677 
1688 char *nm_email_get_folder_rel_db(struct Mailbox *m, struct Email *e)
1689 {
1690  char *full_folder = nm_email_get_folder(e);
1691  if (!full_folder)
1692  return NULL;
1693 
1694  const char *db_path = nm_db_get_filename(m);
1695  if (!db_path)
1696  return NULL;
1697 
1698  return full_folder + strlen(db_path);
1699 }
1700 
1708 int nm_read_entire_thread(struct Mailbox *m, struct Email *e)
1709 {
1710  if (!m)
1711  return -1;
1712 
1713  struct NmMboxData *mdata = nm_mdata_get(m);
1714  if (!mdata)
1715  return -1;
1716 
1717  notmuch_query_t *q = NULL;
1718  notmuch_database_t *db = NULL;
1719  notmuch_message_t *msg = NULL;
1720  int rc = -1;
1721 
1722  if (!(db = nm_db_get(m, false)) || !(msg = get_nm_message(db, e)))
1723  goto done;
1724 
1725  mutt_debug(LL_DEBUG1, "nm: reading entire-thread messages...[current count=%d]\n",
1726  m->msg_count);
1727 
1728  progress_reset(m);
1729  const char *id = notmuch_message_get_thread_id(msg);
1730  if (!id)
1731  goto done;
1732 
1733  char *qstr = NULL;
1734  mutt_str_append_item(&qstr, "thread:", '\0');
1735  mutt_str_append_item(&qstr, id, '\0');
1736 
1737  q = notmuch_query_create(db, qstr);
1738  FREE(&qstr);
1739  if (!q)
1740  goto done;
1741  apply_exclude_tags(q);
1742  notmuch_query_set_sort(q, NOTMUCH_SORT_NEWEST_FIRST);
1743 
1744  read_threads_query(m, q, true, 0);
1745  m->mtime.tv_sec = mutt_date_epoch();
1746  m->mtime.tv_nsec = 0;
1747  rc = 0;
1748 
1749  if (m->msg_count > mdata->oldmsgcount)
1751 done:
1752  if (q)
1753  notmuch_query_destroy(q);
1754 
1755  nm_db_release(m);
1756 
1757  if (m->msg_count == mdata->oldmsgcount)
1758  mutt_message(_("No more messages in the thread"));
1759 
1760  mdata->oldmsgcount = 0;
1761  mutt_debug(LL_DEBUG1, "nm: reading entire-thread messages... done [rc=%d, count=%d]\n",
1762  rc, m->msg_count);
1763  return rc;
1764 }
1765 
1777 {
1778  if (!buf)
1779  return;
1780 
1781  // The six variations of how type= could appear.
1782  const char *variants[6] = { "&type=threads", "&type=messages",
1783  "type=threads&", "type=messages&",
1784  "type=threads", "type=messages" };
1785 
1786  int variants_size = mutt_array_size(variants);
1787  for (int i = 0; i < variants_size; i++)
1788  {
1789  if (strcasestr(buf, variants[i]) != NULL)
1790  {
1791  // variants[] is setup such that type can be determined via modulo 2.
1792  mdata->query_type = ((i % 2) == 0) ? NM_QUERY_TYPE_THREADS : NM_QUERY_TYPE_MESGS;
1793 
1794  mutt_istr_remall(buf, variants[i]);
1795  }
1796  }
1797 }
1798 
1807 char *nm_url_from_query(struct Mailbox *m, char *buf, size_t buflen)
1808 {
1809  mutt_debug(LL_DEBUG2, "(%s)\n", buf);
1810  struct NmMboxData *mdata = nm_mdata_get(m);
1811  char url[PATH_MAX + 1024 + 32]; /* path to DB + query + URL "decoration" */
1812  int added;
1813  bool using_default_data = false;
1814 
1815  // No existing data. Try to get a default NmMboxData.
1816  if (!mdata)
1817  {
1818  mdata = nm_get_default_data();
1819 
1820  // Failed to get default data.
1821  if (!mdata)
1822  return NULL;
1823 
1824  using_default_data = true;
1825  }
1826 
1827  nm_parse_type_from_query(mdata, buf);
1828 
1829  if (get_limit(mdata) == C_NmDbLimit)
1830  {
1831  added = snprintf(url, sizeof(url), "%s%s?type=%s&query=", NmUrlProtocol,
1833  }
1834  else
1835  {
1836  added = snprintf(url, sizeof(url), "%s%s?type=%s&limit=%d&query=", NmUrlProtocol,
1837  nm_db_get_filename(m),
1838  query_type_to_string(mdata->query_type), get_limit(mdata));
1839  }
1840 
1841  if (added >= sizeof(url))
1842  {
1843  // snprintf output was truncated, so can't create URL
1844  return NULL;
1845  }
1846 
1847  url_pct_encode(&url[added], sizeof(url) - added, buf);
1848 
1849  mutt_str_copy(buf, url, buflen);
1850  buf[buflen - 1] = '\0';
1851 
1852  if (using_default_data)
1853  nm_mdata_free((void **) &mdata);
1854 
1855  mutt_debug(LL_DEBUG1, "nm: url from query '%s'\n", buf);
1856  return buf;
1857 }
1858 
1869 {
1872 
1874 }
1875 
1885 {
1888 }
1889 
1896 bool nm_message_is_still_queried(struct Mailbox *m, struct Email *e)
1897 {
1898  struct NmMboxData *mdata = nm_mdata_get(m);
1899  notmuch_database_t *db = nm_db_get(m, false);
1900  char *orig_str = get_query_string(mdata, true);
1901 
1902  if (!db || !orig_str)
1903  return false;
1904 
1905  char *new_str = NULL;
1906  bool rc = false;
1907  if (mutt_str_asprintf(&new_str, "id:%s and (%s)", email_get_id(e), orig_str) < 0)
1908  return false;
1909 
1910  mutt_debug(LL_DEBUG2, "nm: checking if message is still queried: %s\n", new_str);
1911 
1912  notmuch_query_t *q = notmuch_query_create(db, new_str);
1913 
1914  switch (mdata->query_type)
1915  {
1916  case NM_QUERY_TYPE_MESGS:
1917  {
1918  notmuch_messages_t *messages = get_messages(q);
1919 
1920  if (!messages)
1921  return false;
1922 
1923  rc = notmuch_messages_valid(messages);
1924  notmuch_messages_destroy(messages);
1925  break;
1926  }
1927  case NM_QUERY_TYPE_THREADS:
1928  {
1929  notmuch_threads_t *threads = get_threads(q);
1930 
1931  if (!threads)
1932  return false;
1933 
1934  rc = notmuch_threads_valid(threads);
1935  notmuch_threads_destroy(threads);
1936  break;
1937  }
1938  }
1939 
1940  notmuch_query_destroy(q);
1941 
1942  mutt_debug(LL_DEBUG2, "nm: checking if message is still queried: %s = %s\n",
1943  new_str, rc ? "true" : "false");
1944 
1945  return rc;
1946 }
1947 
1957 int nm_update_filename(struct Mailbox *m, const char *old_file,
1958  const char *new_file, struct Email *e)
1959 {
1960  char buf[PATH_MAX];
1961  struct NmMboxData *mdata = nm_mdata_get(m);
1962  if (!mdata || !new_file)
1963  return -1;
1964 
1965  if (!old_file && nm_edata_get(e))
1966  {
1967  email_get_fullpath(e, buf, sizeof(buf));
1968  old_file = buf;
1969  }
1970 
1971  int rc = rename_filename(m, old_file, new_file, e);
1972 
1973  nm_db_release(m);
1974  m->mtime.tv_sec = mutt_date_epoch();
1975  m->mtime.tv_nsec = 0;
1976  return rc;
1977 }
1978 
1982 static int nm_mbox_check_stats(struct Mailbox *m, int flags)
1983 {
1984  struct UrlQuery *item = NULL;
1985  struct Url *url = NULL;
1986  char *db_filename = NULL, *db_query = NULL;
1987  notmuch_database_t *db = NULL;
1988  int rc = -1;
1989  int limit = C_NmDbLimit;
1990  mutt_debug(LL_DEBUG1, "nm: count\n");
1991 
1992  url = url_parse(mailbox_path(m));
1993  if (!url)
1994  {
1995  mutt_error(_("failed to parse notmuch url: %s"), mailbox_path(m));
1996  goto done;
1997  }
1998 
1999  STAILQ_FOREACH(item, &url->query_strings, entries)
2000  {
2001  if (item->value && (strcmp(item->name, "query") == 0))
2002  db_query = item->value;
2003  else if (item->value && (strcmp(item->name, "limit") == 0))
2004  {
2005  // Try to parse the limit
2006  if (mutt_str_atoi(item->value, &limit) != 0)
2007  {
2008  mutt_error(_("failed to parse limit: %s"), item->value);
2009  goto done;
2010  }
2011  }
2012  }
2013 
2014  if (!db_query)
2015  goto done;
2016 
2017  db_filename = url->path;
2018  if (!db_filename)
2019  {
2020  if (C_NmDefaultUrl)
2021  {
2023  db_filename = C_NmDefaultUrl + NmUrlProtocolLen;
2024  else
2025  db_filename = C_NmDefaultUrl;
2026  }
2027  else if (C_Folder)
2028  db_filename = C_Folder;
2029  }
2030 
2031  /* don't be verbose about connection, as we're called from
2032  * sidebar/mailbox very often */
2033  db = nm_db_do_open(db_filename, false, false);
2034  if (!db)
2035  goto done;
2036 
2037  /* all emails */
2038  m->msg_count = count_query(db, db_query, limit);
2039  while (m->email_max < m->msg_count)
2040  mx_alloc_memory(m);
2041 
2042  // holder variable for extending query to unread/flagged
2043  char *qstr = NULL;
2044 
2045  // unread messages
2046  mutt_str_asprintf(&qstr, "( %s ) tag:%s", db_query, C_NmUnreadTag);
2047  m->msg_unread = count_query(db, qstr, limit);
2048  FREE(&qstr);
2049 
2050  // flagged messages
2051  mutt_str_asprintf(&qstr, "( %s ) tag:%s", db_query, C_NmFlaggedTag);
2052  m->msg_flagged = count_query(db, qstr, limit);
2053  FREE(&qstr);
2054 
2055  rc = (m->msg_new > 0);
2056 done:
2057  if (db)
2058  {
2059  nm_db_free(db);
2060  mutt_debug(LL_DEBUG1, "nm: count close DB\n");
2061  }
2062  url_free(&url);
2063 
2064  mutt_debug(LL_DEBUG1, "nm: count done [rc=%d]\n", rc);
2065  return rc;
2066 }
2067 
2076 int nm_record_message(struct Mailbox *m, char *path, struct Email *e)
2077 {
2078  notmuch_database_t *db = NULL;
2079  notmuch_status_t st;
2080  notmuch_message_t *msg = NULL;
2081  int rc = -1;
2082  struct NmMboxData *mdata = nm_mdata_get(m);
2083 
2084  if (!path || !mdata || (access(path, F_OK) != 0))
2085  return 0;
2086  db = nm_db_get(m, true);
2087  if (!db)
2088  return -1;
2089 
2090  mutt_debug(LL_DEBUG1, "nm: record message: %s\n", path);
2091  int trans = nm_db_trans_begin(m);
2092  if (trans < 0)
2093  goto done;
2094 
2095 #ifdef HAVE_NOTMUCH_DATABASE_INDEX_FILE
2096  st = notmuch_database_index_file(db, path, NULL, &msg);
2097 #else
2098  st = notmuch_database_add_message(db, path, &msg);
2099 #endif
2100 
2101  if ((st != NOTMUCH_STATUS_SUCCESS) && (st != NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID))
2102  {
2103  mutt_debug(LL_DEBUG1, "nm: failed to add '%s' [st=%d]\n", path, (int) st);
2104  goto done;
2105  }
2106 
2107  if ((st == NOTMUCH_STATUS_SUCCESS) && msg)
2108  {
2109  notmuch_message_maildir_flags_to_tags(msg);
2110  if (e)
2111  {
2112  char *tags = driver_tags_get(&e->tags);
2113  update_tags(msg, tags);
2114  FREE(&tags);
2115  }
2116  if (C_NmRecordTags)
2118  }
2119 
2120  rc = 0;
2121 done:
2122  if (msg)
2123  notmuch_message_destroy(msg);
2124  if (trans == 1)
2125  nm_db_trans_end(m);
2126 
2127  nm_db_release(m);
2128  return rc;
2129 }
2130 
2141 int nm_get_all_tags(struct Mailbox *m, char **tag_list, int *tag_count)
2142 {
2143  struct NmMboxData *mdata = nm_mdata_get(m);
2144  if (!mdata)
2145  return -1;
2146 
2147  notmuch_database_t *db = NULL;
2148  notmuch_tags_t *tags = NULL;
2149  const char *tag = NULL;
2150  int rc = -1;
2151 
2152  if (!(db = nm_db_get(m, false)) || !(tags = notmuch_database_get_all_tags(db)))
2153  goto done;
2154 
2155  *tag_count = 0;
2156  mutt_debug(LL_DEBUG1, "nm: get all tags\n");
2157 
2158  while (notmuch_tags_valid(tags))
2159  {
2160  tag = notmuch_tags_get(tags);
2161  /* Skip empty string */
2162  if (*tag)
2163  {
2164  if (tag_list)
2165  tag_list[*tag_count] = mutt_str_dup(tag);
2166  (*tag_count)++;
2167  }
2168  notmuch_tags_move_to_next(tags);
2169  }
2170 
2171  rc = 0;
2172 done:
2173  if (tags)
2174  notmuch_tags_destroy(tags);
2175 
2176  nm_db_release(m);
2177 
2178  mutt_debug(LL_DEBUG1, "nm: get all tags done [rc=%d tag_count=%u]\n", rc, *tag_count);
2179  return rc;
2180 }
2181 
2185 static struct Account *nm_ac_find(struct Account *a, const char *path)
2186 {
2187  return a;
2188 }
2189 
2193 static int nm_ac_add(struct Account *a, struct Mailbox *m)
2194 {
2195  if (a->adata)
2196  return 0;
2197 
2198  struct NmAccountData *adata = nm_adata_new();
2199  a->adata = adata;
2201 
2202  return 0;
2203 }
2204 
2208 static int nm_mbox_open(struct Mailbox *m)
2209 {
2210  if (init_mailbox(m) != 0)
2211  return -1;
2212 
2213  struct NmMboxData *mdata = nm_mdata_get(m);
2214  if (!mdata)
2215  return -1;
2216 
2217  mutt_debug(LL_DEBUG1, "nm: reading messages...[current count=%d]\n", m->msg_count);
2218 
2219  progress_reset(m);
2220 
2221  int rc = -1;
2222 
2223  notmuch_query_t *q = get_query(m, false);
2224  if (q)
2225  {
2226  rc = 0;
2227  switch (mdata->query_type)
2228  {
2229  case NM_QUERY_TYPE_MESGS:
2230  if (!read_mesgs_query(m, q, false))
2231  rc = -2;
2232  break;
2233  case NM_QUERY_TYPE_THREADS:
2234  if (!read_threads_query(m, q, false, get_limit(mdata)))
2235  rc = -2;
2236  break;
2237  }
2238  notmuch_query_destroy(q);
2239  }
2240 
2241  nm_db_release(m);
2242 
2243  m->mtime.tv_sec = mutt_date_epoch();
2244  m->mtime.tv_nsec = 0;
2245 
2246  mdata->oldmsgcount = 0;
2247 
2248  mutt_debug(LL_DEBUG1, "nm: reading messages... done [rc=%d, count=%d]\n", rc, m->msg_count);
2249  return rc;
2250 }
2251 
2261 static int nm_mbox_check(struct Mailbox *m)
2262 {
2263  struct NmMboxData *mdata = nm_mdata_get(m);
2264  time_t mtime = 0;
2265  if (!mdata || (nm_db_get_mtime(m, &mtime) != 0))
2266  return -1;
2267 
2268  int new_flags = 0;
2269  bool occult = false;
2270 
2271  if (m->mtime.tv_sec >= mtime)
2272  {
2273  mutt_debug(LL_DEBUG2, "nm: check unnecessary (db=%lu mailbox=%lu)\n", mtime,
2274  m->mtime.tv_sec);
2275  return 0;
2276  }
2277 
2278  mutt_debug(LL_DEBUG1, "nm: checking (db=%lu mailbox=%lu)\n", mtime, m->mtime.tv_sec);
2279 
2280  notmuch_query_t *q = get_query(m, false);
2281  if (!q)
2282  goto done;
2283 
2284  mutt_debug(LL_DEBUG1, "nm: start checking (count=%d)\n", m->msg_count);
2285  mdata->oldmsgcount = m->msg_count;
2286  mdata->noprogress = true;
2287 
2288  for (int i = 0; i < m->msg_count; i++)
2289  {
2290  struct Email *e = m->emails[i];
2291  if (!e)
2292  break;
2293 
2294  e->active = false;
2295  }
2296 
2297  int limit = get_limit(mdata);
2298 
2299  notmuch_messages_t *msgs = get_messages(q);
2300 
2301  // TODO: Analyze impact of removing this version guard.
2302 #if LIBNOTMUCH_CHECK_VERSION(5, 0, 0)
2303  if (!msgs)
2304  return false;
2305 #elif LIBNOTMUCH_CHECK_VERSION(4, 3, 0)
2306  if (!msgs)
2307  goto done;
2308 #endif
2309 
2310  struct HeaderCache *h = nm_hcache_open(m);
2311 
2312  for (int i = 0; notmuch_messages_valid(msgs) && ((limit == 0) || (i < limit));
2313  notmuch_messages_move_to_next(msgs), i++)
2314  {
2315  notmuch_message_t *msg = notmuch_messages_get(msgs);
2316  struct Email *e = get_mutt_email(m, msg);
2317 
2318  if (!e)
2319  {
2320  /* new email */
2321  append_message(h, m, NULL, msg, false);
2322  notmuch_message_destroy(msg);
2323  continue;
2324  }
2325 
2326  /* message already exists, merge flags */
2327  e->active = true;
2328 
2329  /* Check to see if the message has moved to a different subdirectory.
2330  * If so, update the associated filename. */
2331  const char *new_file = get_message_last_filename(msg);
2332  char old_file[PATH_MAX];
2333  email_get_fullpath(e, old_file, sizeof(old_file));
2334 
2335  if (!mutt_str_equal(old_file, new_file))
2336  update_message_path(e, new_file);
2337 
2338  if (!e->changed)
2339  {
2340  /* if the user hasn't modified the flags on this message, update the
2341  * flags we just detected. */
2342  struct Email e_tmp = { 0 };
2343  e_tmp.edata = maildir_edata_new();
2344  maildir_parse_flags(&e_tmp, new_file);
2345  maildir_update_flags(m, e, &e_tmp);
2346  maildir_edata_free(&e_tmp.edata);
2347  }
2348 
2349  if (update_email_tags(e, msg) == 0)
2350  new_flags++;
2351 
2352  notmuch_message_destroy(msg);
2353  }
2354 
2355  nm_hcache_close(h);
2356 
2357  for (int i = 0; i < m->msg_count; i++)
2358  {
2359  struct Email *e = m->emails[i];
2360  if (!e)
2361  break;
2362 
2363  if (!e->active)
2364  {
2365  occult = true;
2366  break;
2367  }
2368  }
2369 
2370  if (m->msg_count > mdata->oldmsgcount)
2372 done:
2373  if (q)
2374  notmuch_query_destroy(q);
2375 
2376  nm_db_release(m);
2377 
2378  m->mtime.tv_sec = mutt_date_epoch();
2379  m->mtime.tv_nsec = 0;
2380 
2381  mutt_debug(LL_DEBUG1, "nm: ... check done [count=%d, new_flags=%d, occult=%d]\n",
2382  m->msg_count, new_flags, occult);
2383 
2384  return occult ? MUTT_REOPENED :
2385  (m->msg_count > mdata->oldmsgcount) ? MUTT_NEW_MAIL :
2386  new_flags ? MUTT_FLAGS : 0;
2387 }
2388 
2392 static int nm_mbox_sync(struct Mailbox *m)
2393 {
2394  struct NmMboxData *mdata = nm_mdata_get(m);
2395  if (!mdata)
2396  return -1;
2397 
2398  int rc = 0;
2399  struct Progress progress;
2400  char *url = mutt_str_dup(mailbox_path(m));
2401  bool changed = false;
2402 
2403  mutt_debug(LL_DEBUG1, "nm: sync start\n");
2404 
2405  if (m->verbose)
2406  {
2407  /* all is in this function so we don't use data->progress here */
2408  char msg[PATH_MAX];
2409  snprintf(msg, sizeof(msg), _("Writing %s..."), mailbox_path(m));
2410  mutt_progress_init(&progress, msg, MUTT_PROGRESS_WRITE, m->msg_count);
2411  }
2412 
2413  struct HeaderCache *h = nm_hcache_open(m);
2414 
2415  int mh_sync_errors = 0;
2416  for (int i = 0; i < m->msg_count; i++)
2417  {
2418  char old_file[PATH_MAX], new_file[PATH_MAX];
2419  struct Email *e = m->emails[i];
2420  if (!e)
2421  break;
2422 
2423  struct NmEmailData *edata = nm_edata_get(e);
2424 
2425  if (m->verbose)
2426  mutt_progress_update(&progress, i, -1);
2427 
2428  *old_file = '\0';
2429  *new_file = '\0';
2430 
2431  if (edata->oldpath)
2432  {
2433  mutt_str_copy(old_file, edata->oldpath, sizeof(old_file));
2434  old_file[sizeof(old_file) - 1] = '\0';
2435  mutt_debug(LL_DEBUG2, "nm: fixing obsolete path '%s'\n", old_file);
2436  }
2437  else
2438  email_get_fullpath(e, old_file, sizeof(old_file));
2439 
2440  mutt_buffer_strcpy(&m->pathbuf, edata->folder);
2441  m->type = edata->type;
2442  rc = maildir_sync_mailbox_message(m, i, h);
2443 
2444  // Syncing file failed, query notmuch for new filepath.
2445  if (rc)
2446  {
2447  notmuch_database_t *db = nm_db_get(m, true);
2448  if (db)
2449  {
2450  notmuch_message_t *msg = get_nm_message(db, e);
2451 
2452  sync_email_path_with_nm(e, msg);
2453 
2454  rc = maildir_sync_mailbox_message(m, i, h);
2455  }
2456  nm_db_release(m);
2457  }
2458 
2459  mutt_buffer_strcpy(&m->pathbuf, url);
2460  m->type = MUTT_NOTMUCH;
2461 
2462  if (rc)
2463  {
2464  mh_sync_errors += 1;
2465  continue;
2466  }
2467 
2468  if (!e->deleted)
2469  email_get_fullpath(e, new_file, sizeof(new_file));
2470 
2471  if (e->deleted || (strcmp(old_file, new_file) != 0))
2472  {
2473  if (e->deleted && (remove_filename(m, old_file) == 0))
2474  changed = true;
2475  else if (*new_file && *old_file && (rename_filename(m, old_file, new_file, e) == 0))
2476  changed = true;
2477  }
2478 
2479  FREE(&edata->oldpath);
2480  }
2481 
2482  if (mh_sync_errors > 0)
2483  {
2484  mutt_error(
2485  ngettext(
2486  "Unable to sync %d message due to external mailbox modification",
2487  "Unable to sync %d messages due to external mailbox modification", mh_sync_errors),
2488  mh_sync_errors);
2489  }
2490 
2491  mutt_buffer_strcpy(&m->pathbuf, url);
2492  m->type = MUTT_NOTMUCH;
2493 
2494  nm_db_release(m);
2495 
2496  if (changed)
2497  {
2498  m->mtime.tv_sec = mutt_date_epoch();
2499  m->mtime.tv_nsec = 0;
2500  }
2501 
2502  nm_hcache_close(h);
2503 
2504  FREE(&url);
2505  mutt_debug(LL_DEBUG1, "nm: .... sync done [rc=%d]\n", rc);
2506  return rc;
2507 }
2508 
2514 static int nm_mbox_close(struct Mailbox *m)
2515 {
2516  return 0;
2517 }
2518 
2522 static int nm_msg_open(struct Mailbox *m, struct Message *msg, int msgno)
2523 {
2524  struct Email *e = m->emails[msgno];
2525  if (!e)
2526  return -1;
2527 
2528  char path[PATH_MAX];
2529  char *folder = nm_email_get_folder(e);
2530 
2531  snprintf(path, sizeof(path), "%s/%s", folder, e->path);
2532 
2533  msg->fp = fopen(path, "r");
2534  if (!msg->fp && (errno == ENOENT) && ((m->type == MUTT_MAILDIR) || (m->type == MUTT_NOTMUCH)))
2535  {
2536  msg->fp = maildir_open_find_message(folder, e->path, NULL);
2537  }
2538 
2539  if (!msg->fp)
2540  return -1;
2541 
2542  return 0;
2543 }
2544 
2549 static int nm_msg_commit(struct Mailbox *m, struct Message *msg)
2550 {
2551  mutt_error(_("Can't write to virtual folder"));
2552  return -1;
2553 }
2554 
2558 static int nm_msg_close(struct Mailbox *m, struct Message *msg)
2559 {
2560  mutt_file_fclose(&(msg->fp));
2561  return 0;
2562 }
2563 
2567 static int nm_tags_edit(struct Mailbox *m, const char *tags, char *buf, size_t buflen)
2568 {
2569  *buf = '\0';
2570  if (mutt_get_field("Add/remove labels: ", buf, buflen, MUTT_NM_TAG) != 0)
2571  return -1;
2572  return 1;
2573 }
2574 
2578 static int nm_tags_commit(struct Mailbox *m, struct Email *e, char *buf)
2579 {
2580  if (*buf == '\0')
2581  return 0; /* no tag change, so nothing to do */
2582 
2583  struct NmMboxData *mdata = nm_mdata_get(m);
2584  if (!mdata)
2585  return -1;
2586 
2587  notmuch_database_t *db = NULL;
2588  notmuch_message_t *msg = NULL;
2589  int rc = -1;
2590 
2591  if (!(db = nm_db_get(m, true)) || !(msg = get_nm_message(db, e)))
2592  goto done;
2593 
2594  mutt_debug(LL_DEBUG1, "nm: tags modify: '%s'\n", buf);
2595 
2596  update_tags(msg, buf);
2597  update_email_flags(m, e, buf);
2598  update_email_tags(e, msg);
2599  mutt_set_header_color(m, e);
2600 
2601  rc = 0;
2602  e->changed = true;
2603 done:
2604  nm_db_release(m);
2605  if (e->changed)
2606  {
2607  m->mtime.tv_sec = mutt_date_epoch();
2608  m->mtime.tv_nsec = 0;
2609  }
2610  mutt_debug(LL_DEBUG1, "nm: tags modify done [rc=%d]\n", rc);
2611  return rc;
2612 }
2613 
2617 enum MailboxType nm_path_probe(const char *path, const struct stat *st)
2618 {
2619  if (!mutt_istr_startswith(path, NmUrlProtocol))
2620  return MUTT_UNKNOWN;
2621 
2622  return MUTT_NOTMUCH;
2623 }
2624 
2628 static int nm_path_canon(char *buf, size_t buflen)
2629 {
2630  return 0;
2631 }
2632 
2636 static int nm_path_pretty(char *buf, size_t buflen, const char *folder)
2637 {
2638  /* Succeed, but don't do anything, for now */
2639  return 0;
2640 }
2641 
2645 static int nm_path_parent(char *buf, size_t buflen)
2646 {
2647  /* Succeed, but don't do anything, for now */
2648  return 0;
2649 }
2650 
2651 // clang-format off
2655 struct MxOps MxNotmuchOps = {
2656  .type = MUTT_NOTMUCH,
2657  .name = "notmuch",
2658  .is_local = false,
2659  .ac_find = nm_ac_find,
2660  .ac_add = nm_ac_add,
2661  .mbox_open = nm_mbox_open,
2662  .mbox_open_append = NULL,
2663  .mbox_check = nm_mbox_check,
2664  .mbox_check_stats = nm_mbox_check_stats,
2665  .mbox_sync = nm_mbox_sync,
2666  .mbox_close = nm_mbox_close,
2667  .msg_open = nm_msg_open,
2668  .msg_open_new = maildir_msg_open_new,
2669  .msg_commit = nm_msg_commit,
2670  .msg_close = nm_msg_close,
2671  .msg_padding_size = NULL,
2672  .msg_save_hcache = NULL,
2673  .tags_edit = nm_tags_edit,
2674  .tags_commit = nm_tags_commit,
2675  .path_probe = nm_path_probe,
2676  .path_canon = nm_path_canon,
2677  .path_pretty = nm_path_pretty,
2678  .path_parent = nm_path_parent,
2679  .path_is_empty = NULL,
2680 };
2681 // 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:621
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:203
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:1474
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:123
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:123
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:191
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: maildir.c:908
char * nm_email_get_folder_rel_db(struct Mailbox *m, struct Email *e)
Get the folder for a Email from the same level as the notmuch database.
Definition: notmuch.c:1688
static void progress_reset(struct Mailbox *m)
Reset the progress counter.
Definition: notmuch.c:848
static notmuch_threads_t * get_threads(notmuch_query_t *query)
load threads for a query
Definition: notmuch.c:1162
static int nm_path_parent(char *buf, size_t buflen)
Find the parent of a Mailbox path - Implements MxOps::path_parent()
Definition: notmuch.c:2645
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 $write_inc
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:1284
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:453
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:1708
header cache structure
Definition: lib.h:85
struct timespec mtime
Time Mailbox was last changed.
Definition: mailbox.h:107
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:1230
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
#define MUTT_NAMED
Definition: mutt_commands.h:74
A user-callable command.
Definition: mutt_commands.h:45
static int nm_mbox_sync(struct Mailbox *m)
Save changes to the Mailbox - Implements MxOps::mbox_sync()
Definition: notmuch.c:2392
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:787
struct NmAccountData * nm_adata_new(void)
Allocate and initialise a new NmAccountData structure.
Definition: notmuch.c:156
char * value
Query value.
Definition: url.h:58
Email list was changed.
Definition: mailbox.h:173
void url_free(struct Url **ptr)
Free the contents of a URL.
Definition: url.c:123
static int update_message_path(struct Email *e, const char *path)
Set the path for a message.
Definition: notmuch.c:703
static bool query_window_check_timebase(const char *timebase)
Checks if a given timebase string is valid.
Definition: notmuch.c:389
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:2514
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 COMMANDS_REGISTER(cmds)
Definition: mutt_commands.h:77
#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:2558
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:2522
static const char * get_message_last_filename(notmuch_message_t *msg)
Get a message&#39;s last filename.
Definition: notmuch.c:831
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: maildir.c:796
bool nm_message_is_still_queried(struct Mailbox *m, struct Email *e)
Is a message still visible in the query?
Definition: notmuch.c:1896
int nm_get_all_tags(struct Mailbox *m, char **tag_list, int *tag_count)
Fill a list with all notmuch tags.
Definition: notmuch.c:2141
struct NmEmailData * nm_edata_get(struct Email *e)
Get the Notmuch Email data.
Definition: notmuch.c:281
int nm_record_message(struct Mailbox *m, char *path, struct Email *e)
Add a message to the Notmuch database.
Definition: notmuch.c:2076
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:169
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:571
API for mailboxes.
Notmuch-specific Mailbox data -.
Definition: private.h:69
void nm_init(void)
Setup feature commands.
Definition: notmuch.c:88
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:343
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:1353
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: maildir.c:974
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.
enum CommandResult parse_mailboxes(struct Buffer *buf, struct Buffer *s, intptr_t data, struct Buffer *err)
Parse the &#39;mailboxes&#39; command - Implements Command::parse()
Progress tracks elements, according to $read_inc
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:1982
static notmuch_messages_t * get_messages(notmuch_query_t *query)
load messages for a query
Definition: notmuch.c:1092
void nm_query_window_backward(void)
Function to move the current search window backward in time.
Definition: notmuch.c:1884
int maildir_sync_mailbox_message(struct Mailbox *m, int msgno, struct HeaderCache *hc)
Save changes to the mailbox.
Definition: maildir.c:928
Progress bar.
static const char * query_type_to_string(enum NmQueryType query_type)
Turn a query type into a string.
Definition: notmuch.c:372
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:251
static int rename_filename(struct Mailbox *m, const char *old_file, const char *new_file, struct Email *e)
Rename the file.
Definition: notmuch.c:1532
static notmuch_message_t * get_nm_message(notmuch_database_t *db, struct Email *e)
Find a Notmuch message.
Definition: notmuch.c:1226
void nm_parse_type_from_query(struct NmMboxData *mdata, char *buf)
Parse a query type out of a query.
Definition: notmuch.c:1776
Prototypes for many functions.
void nm_query_window_forward(void)
Function to move the current search window forward in time.
Definition: notmuch.c:1868
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:1807
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: maildir.c:864
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
Functions to parse commands in a config file.
static int nm_ac_add(struct Account *a, struct Mailbox *m)
Add a Mailbox to an Account - Implements MxOps::ac_add()
Definition: notmuch.c:2193
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:1669
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:98
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:321
char * driver_tags_get(struct TagList *list)
Get tags.
Definition: tags.c:142
void maildir_edata_free(void **ptr)
Free the private Email data - Implements Email::edata_free()
Definition: edata.c:38
static int nm_path_canon(char *buf, size_t buflen)
Canonicalise a Mailbox path - Implements MxOps::path_canon()
Definition: notmuch.c:2628
static char * get_query_string(struct NmMboxData *mdata, bool window)
builds the notmuch vfolder search string
Definition: notmuch.c:513
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:1068
Messages that have been read.
Definition: mutt.h:96
Definitions of NeoMutt commands.
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:1636
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:2208
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:2185
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:1267
struct MaildirEmailData * maildir_edata_new(void)
Create a new MaildirEmailData object.
Definition: edata.c:53
#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:111
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:2549
const char NmUrlProtocol[]
Definition: notmuch.c:82
void mutt_set_header_color(struct Mailbox *m, struct Email *e)
Select a colour for a message.
Definition: index.c:3956
void maildir_gen_flags(char *dest, size_t destlen, struct Email *e)
Generate the Maildir flags for an email.
Definition: maildir.c:178
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:236
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:1120
struct NmMboxData * nm_mdata_new(const char *url)
Create a new NmMboxData object from a query.
Definition: notmuch.c:210
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:2578
#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:1245
static char * email_get_fullpath(struct Email *e, char *buf, size_t buflen)
Get the full path of an email.
Definition: notmuch.c:359
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:270
static char * get_folder_from_path(const char *path)
Find an email&#39;s folder from its path.
Definition: notmuch.c:740
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:932
static int nm_path_pretty(char *buf, size_t buflen, const char *folder)
Abbreviate a Mailbox path - Implements MxOps::path_pretty()
Definition: notmuch.c:2636
#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:653
static struct Email * get_mutt_email(struct Mailbox *m, notmuch_message_t *msg)
Get the Email of a Notmuch message.
Definition: notmuch.c:897
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:765
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:2567
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:1041
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:83
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:1468
void nm_mdata_free(void **ptr)
Free the private Mailbox data - Implements Mailbox::mdata_free()
Definition: notmuch.c:188
enum CommandResult parse_unmailboxes(struct Buffer *buf, struct Buffer *s, intptr_t data, struct Buffer *err)
Parse the &#39;unmailboxes&#39; command - Implements Command::parse()
static void query_window_reset(void)
Restore vfolder&#39;s search window to its original position.
Definition: notmuch.c:409
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:294
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:137
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:580
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:1957
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:177
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:2617
static bool read_threads_query(struct Mailbox *m, notmuch_query_t *q, bool dedup, int limit)
Perform a query with threads.
Definition: notmuch.c:1190
static int rename_maildir_filename(const char *old, char *buf, size_t buflen, struct Email *e)
Rename a Maildir file.
Definition: notmuch.c:1415
static void progress_update(struct Mailbox *m, notmuch_query_t *q)
Update the progress counter.
Definition: notmuch.c:869
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:2261
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