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