NeoMutt  2022-04-29-145-g9b6a0e
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 "mutt.h"
62 #include "lib.h"
63 #include "enter/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 = cs_subset_path(NeoMutt->sub, "header_cache");
113  return mutt_hcache_open(c_header_cache, mailbox_path(m), NULL);
114 #else
115  return NULL;
116 #endif
117 }
118 
123 static void nm_hcache_close(struct HeaderCache *h)
124 {
125 #ifdef USE_HCACHE
127 #endif
128 }
129 
135 static char *nm_get_default_url(void)
136 {
137  // path to DB + query + url "decoration"
138  size_t len = PATH_MAX + 1024 + 32;
139  char *url = mutt_mem_malloc(len);
140 
141  // Try to use `$nm_default_url` or `$folder`.
142  // If neither are set, it is impossible to create a Notmuch URL.
143  const char *const c_nm_default_url = cs_subset_string(NeoMutt->sub, "nm_default_url");
144  const char *const c_folder = cs_subset_string(NeoMutt->sub, "folder");
145  if (c_nm_default_url)
146  snprintf(url, len, "%s", c_nm_default_url);
147  else if (c_folder)
148  snprintf(url, len, "notmuch://%s", c_folder);
149  else
150  {
151  FREE(&url);
152  return NULL;
153  }
154 
155  return url;
156 }
157 
163 static struct NmMboxData *nm_get_default_data(void)
164 {
165  // path to DB + query + url "decoration"
166  char *url = nm_get_default_url();
167  if (!url)
168  return NULL;
169 
170  struct NmMboxData *default_data = nm_mdata_new(url);
171  FREE(&url);
172 
173  return default_data;
174 }
175 
186 static int init_mailbox(struct Mailbox *m)
187 {
188  if (!m || (m->type != MUTT_NOTMUCH))
189  return -1;
190 
191  if (m->mdata)
192  return 0;
193 
195  if (!m->mdata)
196  return -1;
197 
199  return 0;
200 }
201 
208 static char *email_get_id(struct Email *e)
209 {
210  struct NmEmailData *edata = nm_edata_get(e);
211  if (!edata)
212  return NULL;
213 
214  return edata->virtual_id;
215 }
216 
224 static char *email_get_fullpath(struct Email *e, char *buf, size_t buflen)
225 {
226  snprintf(buf, buflen, "%s/%s", nm_email_get_folder(e), e->path);
227  return buf;
228 }
229 
239 static void query_window_reset(void)
240 {
241  mutt_debug(LL_DEBUG2, "entering\n");
242  cs_subset_str_native_set(NeoMutt->sub, "nm_query_window_current_position", 0, NULL);
243 }
244 
269 static bool windowed_query_from_query(const char *query, char *buf, size_t buflen)
270 {
271  mutt_debug(LL_DEBUG2, "nm: %s\n", query);
272 
273  const bool c_nm_query_window_enable = cs_subset_bool(NeoMutt->sub, "nm_query_window_enable");
274  const short c_nm_query_window_duration = cs_subset_number(NeoMutt->sub, "nm_query_window_duration");
275  const short c_nm_query_window_current_position = cs_subset_number(NeoMutt->sub, "nm_query_window_current_position");
276  const char *const c_nm_query_window_current_search = cs_subset_string(NeoMutt->sub, "nm_query_window_current_search");
277  const char *const c_nm_query_window_timebase = cs_subset_string(NeoMutt->sub, "nm_query_window_timebase");
278  const char *const c_nm_query_window_or_terms = cs_subset_string(NeoMutt->sub, "nm_query_window_or_terms");
279 
280  /* if the query has changed, reset the window position */
281  if (!c_nm_query_window_current_search ||
282  (strcmp(query, c_nm_query_window_current_search) != 0))
283  {
285  }
286 
288  buf, buflen, c_nm_query_window_enable, c_nm_query_window_duration,
289  c_nm_query_window_current_position, c_nm_query_window_current_search,
290  c_nm_query_window_timebase, c_nm_query_window_or_terms);
291 
292  switch (rc)
293  {
295  {
296  mutt_debug(LL_DEBUG2, "nm: %s -> %s\n", query, buf);
297  break;
298  }
300  {
302  return false;
303  }
305  {
306  mutt_message(
307  // L10N: The values 'hour', 'day', 'week', 'month', 'year' are literal.
308  // They should not be translated.
309  _("Invalid nm_query_window_timebase value (valid values are: hour, day, week, month, year)"));
310  mutt_debug(LL_DEBUG2, "Invalid nm_query_window_timebase value\n");
311  return false;
312  }
313  }
314 
315  return true;
316 }
317 
334 static char *get_query_string(struct NmMboxData *mdata, bool window)
335 {
336  mutt_debug(LL_DEBUG2, "nm: %s\n", window ? "true" : "false");
337 
338  if (!mdata)
339  return NULL;
340  if (mdata->db_query && !window)
341  return mdata->db_query;
342 
343  const char *const c_nm_query_type = cs_subset_string(NeoMutt->sub, "nm_query_type");
344  mdata->query_type = nm_string_to_query_type(c_nm_query_type); /* user's default */
345 
346  struct UrlQuery *item = NULL;
347  STAILQ_FOREACH(item, &mdata->db_url->query_strings, entries)
348  {
349  if (!item->value || !item->name)
350  continue;
351 
352  if (strcmp(item->name, "limit") == 0)
353  {
354  if (!mutt_str_atoi_full(item->value, &mdata->db_limit))
355  {
356  mutt_error(_("failed to parse notmuch limit: %s"), item->value);
357  }
358  }
359  else if (strcmp(item->name, "type") == 0)
360  mdata->query_type = nm_string_to_query_type(item->value);
361  else if (strcmp(item->name, "query") == 0)
362  mutt_str_replace(&mdata->db_query, item->value);
363  }
364 
365  if (!mdata->db_query)
366  return NULL;
367 
368  if (window)
369  {
370  char buf[1024];
371  cs_subset_str_string_set(NeoMutt->sub, "nm_query_window_current_search",
372  mdata->db_query, NULL);
373 
374  /* if a date part is defined, do not apply windows (to avoid the risk of
375  * having a non-intersected date frame). A good improvement would be to
376  * accept if they intersect */
377  if (!strstr(mdata->db_query, "date:") &&
378  windowed_query_from_query(mdata->db_query, buf, sizeof(buf)))
379  {
380  mutt_str_replace(&mdata->db_query, buf);
381  }
382 
383  mutt_debug(LL_DEBUG2, "nm: query (windowed) '%s'\n", mdata->db_query);
384  }
385  else
386  mutt_debug(LL_DEBUG2, "nm: query '%s'\n", mdata->db_query);
387 
388  return mdata->db_query;
389 }
390 
396 static int get_limit(struct NmMboxData *mdata)
397 {
398  return mdata ? mdata->db_limit : 0;
399 }
400 
405 static void apply_exclude_tags(notmuch_query_t *query)
406 {
407  const char *const c_nm_exclude_tags = cs_subset_string(NeoMutt->sub, "nm_exclude_tags");
408  if (!c_nm_exclude_tags || !query)
409  return;
410 
411  struct TagArray tags = nm_tag_str_to_tags(c_nm_exclude_tags);
412 
413  char **tag = NULL;
414  ARRAY_FOREACH(tag, &tags.tags)
415  {
416  mutt_debug(LL_DEBUG2, "nm: query exclude tag '%s'\n", *tag);
417  notmuch_query_add_tag_exclude(query, *tag);
418  }
419 
420  notmuch_query_set_omit_excluded(query, 1);
422 }
423 
431 static notmuch_query_t *get_query(struct Mailbox *m, bool writable)
432 {
433  struct NmMboxData *mdata = nm_mdata_get(m);
434  if (!mdata)
435  return NULL;
436 
437  notmuch_database_t *db = nm_db_get(m, writable);
438  const char *str = get_query_string(mdata, true);
439 
440  if (!db || !str)
441  goto err;
442 
443  notmuch_query_t *q = notmuch_query_create(db, str);
444  if (!q)
445  goto err;
446 
448  notmuch_query_set_sort(q, NOTMUCH_SORT_NEWEST_FIRST);
449  mutt_debug(LL_DEBUG2, "nm: query successfully initialized (%s)\n", str);
450  return q;
451 err:
452  nm_db_release(m);
453  return NULL;
454 }
455 
463 static int update_email_tags(struct Email *e, notmuch_message_t *msg)
464 {
465  struct NmEmailData *edata = nm_edata_get(e);
466  char *new_tags = NULL;
467  char *old_tags = NULL;
468 
469  mutt_debug(LL_DEBUG2, "nm: tags update requested (%s)\n", edata->virtual_id);
470 
471  for (notmuch_tags_t *tags = notmuch_message_get_tags(msg);
472  tags && notmuch_tags_valid(tags); notmuch_tags_move_to_next(tags))
473  {
474  const char *t = notmuch_tags_get(tags);
475  if (!t || (*t == '\0'))
476  continue;
477 
478  mutt_str_append_item(&new_tags, t, ' ');
479  }
480 
481  old_tags = driver_tags_get(&e->tags);
482 
483  if (new_tags && old_tags && (strcmp(old_tags, new_tags) == 0))
484  {
485  FREE(&old_tags);
486  FREE(&new_tags);
487  mutt_debug(LL_DEBUG2, "nm: tags unchanged\n");
488  return 1;
489  }
490  FREE(&old_tags);
491 
492  /* new version */
493  driver_tags_replace(&e->tags, new_tags);
494  FREE(&new_tags);
495 
496  new_tags = driver_tags_get_transformed(&e->tags);
497  mutt_debug(LL_DEBUG2, "nm: new tags: '%s'\n", new_tags);
498  FREE(&new_tags);
499 
500  new_tags = driver_tags_get(&e->tags);
501  mutt_debug(LL_DEBUG2, "nm: new tag transforms: '%s'\n", new_tags);
502  FREE(&new_tags);
503 
504  return 0;
505 }
506 
514 static int update_message_path(struct Email *e, const char *path)
515 {
516  struct NmEmailData *edata = nm_edata_get(e);
517 
518  mutt_debug(LL_DEBUG2, "nm: path update requested path=%s, (%s)\n", path, edata->virtual_id);
519 
520  char *p = strrchr(path, '/');
521  if (p && ((p - path) > 3) &&
522  (mutt_strn_equal(p - 3, "cur", 3) || mutt_strn_equal(p - 3, "new", 3) ||
523  mutt_strn_equal(p - 3, "tmp", 3)))
524  {
525  edata->type = MUTT_MAILDIR;
526 
527  FREE(&e->path);
528  FREE(&edata->folder);
529 
530  p -= 3; /* skip subfolder (e.g. "new") */
531  if (cs_subset_bool(NeoMutt->sub, "mark_old"))
532  {
533  e->old = mutt_str_startswith(p, "cur");
534  }
535  e->path = mutt_str_dup(p);
536 
537  for (; (p > path) && (*(p - 1) == '/'); p--)
538  ; // do nothing
539 
540  edata->folder = mutt_strn_dup(path, p - path);
541 
542  mutt_debug(LL_DEBUG2, "nm: folder='%s', file='%s'\n", edata->folder, e->path);
543  return 0;
544  }
545 
546  return 1;
547 }
548 
555 static char *get_folder_from_path(const char *path)
556 {
557  char *p = strrchr(path, '/');
558 
559  if (p && ((p - path) > 3) &&
560  (mutt_strn_equal(p - 3, "cur", 3) || mutt_strn_equal(p - 3, "new", 3) ||
561  mutt_strn_equal(p - 3, "tmp", 3)))
562  {
563  p -= 3;
564  for (; (p > path) && (*(p - 1) == '/'); p--)
565  ; // do nothing
566 
567  return mutt_strn_dup(path, p - path);
568  }
569 
570  return NULL;
571 }
572 
580 static char *nm2mutt_message_id(const char *id)
581 {
582  size_t sz;
583  char *mid = NULL;
584 
585  if (!id)
586  return NULL;
587  sz = strlen(id) + 3;
588  mid = mutt_mem_malloc(sz);
589 
590  snprintf(mid, sz, "<%s>", id);
591  return mid;
592 }
593 
602 static int init_email(struct Email *e, const char *path, notmuch_message_t *msg)
603 {
604  if (nm_edata_get(e))
605  return 0;
606 
607  struct NmEmailData *edata = nm_edata_new();
608  e->nm_edata = edata;
609 
610  /* Notmuch ensures that message Id exists (if not notmuch Notmuch will
611  * generate an ID), so it's more safe than use neomutt Email->env->id */
612  const char *id = notmuch_message_get_message_id(msg);
613  edata->virtual_id = mutt_str_dup(id);
614 
615  mutt_debug(LL_DEBUG2, "nm: [e=%p, edata=%p] (%s)\n", (void *) e, (void *) edata, id);
616 
617  char *nm_msg_id = nm2mutt_message_id(id);
618  if (!e->env->message_id)
619  {
620  e->env->message_id = nm_msg_id;
621  }
622  else if (!mutt_str_equal(e->env->message_id, nm_msg_id))
623  {
624  FREE(&e->env->message_id);
625  e->env->message_id = nm_msg_id;
626  }
627  else
628  {
629  FREE(&nm_msg_id);
630  }
631 
632  if (update_message_path(e, path) != 0)
633  return -1;
634 
635  update_email_tags(e, msg);
636 
637  return 0;
638 }
639 
646 static const char *get_message_last_filename(notmuch_message_t *msg)
647 {
648  const char *name = NULL;
649 
650  for (notmuch_filenames_t *ls = notmuch_message_get_filenames(msg);
651  ls && notmuch_filenames_valid(ls); notmuch_filenames_move_to_next(ls))
652  {
653  name = notmuch_filenames_get(ls);
654  }
655 
656  return name;
657 }
658 
663 static void progress_setup(struct Mailbox *m)
664 {
665  if (!m->verbose)
666  return;
667 
668  struct NmMboxData *mdata = nm_mdata_get(m);
669  if (!mdata)
670  return;
671 
672  mdata->oldmsgcount = m->msg_count;
673  mdata->ignmsgcount = 0;
674  mdata->progress = progress_new(_("Reading messages..."), MUTT_PROGRESS_READ,
675  mdata->oldmsgcount);
676 }
677 
682 static void nm_progress_update(struct Mailbox *m)
683 {
684  struct NmMboxData *mdata = nm_mdata_get(m);
685 
686  if (!m->verbose || !mdata || !mdata->progress)
687  return;
688 
689  progress_update(mdata->progress, m->msg_count + mdata->ignmsgcount, -1);
690 }
691 
699 static struct Email *get_mutt_email(struct Mailbox *m, notmuch_message_t *msg)
700 {
701  if (!m || !msg)
702  return NULL;
703 
704  const char *id = notmuch_message_get_message_id(msg);
705  if (!id)
706  return NULL;
707 
708  mutt_debug(LL_DEBUG2, "nm: neomutt email, id='%s'\n", id);
709 
710  if (!m->id_hash)
711  {
712  mutt_debug(LL_DEBUG2, "nm: init hash\n");
713  m->id_hash = mutt_make_id_hash(m);
714  if (!m->id_hash)
715  return NULL;
716  }
717 
718  char *mid = nm2mutt_message_id(id);
719  mutt_debug(LL_DEBUG2, "nm: neomutt id='%s'\n", mid);
720 
721  struct Email *e = mutt_hash_find(m->id_hash, mid);
722  FREE(&mid);
723  return e;
724 }
725 
733 static void append_message(struct HeaderCache *h, struct Mailbox *m,
734  notmuch_message_t *msg, bool dedup)
735 {
736  struct NmMboxData *mdata = nm_mdata_get(m);
737  if (!mdata)
738  return;
739 
740  char *newpath = NULL;
741  struct Email *e = NULL;
742 
743  /* deduplicate */
744  if (dedup && get_mutt_email(m, msg))
745  {
746  mdata->ignmsgcount++;
748  mutt_debug(LL_DEBUG2, "nm: ignore id=%s, already in the m\n",
749  notmuch_message_get_message_id(msg));
750  return;
751  }
752 
753  const char *path = get_message_last_filename(msg);
754  if (!path)
755  return;
756 
757  mutt_debug(LL_DEBUG2, "nm: appending message, i=%d, id=%s, path=%s\n",
758  m->msg_count, notmuch_message_get_message_id(msg), path);
759 
760  if (m->msg_count >= m->email_max)
761  {
762  mutt_debug(LL_DEBUG2, "nm: allocate mx memory\n");
763  mx_alloc_memory(m);
764  }
765 
766 #ifdef USE_HCACHE
768  if (!e)
769 #endif
770  {
771  if (access(path, F_OK) == 0)
772  {
773  /* We pass is_old=false as argument here, but e->old will be updated later
774  * by update_message_path() (called by init_email() below). */
775  e = maildir_parse_message(MUTT_MAILDIR, path, false, NULL);
776  }
777  else
778  {
779  /* maybe moved try find it... */
780  char *folder = get_folder_from_path(path);
781 
782  if (folder)
783  {
784  FILE *fp = maildir_open_find_message(folder, path, &newpath);
785  if (fp)
786  {
787  e = maildir_parse_stream(MUTT_MAILDIR, fp, newpath, false, NULL);
788  mutt_file_fclose(&fp);
789 
790  mutt_debug(LL_DEBUG1, "nm: not up-to-date: %s -> %s\n", path, newpath);
791  }
792  }
793  FREE(&folder);
794  }
795 
796  if (!e)
797  {
798  mutt_debug(LL_DEBUG1, "nm: failed to parse message: %s\n", path);
799  goto done;
800  }
801 
802 #ifdef USE_HCACHE
803  mutt_hcache_store(h, newpath ? newpath : path,
804  mutt_str_len(newpath ? newpath : path), e, 0);
805 #endif
806  }
807 
808  if (init_email(e, newpath ? newpath : path, msg) != 0)
809  {
810  email_free(&e);
811  mutt_debug(LL_DEBUG1, "nm: failed to append email!\n");
812  goto done;
813  }
814 
815  e->active = true;
816  e->index = m->msg_count;
817  mailbox_size_add(m, e);
818  m->emails[m->msg_count] = e;
819  m->msg_count++;
820 
821  if (newpath)
822  {
823  /* remember that file has been moved -- nm_mbox_sync() will update the DB */
824  struct NmEmailData *edata = nm_edata_get(e);
825  if (edata)
826  {
827  mutt_debug(LL_DEBUG1, "nm: remember obsolete path: %s\n", path);
828  edata->oldpath = mutt_str_dup(path);
829  }
830  }
832 done:
833  FREE(&newpath);
834 }
835 
846 static void append_replies(struct HeaderCache *h, struct Mailbox *m,
847  notmuch_query_t *q, notmuch_message_t *top, bool dedup)
848 {
849  notmuch_messages_t *msgs = NULL;
850 
851  for (msgs = notmuch_message_get_replies(top); notmuch_messages_valid(msgs);
852  notmuch_messages_move_to_next(msgs))
853  {
854  notmuch_message_t *nm = notmuch_messages_get(msgs);
855  append_message(h, m, nm, dedup);
856  /* recurse through all the replies to this message too */
857  append_replies(h, m, q, nm, dedup);
858  notmuch_message_destroy(nm);
859  }
860 }
861 
873 static void append_thread(struct HeaderCache *h, struct Mailbox *m,
874  notmuch_query_t *q, notmuch_thread_t *thread, bool dedup)
875 {
876  notmuch_messages_t *msgs = NULL;
877 
878  for (msgs = notmuch_thread_get_toplevel_messages(thread);
879  notmuch_messages_valid(msgs); notmuch_messages_move_to_next(msgs))
880  {
881  notmuch_message_t *nm = notmuch_messages_get(msgs);
882  append_message(h, m, nm, dedup);
883  append_replies(h, m, q, nm, dedup);
884  notmuch_message_destroy(nm);
885  }
886 }
887 
897 static notmuch_messages_t *get_messages(notmuch_query_t *query)
898 {
899  if (!query)
900  return NULL;
901 
902  notmuch_messages_t *msgs = NULL;
903 
904 #if LIBNOTMUCH_CHECK_VERSION(5, 0, 0)
905  if (notmuch_query_search_messages(query, &msgs) != NOTMUCH_STATUS_SUCCESS)
906  return NULL;
907 #elif LIBNOTMUCH_CHECK_VERSION(4, 3, 0)
908  if (notmuch_query_search_messages_st(query, &msgs) != NOTMUCH_STATUS_SUCCESS)
909  return NULL;
910 #else
911  msgs = notmuch_query_search_messages(query);
912 #endif
913 
914  return msgs;
915 }
916 
925 static bool read_mesgs_query(struct Mailbox *m, notmuch_query_t *q, bool dedup)
926 {
927  struct NmMboxData *mdata = nm_mdata_get(m);
928  if (!mdata)
929  return false;
930 
931  int limit = get_limit(mdata);
932 
933  notmuch_messages_t *msgs = get_messages(q);
934 
935  if (!msgs)
936  return false;
937 
938  struct HeaderCache *h = nm_hcache_open(m);
939 
940  for (; notmuch_messages_valid(msgs) && ((limit == 0) || (m->msg_count < limit));
941  notmuch_messages_move_to_next(msgs))
942  {
943  if (SigInt)
944  {
945  nm_hcache_close(h);
946  SigInt = false;
947  return false;
948  }
949  notmuch_message_t *nm = notmuch_messages_get(msgs);
950  append_message(h, m, nm, dedup);
951  notmuch_message_destroy(nm);
952  }
953 
954  nm_hcache_close(h);
955  return true;
956 }
957 
967 static notmuch_threads_t *get_threads(notmuch_query_t *query)
968 {
969  if (!query)
970  return NULL;
971 
972  notmuch_threads_t *threads = NULL;
973 #if LIBNOTMUCH_CHECK_VERSION(5, 0, 0)
974  if (notmuch_query_search_threads(query, &threads) != NOTMUCH_STATUS_SUCCESS)
975  return false;
976 #elif LIBNOTMUCH_CHECK_VERSION(4, 3, 0)
977  if (notmuch_query_search_threads_st(query, &threads) != NOTMUCH_STATUS_SUCCESS)
978  return false;
979 #else
980  threads = notmuch_query_search_threads(query);
981 #endif
982 
983  return threads;
984 }
985 
995 static bool read_threads_query(struct Mailbox *m, notmuch_query_t *q, bool dedup, int limit)
996 {
997  struct NmMboxData *mdata = nm_mdata_get(m);
998  if (!mdata)
999  return false;
1000 
1001  notmuch_threads_t *threads = get_threads(q);
1002  if (!threads)
1003  return false;
1004 
1005  struct HeaderCache *h = nm_hcache_open(m);
1006 
1007  for (; notmuch_threads_valid(threads) && ((limit == 0) || (m->msg_count < limit));
1008  notmuch_threads_move_to_next(threads))
1009  {
1010  if (SigInt)
1011  {
1012  nm_hcache_close(h);
1013  SigInt = false;
1014  return false;
1015  }
1016  notmuch_thread_t *thread = notmuch_threads_get(threads);
1017  append_thread(h, m, q, thread, dedup);
1018  notmuch_thread_destroy(thread);
1019  }
1020 
1021  nm_hcache_close(h);
1022  return true;
1023 }
1024 
1031 static notmuch_message_t *get_nm_message(notmuch_database_t *db, struct Email *e)
1032 {
1033  notmuch_message_t *msg = NULL;
1034  char *id = email_get_id(e);
1035 
1036  mutt_debug(LL_DEBUG2, "nm: find message (%s)\n", id);
1037 
1038  if (id && db)
1039  notmuch_database_find_message(db, id, &msg);
1040 
1041  return msg;
1042 }
1043 
1050 static bool nm_message_has_tag(notmuch_message_t *msg, char *tag)
1051 {
1052  const char *possible_match_tag = NULL;
1053  notmuch_tags_t *tags = NULL;
1054 
1055  for (tags = notmuch_message_get_tags(msg); notmuch_tags_valid(tags);
1056  notmuch_tags_move_to_next(tags))
1057  {
1058  possible_match_tag = notmuch_tags_get(tags);
1059  if (mutt_str_equal(possible_match_tag, tag))
1060  {
1061  return true;
1062  }
1063  }
1064  return false;
1065 }
1066 
1072 static void sync_email_path_with_nm(struct Email *e, notmuch_message_t *msg)
1073 {
1074  const char *new_file = get_message_last_filename(msg);
1075  char old_file[PATH_MAX];
1076  email_get_fullpath(e, old_file, sizeof(old_file));
1077 
1078  if (!mutt_str_equal(old_file, new_file))
1079  update_message_path(e, new_file);
1080 }
1081 
1089 static int update_tags(notmuch_message_t *msg, const char *tag_str)
1090 {
1091  if (!tag_str)
1092  return -1;
1093 
1094  notmuch_message_freeze(msg);
1095 
1097  char **tag_elem = NULL;
1098  ARRAY_FOREACH(tag_elem, &tags.tags)
1099  {
1100  char *tag = *tag_elem;
1101 
1102  if (tag[0] == '-')
1103  {
1104  mutt_debug(LL_DEBUG1, "nm: remove tag: '%s'\n", tag + 1);
1105  notmuch_message_remove_tag(msg, tag + 1);
1106  }
1107  else if (tag[0] == '!')
1108  {
1109  mutt_debug(LL_DEBUG1, "nm: toggle tag: '%s'\n", tag + 1);
1110  if (nm_message_has_tag(msg, tag + 1))
1111  {
1112  notmuch_message_remove_tag(msg, tag + 1);
1113  }
1114  else
1115  {
1116  notmuch_message_add_tag(msg, tag + 1);
1117  }
1118  }
1119  else
1120  {
1121  mutt_debug(LL_DEBUG1, "nm: add tag: '%s'\n", (tag[0] == '+') ? tag + 1 : tag);
1122  notmuch_message_add_tag(msg, (tag[0] == '+') ? tag + 1 : tag);
1123  }
1124  }
1125 
1126  notmuch_message_thaw(msg);
1128 
1129  return 0;
1130 }
1131 
1143 static int update_email_flags(struct Mailbox *m, struct Email *e, const char *tag_str)
1144 {
1145  if (!tag_str)
1146  return -1;
1147 
1148  const char *const c_nm_unread_tag = cs_subset_string(NeoMutt->sub, "nm_unread_tag");
1149  const char *const c_nm_replied_tag = cs_subset_string(NeoMutt->sub, "nm_replied_tag");
1150  const char *const c_nm_flagged_tag = cs_subset_string(NeoMutt->sub, "nm_flagged_tag");
1151 
1153  char **tag_elem = NULL;
1154  ARRAY_FOREACH(tag_elem, &tags.tags)
1155  {
1156  char *tag = *tag_elem;
1157 
1158  if (tag[0] == '-')
1159  {
1160  tag++;
1161  if (strcmp(tag, c_nm_unread_tag) == 0)
1162  mutt_set_flag(m, e, MUTT_READ, true);
1163  else if (strcmp(tag, c_nm_replied_tag) == 0)
1164  mutt_set_flag(m, e, MUTT_REPLIED, false);
1165  else if (strcmp(tag, c_nm_flagged_tag) == 0)
1166  mutt_set_flag(m, e, MUTT_FLAG, false);
1167  }
1168  else
1169  {
1170  tag = (tag[0] == '+') ? tag + 1 : tag;
1171  if (strcmp(tag, c_nm_unread_tag) == 0)
1172  mutt_set_flag(m, e, MUTT_READ, false);
1173  else if (strcmp(tag, c_nm_replied_tag) == 0)
1174  mutt_set_flag(m, e, MUTT_REPLIED, true);
1175  else if (strcmp(tag, c_nm_flagged_tag) == 0)
1176  mutt_set_flag(m, e, MUTT_FLAG, true);
1177  }
1178  }
1179 
1181 
1182  return 0;
1183 }
1184 
1195 static int rename_maildir_filename(const char *old, char *buf, size_t buflen, struct Email *e)
1196 {
1197  char filename[PATH_MAX];
1198  char suffix[PATH_MAX];
1199  char folder[PATH_MAX];
1200 
1201  mutt_str_copy(folder, old, sizeof(folder));
1202  char *p = strrchr(folder, '/');
1203  if (p)
1204  {
1205  *p = '\0';
1206  p++;
1207  }
1208  else
1209  p = folder;
1210 
1211  mutt_str_copy(filename, p, sizeof(filename));
1212 
1213  /* remove (new,cur,...) from folder path */
1214  p = strrchr(folder, '/');
1215  if (p)
1216  *p = '\0';
1217 
1218  /* remove old flags from filename */
1219  p = strchr(filename, ':');
1220  if (p)
1221  *p = '\0';
1222 
1223  /* compose new flags */
1224  maildir_gen_flags(suffix, sizeof(suffix), e);
1225 
1226  snprintf(buf, buflen, "%s/%s/%s%s", folder,
1227  (e->read || e->old) ? "cur" : "new", filename, suffix);
1228 
1229  if (strcmp(old, buf) == 0)
1230  return 1;
1231 
1232  if (rename(old, buf) != 0)
1233  {
1234  mutt_debug(LL_DEBUG1, "nm: rename(2) failed %s -> %s\n", old, buf);
1235  return -1;
1236  }
1237 
1238  return 0;
1239 }
1240 
1248 static int remove_filename(struct Mailbox *m, const char *path)
1249 {
1250  struct NmMboxData *mdata = nm_mdata_get(m);
1251  if (!mdata)
1252  return -1;
1253 
1254  mutt_debug(LL_DEBUG2, "nm: remove filename '%s'\n", path);
1255 
1256  notmuch_database_t *db = nm_db_get(m, true);
1257  if (!db)
1258  return -1;
1259 
1260  notmuch_message_t *msg = NULL;
1261  notmuch_status_t st = notmuch_database_find_message_by_filename(db, path, &msg);
1262  if (st || !msg)
1263  return -1;
1264 
1265  int trans = nm_db_trans_begin(m);
1266  if (trans < 0)
1267  return -1;
1268 
1269  /* note that unlink() is probably unnecessary here, it's already removed
1270  * by mh_sync_mailbox_message(), but for sure... */
1271  notmuch_filenames_t *ls = NULL;
1272  st = notmuch_database_remove_message(db, path);
1273  switch (st)
1274  {
1275  case NOTMUCH_STATUS_SUCCESS:
1276  mutt_debug(LL_DEBUG2, "nm: remove success, call unlink\n");
1277  unlink(path);
1278  break;
1279  case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:
1280  mutt_debug(LL_DEBUG2, "nm: remove success (duplicate), call unlink\n");
1281  unlink(path);
1282  for (ls = notmuch_message_get_filenames(msg);
1283  ls && notmuch_filenames_valid(ls); notmuch_filenames_move_to_next(ls))
1284  {
1285  path = notmuch_filenames_get(ls);
1286 
1287  mutt_debug(LL_DEBUG2, "nm: remove duplicate: '%s'\n", path);
1288  unlink(path);
1289  notmuch_database_remove_message(db, path);
1290  }
1291  break;
1292  default:
1293  mutt_debug(LL_DEBUG1, "nm: failed to remove '%s' [st=%d]\n", path, (int) st);
1294  break;
1295  }
1296 
1297  notmuch_message_destroy(msg);
1298  if (trans)
1299  nm_db_trans_end(m);
1300  return 0;
1301 }
1302 
1312 static int rename_filename(struct Mailbox *m, const char *old_file,
1313  const char *new_file, struct Email *e)
1314 {
1315  struct NmMboxData *mdata = nm_mdata_get(m);
1316  if (!mdata)
1317  return -1;
1318 
1319  notmuch_database_t *db = nm_db_get(m, true);
1320  if (!db || !new_file || !old_file || (access(new_file, F_OK) != 0))
1321  return -1;
1322 
1323  int rc = -1;
1324  notmuch_status_t st;
1325  notmuch_filenames_t *ls = NULL;
1326  notmuch_message_t *msg = NULL;
1327 
1328  mutt_debug(LL_DEBUG1, "nm: rename filename, %s -> %s\n", old_file, new_file);
1329  int trans = nm_db_trans_begin(m);
1330  if (trans < 0)
1331  return -1;
1332 
1333  mutt_debug(LL_DEBUG2, "nm: rename: add '%s'\n", new_file);
1334 #if LIBNOTMUCH_CHECK_VERSION(5, 1, 0)
1335  st = notmuch_database_index_file(db, new_file, NULL, &msg);
1336 #else
1337  st = notmuch_database_add_message(db, new_file, &msg);
1338 #endif
1339 
1340  if ((st != NOTMUCH_STATUS_SUCCESS) && (st != NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID))
1341  {
1342  mutt_debug(LL_DEBUG1, "nm: failed to add '%s' [st=%d]\n", new_file, (int) st);
1343  goto done;
1344  }
1345 
1346  mutt_debug(LL_DEBUG2, "nm: rename: rem '%s'\n", old_file);
1347  st = notmuch_database_remove_message(db, old_file);
1348  switch (st)
1349  {
1350  case NOTMUCH_STATUS_SUCCESS:
1351  break;
1352  case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:
1353  mutt_debug(LL_DEBUG2, "nm: rename: syncing duplicate filename\n");
1354  notmuch_message_destroy(msg);
1355  msg = NULL;
1356  notmuch_database_find_message_by_filename(db, new_file, &msg);
1357 
1358  for (ls = notmuch_message_get_filenames(msg);
1359  msg && ls && notmuch_filenames_valid(ls); notmuch_filenames_move_to_next(ls))
1360  {
1361  const char *path = notmuch_filenames_get(ls);
1362  char newpath[PATH_MAX];
1363 
1364  if (strcmp(new_file, path) == 0)
1365  continue;
1366 
1367  mutt_debug(LL_DEBUG2, "nm: rename: syncing duplicate: %s\n", path);
1368 
1369  if (rename_maildir_filename(path, newpath, sizeof(newpath), e) == 0)
1370  {
1371  mutt_debug(LL_DEBUG2, "nm: rename dup %s -> %s\n", path, newpath);
1372  notmuch_database_remove_message(db, path);
1373 #if LIBNOTMUCH_CHECK_VERSION(5, 1, 0)
1374  notmuch_database_index_file(db, newpath, NULL, NULL);
1375 #else
1376  notmuch_database_add_message(db, newpath, NULL);
1377 #endif
1378  }
1379  }
1380  notmuch_message_destroy(msg);
1381  msg = NULL;
1382  notmuch_database_find_message_by_filename(db, new_file, &msg);
1383  st = NOTMUCH_STATUS_SUCCESS;
1384  break;
1385  default:
1386  mutt_debug(LL_DEBUG1, "nm: failed to remove '%s' [st=%d]\n", old_file, (int) st);
1387  break;
1388  }
1389 
1390  if ((st == NOTMUCH_STATUS_SUCCESS) && e && msg)
1391  {
1392  notmuch_message_maildir_flags_to_tags(msg);
1393  update_email_tags(e, msg);
1394 
1395  char *tags = driver_tags_get(&e->tags);
1396  update_tags(msg, tags);
1397  FREE(&tags);
1398  }
1399 
1400  rc = 0;
1401 done:
1402  if (msg)
1403  notmuch_message_destroy(msg);
1404  if (trans)
1405  nm_db_trans_end(m);
1406  return rc;
1407 }
1408 
1416 static unsigned int count_query(notmuch_database_t *db, const char *qstr, int limit)
1417 {
1418  notmuch_query_t *q = notmuch_query_create(db, qstr);
1419  if (!q)
1420  return 0;
1421 
1422  unsigned int res = 0;
1423 
1424  apply_exclude_tags(q);
1425 #if LIBNOTMUCH_CHECK_VERSION(5, 0, 0)
1426  if (notmuch_query_count_messages(q, &res) != NOTMUCH_STATUS_SUCCESS)
1427  res = 0; /* may not be defined on error */
1428 #elif LIBNOTMUCH_CHECK_VERSION(4, 3, 0)
1429  if (notmuch_query_count_messages_st(q, &res) != NOTMUCH_STATUS_SUCCESS)
1430  res = 0; /* may not be defined on error */
1431 #else
1432  res = notmuch_query_count_messages(q);
1433 #endif
1434  notmuch_query_destroy(q);
1435  mutt_debug(LL_DEBUG1, "nm: count '%s', result=%d\n", qstr, res);
1436 
1437  if ((limit > 0) && (res > limit))
1438  res = limit;
1439 
1440  return res;
1441 }
1442 
1449 char *nm_email_get_folder(struct Email *e)
1450 {
1451  struct NmEmailData *edata = nm_edata_get(e);
1452  if (!edata)
1453  return NULL;
1454 
1455  return edata->folder;
1456 }
1457 
1468 char *nm_email_get_folder_rel_db(struct Mailbox *m, struct Email *e)
1469 {
1470  char *full_folder = nm_email_get_folder(e);
1471  if (!full_folder)
1472  return NULL;
1473 
1474  const char *db_path = nm_db_get_filename(m);
1475  if (!db_path)
1476  return NULL;
1477 
1478  return full_folder + strlen(db_path);
1479 }
1480 
1488 int nm_read_entire_thread(struct Mailbox *m, struct Email *e)
1489 {
1490  if (!m)
1491  return -1;
1492 
1493  struct NmMboxData *mdata = nm_mdata_get(m);
1494  if (!mdata)
1495  return -1;
1496 
1497  notmuch_query_t *q = NULL;
1498  notmuch_database_t *db = NULL;
1499  notmuch_message_t *msg = NULL;
1500  int rc = -1;
1501 
1502  if (!(db = nm_db_get(m, false)) || !(msg = get_nm_message(db, e)))
1503  goto done;
1504 
1505  mutt_debug(LL_DEBUG1, "nm: reading entire-thread messages...[current count=%d]\n",
1506  m->msg_count);
1507 
1508  progress_setup(m);
1509  const char *id = notmuch_message_get_thread_id(msg);
1510  if (!id)
1511  goto done;
1512 
1513  char *qstr = NULL;
1514  mutt_str_append_item(&qstr, "thread:", '\0');
1515  mutt_str_append_item(&qstr, id, '\0');
1516 
1517  q = notmuch_query_create(db, qstr);
1518  FREE(&qstr);
1519  if (!q)
1520  goto done;
1521  apply_exclude_tags(q);
1522  notmuch_query_set_sort(q, NOTMUCH_SORT_NEWEST_FIRST);
1523 
1524  read_threads_query(m, q, true, 0);
1525  m->mtime.tv_sec = mutt_date_epoch();
1526  m->mtime.tv_nsec = 0;
1527  rc = 0;
1528 
1529  if (m->msg_count > mdata->oldmsgcount)
1531 done:
1532  if (q)
1533  notmuch_query_destroy(q);
1534 
1535  nm_db_release(m);
1536 
1537  if (m->msg_count == mdata->oldmsgcount)
1538  mutt_message(_("No more messages in the thread"));
1539 
1540  mdata->oldmsgcount = 0;
1541  mutt_debug(LL_DEBUG1, "nm: reading entire-thread messages... done [rc=%d, count=%d]\n",
1542  rc, m->msg_count);
1543  progress_free(&mdata->progress);
1544  return rc;
1545 }
1546 
1555 char *nm_url_from_query(struct Mailbox *m, char *buf, size_t buflen)
1556 {
1557  mutt_debug(LL_DEBUG2, "(%s)\n", buf);
1558  struct NmMboxData *mdata = nm_mdata_get(m);
1559  char url[PATH_MAX + 1024 + 32]; /* path to DB + query + URL "decoration" */
1560  int added;
1561  bool using_default_data = false;
1562 
1563  // No existing data. Try to get a default NmMboxData.
1564  if (!mdata)
1565  {
1567 
1568  // Failed to get default data.
1569  if (!mdata)
1570  return NULL;
1571 
1572  using_default_data = true;
1573  }
1574 
1575  enum NmQueryType c_nm_query_type = nm_string_to_query_type(
1576  cs_subset_string(NeoMutt->sub, "nm_query_type"));
1577  mdata->query_type = nm_parse_type_from_query(buf, c_nm_query_type);
1578 
1579  const short c_nm_db_limit = cs_subset_number(NeoMutt->sub, "nm_db_limit");
1580  if (get_limit(mdata) == c_nm_db_limit)
1581  {
1582  added = snprintf(url, sizeof(url), "%s%s?type=%s&query=", NmUrlProtocol,
1584  }
1585  else
1586  {
1587  added = snprintf(url, sizeof(url), "%s%s?type=%s&limit=%d&query=", NmUrlProtocol,
1588  nm_db_get_filename(m),
1589  nm_query_type_to_string(mdata->query_type), get_limit(mdata));
1590  }
1591 
1592  if (added >= sizeof(url))
1593  {
1594  // snprintf output was truncated, so can't create URL
1595  return NULL;
1596  }
1597 
1598  url_pct_encode(&url[added], sizeof(url) - added, buf);
1599 
1600  mutt_str_copy(buf, url, buflen);
1601  buf[buflen - 1] = '\0';
1602 
1603  if (using_default_data)
1604  nm_mdata_free((void **) &mdata);
1605 
1606  mutt_debug(LL_DEBUG1, "nm: url from query '%s'\n", buf);
1607  return buf;
1608 }
1609 
1615 {
1616  const short c_nm_query_window_duration = cs_subset_number(NeoMutt->sub, "nm_query_window_duration");
1617  const bool c_nm_query_window_enable = cs_subset_bool(NeoMutt->sub, "nm_query_window_enable");
1618 
1619  return c_nm_query_window_enable || (c_nm_query_window_duration > 0);
1620 }
1621 
1632 {
1633  const short c_nm_query_window_current_position = cs_subset_number(NeoMutt->sub, "nm_query_window_current_position");
1634  if (c_nm_query_window_current_position != 0)
1635  {
1636  cs_subset_str_native_set(NeoMutt->sub, "nm_query_window_current_position",
1637  c_nm_query_window_current_position - 1, NULL);
1638  }
1639 
1640  mutt_debug(LL_DEBUG2, "(%d)\n", c_nm_query_window_current_position - 1);
1641 }
1642 
1652 {
1653  const short c_nm_query_window_current_position = cs_subset_number(NeoMutt->sub, "nm_query_window_current_position");
1654  cs_subset_str_native_set(NeoMutt->sub, "nm_query_window_current_position",
1655  c_nm_query_window_current_position + 1, NULL);
1656  mutt_debug(LL_DEBUG2, "(%d)\n", c_nm_query_window_current_position + 1);
1657 }
1658 
1663 {
1664  cs_subset_str_native_set(NeoMutt->sub, "nm_query_window_current_position", 0, NULL);
1665  mutt_debug(LL_DEBUG2, "Reset nm_query_window_current_position to 0\n");
1666 }
1667 
1674 bool nm_message_is_still_queried(struct Mailbox *m, struct Email *e)
1675 {
1676  struct NmMboxData *mdata = nm_mdata_get(m);
1677  notmuch_database_t *db = nm_db_get(m, false);
1678  char *orig_str = get_query_string(mdata, true);
1679 
1680  if (!db || !orig_str)
1681  return false;
1682 
1683  char *new_str = NULL;
1684  bool rc = false;
1685  if (mutt_str_asprintf(&new_str, "id:%s and (%s)", email_get_id(e), orig_str) < 0)
1686  return false;
1687 
1688  mutt_debug(LL_DEBUG2, "nm: checking if message is still queried: %s\n", new_str);
1689 
1690  notmuch_query_t *q = notmuch_query_create(db, new_str);
1691 
1692  switch (mdata->query_type)
1693  {
1694  case NM_QUERY_TYPE_UNKNOWN: // UNKNOWN should never occur, but MESGS is default
1695  case NM_QUERY_TYPE_MESGS:
1696  {
1697  notmuch_messages_t *messages = get_messages(q);
1698 
1699  if (!messages)
1700  return false;
1701 
1702  rc = notmuch_messages_valid(messages);
1703  notmuch_messages_destroy(messages);
1704  break;
1705  }
1706  case NM_QUERY_TYPE_THREADS:
1707  {
1708  notmuch_threads_t *threads = get_threads(q);
1709 
1710  if (!threads)
1711  return false;
1712 
1713  rc = notmuch_threads_valid(threads);
1714  notmuch_threads_destroy(threads);
1715  break;
1716  }
1717  }
1718 
1719  notmuch_query_destroy(q);
1720 
1721  mutt_debug(LL_DEBUG2, "nm: checking if message is still queried: %s = %s\n",
1722  new_str, rc ? "true" : "false");
1723 
1724  return rc;
1725 }
1726 
1736 int nm_update_filename(struct Mailbox *m, const char *old_file,
1737  const char *new_file, struct Email *e)
1738 {
1739  char buf[PATH_MAX];
1740  struct NmMboxData *mdata = nm_mdata_get(m);
1741  if (!mdata || !new_file)
1742  return -1;
1743 
1744  if (!old_file && nm_edata_get(e))
1745  {
1746  email_get_fullpath(e, buf, sizeof(buf));
1747  old_file = buf;
1748  }
1749 
1750  int rc = rename_filename(m, old_file, new_file, e);
1751 
1752  nm_db_release(m);
1753  m->mtime.tv_sec = mutt_date_epoch();
1754  m->mtime.tv_nsec = 0;
1755  return rc;
1756 }
1757 
1761 static enum MxStatus nm_mbox_check_stats(struct Mailbox *m, uint8_t flags)
1762 {
1763  struct UrlQuery *item = NULL;
1764  struct Url *url = NULL;
1765  const char *db_filename = NULL;
1766  char *db_query = NULL;
1767  notmuch_database_t *db = NULL;
1768  enum MxStatus rc = MX_STATUS_ERROR;
1769  const short c_nm_db_limit = cs_subset_number(NeoMutt->sub, "nm_db_limit");
1770  int limit = c_nm_db_limit;
1771  mutt_debug(LL_DEBUG1, "nm: count\n");
1772 
1773  url = url_parse(mailbox_path(m));
1774  if (!url)
1775  {
1776  mutt_error(_("failed to parse notmuch url: %s"), mailbox_path(m));
1777  goto done;
1778  }
1779 
1780  STAILQ_FOREACH(item, &url->query_strings, entries)
1781  {
1782  if (item->value && (strcmp(item->name, "query") == 0))
1783  db_query = item->value;
1784  else if (item->value && (strcmp(item->name, "limit") == 0))
1785  {
1786  // Try to parse the limit
1787  if (!mutt_str_atoi_full(item->value, &limit))
1788  {
1789  mutt_error(_("failed to parse limit: %s"), item->value);
1790  goto done;
1791  }
1792  }
1793  }
1794 
1795  if (!db_query)
1796  goto done;
1797 
1798  db_filename = url->path;
1799  if (!db_filename)
1800  db_filename = nm_db_get_filename(m);
1801 
1802  /* don't be verbose about connection, as we're called from
1803  * sidebar/mailbox very often */
1804  db = nm_db_do_open(db_filename, false, false);
1805  if (!db)
1806  goto done;
1807 
1808  /* all emails */
1809  m->msg_count = count_query(db, db_query, limit);
1810  while (m->email_max < m->msg_count)
1811  mx_alloc_memory(m);
1812 
1813  // holder variable for extending query to unread/flagged
1814  char *qstr = NULL;
1815 
1816  // unread messages
1817  const char *const c_nm_unread_tag = cs_subset_string(NeoMutt->sub, "nm_unread_tag");
1818  mutt_str_asprintf(&qstr, "( %s ) tag:%s", db_query, c_nm_unread_tag);
1819  m->msg_unread = count_query(db, qstr, limit);
1820  FREE(&qstr);
1821 
1822  // flagged messages
1823  const char *const c_nm_flagged_tag = cs_subset_string(NeoMutt->sub, "nm_flagged_tag");
1824  mutt_str_asprintf(&qstr, "( %s ) tag:%s", db_query, c_nm_flagged_tag);
1825  m->msg_flagged = count_query(db, qstr, limit);
1826  FREE(&qstr);
1827 
1828  rc = (m->msg_new > 0) ? MX_STATUS_NEW_MAIL : MX_STATUS_OK;
1829 done:
1830  if (db)
1831  {
1832  nm_db_free(db);
1833  mutt_debug(LL_DEBUG1, "nm: count close DB\n");
1834  }
1835  url_free(&url);
1836 
1837  mutt_debug(LL_DEBUG1, "nm: count done [rc=%d]\n", rc);
1838  return rc;
1839 }
1840 
1845 static struct Mailbox *get_default_mailbox(void)
1846 {
1847  // Create a new notmuch mailbox from scratch and add plumbing for DB access.
1848  char *default_url = nm_get_default_url();
1849  struct Mailbox *m = mx_path_resolve(default_url);
1850 
1851  FREE(&default_url);
1852 
1853  // These are no-ops for an initialized mailbox.
1854  init_mailbox(m);
1855  mx_mbox_ac_link(m);
1856 
1857  return m;
1858 }
1859 
1868 int nm_record_message(struct Mailbox *m, char *path, struct Email *e)
1869 {
1870  notmuch_database_t *db = NULL;
1871  notmuch_status_t st;
1872  notmuch_message_t *msg = NULL;
1873  int rc = -1;
1874 
1875  struct NmMboxData *mdata = nm_mdata_get(m);
1876 
1877  // If no notmuch data, fall back to the default mailbox.
1878  //
1879  // IMPORTANT: DO NOT FREE THIS MAILBOX. Two reasons:
1880  // 1) If user has default mailbox in config, we'll be removing it. That's not
1881  // good program behavior!
1882  // 2) If not in user's config, keep mailbox around for future nm_record calls.
1883  // It saves NeoMutt from allocating/deallocating repeatedly.
1884  if (!mdata)
1885  {
1886  mutt_debug(LL_DEBUG1, "nm: non-nm mailbox. trying the default nm mailbox.");
1887  m = get_default_mailbox();
1888  mdata = nm_mdata_get(m);
1889  }
1890 
1891  if (!path || !mdata || (access(path, F_OK) != 0))
1892  return 0;
1893  db = nm_db_get(m, true);
1894  if (!db)
1895  return -1;
1896 
1897  mutt_debug(LL_DEBUG1, "nm: record message: %s\n", path);
1898  int trans = nm_db_trans_begin(m);
1899  if (trans < 0)
1900  goto done;
1901 
1902 #if LIBNOTMUCH_CHECK_VERSION(5, 1, 0)
1903  st = notmuch_database_index_file(db, path, NULL, &msg);
1904 #else
1905  st = notmuch_database_add_message(db, path, &msg);
1906 #endif
1907 
1908  if ((st != NOTMUCH_STATUS_SUCCESS) && (st != NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID))
1909  {
1910  mutt_debug(LL_DEBUG1, "nm: failed to add '%s' [st=%d]\n", path, (int) st);
1911  goto done;
1912  }
1913 
1914  if ((st == NOTMUCH_STATUS_SUCCESS) && msg)
1915  {
1916  notmuch_message_maildir_flags_to_tags(msg);
1917  if (e)
1918  {
1919  char *tags = driver_tags_get(&e->tags);
1920  update_tags(msg, tags);
1921  FREE(&tags);
1922  }
1923  const char *const c_nm_record_tags = cs_subset_string(NeoMutt->sub, "nm_record_tags");
1924  if (c_nm_record_tags)
1925  update_tags(msg, c_nm_record_tags);
1926  }
1927 
1928  rc = 0;
1929 done:
1930  if (msg)
1931  notmuch_message_destroy(msg);
1932  if (trans == 1)
1933  nm_db_trans_end(m);
1934 
1935  nm_db_release(m);
1936 
1937  return rc;
1938 }
1939 
1950 int nm_get_all_tags(struct Mailbox *m, const char **tag_list, int *tag_count)
1951 {
1952  struct NmMboxData *mdata = nm_mdata_get(m);
1953  if (!mdata)
1954  return -1;
1955 
1956  notmuch_database_t *db = NULL;
1957  notmuch_tags_t *tags = NULL;
1958  const char *tag = NULL;
1959  int rc = -1;
1960 
1961  if (!(db = nm_db_get(m, false)) || !(tags = notmuch_database_get_all_tags(db)))
1962  goto done;
1963 
1964  *tag_count = 0;
1965  mutt_debug(LL_DEBUG1, "nm: get all tags\n");
1966 
1967  while (notmuch_tags_valid(tags))
1968  {
1969  tag = notmuch_tags_get(tags);
1970  /* Skip empty string */
1971  if (*tag)
1972  {
1973  if (tag_list)
1974  tag_list[*tag_count] = mutt_str_dup(tag);
1975  (*tag_count)++;
1976  }
1977  notmuch_tags_move_to_next(tags);
1978  }
1979 
1980  rc = 0;
1981 done:
1982  if (tags)
1983  notmuch_tags_destroy(tags);
1984 
1985  nm_db_release(m);
1986 
1987  mutt_debug(LL_DEBUG1, "nm: get all tags done [rc=%d tag_count=%u]\n", rc, *tag_count);
1988  return rc;
1989 }
1990 
1994 static bool nm_ac_owns_path(struct Account *a, const char *path)
1995 {
1996  return true;
1997 }
1998 
2002 static bool nm_ac_add(struct Account *a, struct Mailbox *m)
2003 {
2004  if (a->adata)
2005  return true;
2006 
2007  struct NmAccountData *adata = nm_adata_new();
2008  a->adata = adata;
2010 
2011  return true;
2012 }
2013 
2017 static enum MxOpenReturns nm_mbox_open(struct Mailbox *m)
2018 {
2019  if (init_mailbox(m) != 0)
2020  return MX_OPEN_ERROR;
2021 
2022  struct NmMboxData *mdata = nm_mdata_get(m);
2023  if (!mdata)
2024  return MX_OPEN_ERROR;
2025 
2026  mutt_debug(LL_DEBUG1, "nm: reading messages...[current count=%d]\n", m->msg_count);
2027 
2028  progress_setup(m);
2029  enum MxOpenReturns rc = MX_OPEN_ERROR;
2030 
2031  notmuch_query_t *q = get_query(m, false);
2032  if (q)
2033  {
2034  rc = MX_OPEN_OK;
2035  switch (mdata->query_type)
2036  {
2037  case NM_QUERY_TYPE_UNKNOWN: // UNKNOWN should never occur, but MESGS is default
2038  case NM_QUERY_TYPE_MESGS:
2039  if (!read_mesgs_query(m, q, false))
2040  rc = MX_OPEN_ABORT;
2041  break;
2042  case NM_QUERY_TYPE_THREADS:
2043  if (!read_threads_query(m, q, false, get_limit(mdata)))
2044  rc = MX_OPEN_ABORT;
2045  break;
2046  }
2047  notmuch_query_destroy(q);
2048  }
2049 
2050  nm_db_release(m);
2051 
2052  m->mtime.tv_sec = mutt_date_epoch();
2053  m->mtime.tv_nsec = 0;
2054 
2055  mdata->oldmsgcount = 0;
2056 
2057  mutt_debug(LL_DEBUG1, "nm: reading messages... done [rc=%d, count=%d]\n", rc, m->msg_count);
2058  progress_free(&mdata->progress);
2059  return rc;
2060 }
2061 
2067 static enum MxStatus nm_mbox_check(struct Mailbox *m)
2068 {
2069  struct NmMboxData *mdata = nm_mdata_get(m);
2070  time_t mtime = 0;
2071  if (!mdata || (nm_db_get_mtime(m, &mtime) != 0))
2072  return MX_STATUS_ERROR;
2073 
2074  int new_flags = 0;
2075  bool occult = false;
2076 
2077  if (m->mtime.tv_sec >= mtime)
2078  {
2079  mutt_debug(LL_DEBUG2, "nm: check unnecessary (db=%lu mailbox=%lu)\n", mtime,
2080  m->mtime.tv_sec);
2081  return MX_STATUS_OK;
2082  }
2083 
2084  mutt_debug(LL_DEBUG1, "nm: checking (db=%lu mailbox=%lu)\n", mtime, m->mtime.tv_sec);
2085 
2086  notmuch_query_t *q = get_query(m, false);
2087  if (!q)
2088  goto done;
2089 
2090  mutt_debug(LL_DEBUG1, "nm: start checking (count=%d)\n", m->msg_count);
2091  mdata->oldmsgcount = m->msg_count;
2092 
2093  for (int i = 0; i < m->msg_count; i++)
2094  {
2095  struct Email *e = m->emails[i];
2096  if (!e)
2097  break;
2098 
2099  e->active = false;
2100  }
2101 
2102  int limit = get_limit(mdata);
2103 
2104  notmuch_messages_t *msgs = get_messages(q);
2105 
2106  // TODO: Analyze impact of removing this version guard.
2107 #if LIBNOTMUCH_CHECK_VERSION(5, 0, 0)
2108  if (!msgs)
2109  return MX_STATUS_OK;
2110 #elif LIBNOTMUCH_CHECK_VERSION(4, 3, 0)
2111  if (!msgs)
2112  goto done;
2113 #endif
2114 
2115  struct HeaderCache *h = nm_hcache_open(m);
2116 
2117  for (int i = 0; notmuch_messages_valid(msgs) && ((limit == 0) || (i < limit));
2118  notmuch_messages_move_to_next(msgs), i++)
2119  {
2120  notmuch_message_t *msg = notmuch_messages_get(msgs);
2121  struct Email *e = get_mutt_email(m, msg);
2122 
2123  if (!e)
2124  {
2125  /* new email */
2126  append_message(h, m, msg, false);
2127  notmuch_message_destroy(msg);
2128  continue;
2129  }
2130 
2131  /* message already exists, merge flags */
2132  e->active = true;
2133 
2134  /* Check to see if the message has moved to a different subdirectory.
2135  * If so, update the associated filename. */
2136  const char *new_file = get_message_last_filename(msg);
2137  char old_file[PATH_MAX];
2138  email_get_fullpath(e, old_file, sizeof(old_file));
2139 
2140  if (!mutt_str_equal(old_file, new_file))
2141  update_message_path(e, new_file);
2142 
2143  if (!e->changed)
2144  {
2145  /* if the user hasn't modified the flags on this message, update the
2146  * flags we just detected. */
2147  struct Email e_tmp = { 0 };
2148  e_tmp.edata = maildir_edata_new();
2149  maildir_parse_flags(&e_tmp, new_file);
2150  e_tmp.old = e->old;
2151  maildir_update_flags(m, e, &e_tmp);
2152  maildir_edata_free(&e_tmp.edata);
2153  }
2154 
2155  if (update_email_tags(e, msg) == 0)
2156  new_flags++;
2157 
2158  notmuch_message_destroy(msg);
2159  }
2160 
2161  nm_hcache_close(h);
2162 
2163  for (int i = 0; i < m->msg_count; i++)
2164  {
2165  struct Email *e = m->emails[i];
2166  if (!e)
2167  break;
2168 
2169  if (!e->active)
2170  {
2171  occult = true;
2172  break;
2173  }
2174  }
2175 
2176  if (m->msg_count > mdata->oldmsgcount)
2178 done:
2179  if (q)
2180  notmuch_query_destroy(q);
2181 
2182  nm_db_release(m);
2183 
2184  m->mtime.tv_sec = mutt_date_epoch();
2185  m->mtime.tv_nsec = 0;
2186 
2187  mutt_debug(LL_DEBUG1, "nm: ... check done [count=%d, new_flags=%d, occult=%d]\n",
2188  m->msg_count, new_flags, occult);
2189 
2190  if (occult)
2191  return MX_STATUS_REOPENED;
2192  if (m->msg_count > mdata->oldmsgcount)
2193  return MX_STATUS_NEW_MAIL;
2194  if (new_flags)
2195  return MX_STATUS_FLAGS;
2196  return MX_STATUS_OK;
2197 }
2198 
2202 static enum MxStatus nm_mbox_sync(struct Mailbox *m)
2203 {
2204  struct NmMboxData *mdata = nm_mdata_get(m);
2205  if (!mdata)
2206  return MX_STATUS_ERROR;
2207 
2208  enum MxStatus rc = MX_STATUS_OK;
2209  struct Progress *progress = NULL;
2210  char *url = mutt_str_dup(mailbox_path(m));
2211  bool changed = false;
2212 
2213  mutt_debug(LL_DEBUG1, "nm: sync start\n");
2214 
2215  if (m->verbose)
2216  {
2217  /* all is in this function so we don't use data->progress here */
2218  char msg[PATH_MAX];
2219  snprintf(msg, sizeof(msg), _("Writing %s..."), mailbox_path(m));
2220  progress = progress_new(msg, MUTT_PROGRESS_WRITE, m->msg_count);
2221  }
2222 
2223  struct HeaderCache *h = nm_hcache_open(m);
2224 
2225  int mh_sync_errors = 0;
2226  for (int i = 0; i < m->msg_count; i++)
2227  {
2228  char old_file[PATH_MAX], new_file[PATH_MAX];
2229  struct Email *e = m->emails[i];
2230  if (!e)
2231  break;
2232 
2233  struct NmEmailData *edata = nm_edata_get(e);
2234 
2235  if (m->verbose)
2236  progress_update(progress, i, -1);
2237 
2238  *old_file = '\0';
2239  *new_file = '\0';
2240 
2241  if (edata->oldpath)
2242  {
2243  mutt_str_copy(old_file, edata->oldpath, sizeof(old_file));
2244  old_file[sizeof(old_file) - 1] = '\0';
2245  mutt_debug(LL_DEBUG2, "nm: fixing obsolete path '%s'\n", old_file);
2246  }
2247  else
2248  email_get_fullpath(e, old_file, sizeof(old_file));
2249 
2250  mutt_buffer_strcpy(&m->pathbuf, edata->folder);
2251  m->type = edata->type;
2252 
2253  bool ok = maildir_sync_mailbox_message(m, i, h);
2254  if (!ok)
2255  {
2256  // Syncing file failed, query notmuch for new filepath.
2257  m->type = MUTT_NOTMUCH;
2258  notmuch_database_t *db = nm_db_get(m, true);
2259  if (db)
2260  {
2261  notmuch_message_t *msg = get_nm_message(db, e);
2262 
2263  sync_email_path_with_nm(e, msg);
2264 
2265  mutt_buffer_strcpy(&m->pathbuf, edata->folder);
2266  m->type = edata->type;
2267  ok = maildir_sync_mailbox_message(m, i, h);
2268  m->type = MUTT_NOTMUCH;
2269  }
2270  nm_db_release(m);
2271  m->type = edata->type;
2272  }
2273 
2274  mutt_buffer_strcpy(&m->pathbuf, url);
2275  m->type = MUTT_NOTMUCH;
2276 
2277  if (!ok)
2278  {
2279  mh_sync_errors += 1;
2280  continue;
2281  }
2282 
2283  if (!e->deleted)
2284  email_get_fullpath(e, new_file, sizeof(new_file));
2285 
2286  if (e->deleted || (strcmp(old_file, new_file) != 0))
2287  {
2288  if (e->deleted && (remove_filename(m, old_file) == 0))
2289  changed = true;
2290  else if (*new_file && *old_file && (rename_filename(m, old_file, new_file, e) == 0))
2291  changed = true;
2292  }
2293 
2294  FREE(&edata->oldpath);
2295  }
2296 
2297  if (mh_sync_errors > 0)
2298  {
2299  mutt_error(ngettext("Unable to sync %d message due to external mailbox modification",
2300  "Unable to sync %d messages due to external mailbox modification",
2301  mh_sync_errors),
2302  mh_sync_errors);
2303  }
2304 
2305  mutt_buffer_strcpy(&m->pathbuf, url);
2306  m->type = MUTT_NOTMUCH;
2307 
2308  nm_db_release(m);
2309 
2310  if (changed)
2311  {
2312  m->mtime.tv_sec = mutt_date_epoch();
2313  m->mtime.tv_nsec = 0;
2314  }
2315 
2316  nm_hcache_close(h);
2317 
2318  progress_free(&progress);
2319  FREE(&url);
2320  mutt_debug(LL_DEBUG1, "nm: .... sync done [rc=%d]\n", rc);
2321  return rc;
2322 }
2323 
2329 static enum MxStatus nm_mbox_close(struct Mailbox *m)
2330 {
2331  return MX_STATUS_OK;
2332 }
2333 
2337 static bool nm_msg_open(struct Mailbox *m, struct Message *msg, int msgno)
2338 {
2339  struct Email *e = m->emails[msgno];
2340  if (!e)
2341  return false;
2342 
2343  char path[PATH_MAX];
2344  char *folder = nm_email_get_folder(e);
2345 
2346  snprintf(path, sizeof(path), "%s/%s", folder, e->path);
2347 
2348  msg->fp = fopen(path, "r");
2349  if (!msg->fp && (errno == ENOENT) && ((m->type == MUTT_MAILDIR) || (m->type == MUTT_NOTMUCH)))
2350  {
2351  msg->fp = maildir_open_find_message(folder, e->path, NULL);
2352  }
2353 
2354  return msg->fp != NULL;
2355 }
2356 
2361 static int nm_msg_commit(struct Mailbox *m, struct Message *msg)
2362 {
2363  mutt_error(_("Can't write to virtual folder"));
2364  return -1;
2365 }
2366 
2370 static int nm_msg_close(struct Mailbox *m, struct Message *msg)
2371 {
2372  mutt_file_fclose(&(msg->fp));
2373  return 0;
2374 }
2375 
2379 static int nm_tags_edit(struct Mailbox *m, const char *tags, struct Buffer *buf)
2380 {
2381  mutt_buffer_reset(buf);
2382  if (mutt_buffer_get_field("Add/remove labels: ", buf, MUTT_COMP_NM_TAG, false,
2383  NULL, NULL, NULL) != 0)
2384  {
2385  return -1;
2386  }
2387  return 1;
2388 }
2389 
2393 static int nm_tags_commit(struct Mailbox *m, struct Email *e, const char *buf)
2394 {
2395  if (*buf == '\0')
2396  return 0; /* no tag change, so nothing to do */
2397 
2398  struct NmMboxData *mdata = nm_mdata_get(m);
2399  if (!mdata)
2400  return -1;
2401 
2402  notmuch_database_t *db = NULL;
2403  notmuch_message_t *msg = NULL;
2404  int rc = -1;
2405 
2406  if (!(db = nm_db_get(m, true)) || !(msg = get_nm_message(db, e)))
2407  goto done;
2408 
2409  mutt_debug(LL_DEBUG1, "nm: tags modify: '%s'\n", buf);
2410 
2411  update_tags(msg, buf);
2412  update_email_flags(m, e, buf);
2413  update_email_tags(e, msg);
2414  mutt_set_header_color(m, e);
2415 
2416  rc = 0;
2417  e->changed = true;
2418 done:
2419  nm_db_release(m);
2420  if (e->changed)
2421  {
2422  m->mtime.tv_sec = mutt_date_epoch();
2423  m->mtime.tv_nsec = 0;
2424  }
2425  mutt_debug(LL_DEBUG1, "nm: tags modify done [rc=%d]\n", rc);
2426  return rc;
2427 }
2428 
2432 enum MailboxType nm_path_probe(const char *path, const struct stat *st)
2433 {
2434  if (!mutt_istr_startswith(path, NmUrlProtocol))
2435  return MUTT_UNKNOWN;
2436 
2437  return MUTT_NOTMUCH;
2438 }
2439 
2443 static int nm_path_canon(char *buf, size_t buflen)
2444 {
2445  return 0;
2446 }
2447 
2451 static int nm_path_pretty(char *buf, size_t buflen, const char *folder)
2452 {
2453  /* Succeed, but don't do anything, for now */
2454  return 0;
2455 }
2456 
2460 static int nm_path_parent(char *buf, size_t buflen)
2461 {
2462  /* Succeed, but don't do anything, for now */
2463  return 0;
2464 }
2465 
2469 struct MxOps MxNotmuchOps = {
2470  // clang-format off
2471  .type = MUTT_NOTMUCH,
2472  .name = "notmuch",
2473  .is_local = false,
2474  .ac_owns_path = nm_ac_owns_path,
2475  .ac_add = nm_ac_add,
2476  .mbox_open = nm_mbox_open,
2477  .mbox_open_append = NULL,
2478  .mbox_check = nm_mbox_check,
2479  .mbox_check_stats = nm_mbox_check_stats,
2480  .mbox_sync = nm_mbox_sync,
2481  .mbox_close = nm_mbox_close,
2482  .msg_open = nm_msg_open,
2483  .msg_open_new = maildir_msg_open_new,
2484  .msg_commit = nm_msg_commit,
2485  .msg_close = nm_msg_close,
2486  .msg_padding_size = NULL,
2487  .msg_save_hcache = NULL,
2488  .tags_edit = nm_tags_edit,
2489  .tags_commit = nm_tags_commit,
2490  .path_probe = nm_path_probe,
2491  .path_canon = nm_path_canon,
2492  .path_pretty = nm_path_pretty,
2493  .path_parent = nm_path_parent,
2494  .path_is_empty = NULL,
2495  // clang-format on
2496 };
#define ARRAY_FOREACH(elem, head)
Iterate over all elements of the array.
Definition: array.h:211
size_t mutt_buffer_strcpy(struct Buffer *buf, const char *s)
Copy a string into a Buffer.
Definition: buffer.c:310
void mutt_buffer_reset(struct Buffer *buf)
Reset an existing Buffer.
Definition: buffer.c:81
Functions to parse commands in a config file.
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
Convenience wrapper for the config headers.
Convenience wrapper for the core headers.
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:1330
void email_free(struct Email **ptr)
Free an Email.
Definition: email.c:44
Structs that make up an email.
Enter a string.
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: window.c:180
int mutt_file_fclose(FILE **fp)
Close a FILE handle (and NULL the pointer)
Definition: file.c:152
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:2002
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:1994
struct MxOps MxNotmuchOps
Notmuch Mailbox - Implements MxOps -.
Definition: notmuch.c:2469
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:1761
static enum MxStatus nm_mbox_check(struct Mailbox *m)
Check for new mail - Implements MxOps::mbox_check() -.
Definition: notmuch.c:2067
static enum MxStatus nm_mbox_close(struct Mailbox *m)
Close a Mailbox - Implements MxOps::mbox_close() -.
Definition: notmuch.c:2329
static enum MxOpenReturns nm_mbox_open(struct Mailbox *m)
Open a Mailbox - Implements MxOps::mbox_open() -.
Definition: notmuch.c:2017
static enum MxStatus nm_mbox_sync(struct Mailbox *m)
Save changes to the Mailbox - Implements MxOps::mbox_sync() -.
Definition: notmuch.c:2202
static int nm_msg_close(struct Mailbox *m, struct Message *msg)
Close an email - Implements MxOps::msg_close() -.
Definition: notmuch.c:2370
static int nm_msg_commit(struct Mailbox *m, struct Message *msg)
Save changes to an email - Implements MxOps::msg_commit() -.
Definition: notmuch.c:2361
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:1487
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:2337
static int nm_path_canon(char *buf, size_t buflen)
Canonicalise a Mailbox path - Implements MxOps::path_canon() -.
Definition: notmuch.c:2443
static int nm_path_parent(char *buf, size_t buflen)
Find the parent of a Mailbox path - Implements MxOps::path_parent() -.
Definition: notmuch.c:2460
static int nm_path_pretty(char *buf, size_t buflen, const char *folder)
Abbreviate a Mailbox path - Implements MxOps::path_pretty() -.
Definition: notmuch.c:2451
enum MailboxType nm_path_probe(const char *path, const struct stat *st)
Is this a Notmuch Mailbox? - Implements MxOps::path_probe() -.
Definition: notmuch.c:2432
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:2393
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:2379
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:548
struct HeaderCache * mutt_hcache_open(const char *path, const char *folder, hcache_namer_t namer)
Multiplexor for StoreOps::open.
Definition: hcache.c:332
void mutt_hcache_close(struct HeaderCache *hc)
Multiplexor for StoreOps::close.
Definition: hcache.c:432
struct HCacheEntry mutt_hcache_fetch(struct HeaderCache *hc, const char *key, size_t keylen, uint32_t uidvalidity)
Multiplexor for StoreOps::fetch.
Definition: hcache.c:456
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:237
void mailbox_changed(struct Mailbox *m, enum NotifyMailbox action)
Notify observers of a change to a Mailbox.
Definition: mailbox.c:222
@ NT_MAILBOX_INVALID
Email list was changed.
Definition: mailbox.h:177
static const char * mailbox_path(const struct Mailbox *m)
Get the Mailbox's path string.
Definition: mailbox.h:211
MailboxType
Supported mailbox formats.
Definition: mailbox.h:41
@ MUTT_NOTMUCH
'Notmuch' (virtual) Mailbox type
Definition: mailbox.h:51
@ MUTT_UNKNOWN
Mailbox wasn't recognised.
Definition: mailbox.h:44
@ MUTT_MAILDIR
'Maildir' Mailbox type
Definition: mailbox.h:48
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:881
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:995
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:928
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:948
void maildir_parse_flags(struct Email *e, const char *path)
Parse Maildir file flags.
Definition: maildir.c:809
void * mutt_mem_malloc(size_t size)
Allocate memory on the heap.
Definition: memory.c:90
#define FREE(x)
Definition: memory.h:43
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:428
int mutt_str_asprintf(char **strp, const char *fmt,...)
Definition: string.c:1008
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:250
void mutt_str_append_item(char **str, const char *item, char sep)
Add string to another separated by sep.
Definition: string.c:346
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition: string.c:784
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:473
size_t mutt_str_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix.
Definition: string.c:227
size_t mutt_str_len(const char *a)
Calculate the length of a string, safely.
Definition: string.c:544
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:629
char * mutt_str_replace(char **p, const char *s)
Replace one string with another.
Definition: string.c:326
size_t mutt_istr_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix, ignoring case.
Definition: string.c:239
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:93
@ MUTT_FLAG
Flagged messages.
Definition: mutt.h:99
@ MUTT_REPLIED
Messages that have been replied to.
Definition: mutt.h:92
#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:69
struct HashTable * mutt_make_id_hash(struct Mailbox *m)
Create a Hash Table for message-ids.
Definition: mutt_thread.c:1651
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:1219
API for mailboxes.
MxOpenReturns
Return values for mbox_open()
Definition: mxapi.h:97
@ MX_OPEN_ERROR
Open failed with an error.
Definition: mxapi.h:99
@ MX_OPEN_ABORT
Open was aborted.
Definition: mxapi.h:100
@ MX_OPEN_OK
Open succeeded.
Definition: mxapi.h:98
MxStatus
Return values from mbox_check(), mbox_check_stats(), mbox_snc(), and mbox_close()
Definition: mxapi.h:84
@ MX_STATUS_ERROR
An error occurred.
Definition: mxapi.h:85
@ MX_STATUS_OK
No changes.
Definition: mxapi.h:86
@ MX_STATUS_FLAGS
Nondestructive flags change (IMAP)
Definition: mxapi.h:90
@ MX_STATUS_REOPENED
Mailbox was reopened.
Definition: mxapi.h:89
@ MX_STATUS_NEW_MAIL
New mail received in Mailbox.
Definition: mxapi.h:87
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:226
const char * nm_db_get_filename(struct Mailbox *m)
Get the filename of the Notmuch database.
Definition: db.c:54
notmuch_database_t * nm_db_get(struct Mailbox *m, bool writable)
Get the Notmuch database.
Definition: db.c:169
int nm_db_get_mtime(struct Mailbox *m, time_t *mtime)
Get the database modification time.
Definition: db.c:275
int nm_db_release(struct Mailbox *m)
Close the Notmuch database.
Definition: db.c:193
notmuch_database_t * nm_db_do_open(const char *filename, bool writable, bool verbose)
Open a Notmuch database.
Definition: db.c:86
void nm_db_free(notmuch_database_t *db)
Decoupled way to close a Notmuch database.
Definition: db.c:210
int nm_db_trans_end(struct Mailbox *m)
End a database transaction.
Definition: db.c:248
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:97
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:1845
int nm_update_filename(struct Mailbox *m, const char *old_file, const char *new_file, struct Email *e)
Change the filename.
Definition: notmuch.c:1736
void nm_query_window_reset(void)
Resets the vfolder window position to the present.
Definition: notmuch.c:1662
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:405
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:269
int nm_get_all_tags(struct Mailbox *m, const char **tag_list, int *tag_count)
Fill a list with all notmuch tags.
Definition: notmuch.c:1950
static unsigned int count_query(notmuch_database_t *db, const char *qstr, int limit)
Count the results of a query.
Definition: notmuch.c:1416
static char * nm_get_default_url(void)
Create a Mailbox with default Notmuch settings.
Definition: notmuch.c:135
static int init_mailbox(struct Mailbox *m)
Add Notmuch data to the Mailbox.
Definition: notmuch.c:186
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:1468
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:846
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:646
static int rename_maildir_filename(const char *old, char *buf, size_t buflen, struct Email *e)
Rename a Maildir file.
Definition: notmuch.c:1195
static void sync_email_path_with_nm(struct Email *e, notmuch_message_t *msg)
Synchronize Neomutt's Email path with notmuch.
Definition: notmuch.c:1072
static char * get_folder_from_path(const char *path)
Find an email's folder from its path.
Definition: notmuch.c:555
static int init_email(struct Email *e, const char *path, notmuch_message_t *msg)
Set up an email's Notmuch data.
Definition: notmuch.c:602
static void nm_hcache_close(struct HeaderCache *h)
Close the header cache.
Definition: notmuch.c:123
static int get_limit(struct NmMboxData *mdata)
Get the database limit.
Definition: notmuch.c:396
static notmuch_message_t * get_nm_message(notmuch_database_t *db, struct Email *e)
Find a Notmuch message.
Definition: notmuch.c:1031
static bool read_threads_query(struct Mailbox *m, notmuch_query_t *q, bool dedup, int limit)
Perform a query with threads.
Definition: notmuch.c:995
int nm_record_message(struct Mailbox *m, char *path, struct Email *e)
Add a message to the Notmuch database.
Definition: notmuch.c:1868
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:873
char * nm_email_get_folder(struct Email *e)
Get the folder for a Email.
Definition: notmuch.c:1449
static char * nm2mutt_message_id(const char *id)
Converts notmuch message Id to neomutt message Id.
Definition: notmuch.c:580
static void append_message(struct HeaderCache *h, struct Mailbox *m, notmuch_message_t *msg, bool dedup)
Associate a message.
Definition: notmuch.c:733
static struct NmMboxData * nm_get_default_data(void)
Create a Mailbox with default Notmuch settings.
Definition: notmuch.c:163
static notmuch_query_t * get_query(struct Mailbox *m, bool writable)
Create a new query.
Definition: notmuch.c:431
int nm_read_entire_thread(struct Mailbox *m, struct Email *e)
Get the entire thread of an email.
Definition: notmuch.c:1488
static void query_window_reset(void)
Restore vfolder's search window to its original position.
Definition: notmuch.c:239
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:1312
static char * get_query_string(struct NmMboxData *mdata, bool window)
Builds the notmuch vfolder search string.
Definition: notmuch.c:334
void nm_query_window_backward(void)
Function to move the current search window backward in time.
Definition: notmuch.c:1651
static char * email_get_id(struct Email *e)
Get the unique Notmuch Id.
Definition: notmuch.c:208
static bool nm_message_has_tag(notmuch_message_t *msg, char *tag)
Does a message have this tag?
Definition: notmuch.c:1050
static int update_message_path(struct Email *e, const char *path)
Set the path for a message.
Definition: notmuch.c:514
bool nm_query_window_available(void)
Are windowed queries enabled for use?
Definition: notmuch.c:1614
static int update_tags(notmuch_message_t *msg, const char *tag_str)
Update the tags on a message.
Definition: notmuch.c:1089
static bool read_mesgs_query(struct Mailbox *m, notmuch_query_t *q, bool dedup)
Search for matching messages.
Definition: notmuch.c:925
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:463
bool nm_message_is_still_queried(struct Mailbox *m, struct Email *e)
Is a message still visible in the query?
Definition: notmuch.c:1674
static int update_email_flags(struct Mailbox *m, struct Email *e, const char *tag_str)
Update the Email's flags.
Definition: notmuch.c:1143
static int remove_filename(struct Mailbox *m, const char *path)
Delete a file.
Definition: notmuch.c:1248
static notmuch_threads_t * get_threads(notmuch_query_t *query)
Load threads for a query.
Definition: notmuch.c:967
static void progress_setup(struct Mailbox *m)
Set up the Progress Bar.
Definition: notmuch.c:663
void nm_query_window_forward(void)
Function to move the current search window forward in time.
Definition: notmuch.c:1631
const char NmUrlProtocol[]
Definition: notmuch.c:93
static void nm_progress_update(struct Mailbox *m)
Update the progress counter.
Definition: notmuch.c:682
char * nm_url_from_query(struct Mailbox *m, char *buf, size_t buflen)
Turn a query into a URL.
Definition: notmuch.c:1555
static struct Email * get_mutt_email(struct Mailbox *m, notmuch_message_t *msg)
Get the Email of a Notmuch message.
Definition: notmuch.c:699
static char * email_get_fullpath(struct Email *e, char *buf, size_t buflen)
Get the full path of an email.
Definition: notmuch.c:224
static notmuch_messages_t * get_messages(notmuch_query_t *query)
Load messages for a query.
Definition: notmuch.c:897
Pop-specific Account data.
Pop-specific Email data.
Progress bar.
@ MUTT_PROGRESS_READ
Progress tracks elements, according to $read_inc
Definition: lib.h:49
@ MUTT_PROGRESS_WRITE
Progress tracks elements, according to $write_inc
Definition: lib.h:50
void progress_free(struct Progress **ptr)
Free a Progress Bar.
Definition: progress.c:86
struct Progress * progress_new(const char *msg, enum ProgressType type, size_t size)
Create a new Progress Bar.
Definition: progress.c:118
bool progress_update(struct Progress *progress, size_t pos, int percent)
Update the state of the progress bar.
Definition: progress.c:73
Prototypes for many functions.
#define mutt_set_flag(m, e, flag, bf)
Definition: protos.h:64
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:73
struct Email * email
Retrieved email.
Definition: lib.h:101
Header cache structure.
Definition: lib.h:87
A mailbox.
Definition: mailbox.h:79
void(* mdata_free)(void **ptr)
Free the private data attached to the Mailbox.
Definition: mailbox.h:143
struct timespec mtime
Time Mailbox was last changed.
Definition: mailbox.h:104
int msg_new
Number of new messages.
Definition: mailbox.h:92
int msg_count
Total number of messages.
Definition: mailbox.h:88
int email_max
Number of pointers in emails.
Definition: mailbox.h:97
enum MailboxType type
Mailbox type.
Definition: mailbox.h:102
void * mdata
Driver specific data.
Definition: mailbox.h:133
struct Email ** emails
Array of Emails.
Definition: mailbox.h:96
struct HashTable * id_hash
Hash Table by msg id.
Definition: mailbox.h:124
struct Buffer pathbuf
Path of the Mailbox.
Definition: mailbox.h:80
int msg_flagged
Number of flagged messages.
Definition: mailbox.h:90
bool verbose
Display status messages?
Definition: mailbox.h:115
int msg_unread
Number of unread messages.
Definition: mailbox.h:89
A local copy of an email.
Definition: mxapi.h:43
FILE * fp
pointer to the message data
Definition: mxapi.h:44
Definition: mxapi.h:112
enum MailboxType type
Mailbox type, e.g. MUTT_IMAP.
Definition: mxapi.h:113
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
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:186
char * driver_tags_get_transformed(struct TagList *list)
Get transformed tags.
Definition: tags.c:133
char * driver_tags_get(struct TagList *list)
Get tags.
Definition: tags.c:145
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