NeoMutt  2025-12-11-911-gd8d604
Teaching an old dog new tricks
DOXYGEN
Loading...
Searching...
No Matches
notmuch.c
Go to the documentation of this file.
1
34
51
52#include "config.h"
53#include <errno.h>
54#include <limits.h>
55#include <notmuch.h>
56#include <stdbool.h>
57#include <stdint.h>
58#include <stdio.h>
59#include <string.h>
60#include <time.h>
61#include <unistd.h>
62#include "private.h"
63#include "mutt/lib.h"
64#include "config/lib.h"
65#include "email/lib.h"
66#include "core/lib.h"
67#include "gui/lib.h"
68#include "mutt.h"
69#include "lib.h"
70#include "editor/lib.h"
71#include "hcache/lib.h"
72#include "history/lib.h"
73#include "index/lib.h"
74#include "progress/lib.h"
75#include "adata.h"
76#include "edata.h"
77#include "maildir/shared.h"
78#include "mdata.h"
79#include "mx.h"
80#include "query.h"
81#include "tag.h"
82#ifdef ENABLE_NLS
83#include <libintl.h>
84#endif
85
86struct stat;
87
91const struct Command NmCommands[] = {
92 // clang-format off
93 // Deprecated
94 { "unvirtual-mailboxes", CMD_NONE, NULL, "unmailboxes", NULL, NULL, CF_SYNONYM },
95 { "virtual-mailboxes", CMD_NONE, NULL, "named-mailboxes", NULL, NULL, CF_SYNONYM },
96
97 { NULL, CMD_NONE, NULL, NULL, NULL, NULL, CF_NONE },
98 // clang-format on
99};
100
102const char NmUrlProtocol[] = "notmuch://";
104const int NmUrlProtocolLen = sizeof(NmUrlProtocol) - 1;
105
111static struct HeaderCache *nm_hcache_open(struct Mailbox *m)
112{
113#ifdef USE_HCACHE
114 const char *const c_header_cache = cs_subset_path(NeoMutt->sub, "header_cache");
115 return hcache_open(c_header_cache, mailbox_path(m), NULL, true);
116#else
117 return NULL;
118#endif
119}
120
125static void nm_hcache_close(struct HeaderCache **ptr)
126{
127#ifdef USE_HCACHE
128 hcache_close(ptr);
129#endif
130}
131
137static char *nm_get_default_url(void)
138{
139 // path to DB + query + url "decoration"
140 size_t len = PATH_MAX + 1024 + 32;
141 char *url = MUTT_MEM_MALLOC(len, char);
142
143 // Try to use `$nm_default_url` or `$folder`.
144 // If neither are set, it is impossible to create a Notmuch URL.
145 const char *const c_nm_default_url = 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 {
149 snprintf(url, len, "%s", c_nm_default_url);
150 }
151 else if (c_folder)
152 {
153 snprintf(url, len, "notmuch://%s", c_folder);
154 }
155 else
156 {
157 FREE(&url);
158 return NULL;
159 }
160
161 return url;
162}
163
169static struct NmMboxData *nm_get_default_data(void)
170{
171 // path to DB + query + url "decoration"
172 char *url = nm_get_default_url();
173 if (!url)
174 return NULL;
175
176 struct NmMboxData *default_data = nm_mdata_new(url);
177 FREE(&url);
178
179 return default_data;
180}
181
192static int init_mailbox(struct Mailbox *m)
193{
194 if (!m || (m->type != MUTT_NOTMUCH))
195 return -1;
196
197 if (m->mdata)
198 return 0;
199
201 if (!m->mdata)
202 return -1;
203
205 return 0;
206}
207
214static char *email_get_id(struct Email *e)
215{
216 struct NmEmailData *edata = nm_edata_get(e);
217 if (!edata)
218 return NULL;
219
220 return edata->virtual_id;
221}
222
230static char *email_get_fullpath(struct Email *e, char *buf, size_t buflen)
231{
232 char *folder = nm_email_get_folder(e);
233 if (!folder)
234 {
235 *buf = '\0';
236 return buf;
237 }
238
239 snprintf(buf, buflen, "%s/%s", folder, e->path);
240 return buf;
241}
242
252static void query_window_reset(void)
253{
254 mutt_debug(LL_DEBUG2, "entering\n");
255 cs_subset_str_native_set(NeoMutt->sub, "nm_query_window_current_position", 0, NULL);
256}
257
282static bool windowed_query_from_query(const char *query, char *buf, size_t buflen)
283{
284 mutt_debug(LL_DEBUG2, "nm: %s\n", query);
285
286 const bool c_nm_query_window_enable = cs_subset_bool(NeoMutt->sub, "nm_query_window_enable");
287 const short c_nm_query_window_duration = cs_subset_number(NeoMutt->sub, "nm_query_window_duration");
288 const short c_nm_query_window_current_position = cs_subset_number(NeoMutt->sub, "nm_query_window_current_position");
289 const char *const c_nm_query_window_current_search = cs_subset_string(NeoMutt->sub, "nm_query_window_current_search");
290 const enum NmTimebase c_nm_query_window_timebase = cs_subset_enum(NeoMutt->sub, "nm_query_window_timebase");
291 const char *const c_nm_query_window_or_terms = cs_subset_string(NeoMutt->sub, "nm_query_window_or_terms");
292
293 /* if the query has changed, reset the window position */
294 if (!c_nm_query_window_current_search || !mutt_str_equal(query, c_nm_query_window_current_search))
295 {
297 }
298
300 buf, buflen, c_nm_query_window_enable, c_nm_query_window_duration,
301 c_nm_query_window_current_position, c_nm_query_window_current_search,
302 c_nm_query_window_timebase, c_nm_query_window_or_terms);
303
304 switch (rc)
305 {
307 {
308 mutt_debug(LL_DEBUG2, "nm: %s -> %s\n", query, buf);
309 break;
310 }
312 {
314 return false;
315 }
317 {
319 // L10N: The values 'hour', 'day', 'week', 'month', 'year' are literal.
320 // They should not be translated.
321 _("Invalid nm_query_window_timebase value (valid values are: hour, day, week, month, year)"));
322 mutt_debug(LL_DEBUG2, "Invalid nm_query_window_timebase value\n");
323 return false;
324 }
325 }
326
327 return true;
328}
329
346static char *get_query_string(struct NmMboxData *mdata, bool window)
347{
348 mutt_debug(LL_DEBUG2, "nm: %s\n", window ? "true" : "false");
349
350 if (!mdata)
351 return NULL;
352 if (mdata->db_query && !window)
353 return mdata->db_query;
354
355 const enum NmQueryType c_nm_query_type = cs_subset_enum(NeoMutt->sub, "nm_query_type");
356 mdata->query_type = c_nm_query_type; /* user's default */
357
358 struct UrlQuery *item = NULL;
359 STAILQ_FOREACH(item, &mdata->db_url->query_strings, entries)
360 {
361 if (!item->value || !item->name)
362 continue;
363
364 if (mutt_str_equal(item->name, "limit"))
365 {
366 if (!mutt_str_atoi_full(item->value, &mdata->db_limit))
367 {
368 mutt_error(_("failed to parse notmuch limit: %s"), item->value);
369 }
370 }
371 else if (mutt_str_equal(item->name, "type"))
372 {
374 }
375 else if (mutt_str_equal(item->name, "query"))
376 {
377 mutt_str_replace(&mdata->db_query, item->value);
378 }
379 }
380
381 if (!mdata->db_query)
382 return NULL;
383
384 if (window)
385 {
386 char buf[1024] = { 0 };
387 cs_subset_str_string_set(NeoMutt->sub, "nm_query_window_current_search",
388 mdata->db_query, NULL);
389
390 /* if a date part is defined, do not apply windows (to avoid the risk of
391 * having a non-intersected date frame). A good improvement would be to
392 * accept if they intersect */
393 if (!strstr(mdata->db_query, "date:") &&
394 windowed_query_from_query(mdata->db_query, buf, sizeof(buf)))
395 {
396 mutt_str_replace(&mdata->db_query, buf);
397 }
398
399 mutt_debug(LL_DEBUG2, "nm: query (windowed) '%s'\n", mdata->db_query);
400 }
401 else
402 {
403 mutt_debug(LL_DEBUG2, "nm: query '%s'\n", mdata->db_query);
404 }
405
406 return mdata->db_query;
407}
408
414static int get_limit(struct NmMboxData *mdata)
415{
416 return mdata ? mdata->db_limit : 0;
417}
418
423static void apply_exclude_tags(notmuch_query_t *query)
424{
425 const char *const c_nm_exclude_tags = cs_subset_string(NeoMutt->sub, "nm_exclude_tags");
426 if (!c_nm_exclude_tags || !query)
427 return;
428
429 struct NmTags tags = nm_tag_str_to_tags(c_nm_exclude_tags);
430
431 const char **tag = NULL;
432 ARRAY_FOREACH(tag, &tags.tags)
433 {
434 mutt_debug(LL_DEBUG2, "nm: query exclude tag '%s'\n", *tag);
435 notmuch_query_add_tag_exclude(query, *tag);
436 }
437
438 notmuch_query_set_omit_excluded(query, 1);
440}
441
449static notmuch_query_t *get_query(struct Mailbox *m, bool writable)
450{
451 struct NmMboxData *mdata = nm_mdata_get(m);
452 if (!mdata)
453 return NULL;
454
455 notmuch_database_t *db = nm_db_get(m, writable);
456 const char *str = get_query_string(mdata, true);
457
458 if (!db || !str)
459 goto err;
460
461 notmuch_query_t *q = notmuch_query_create(db, str);
462 if (!q)
463 goto err;
464
466 notmuch_query_set_sort(q, NOTMUCH_SORT_NEWEST_FIRST);
467 mutt_debug(LL_DEBUG2, "nm: query successfully initialized (%s)\n", str);
468 return q;
469err:
470 nm_db_release(m);
471 return NULL;
472}
473
481static int update_email_tags(struct Email *e, notmuch_message_t *msg)
482{
483 struct NmEmailData *edata = nm_edata_get(e);
484 if (!edata)
485 return 1;
486
487 struct Buffer *new_tags = buf_pool_get();
488 struct Buffer *old_tags = buf_pool_get();
489
490 mutt_debug(LL_DEBUG2, "nm: tags update requested (%s)\n", edata->virtual_id);
491
492 for (notmuch_tags_t *tags = notmuch_message_get_tags(msg);
493 tags && notmuch_tags_valid(tags); notmuch_tags_move_to_next(tags))
494 {
495 const char *t = notmuch_tags_get(tags);
496 if (!t || (*t == '\0'))
497 continue;
498
499 buf_join_str(new_tags, t, ' ');
500 }
501
502 driver_tags_get(&e->tags, old_tags);
503
504 if (!buf_is_empty(new_tags) && !buf_is_empty(old_tags) &&
505 (buf_str_equal(old_tags, new_tags)))
506 {
507 buf_pool_release(&new_tags);
508 buf_pool_release(&old_tags);
509 mutt_debug(LL_DEBUG2, "nm: tags unchanged\n");
510 return 1;
511 }
512 buf_pool_release(&old_tags);
513
514 /* new version */
515 driver_tags_replace(&e->tags, buf_string(new_tags));
516 buf_reset(new_tags);
517
518 driver_tags_get_transformed(&e->tags, new_tags);
519 mutt_debug(LL_DEBUG2, "nm: new tags transformed: '%s'\n", buf_string(new_tags));
520 buf_reset(new_tags);
521
522 driver_tags_get(&e->tags, new_tags);
523 mutt_debug(LL_DEBUG2, "nm: new tag: '%s'\n", buf_string(new_tags));
524 buf_pool_release(&new_tags);
525
526 return 0;
527}
528
536static int update_message_path(struct Email *e, const char *path)
537{
538 struct NmEmailData *edata = nm_edata_get(e);
539 if (!edata)
540 return 1;
541
542 mutt_debug(LL_DEBUG2, "nm: path update requested path=%s, (%s)\n", path, edata->virtual_id);
543
544 const char *p = strrchr(path, '/');
545 if (p && ((p - path) > 3) &&
546 (mutt_strn_equal(p - 3, "cur", 3) || mutt_strn_equal(p - 3, "new", 3) ||
547 mutt_strn_equal(p - 3, "tmp", 3)))
548 {
549 edata->type = MUTT_MAILDIR;
550
551 FREE(&e->path);
552 FREE(&edata->folder);
553
554 p -= 3; /* skip subfolder (e.g. "new") */
555 if (cs_subset_bool(NeoMutt->sub, "mark_old"))
556 {
557 e->old = mutt_str_startswith(p, "cur");
558 }
559 e->path = mutt_str_dup(p);
560
561 for (; (p > path) && (*(p - 1) == '/'); p--)
562 ; // do nothing
563
564 edata->folder = mutt_strn_dup(path, p - path);
565
566 mutt_debug(LL_DEBUG2, "nm: folder='%s', file='%s'\n", edata->folder, e->path);
567
568 // We _might_ be looking at a different file (with the same Message-ID)
569 // so reparse it from scratch.
570
571 // Preserve the Message-ID as it's used in the Email HashTable
572 mutt_debug(LL_DEBUG1, "nm: reparse the message\n");
573 char *message_id = e->env->message_id;
574 e->env->message_id = NULL;
575
576 mutt_body_free(&e->body);
577 mutt_env_free(&e->env);
578 if (!maildir_parse_message(path, e->old, e))
579 return 1;
580
581 ASSERT(e->body);
582 ASSERT(e->env);
583
584 FREE(&e->env->message_id);
585 e->env->message_id = message_id;
586 message_id = NULL;
587
588 return 0;
589 }
590
591 return 1;
592}
593
600static char *get_folder_from_path(const char *path)
601{
602 const char *p = strrchr(path, '/');
603
604 if (p && ((p - path) > 3) &&
605 (mutt_strn_equal(p - 3, "cur", 3) || mutt_strn_equal(p - 3, "new", 3) ||
606 mutt_strn_equal(p - 3, "tmp", 3)))
607 {
608 p -= 3;
609 for (; (p > path) && (*(p - 1) == '/'); p--)
610 ; // do nothing
611
612 return mutt_strn_dup(path, p - path);
613 }
614
615 return NULL;
616}
617
625static char *nm2mutt_message_id(const char *id)
626{
627 if (!id)
628 return NULL;
629
630 char *mid = NULL;
631 mutt_str_asprintf(&mid, "<%s>", id);
632 return mid;
633}
634
643static int init_email(struct Email *e, const char *path, notmuch_message_t *msg)
644{
645 if (nm_edata_get(e))
646 return 0;
647
648 struct NmEmailData *edata = nm_edata_new();
649 e->nm_edata = edata;
650
651 /* Notmuch ensures that message Id exists (if not notmuch Notmuch will
652 * generate an ID), so it's more safe than use neomutt Email->env->id */
653 const char *id = notmuch_message_get_message_id(msg);
654 edata->virtual_id = mutt_str_dup(id);
655
656 mutt_debug(LL_DEBUG2, "nm: [e=%p, edata=%p] (%s)\n", (void *) e, (void *) edata, id);
657
658 char *nm_msg_id = nm2mutt_message_id(id);
659 if (!e->env->message_id)
660 {
661 e->env->message_id = nm_msg_id;
662 }
663 else if (!mutt_str_equal(e->env->message_id, nm_msg_id))
664 {
665 FREE(&e->env->message_id);
666 e->env->message_id = nm_msg_id;
667 }
668 else
669 {
670 FREE(&nm_msg_id);
671 }
672
673 if (update_message_path(e, path) != 0)
674 return -1;
675
676 update_email_tags(e, msg);
677
678 return 0;
679}
680
687static char *get_message_last_filename(notmuch_message_t *msg)
688{
689 const char *name = NULL;
690
691 for (notmuch_filenames_t *ls = notmuch_message_get_filenames(msg);
692 ls && notmuch_filenames_valid(ls); notmuch_filenames_move_to_next(ls))
693 {
694 name = notmuch_filenames_get(ls);
695 }
696
697 return mutt_str_dup(name);
698}
699
704static void progress_setup(struct Mailbox *m)
705{
706 if (!m->verbose)
707 return;
708
709 struct NmMboxData *mdata = nm_mdata_get(m);
710 if (!mdata)
711 return;
712
713 mdata->oldmsgcount = m->msg_count;
714 mdata->ignmsgcount = 0;
715 mdata->progress = progress_new(MUTT_PROGRESS_READ, mdata->oldmsgcount);
716 progress_set_message(mdata->progress, _("Reading messages..."));
717}
718
723static void nm_progress_update(struct Mailbox *m)
724{
725 struct NmMboxData *mdata = nm_mdata_get(m);
726
727 if (!m->verbose || !mdata || !mdata->progress)
728 return;
729
730 progress_update(mdata->progress, m->msg_count + mdata->ignmsgcount, -1);
731}
732
740static struct Email *get_mutt_email(struct Mailbox *m, notmuch_message_t *msg)
741{
742 if (!m || !msg)
743 return NULL;
744
745 const char *id = notmuch_message_get_message_id(msg);
746 if (!id)
747 return NULL;
748
749 mutt_debug(LL_DEBUG2, "nm: neomutt email, id='%s'\n", id);
750
751 if (!m->id_hash)
752 {
753 mutt_debug(LL_DEBUG2, "nm: init hash\n");
755 if (!m->id_hash)
756 return NULL;
757 }
758
759 char *mid = nm2mutt_message_id(id);
760 mutt_debug(LL_DEBUG2, "nm: neomutt id='%s'\n", mid);
761
762 struct Email *e = mutt_hash_find(m->id_hash, mid);
763 FREE(&mid);
764 return e;
765}
766
774static void append_message(struct HeaderCache *hc, struct Mailbox *m,
775 notmuch_message_t *msg, bool dedup)
776{
777 struct NmMboxData *mdata = nm_mdata_get(m);
778 if (!mdata)
779 return;
780
781 char *newpath = NULL;
782 struct Email *e = NULL;
783
784 /* deduplicate */
785 if (dedup && get_mutt_email(m, msg))
786 {
787 mdata->ignmsgcount++;
789 mutt_debug(LL_DEBUG2, "nm: ignore id=%s, already in the m\n",
790 notmuch_message_get_message_id(msg));
791 return;
792 }
793
794 char *path = get_message_last_filename(msg);
795 if (!path)
796 return;
797
798 mutt_debug(LL_DEBUG2, "nm: appending message, i=%d, id=%s, path=%s\n",
799 m->msg_count, notmuch_message_get_message_id(msg), path);
800
802
803#ifdef USE_HCACHE
805 if (!e)
806#endif
807 {
808 if (access(path, F_OK) == 0)
809 {
810 /* We pass is_old=false as argument here, but e->old will be updated later
811 * by update_message_path() (called by init_email() below). */
812 e = maildir_email_new();
813 if (!maildir_parse_message(path, false, e))
814 email_free(&e);
815 }
816 else
817 {
818 /* maybe moved try find it... */
819 char *folder = get_folder_from_path(path);
820
821 if (folder)
822 {
823 FILE *fp = maildir_open_find_message(folder, path, &newpath);
824 if (fp)
825 {
826 e = maildir_email_new();
827 if (!maildir_parse_stream(fp, newpath, false, e))
828 email_free(&e);
829 mutt_file_fclose(&fp);
830
831 mutt_debug(LL_DEBUG1, "nm: not up-to-date: %s -> %s\n", path, newpath);
832 }
833 }
834 FREE(&folder);
835 }
836
837 if (!e)
838 {
839 mutt_debug(LL_DEBUG1, "nm: failed to parse message: %s\n", path);
840 goto done;
841 }
842
843#ifdef USE_HCACHE
844 hcache_store_email(hc, newpath ? newpath : path,
845 mutt_str_len(newpath ? newpath : path), e, 0);
846#endif
847 }
848
849 if (init_email(e, newpath ? newpath : path, msg) != 0)
850 {
851 email_free(&e);
852 mutt_debug(LL_DEBUG1, "nm: failed to append email!\n");
853 goto done;
854 }
855
856 e->active = true;
857 e->index = m->msg_count;
858 mailbox_size_add(m, e);
859 m->emails[m->msg_count] = e;
860 m->msg_count++;
861
862 if (newpath)
863 {
864 /* remember that file has been moved -- nm_mbox_sync() will update the DB */
865 struct NmEmailData *edata = nm_edata_get(e);
866 if (edata)
867 {
868 mutt_debug(LL_DEBUG1, "nm: remember obsolete path: %s\n", path);
869 edata->oldpath = mutt_str_dup(path);
870 }
871 }
873done:
874 FREE(&newpath);
875 FREE(&path);
876}
877
889static void append_replies(struct HeaderCache *hc, struct Mailbox *m, notmuch_query_t *q,
890 notmuch_message_t *top, bool dedup, int depth)
891{
892 if (depth > 512)
893 {
894 mutt_debug(LL_DEBUG1, "nm: stripping thread replies stripping at depth %d\n", depth);
895 return;
896 }
897
898 notmuch_messages_t *msgs = NULL;
899
900 for (msgs = notmuch_message_get_replies(top); notmuch_messages_valid(msgs);
901 notmuch_messages_move_to_next(msgs))
902 {
903 notmuch_message_t *nm = notmuch_messages_get(msgs);
904 append_message(hc, m, nm, dedup);
905 /* recurse through all the replies to this message too */
906 append_replies(hc, m, q, nm, dedup, depth + 1);
907 notmuch_message_destroy(nm);
908 }
909}
910
922static void append_thread(struct HeaderCache *hc, struct Mailbox *m,
923 notmuch_query_t *q, notmuch_thread_t *thread, bool dedup)
924{
925 notmuch_messages_t *msgs = NULL;
926
927 for (msgs = notmuch_thread_get_toplevel_messages(thread);
928 notmuch_messages_valid(msgs); notmuch_messages_move_to_next(msgs))
929 {
930 notmuch_message_t *nm = notmuch_messages_get(msgs);
931 append_message(hc, m, nm, dedup);
932 append_replies(hc, m, q, nm, dedup, 0);
933 notmuch_message_destroy(nm);
934 }
935}
936
946static notmuch_messages_t *get_messages(notmuch_query_t *query)
947{
948 if (!query)
949 return NULL;
950
951 notmuch_messages_t *msgs = NULL;
952
953#if LIBNOTMUCH_CHECK_VERSION(5, 0, 0)
954 if (notmuch_query_search_messages(query, &msgs) != NOTMUCH_STATUS_SUCCESS)
955 return NULL;
956#elif LIBNOTMUCH_CHECK_VERSION(4, 3, 0)
957 if (notmuch_query_search_messages_st(query, &msgs) != NOTMUCH_STATUS_SUCCESS)
958 return NULL;
959#else
960 msgs = notmuch_query_search_messages(query);
961#endif
962
963 return msgs;
964}
965
974static bool read_mesgs_query(struct Mailbox *m, notmuch_query_t *q, bool dedup)
975{
976 struct NmMboxData *mdata = nm_mdata_get(m);
977 if (!mdata)
978 return false;
979
980 int limit = get_limit(mdata);
981
982 notmuch_messages_t *msgs = get_messages(q);
983
984 if (!msgs)
985 return false;
986
987 struct HeaderCache *hc = nm_hcache_open(m);
988
989 for (; notmuch_messages_valid(msgs) && ((limit == 0) || (m->msg_count < limit));
990 notmuch_messages_move_to_next(msgs))
991 {
992 if (SigInt)
993 {
994 nm_hcache_close(&hc);
995 SigInt = false;
996 return false;
997 }
998 notmuch_message_t *nm = notmuch_messages_get(msgs);
999 append_message(hc, m, nm, dedup);
1000 notmuch_message_destroy(nm);
1001 }
1002
1003 nm_hcache_close(&hc);
1004 return true;
1005}
1006
1016static notmuch_threads_t *get_threads(notmuch_query_t *query)
1017{
1018 if (!query)
1019 return NULL;
1020
1021 notmuch_threads_t *threads = NULL;
1022#if LIBNOTMUCH_CHECK_VERSION(5, 0, 0)
1023 if (notmuch_query_search_threads(query, &threads) != NOTMUCH_STATUS_SUCCESS)
1024 return NULL;
1025#elif LIBNOTMUCH_CHECK_VERSION(4, 3, 0)
1026 if (notmuch_query_search_threads_st(query, &threads) != NOTMUCH_STATUS_SUCCESS)
1027 return NULL;
1028#else
1029 threads = notmuch_query_search_threads(query);
1030#endif
1031
1032 return threads;
1033}
1034
1044static bool read_threads_query(struct Mailbox *m, notmuch_query_t *q, bool dedup, int limit)
1045{
1046 struct NmMboxData *mdata = nm_mdata_get(m);
1047 if (!mdata)
1048 return false;
1049
1050 notmuch_threads_t *threads = get_threads(q);
1051 if (!threads)
1052 return false;
1053
1054 struct HeaderCache *hc = nm_hcache_open(m);
1055
1056 for (; notmuch_threads_valid(threads) && ((limit == 0) || (m->msg_count < limit));
1057 notmuch_threads_move_to_next(threads))
1058 {
1059 if (SigInt)
1060 {
1061 nm_hcache_close(&hc);
1062 SigInt = false;
1063 return false;
1064 }
1065 notmuch_thread_t *thread = notmuch_threads_get(threads);
1066 append_thread(hc, m, q, thread, dedup);
1067 notmuch_thread_destroy(thread);
1068 }
1069
1070 nm_hcache_close(&hc);
1071 return true;
1072}
1073
1081static notmuch_message_t *get_nm_message(notmuch_database_t *db, struct Email *e)
1082{
1083 notmuch_message_t *msg = NULL;
1084 char *id = email_get_id(e);
1085
1086 mutt_debug(LL_DEBUG2, "nm: find message (%s)\n", id);
1087
1088 if (id && db)
1089 notmuch_database_find_message(db, id, &msg);
1090
1091 return msg;
1092}
1093
1100static bool nm_message_has_tag(notmuch_message_t *msg, const char *tag)
1101{
1102 const char *possible_match_tag = NULL;
1103 notmuch_tags_t *tags = NULL;
1104
1105 for (tags = notmuch_message_get_tags(msg); notmuch_tags_valid(tags);
1106 notmuch_tags_move_to_next(tags))
1107 {
1108 possible_match_tag = notmuch_tags_get(tags);
1109 if (mutt_str_equal(possible_match_tag, tag))
1110 {
1111 return true;
1112 }
1113 }
1114 return false;
1115}
1116
1122static void sync_email_path_with_nm(struct Email *e, notmuch_message_t *msg)
1123{
1124 char *new_file = get_message_last_filename(msg);
1125 char old_file[PATH_MAX] = { 0 };
1126 email_get_fullpath(e, old_file, sizeof(old_file));
1127
1128 if (!mutt_str_equal(old_file, new_file))
1129 update_message_path(e, new_file);
1130
1131 FREE(&new_file);
1132}
1133
1141static int update_tags(notmuch_message_t *msg, const char *tag_str)
1142{
1143 if (!tag_str)
1144 return -1;
1145
1146 notmuch_message_freeze(msg);
1147
1149 const char **tag_elem = NULL;
1150 ARRAY_FOREACH(tag_elem, &tags.tags)
1151 {
1152 const char *tag = *tag_elem;
1153
1154 if (tag[0] == '-')
1155 {
1156 mutt_debug(LL_DEBUG1, "nm: remove tag: '%s'\n", tag + 1);
1157 notmuch_message_remove_tag(msg, tag + 1);
1158 }
1159 else if (tag[0] == '!')
1160 {
1161 mutt_debug(LL_DEBUG1, "nm: toggle tag: '%s'\n", tag + 1);
1162 if (nm_message_has_tag(msg, tag + 1))
1163 {
1164 notmuch_message_remove_tag(msg, tag + 1);
1165 }
1166 else
1167 {
1168 notmuch_message_add_tag(msg, tag + 1);
1169 }
1170 }
1171 else
1172 {
1173 mutt_debug(LL_DEBUG1, "nm: add tag: '%s'\n", (tag[0] == '+') ? tag + 1 : tag);
1174 notmuch_message_add_tag(msg, (tag[0] == '+') ? tag + 1 : tag);
1175 }
1176 }
1177
1178 notmuch_message_thaw(msg);
1180
1181 return 0;
1182}
1183
1195static int update_email_flags(struct Mailbox *m, struct Email *e, const char *tag_str)
1196{
1197 if (!tag_str)
1198 return -1;
1199
1200 const char *const c_nm_unread_tag = cs_subset_string(NeoMutt->sub, "nm_unread_tag");
1201 const char *const c_nm_replied_tag = cs_subset_string(NeoMutt->sub, "nm_replied_tag");
1202 const char *const c_nm_flagged_tag = cs_subset_string(NeoMutt->sub, "nm_flagged_tag");
1203
1205 const char **tag_elem = NULL;
1206 ARRAY_FOREACH(tag_elem, &tags.tags)
1207 {
1208 const char *tag = *tag_elem;
1209
1210 if (tag[0] == '-')
1211 {
1212 tag++;
1213 if (mutt_str_equal(tag, c_nm_unread_tag))
1214 mutt_set_flag(m, e, MUTT_READ, true, true);
1215 else if (mutt_str_equal(tag, c_nm_replied_tag))
1216 mutt_set_flag(m, e, MUTT_REPLIED, false, true);
1217 else if (mutt_str_equal(tag, c_nm_flagged_tag))
1218 mutt_set_flag(m, e, MUTT_FLAG, false, true);
1219 }
1220 else
1221 {
1222 tag = (tag[0] == '+') ? tag + 1 : tag;
1223 if (mutt_str_equal(tag, c_nm_unread_tag))
1224 mutt_set_flag(m, e, MUTT_READ, false, true);
1225 else if (mutt_str_equal(tag, c_nm_replied_tag))
1226 mutt_set_flag(m, e, MUTT_REPLIED, true, true);
1227 else if (mutt_str_equal(tag, c_nm_flagged_tag))
1228 mutt_set_flag(m, e, MUTT_FLAG, true, true);
1229 }
1230 }
1231
1233
1234 return 0;
1235}
1236
1247static int rename_maildir_filename(const char *old, char *buf, size_t buflen, struct Email *e)
1248{
1249 char filename[PATH_MAX] = { 0 };
1250 char suffix[PATH_MAX] = { 0 };
1251 char folder[PATH_MAX] = { 0 };
1252
1253 mutt_str_copy(folder, old, sizeof(folder));
1254 char *p = strrchr(folder, '/');
1255 if (p)
1256 {
1257 *p = '\0';
1258 p++;
1259 }
1260 else
1261 {
1262 p = folder;
1263 }
1264
1265 mutt_str_copy(filename, p, sizeof(filename));
1266
1267 /* remove (new,cur,...) from folder path */
1268 p = strrchr(folder, '/');
1269 if (p)
1270 *p = '\0';
1271
1272 /* remove old flags from filename */
1273 const char c_maildir_field_delimiter = *cc_maildir_field_delimiter();
1274 p = strchr(filename, c_maildir_field_delimiter);
1275 if (p)
1276 *p = '\0';
1277
1278 /* compose new flags */
1279 maildir_gen_flags(suffix, sizeof(suffix), e);
1280
1281 snprintf(buf, buflen, "%s/%s/%s%s", folder,
1282 (e->read || e->old) ? "cur" : "new", filename, suffix);
1283
1284 if (mutt_str_equal(old, buf))
1285 return 1;
1286
1287 if (rename(old, buf) != 0)
1288 {
1289 mutt_debug(LL_DEBUG1, "nm: rename(2) failed %s -> %s\n", old, buf);
1290 return -1;
1291 }
1292
1293 return 0;
1294}
1295
1303static int remove_filename(struct Mailbox *m, const char *path)
1304{
1305 struct NmMboxData *mdata = nm_mdata_get(m);
1306 if (!mdata)
1307 return -1;
1308
1309 mutt_debug(LL_DEBUG2, "nm: remove filename '%s'\n", path);
1310
1311 notmuch_database_t *db = nm_db_get(m, true);
1312 if (!db)
1313 return -1;
1314
1315 notmuch_message_t *msg = NULL;
1316 notmuch_status_t st = notmuch_database_find_message_by_filename(db, path, &msg);
1317 if (st || !msg)
1318 return -1;
1319
1320 int trans = nm_db_trans_begin(m);
1321 if (trans < 0)
1322 return -1;
1323
1324 /* note that unlink() is probably unnecessary here, it's already removed
1325 * by mh_sync_mailbox_message(), but for sure... */
1326 notmuch_filenames_t *ls = NULL;
1327 st = notmuch_database_remove_message(db, path);
1328 switch (st)
1329 {
1330 case NOTMUCH_STATUS_SUCCESS:
1331 mutt_debug(LL_DEBUG2, "nm: remove success, call unlink\n");
1332 unlink(path);
1333 break;
1334 case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:
1335 mutt_debug(LL_DEBUG2, "nm: remove success (duplicate), call unlink\n");
1336 unlink(path);
1337 for (ls = notmuch_message_get_filenames(msg);
1338 ls && notmuch_filenames_valid(ls); notmuch_filenames_move_to_next(ls))
1339 {
1340 path = notmuch_filenames_get(ls);
1341
1342 mutt_debug(LL_DEBUG2, "nm: remove duplicate: '%s'\n", path);
1343 unlink(path);
1344 notmuch_database_remove_message(db, path);
1345 }
1346 break;
1347 default:
1348 mutt_debug(LL_DEBUG1, "nm: failed to remove '%s' [st=%d]\n", path, (int) st);
1349 break;
1350 }
1351
1352 notmuch_message_destroy(msg);
1353 if (trans)
1354 nm_db_trans_end(m);
1355 return 0;
1356}
1357
1367static int rename_filename(struct Mailbox *m, const char *old_file,
1368 const char *new_file, struct Email *e)
1369{
1370 struct NmMboxData *mdata = nm_mdata_get(m);
1371 if (!mdata)
1372 return -1;
1373
1374 notmuch_database_t *db = nm_db_get(m, true);
1375 if (!db || !new_file || !old_file || (access(new_file, F_OK) != 0))
1376 return -1;
1377
1378 int rc = -1;
1379 notmuch_status_t st;
1380 notmuch_filenames_t *ls = NULL;
1381 notmuch_message_t *msg = NULL;
1382
1383 mutt_debug(LL_DEBUG1, "nm: rename filename, %s -> %s\n", old_file, new_file);
1384 int trans = nm_db_trans_begin(m);
1385 if (trans < 0)
1386 return -1;
1387
1388 mutt_debug(LL_DEBUG2, "nm: rename: add '%s'\n", new_file);
1389#if LIBNOTMUCH_CHECK_VERSION(5, 1, 0)
1390 st = notmuch_database_index_file(db, new_file, NULL, &msg);
1391#else
1392 st = notmuch_database_add_message(db, new_file, &msg);
1393#endif
1394
1395 if ((st != NOTMUCH_STATUS_SUCCESS) && (st != NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID))
1396 {
1397 mutt_debug(LL_DEBUG1, "nm: failed to add '%s' [st=%d]\n", new_file, (int) st);
1398 goto done;
1399 }
1400
1401 mutt_debug(LL_DEBUG2, "nm: rename: rem '%s'\n", old_file);
1402 st = notmuch_database_remove_message(db, old_file);
1403 switch (st)
1404 {
1405 case NOTMUCH_STATUS_SUCCESS:
1406 break;
1407 case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:
1408 mutt_debug(LL_DEBUG2, "nm: rename: syncing duplicate filename\n");
1409 notmuch_message_destroy(msg);
1410 msg = NULL;
1411 notmuch_database_find_message_by_filename(db, new_file, &msg);
1412
1413 for (ls = notmuch_message_get_filenames(msg);
1414 msg && ls && notmuch_filenames_valid(ls); notmuch_filenames_move_to_next(ls))
1415 {
1416 const char *path = notmuch_filenames_get(ls);
1417 char newpath[PATH_MAX] = { 0 };
1418
1419 if (mutt_str_equal(new_file, path))
1420 continue;
1421
1422 mutt_debug(LL_DEBUG2, "nm: rename: syncing duplicate: %s\n", path);
1423
1424 if (rename_maildir_filename(path, newpath, sizeof(newpath), e) == 0)
1425 {
1426 mutt_debug(LL_DEBUG2, "nm: rename dup %s -> %s\n", path, newpath);
1427 notmuch_database_remove_message(db, path);
1428#if LIBNOTMUCH_CHECK_VERSION(5, 1, 0)
1429 notmuch_database_index_file(db, newpath, NULL, NULL);
1430#else
1431 notmuch_database_add_message(db, newpath, NULL);
1432#endif
1433 }
1434 }
1435 notmuch_message_destroy(msg);
1436 msg = NULL;
1437 notmuch_database_find_message_by_filename(db, new_file, &msg);
1438 st = NOTMUCH_STATUS_SUCCESS;
1439 break;
1440 default:
1441 mutt_debug(LL_DEBUG1, "nm: failed to remove '%s' [st=%d]\n", old_file, (int) st);
1442 break;
1443 }
1444
1445 if ((st == NOTMUCH_STATUS_SUCCESS) && e && msg)
1446 {
1447 notmuch_message_maildir_flags_to_tags(msg);
1448 update_email_tags(e, msg);
1449
1450 struct Buffer *tags = buf_pool_get();
1451 driver_tags_get(&e->tags, tags);
1452 update_tags(msg, buf_string(tags));
1453 buf_pool_release(&tags);
1454 }
1455
1456 rc = 0;
1457done:
1458 if (msg)
1459 notmuch_message_destroy(msg);
1460 if (trans)
1461 nm_db_trans_end(m);
1462 return rc;
1463}
1464
1472static unsigned int count_query(notmuch_database_t *db, const char *qstr, int limit)
1473{
1474 notmuch_query_t *q = notmuch_query_create(db, qstr);
1475 if (!q)
1476 return 0;
1477
1478 unsigned int res = 0;
1479
1481#if LIBNOTMUCH_CHECK_VERSION(5, 0, 0)
1482 if (notmuch_query_count_messages(q, &res) != NOTMUCH_STATUS_SUCCESS)
1483 res = 0; /* may not be defined on error */
1484#elif LIBNOTMUCH_CHECK_VERSION(4, 3, 0)
1485 if (notmuch_query_count_messages_st(q, &res) != NOTMUCH_STATUS_SUCCESS)
1486 res = 0; /* may not be defined on error */
1487#else
1488 res = notmuch_query_count_messages(q);
1489#endif
1490 notmuch_query_destroy(q);
1491 mutt_debug(LL_DEBUG1, "nm: count '%s', result=%d\n", qstr, res);
1492
1493 if ((limit > 0) && (res > limit))
1494 res = limit;
1495
1496 return res;
1497}
1498
1506{
1507 struct NmEmailData *edata = nm_edata_get(e);
1508 if (!edata)
1509 return NULL;
1510
1511 return edata->folder;
1512}
1513
1524char *nm_email_get_folder_rel_db(struct Mailbox *m, struct Email *e)
1525{
1526 char *full_folder = nm_email_get_folder(e);
1527 if (!full_folder)
1528 return NULL;
1529
1530 const char *db_path = nm_db_get_filename(m);
1531 if (!db_path)
1532 return NULL;
1533
1534 size_t prefix = mutt_str_startswith(full_folder, db_path);
1535
1536 char *path = full_folder + prefix;
1537 if (*path == '/')
1538 path++;
1539
1540 return path;
1541}
1542
1550int nm_read_entire_thread(struct Mailbox *m, struct Email *e)
1551{
1552 if (!m)
1553 return -1;
1554
1555 struct NmMboxData *mdata = nm_mdata_get(m);
1556 if (!mdata)
1557 return -1;
1558
1559 notmuch_query_t *q = NULL;
1560 notmuch_database_t *db = NULL;
1561 notmuch_message_t *msg = NULL;
1562 int rc = -1;
1563
1564 if (!(db = nm_db_get(m, false)) || !(msg = get_nm_message(db, e)))
1565 goto done;
1566
1567 mutt_debug(LL_DEBUG1, "nm: reading entire-thread messages...[current count=%d]\n",
1568 m->msg_count);
1569
1570 progress_setup(m);
1571 const char *id = notmuch_message_get_thread_id(msg);
1572 if (!id)
1573 goto done;
1574
1575 struct Buffer *qstr = buf_pool_get();
1576 buf_printf(qstr, "thread:%s", id);
1577 q = notmuch_query_create(db, buf_string(qstr));
1578 buf_pool_release(&qstr);
1579 if (!q)
1580 goto done;
1582 notmuch_query_set_sort(q, NOTMUCH_SORT_NEWEST_FIRST);
1583
1584 read_threads_query(m, q, true, 0);
1585 mdata->mtime.tv_sec = mutt_date_now();
1586 mdata->mtime.tv_nsec = 0;
1587 rc = 0;
1588
1589 if (m->msg_count > mdata->oldmsgcount)
1591done:
1592 if (q)
1593 notmuch_query_destroy(q);
1594
1595 nm_db_release(m);
1596
1597 if (m->msg_count == mdata->oldmsgcount)
1598 mutt_message(_("No more messages in the thread"));
1599
1600 mdata->oldmsgcount = 0;
1601 mutt_debug(LL_DEBUG1, "nm: reading entire-thread messages... done [rc=%d, count=%d]\n",
1602 rc, m->msg_count);
1603 progress_free(&mdata->progress);
1604 return rc;
1605}
1606
1615char *nm_url_from_query(struct Mailbox *m, char *buf, size_t buflen)
1616{
1617 mutt_debug(LL_DEBUG2, "(%s)\n", buf);
1618 struct NmMboxData *mdata = nm_mdata_get(m);
1619 char url[PATH_MAX + 1024 + 32]; /* path to DB + query + URL "decoration" */
1620 int added;
1621 bool using_default_data = false;
1622
1623 // No existing data. Try to get a default NmMboxData.
1624 if (!mdata)
1625 {
1627
1628 // Failed to get default data.
1629 if (!mdata)
1630 return NULL;
1631
1632 using_default_data = true;
1633 }
1634
1635 enum NmQueryType query_type = cs_subset_enum(NeoMutt->sub, "nm_query_type");
1636 mdata->query_type = nm_parse_type_from_query(buf, query_type);
1637
1638 const char *db_filename = nm_db_get_filename(m);
1639 if (!db_filename)
1640 db_filename = "";
1641
1642 const short c_nm_db_limit = cs_subset_number(NeoMutt->sub, "nm_db_limit");
1643 if (get_limit(mdata) == c_nm_db_limit)
1644 {
1645 added = snprintf(url, sizeof(url), "%s%s?type=%s&query=", NmUrlProtocol,
1646 db_filename, nm_query_type_to_string(mdata->query_type));
1647 }
1648 else
1649 {
1650 added = snprintf(url, sizeof(url),
1651 "%s%s?type=%s&limit=%d&query=", NmUrlProtocol, db_filename,
1653 }
1654
1655 if (added >= sizeof(url))
1656 {
1657 // snprintf output was truncated, so can't create URL
1658 return NULL;
1659 }
1660
1661 url_pct_encode(&url[added], sizeof(url) - added, buf);
1662
1663 mutt_str_copy(buf, url, buflen);
1664 buf[buflen - 1] = '\0';
1665
1666 if (using_default_data)
1667 nm_mdata_free((void **) &mdata);
1668
1669 mutt_debug(LL_DEBUG1, "nm: url from query '%s'\n", buf);
1670 return buf;
1671}
1672
1678{
1679 const short c_nm_query_window_duration = cs_subset_number(NeoMutt->sub, "nm_query_window_duration");
1680 const bool c_nm_query_window_enable = cs_subset_bool(NeoMutt->sub, "nm_query_window_enable");
1681
1682 return c_nm_query_window_enable || (c_nm_query_window_duration > 0);
1683}
1684
1695{
1696 const short c_nm_query_window_current_position = cs_subset_number(NeoMutt->sub, "nm_query_window_current_position");
1697 if (c_nm_query_window_current_position != 0)
1698 {
1699 cs_subset_str_native_set(NeoMutt->sub, "nm_query_window_current_position",
1700 c_nm_query_window_current_position - 1, NULL);
1701 }
1702
1703 mutt_debug(LL_DEBUG2, "(%d)\n", c_nm_query_window_current_position - 1);
1704}
1705
1715{
1716 const short c_nm_query_window_current_position = cs_subset_number(NeoMutt->sub, "nm_query_window_current_position");
1717 cs_subset_str_native_set(NeoMutt->sub, "nm_query_window_current_position",
1718 c_nm_query_window_current_position + 1, NULL);
1719 mutt_debug(LL_DEBUG2, "(%d)\n", c_nm_query_window_current_position + 1);
1720}
1721
1726{
1727 cs_subset_str_native_set(NeoMutt->sub, "nm_query_window_current_position", 0, NULL);
1728 mutt_debug(LL_DEBUG2, "Reset nm_query_window_current_position to 0\n");
1729}
1730
1738{
1739 struct NmMboxData *mdata = nm_mdata_get(m);
1740 if (!mdata)
1741 return false;
1742
1743 notmuch_database_t *db = nm_db_get(m, false);
1744 char *orig_str = get_query_string(mdata, true);
1745
1746 if (!db || !orig_str)
1747 {
1748 nm_db_release(m);
1749 return false;
1750 }
1751
1752 char *new_str = NULL;
1753 bool rc = false;
1754 if (mutt_str_asprintf(&new_str, "id:%s and (%s)", email_get_id(e), orig_str) < 0)
1755 {
1756 nm_db_release(m);
1757 return false;
1758 }
1759
1760 mutt_debug(LL_DEBUG2, "nm: checking if message is still queried: %s\n", new_str);
1761
1762 notmuch_query_t *q = notmuch_query_create(db, new_str);
1763
1764 switch (mdata->query_type)
1765 {
1766 case NM_QUERY_TYPE_UNKNOWN: // UNKNOWN should never occur, but MESGS is default
1768 {
1769 notmuch_messages_t *messages = get_messages(q);
1770
1771 if (!messages)
1772 goto done;
1773
1774 rc = notmuch_messages_valid(messages);
1775 notmuch_messages_destroy(messages);
1776 break;
1777 }
1779 {
1780 notmuch_threads_t *threads = get_threads(q);
1781
1782 if (!threads)
1783 goto done;
1784
1785 rc = notmuch_threads_valid(threads);
1786 notmuch_threads_destroy(threads);
1787 break;
1788 }
1789 }
1790
1791done:
1792 notmuch_query_destroy(q);
1793
1794 mutt_debug(LL_DEBUG2, "nm: checking if message is still queried: %s = %s\n",
1795 new_str, rc ? "true" : "false");
1796
1797 FREE(&new_str);
1798 nm_db_release(m);
1799 return rc;
1800}
1801
1811int nm_update_filename(struct Mailbox *m, const char *old_file,
1812 const char *new_file, struct Email *e)
1813{
1814 char buf[PATH_MAX] = { 0 };
1815 struct NmMboxData *mdata = nm_mdata_get(m);
1816 if (!mdata || !new_file)
1817 return -1;
1818
1819 if (!old_file && nm_edata_get(e))
1820 {
1821 email_get_fullpath(e, buf, sizeof(buf));
1822 old_file = buf;
1823 }
1824
1825 int rc = rename_filename(m, old_file, new_file, e);
1826
1827 nm_db_release(m);
1828 mdata->mtime.tv_sec = mutt_date_now();
1829 mdata->mtime.tv_nsec = 0;
1830 return rc;
1831}
1832
1836static enum MxStatus nm_mbox_check_stats(struct Mailbox *m, uint8_t flags)
1837{
1838 struct UrlQuery *item = NULL;
1839 struct Url *url = NULL;
1840 const char *db_filename = NULL;
1841 char *db_query = NULL;
1842 notmuch_database_t *db = NULL;
1843 enum MxStatus rc = MX_STATUS_ERROR;
1844 const short c_nm_db_limit = cs_subset_number(NeoMutt->sub, "nm_db_limit");
1845 int limit = c_nm_db_limit;
1846 mutt_debug(LL_DEBUG1, "nm: count\n");
1847
1848 url = url_parse(mailbox_path(m));
1849 if (!url)
1850 {
1851 mutt_error(_("failed to parse notmuch url: %s"), mailbox_path(m));
1852 goto done;
1853 }
1854
1855 STAILQ_FOREACH(item, &url->query_strings, entries)
1856 {
1857 if (item->value && (mutt_str_equal(item->name, "query")))
1858 {
1859 db_query = item->value;
1860 }
1861 else if (item->value && (mutt_str_equal(item->name, "limit")))
1862 {
1863 // Try to parse the limit
1864 if (!mutt_str_atoi_full(item->value, &limit))
1865 {
1866 mutt_error(_("failed to parse limit: %s"), item->value);
1867 goto done;
1868 }
1869 }
1870 }
1871
1872 if (!db_query)
1873 goto done;
1874
1875 db_filename = url->path;
1876 if (!db_filename)
1877 db_filename = nm_db_get_filename(m);
1878
1879#if LIBNOTMUCH_CHECK_VERSION(5, 4, 0)
1880 /* don't be verbose about connection, as we're called from
1881 * sidebar/mailbox very often */
1882 db = nm_db_do_open(db_filename, false, false);
1883#else
1884 if (!db_filename)
1885 {
1886 const char *const c_folder = cs_subset_string(NeoMutt->sub, "folder");
1887 db_filename = c_folder;
1888 }
1889 /* don't be verbose about connection, as we're called from
1890 * sidebar/mailbox very often */
1891 if (db_filename)
1892 db = nm_db_do_open(db_filename, false, false);
1893#endif
1894 if (!db)
1895 goto done;
1896
1897 /* all emails */
1898 m->msg_count = count_query(db, db_query, limit);
1900
1901 // holder variable for extending query to unread/flagged
1902 char *qstr = NULL;
1903
1904 // unread messages
1905 const char *const c_nm_unread_tag = cs_subset_string(NeoMutt->sub, "nm_unread_tag");
1906 mutt_str_asprintf(&qstr, "( %s ) tag:%s", db_query, c_nm_unread_tag);
1907 m->msg_unread = count_query(db, qstr, limit);
1908 FREE(&qstr);
1909
1910 // flagged messages
1911 const char *const c_nm_flagged_tag = cs_subset_string(NeoMutt->sub, "nm_flagged_tag");
1912 mutt_str_asprintf(&qstr, "( %s ) tag:%s", db_query, c_nm_flagged_tag);
1913 m->msg_flagged = count_query(db, qstr, limit);
1914 FREE(&qstr);
1915
1916 rc = (m->msg_unread > 0) ? MX_STATUS_NEW_MAIL : MX_STATUS_OK;
1917done:
1918 if (db)
1919 {
1920 nm_db_free(db);
1921 mutt_debug(LL_DEBUG1, "nm: count close DB\n");
1922 }
1923 url_free(&url);
1924
1925 mutt_debug(LL_DEBUG1, "nm: count done [rc=%d]\n", rc);
1926 return rc;
1927}
1928
1933static struct Mailbox *get_default_mailbox(void)
1934{
1935 // Create a new notmuch mailbox from scratch and add plumbing for DB access.
1936 char *default_url = nm_get_default_url();
1937 struct Mailbox *m = mx_path_resolve(default_url);
1938
1939 FREE(&default_url);
1940
1941 // These are no-ops for an initialized mailbox.
1942 init_mailbox(m);
1943 mx_mbox_ac_link(m);
1944
1945 return m;
1946}
1947
1956int nm_record_message(struct Mailbox *m, char *path, struct Email *e)
1957{
1958 notmuch_database_t *db = NULL;
1959 notmuch_status_t st;
1960 notmuch_message_t *msg = NULL;
1961 int rc = -1;
1962
1963 struct NmMboxData *mdata = nm_mdata_get(m);
1964
1965 // If no notmuch data, fall back to the default mailbox.
1966 //
1967 // IMPORTANT: DO NOT FREE THIS MAILBOX. Two reasons:
1968 // 1) If user has default mailbox in config, we'll be removing it. That's not
1969 // good program behavior!
1970 // 2) If not in user's config, keep mailbox around for future nm_record calls.
1971 // It saves NeoMutt from allocating/deallocating repeatedly.
1972 if (!mdata)
1973 {
1974 mutt_debug(LL_DEBUG1, "nm: non-nm mailbox. trying the default nm mailbox.\n");
1975 m = get_default_mailbox();
1976 mdata = nm_mdata_get(m);
1977 }
1978
1979 if (!path || !mdata || (access(path, F_OK) != 0))
1980 return 0;
1981 db = nm_db_get(m, true);
1982 if (!db)
1983 return -1;
1984
1985 mutt_debug(LL_DEBUG1, "nm: record message: %s\n", path);
1986 int trans = nm_db_trans_begin(m);
1987 if (trans < 0)
1988 goto done;
1989
1990#if LIBNOTMUCH_CHECK_VERSION(5, 1, 0)
1991 st = notmuch_database_index_file(db, path, NULL, &msg);
1992#else
1993 st = notmuch_database_add_message(db, path, &msg);
1994#endif
1995
1996 if ((st != NOTMUCH_STATUS_SUCCESS) && (st != NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID))
1997 {
1998 mutt_debug(LL_DEBUG1, "nm: failed to add '%s' [st=%d]\n", path, (int) st);
1999 goto done;
2000 }
2001
2002 if ((st == NOTMUCH_STATUS_SUCCESS) && msg)
2003 {
2004 notmuch_message_maildir_flags_to_tags(msg);
2005 if (e)
2006 {
2007 struct Buffer *tags = buf_pool_get();
2008 driver_tags_get(&e->tags, tags);
2009 update_tags(msg, buf_string(tags));
2010 buf_pool_release(&tags);
2011 }
2012 const char *const c_nm_record_tags = cs_subset_string(NeoMutt->sub, "nm_record_tags");
2013 if (c_nm_record_tags)
2014 update_tags(msg, c_nm_record_tags);
2015 }
2016
2017 rc = 0;
2018done:
2019 if (msg)
2020 notmuch_message_destroy(msg);
2021 if (trans == 1)
2022 nm_db_trans_end(m);
2023
2024 nm_db_release(m);
2025
2026 return rc;
2027}
2028
2039int nm_get_all_tags(struct Mailbox *m, const char **tag_list, int *tag_count)
2040{
2041 struct NmMboxData *mdata = nm_mdata_get(m);
2042 if (!mdata || !tag_count)
2043 return -1;
2044
2045 notmuch_database_t *db = NULL;
2046 notmuch_tags_t *tags = NULL;
2047 const char *tag = NULL;
2048 int rc = -1;
2049
2050 if (!(db = nm_db_get(m, false)) || !(tags = notmuch_database_get_all_tags(db)))
2051 goto done;
2052
2053 *tag_count = 0;
2054 mutt_debug(LL_DEBUG1, "nm: get all tags\n");
2055
2056 while (notmuch_tags_valid(tags))
2057 {
2058 tag = notmuch_tags_get(tags);
2059 /* Skip empty string */
2060 if (*tag)
2061 {
2062 if (tag_list)
2063 tag_list[*tag_count] = mutt_str_dup(tag);
2064 (*tag_count)++;
2065 }
2066 notmuch_tags_move_to_next(tags);
2067 }
2068
2069 rc = 0;
2070done:
2071 if (tags)
2072 notmuch_tags_destroy(tags);
2073
2074 nm_db_release(m);
2075
2076 mutt_debug(LL_DEBUG1, "nm: get all tags done [rc=%d tag_count=%u]\n", rc, *tag_count);
2077 return rc;
2078}
2079
2083static bool nm_ac_owns_path(struct Account *a, const char *path)
2084{
2085 return true;
2086}
2087
2091static bool nm_ac_add(struct Account *a, struct Mailbox *m)
2092{
2093 if (a->adata)
2094 return true;
2095
2096 struct NmAccountData *adata = nm_adata_new();
2097 a->adata = adata;
2099
2100 return true;
2101}
2102
2106static enum MxOpenReturns nm_mbox_open(struct Mailbox *m)
2107{
2108 if (init_mailbox(m) != 0)
2109 return MX_OPEN_ERROR;
2110
2111 struct NmMboxData *mdata = nm_mdata_get(m);
2112 if (!mdata)
2113 return MX_OPEN_ERROR;
2114
2115 mutt_debug(LL_DEBUG1, "nm: reading messages...[current count=%d]\n", m->msg_count);
2116
2117 progress_setup(m);
2118 enum MxOpenReturns rc = MX_OPEN_ERROR;
2119
2120 notmuch_query_t *q = get_query(m, false);
2121 if (q)
2122 {
2123 rc = MX_OPEN_OK;
2124 switch (mdata->query_type)
2125 {
2126 case NM_QUERY_TYPE_UNKNOWN: // UNKNOWN should never occur, but MESGS is default
2128 if (!read_mesgs_query(m, q, false))
2129 rc = MX_OPEN_ABORT;
2130 break;
2132 if (!read_threads_query(m, q, false, get_limit(mdata)))
2133 rc = MX_OPEN_ABORT;
2134 break;
2135 }
2136 notmuch_query_destroy(q);
2137 }
2138
2139 nm_db_release(m);
2140
2141 mdata->mtime.tv_sec = mutt_date_now();
2142 mdata->mtime.tv_nsec = 0;
2143
2144 mdata->oldmsgcount = 0;
2145
2146 mutt_debug(LL_DEBUG1, "nm: reading messages... done [rc=%d, count=%d]\n", rc, m->msg_count);
2147 progress_free(&mdata->progress);
2148 return rc;
2149}
2150
2156static enum MxStatus nm_mbox_check(struct Mailbox *m)
2157{
2158 struct NmMboxData *mdata = nm_mdata_get(m);
2159 time_t mtime = 0;
2160 if (!mdata)
2161 return MX_STATUS_ERROR;
2162
2163 int new_flags = 0;
2164 bool occult = false;
2165
2166 if ((nm_db_get_mtime(m, &mtime) == 0) && (mdata->mtime.tv_sec >= mtime))
2167 {
2168 mutt_debug(LL_DEBUG2, "nm: check unnecessary (db=%llu mailbox=%llu)\n",
2169 (unsigned long long) mtime, (unsigned long long) mdata->mtime.tv_sec);
2170 return MX_STATUS_OK;
2171 }
2172
2173 mutt_debug(LL_DEBUG1, "nm: checking (db=%llu mailbox=%llu)\n",
2174 (unsigned long long) mtime, (unsigned long long) mdata->mtime.tv_sec);
2175
2176 notmuch_query_t *q = get_query(m, false);
2177 if (!q)
2178 goto done;
2179
2180 mutt_debug(LL_DEBUG1, "nm: start checking (count=%d)\n", m->msg_count);
2181 mdata->oldmsgcount = m->msg_count;
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 e->active = false;
2190 }
2191
2192 int limit = get_limit(mdata);
2193
2194 notmuch_messages_t *msgs = get_messages(q);
2195
2196 // TODO: Analyze impact of removing this version guard.
2197#if LIBNOTMUCH_CHECK_VERSION(5, 0, 0)
2198 if (!msgs)
2199 goto done;
2200#elif LIBNOTMUCH_CHECK_VERSION(4, 3, 0)
2201 if (!msgs)
2202 goto done;
2203#endif
2204
2205 struct HeaderCache *hc = nm_hcache_open(m);
2206
2207 for (int i = 0; notmuch_messages_valid(msgs) && ((limit == 0) || (i < limit));
2208 notmuch_messages_move_to_next(msgs), i++)
2209 {
2210 notmuch_message_t *msg = notmuch_messages_get(msgs);
2211 struct Email *e = get_mutt_email(m, msg);
2212
2213 if (!e)
2214 {
2215 /* new email */
2216 append_message(hc, m, msg, false);
2217 notmuch_message_destroy(msg);
2218 continue;
2219 }
2220
2221 /* message already exists, merge flags */
2222 e->active = true;
2223
2224 /* Check to see if the message has moved to a different subdirectory.
2225 * If so, update the associated filename. */
2226 char *new_file = get_message_last_filename(msg);
2227 char old_file[PATH_MAX] = { 0 };
2228 email_get_fullpath(e, old_file, sizeof(old_file));
2229
2230 if (!mutt_str_equal(old_file, new_file))
2231 update_message_path(e, new_file);
2232
2233 if (!e->changed)
2234 {
2235 /* if the user hasn't modified the flags on this message, update the
2236 * flags we just detected. */
2237 struct Email *e_tmp = maildir_email_new();
2238 maildir_parse_flags(e_tmp, new_file);
2239 e_tmp->old = e->old;
2240 maildir_update_flags(m, e, e_tmp);
2241 email_free(&e_tmp);
2242 }
2243
2244 FREE(&new_file);
2245
2246 if (update_email_tags(e, msg) == 0)
2247 new_flags++;
2248
2249 notmuch_message_destroy(msg);
2250 }
2251
2252 nm_hcache_close(&hc);
2253
2254 for (int i = 0; i < m->msg_count; i++)
2255 {
2256 struct Email *e = m->emails[i];
2257 if (!e)
2258 break;
2259
2260 if (!e->active)
2261 {
2262 occult = true;
2263 break;
2264 }
2265 }
2266
2267 if (m->msg_count > mdata->oldmsgcount)
2269done:
2270 if (q)
2271 notmuch_query_destroy(q);
2272
2273 nm_db_release(m);
2274
2275 mdata->mtime.tv_sec = mutt_date_now();
2276 mdata->mtime.tv_nsec = 0;
2277
2278 mutt_debug(LL_DEBUG1, "nm: ... check done [count=%d, new_flags=%d, occult=%d]\n",
2279 m->msg_count, new_flags, occult);
2280
2281 if (occult)
2282 return MX_STATUS_REOPENED;
2283 if (m->msg_count > mdata->oldmsgcount)
2284 return MX_STATUS_NEW_MAIL;
2285 if (new_flags)
2286 return MX_STATUS_FLAGS;
2287 return MX_STATUS_OK;
2288}
2289
2293static enum MxStatus nm_mbox_sync(struct Mailbox *m)
2294{
2295 struct NmMboxData *mdata = nm_mdata_get(m);
2296 if (!mdata)
2297 return MX_STATUS_ERROR;
2298
2299 enum MxStatus rc = MX_STATUS_OK;
2300 struct Progress *progress = NULL;
2301 char *url = mutt_str_dup(mailbox_path(m));
2302 bool changed = false;
2303
2304 mutt_debug(LL_DEBUG1, "nm: sync start\n");
2305
2306 if (m->verbose)
2307 {
2308 /* all is in this function so we don't use data->progress here */
2310 progress_set_message(progress, _("Writing %s..."), mailbox_path(m));
2311 }
2312
2313 struct HeaderCache *hc = nm_hcache_open(m);
2314
2315 /* Iterate through all messages, syncing flag changes to the underlying
2316 * maildir/mh files and updating the notmuch database for renames/deletes */
2317 int mh_sync_errors = 0;
2318 for (int i = 0; i < m->msg_count; i++)
2319 {
2320 char old_file[PATH_MAX], new_file[PATH_MAX];
2321 struct Email *e = m->emails[i];
2322 if (!e)
2323 break;
2324
2325 struct NmEmailData *edata = nm_edata_get(e);
2326 if (!edata)
2327 continue;
2328
2329 progress_update(progress, i, -1);
2330
2331 *old_file = '\0';
2332 *new_file = '\0';
2333
2334 if (edata->oldpath)
2335 {
2336 mutt_str_copy(old_file, edata->oldpath, sizeof(old_file));
2337 mutt_debug(LL_DEBUG2, "nm: fixing obsolete path '%s'\n", old_file);
2338 }
2339 else
2340 {
2341 email_get_fullpath(e, old_file, sizeof(old_file));
2342 }
2343
2344 /* Temporarily set mailbox path and type to the message's backing
2345 * maildir folder so maildir_sync_mailbox_message() can operate */
2346 buf_strcpy(&m->pathbuf, edata->folder);
2347 m->type = edata->type;
2348
2349 bool ok = maildir_sync_mailbox_message(m, e, hc);
2350 if (!ok)
2351 {
2352 /* Syncing file failed, query notmuch for an updated filepath
2353 * (another process may have moved the message on disk) */
2354 m->type = MUTT_NOTMUCH;
2355 notmuch_database_t *db = nm_db_get(m, true);
2356 if (db)
2357 {
2358 notmuch_message_t *msg = get_nm_message(db, e);
2359
2361
2362 buf_strcpy(&m->pathbuf, edata->folder);
2363 m->type = edata->type;
2364 ok = maildir_sync_mailbox_message(m, e, hc);
2365 m->type = MUTT_NOTMUCH;
2366 }
2367 nm_db_release(m);
2368 m->type = edata->type;
2369 }
2370
2371 /* Restore the virtual notmuch mailbox path */
2372 buf_strcpy(&m->pathbuf, url);
2373 m->type = MUTT_NOTMUCH;
2374
2375 if (!ok)
2376 {
2377 mh_sync_errors += 1;
2378 continue;
2379 }
2380
2381 /* If the message was deleted or its file was renamed (e.g. flag change),
2382 * update the notmuch database to reflect the new state */
2383 if (!e->deleted)
2384 email_get_fullpath(e, new_file, sizeof(new_file));
2385
2386 if (e->deleted || !mutt_str_equal(old_file, new_file))
2387 {
2388 if (e->deleted && (remove_filename(m, old_file) == 0))
2389 changed = true;
2390 else if (*new_file && *old_file && (rename_filename(m, old_file, new_file, e) == 0))
2391 changed = true;
2392 }
2393
2394 FREE(&edata->oldpath);
2395 }
2396
2397 if (mh_sync_errors > 0)
2398 {
2399 mutt_error(ngettext("Unable to sync %d message due to external mailbox modification",
2400 "Unable to sync %d messages due to external mailbox modification",
2401 mh_sync_errors),
2402 mh_sync_errors);
2403 rc = MX_STATUS_ERROR;
2404 }
2405
2406 buf_strcpy(&m->pathbuf, url);
2407 m->type = MUTT_NOTMUCH;
2408
2409 nm_db_release(m);
2410
2411 if (changed)
2412 {
2413 mdata->mtime.tv_sec = mutt_date_now();
2414 mdata->mtime.tv_nsec = 0;
2415 }
2416
2417 nm_hcache_close(&hc);
2418
2419 progress_free(&progress);
2420 FREE(&url);
2421 mutt_debug(LL_DEBUG1, "nm: .... sync done [rc=%d]\n", rc);
2422 return rc;
2423}
2424
2430static enum MxStatus nm_mbox_close(struct Mailbox *m)
2431{
2432 return MX_STATUS_OK;
2433}
2434
2438static bool nm_msg_open(struct Mailbox *m, struct Message *msg, struct Email *e)
2439{
2440 char path[PATH_MAX] = { 0 };
2441 char *folder = nm_email_get_folder(e);
2442 if (!folder)
2443 return false;
2444
2445 snprintf(path, sizeof(path), "%s/%s", folder, e->path);
2446
2447 msg->fp = mutt_file_fopen(path, "r");
2448 if (!msg->fp && (errno == ENOENT) && ((m->type == MUTT_MAILDIR) || (m->type == MUTT_NOTMUCH)))
2449 {
2450 msg->fp = maildir_open_find_message(folder, e->path, NULL);
2451 }
2452
2453 return msg->fp != NULL;
2454}
2455
2460static int nm_msg_commit(struct Mailbox *m, struct Message *msg)
2461{
2462 mutt_error(_("Can't write to virtual folder"));
2463 return -1;
2464}
2465
2469static int nm_msg_close(struct Mailbox *m, struct Message *msg)
2470{
2471 mutt_file_fclose(&(msg->fp));
2472 return 0;
2473}
2474
2478static int nm_tags_edit(struct Mailbox *m, const char *tags, struct Buffer *buf)
2479{
2480 buf_reset(buf);
2481 if (mw_get_field("Add/remove labels: ", buf, MUTT_COMP_NONE, HC_OTHER,
2482 &CompleteNmTagOps, NULL) != 0)
2483 {
2484 return -1;
2485 }
2486 return 1;
2487}
2488
2492static int nm_tags_commit(struct Mailbox *m, struct Email *e, const char *buf)
2493{
2494 if (*buf == '\0')
2495 return 0; /* no tag change, so nothing to do */
2496
2497 struct NmMboxData *mdata = nm_mdata_get(m);
2498 if (!mdata)
2499 return -1;
2500
2501 notmuch_database_t *db = NULL;
2502 notmuch_message_t *msg = NULL;
2503 int rc = -1;
2504
2505 if (!(db = nm_db_get(m, true)) || !(msg = get_nm_message(db, e)))
2506 goto done;
2507
2508 mutt_debug(LL_DEBUG1, "nm: tags modify: '%s'\n", buf);
2509
2510 update_tags(msg, buf);
2511 update_email_flags(m, e, buf);
2512 update_email_tags(e, msg);
2513 email_set_color(m, e);
2514
2515 rc = 0;
2516 e->changed = true;
2517done:
2518 nm_db_release(m);
2519 if (e->changed)
2520 {
2521 mdata->mtime.tv_sec = mutt_date_now();
2522 mdata->mtime.tv_nsec = 0;
2523 }
2524 mutt_debug(LL_DEBUG1, "nm: tags modify done [rc=%d]\n", rc);
2525 return rc;
2526}
2527
2531enum MailboxType nm_path_probe(const char *path, const struct stat *st)
2532{
2534 return MUTT_UNKNOWN;
2535
2536 return MUTT_NOTMUCH;
2537}
2538
2542static int nm_path_canon(struct Buffer *path)
2543{
2544 return 0;
2545}
2546
2550const struct MxOps MxNotmuchOps = {
2551 // clang-format off
2552 .type = MUTT_NOTMUCH,
2553 .name = "notmuch",
2554 .is_local = false,
2555 .ac_owns_path = nm_ac_owns_path,
2556 .ac_add = nm_ac_add,
2557 .mbox_open = nm_mbox_open,
2558 .mbox_open_append = NULL,
2559 .mbox_check = nm_mbox_check,
2560 .mbox_check_stats = nm_mbox_check_stats,
2561 .mbox_sync = nm_mbox_sync,
2562 .mbox_close = nm_mbox_close,
2563 .msg_open = nm_msg_open,
2564 .msg_open_new = maildir_msg_open_new,
2565 .msg_commit = nm_msg_commit,
2566 .msg_close = nm_msg_close,
2567 .msg_padding_size = NULL,
2568 .msg_save_hcache = NULL,
2569 .tags_edit = nm_tags_edit,
2570 .tags_commit = nm_tags_commit,
2571 .path_probe = nm_path_probe,
2572 .path_canon = nm_path_canon,
2573 .path_is_empty = NULL,
2574 // clang-format on
2575};
#define ARRAY_FOREACH(elem, head)
Iterate over all elements of the array.
Definition array.h:223
int buf_printf(struct Buffer *buf, const char *fmt,...)
Format a string overwriting a Buffer.
Definition buffer.c:161
void buf_reset(struct Buffer *buf)
Reset an existing Buffer.
Definition buffer.c:76
bool buf_is_empty(const struct Buffer *buf)
Is the Buffer empty?
Definition buffer.c:291
void buf_join_str(struct Buffer *buf, const char *str, char sep)
Join a buffer with a string separated by sep.
Definition buffer.c:748
bool buf_str_equal(const struct Buffer *a, const struct Buffer *b)
Return if two buffers are equal.
Definition buffer.c:683
size_t buf_strcpy(struct Buffer *buf, const char *s)
Copy a string into a Buffer.
Definition buffer.c:395
static const char * buf_string(const struct Buffer *buf)
Convert a buffer to a const char * "string".
Definition buffer.h:96
@ CF_SYNONYM
Command is a synonym for another command.
Definition command.h:50
@ CF_NONE
No flags are set.
Definition command.h:49
@ CMD_NONE
No Command.
Definition command.h:62
const char * cs_subset_string(const struct ConfigSubset *sub, const char *name)
Get a string config item by name.
Definition helpers.c:291
unsigned char cs_subset_enum(const struct ConfigSubset *sub, const char *name)
Get a enumeration config item by name.
Definition helpers.c:71
short cs_subset_number(const struct ConfigSubset *sub, const char *name)
Get a number config item by name.
Definition helpers.c:143
const char * cs_subset_path(const struct ConfigSubset *sub, const char *name)
Get a path config item by name.
Definition helpers.c:168
bool cs_subset_bool(const struct ConfigSubset *sub, const char *name)
Get a boolean config item by name.
Definition helpers.c:47
Convenience wrapper for the config headers.
const char * cc_maildir_field_delimiter(void)
Get the cached value of $maildir_field_delimiter.
Convenience wrapper for the core headers.
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:248
void mailbox_changed(struct Mailbox *m, enum NotifyMailbox action)
Notify observers of a change to a Mailbox.
Definition mailbox.c:232
@ NT_MAILBOX_INVALID
Email list was changed.
Definition mailbox.h:182
static const char * mailbox_path(const struct Mailbox *m)
Get the Mailbox's path string.
Definition mailbox.h:216
MailboxType
Supported mailbox formats.
Definition mailbox.h:40
@ MUTT_NOTMUCH
'Notmuch' (virtual) Mailbox type
Definition mailbox.h:50
@ MUTT_UNKNOWN
Mailbox wasn't recognised.
Definition mailbox.h:43
@ MUTT_MAILDIR
'Maildir' Mailbox type
Definition mailbox.h:47
void email_set_color(struct Mailbox *m, struct Email *e)
Select an Index colour for an Email.
Definition dlg_index.c:1431
Edit a string.
@ MUTT_COMP_NONE
No flags are set.
Definition wdata.h:46
void mutt_body_free(struct Body **ptr)
Free a Body.
Definition body.c:58
void email_free(struct Email **ptr)
Free an Email.
Definition email.c:46
Structs that make up an email.
void mutt_env_free(struct Envelope **ptr)
Free an Envelope.
Definition envelope.c:125
#define mutt_file_fclose(FP)
Definition file.h:144
#define mutt_file_fopen(PATH, MODE)
Definition file.h:143
void mutt_set_flag(struct Mailbox *m, struct Email *e, enum MessageType flag, bool bf, bool upd_mbox)
Set a flag on an email.
Definition flags.c:54
void nm_adata_free(void **ptr)
Free the private Account data - Implements Account::adata_free() -.
Definition adata.c:39
int mw_get_field(const char *prompt, struct Buffer *buf, CompletionFlags complete, enum HistoryClass hclass, const struct CompleteOps *comp_api, void *cdata)
Ask the user for a string -.
Definition window.c:502
#define mutt_error(...)
Definition logging2.h:94
#define mutt_message(...)
Definition logging2.h:93
#define mutt_debug(LEVEL,...)
Definition logging2.h:91
void nm_mdata_free(void **ptr)
Free the private Mailbox data - Implements Mailbox::mdata_free() -.
Definition mdata.c:45
static bool nm_ac_add(struct Account *a, struct Mailbox *m)
Add a Mailbox to an Account - Implements MxOps::ac_add() -.
Definition notmuch.c:2091
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:2083
const struct MxOps MxNotmuchOps
Notmuch Mailbox - Implements MxOps -.
Definition notmuch.c:2550
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:1836
static enum MxStatus nm_mbox_check(struct Mailbox *m)
Check for new mail - Implements MxOps::mbox_check() -.
Definition notmuch.c:2156
static enum MxStatus nm_mbox_close(struct Mailbox *m)
Close a Mailbox - Implements MxOps::mbox_close() -.
Definition notmuch.c:2430
static enum MxOpenReturns nm_mbox_open(struct Mailbox *m)
Open a Mailbox - Implements MxOps::mbox_open() -.
Definition notmuch.c:2106
static enum MxStatus nm_mbox_sync(struct Mailbox *m)
Save changes to the Mailbox - Implements MxOps::mbox_sync() -.
Definition notmuch.c:2293
static int nm_msg_close(struct Mailbox *m, struct Message *msg)
Close an email - Implements MxOps::msg_close() -.
Definition notmuch.c:2469
static int nm_msg_commit(struct Mailbox *m, struct Message *msg)
Save changes to an email - Implements MxOps::msg_commit() -.
Definition notmuch.c:2460
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 message.c:549
static bool nm_msg_open(struct Mailbox *m, struct Message *msg, struct Email *e)
Open an email message in a Mailbox - Implements MxOps::msg_open() -.
Definition notmuch.c:2438
static int nm_path_canon(struct Buffer *path)
Canonicalise a Mailbox path - Implements MxOps::path_canon() -.
Definition notmuch.c:2542
enum MailboxType nm_path_probe(const char *path, const struct stat *st)
Is this a Notmuch Mailbox?
Definition notmuch.c:2531
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:2492
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:2478
Convenience wrapper for the gui headers.
struct HashTable * mutt_make_id_hash(struct Mailbox *m)
Create a Hash Table for Message-IDs.
Definition thread.c:1790
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:364
struct HeaderCache * hcache_open(const char *path, const char *folder, hcache_namer_t namer, bool create)
Multiplexor for StoreOps::open.
Definition hcache.c:477
void hcache_close(struct HeaderCache **ptr)
Multiplexor for StoreOps::close.
Definition hcache.c:549
struct HCacheEntry hcache_fetch_email(struct HeaderCache *hc, const char *key, size_t keylen, uint32_t uidvalidity)
Multiplexor for StoreOps::fetch.
Definition hcache.c:569
int hcache_store_email(struct HeaderCache *hc, const char *key, size_t keylen, struct Email *e, uint32_t uidvalidity)
Multiplexor for StoreOps::store.
Definition hcache.c:683
Header cache multiplexor.
Read/write command history from/to a file.
@ HC_OTHER
Miscellaneous strings.
Definition lib.h:61
GUI manage the main index (list of emails)
@ LL_DEBUG2
Log at debug level 2.
Definition logging2.h:46
@ LL_DEBUG1
Log at debug level 1.
Definition logging2.h:45
bool maildir_parse_message(const char *fname, bool is_old, struct Email *e)
Actually parse a maildir message.
Definition mailbox.c:196
struct Email * maildir_email_new(void)
Create a Maildir Email.
Definition mailbox.c:68
bool maildir_parse_stream(FILE *fp, const char *fname, bool is_old, struct Email *e)
Parse a Maildir message.
Definition mailbox.c:159
void maildir_parse_flags(struct Email *e, const char *path)
Parse Maildir file flags.
Definition mailbox.c:82
FILE * maildir_open_find_message(const char *folder, const char *msg, char **newname)
Find a message by name.
Definition message.c:168
bool maildir_sync_mailbox_message(struct Mailbox *m, struct Email *e, struct HeaderCache *hc)
Save changes to the mailbox.
Definition message.c:315
void maildir_gen_flags(char *dest, size_t destlen, struct Email *e)
Generate the Maildir flags for an email.
Definition message.c:71
bool maildir_update_flags(struct Mailbox *m, struct Email *e_old, struct Email *e_new)
Update the mailbox flags.
Definition shared.c:104
Maildir shared functions.
#define FREE(x)
Free memory and set the pointer to NULL.
Definition memory.h:68
#define MUTT_MEM_MALLOC(n, type)
Definition memory.h:53
time_t mutt_date_now(void)
Return the number of seconds since the Unix epoch.
Definition date.c:457
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:384
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition string.c:257
int mutt_str_asprintf(char **strp, const char *fmt,...)
Definition string.c:808
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition string.c:665
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:429
size_t mutt_str_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix.
Definition string.c:234
size_t mutt_str_len(const char *a)
Calculate the length of a string, safely.
Definition string.c:503
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:586
size_t mutt_istr_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix, ignoring case.
Definition string.c:246
char * mutt_str_replace(char **p, const char *s)
Replace one string with another.
Definition string.c:284
Many unsorted constants and some structs.
@ 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:49
void mx_alloc_memory(struct Mailbox *m, int req_size)
Create storage for the emails.
Definition mx.c:1208
bool mx_mbox_ac_link(struct Mailbox *m)
Link a Mailbox to an existing or new Account.
Definition mx.c:248
struct Mailbox * mx_path_resolve(const char *path)
Get a Mailbox for a path.
Definition mx.c:1647
API for mailboxes.
MxOpenReturns
Return values for mbox_open()
Definition mxapi.h:83
@ MX_OPEN_ERROR
Open failed with an error.
Definition mxapi.h:85
@ MX_OPEN_ABORT
Open was aborted.
Definition mxapi.h:86
@ MX_OPEN_OK
Open succeeded.
Definition mxapi.h:84
MxStatus
Return values from mbox_check(), mbox_check_stats(), mbox_sync(), and mbox_close()
Definition mxapi.h:70
@ MX_STATUS_ERROR
An error occurred.
Definition mxapi.h:71
@ MX_STATUS_OK
No changes.
Definition mxapi.h:72
@ MX_STATUS_FLAGS
Nondestructive flags change (IMAP)
Definition mxapi.h:76
@ MX_STATUS_REOPENED
Mailbox was reopened.
Definition mxapi.h:75
@ MX_STATUS_NEW_MAIL
New mail received in Mailbox.
Definition mxapi.h:73
struct NmAccountData * nm_adata_new(void)
Allocate and initialise a new NmAccountData structure.
Definition adata.c:58
Notmuch-specific Account data.
const struct CompleteOps CompleteNmTagOps
Auto-Completion of NmTags.
Definition complete.c:254
notmuch_database_t * nm_db_get(struct Mailbox *m, bool writable)
Get the Notmuch database.
Definition db.c:210
int nm_db_trans_begin(struct Mailbox *m)
Start a Notmuch database transaction.
Definition db.c:278
notmuch_database_t * nm_db_do_open(const char *filename, bool writable, bool verbose)
Open a Notmuch database.
Definition db.c:116
const char * nm_db_get_filename(struct Mailbox *m)
Get the filename of the Notmuch database.
Definition db.c:63
int nm_db_get_mtime(struct Mailbox *m, time_t *mtime)
Get the database modification time.
Definition db.c:327
int nm_db_release(struct Mailbox *m)
Close the Notmuch database.
Definition db.c:245
void nm_db_free(notmuch_database_t *db)
Decoupled way to close a Notmuch database.
Definition db.c:262
int nm_db_trans_end(struct Mailbox *m)
End a database transaction.
Definition db.c:300
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
Notmuch-specific Email data.
Notmuch virtual mailbox type.
struct NmMboxData * nm_mdata_new(const char *url)
Create a new NmMboxData object from a query.
Definition mdata.c:68
struct NmMboxData * nm_mdata_get(struct Mailbox *m)
Get the Notmuch Mailbox data.
Definition mdata.c:96
Notmuch-specific Mailbox data.
const struct Command NmCommands[]
Notmuch Commands.
Definition notmuch.c:91
Notmuch private types.
static notmuch_threads_t * get_threads(notmuch_query_t *query)
Load threads for a query.
Definition notmuch.c:1016
static char * get_query_string(struct NmMboxData *mdata, bool window)
Builds the notmuch vfolder search string.
Definition notmuch.c:346
static char * nm_get_default_url(void)
Create a Mailbox with default Notmuch settings.
Definition notmuch.c:137
int nm_update_filename(struct Mailbox *m, const char *old_file, const char *new_file, struct Email *e)
Change the filename.
Definition notmuch.c:1811
void nm_query_window_reset(void)
Resets the vfolder window position to the present.
Definition notmuch.c:1725
static char * email_get_id(struct Email *e)
Get the unique Notmuch Id.
Definition notmuch.c:214
static void apply_exclude_tags(notmuch_query_t *query)
Exclude the configured tags.
Definition notmuch.c:423
static char * email_get_fullpath(struct Email *e, char *buf, size_t buflen)
Get the full path of an email.
Definition notmuch.c:230
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:282
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:2039
static unsigned int count_query(notmuch_database_t *db, const char *qstr, int limit)
Count the results of a query.
Definition notmuch.c:1472
static int init_mailbox(struct Mailbox *m)
Add Notmuch data to the Mailbox.
Definition notmuch.c:192
static int rename_maildir_filename(const char *old, char *buf, size_t buflen, struct Email *e)
Rename a Maildir file.
Definition notmuch.c:1247
static void sync_email_path_with_nm(struct Email *e, notmuch_message_t *msg)
Synchronize NeoMutt's Email path with notmuch.
Definition notmuch.c:1122
static notmuch_message_t * get_nm_message(notmuch_database_t *db, struct Email *e)
Find a Notmuch message.
Definition notmuch.c:1081
static int init_email(struct Email *e, const char *path, notmuch_message_t *msg)
Set up an email's Notmuch data.
Definition notmuch.c:643
static int get_limit(struct NmMboxData *mdata)
Get the database limit.
Definition notmuch.c:414
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:1524
static bool read_threads_query(struct Mailbox *m, notmuch_query_t *q, bool dedup, int limit)
Perform a query with threads.
Definition notmuch.c:1044
static struct Mailbox * get_default_mailbox(void)
Get Mailbox for notmuch without any parameters.
Definition notmuch.c:1933
int nm_record_message(struct Mailbox *m, char *path, struct Email *e)
Add a message to the Notmuch database.
Definition notmuch.c:1956
static struct NmMboxData * nm_get_default_data(void)
Create a Mailbox with default Notmuch settings.
Definition notmuch.c:169
int nm_read_entire_thread(struct Mailbox *m, struct Email *e)
Get the entire thread of an email.
Definition notmuch.c:1550
static void append_thread(struct HeaderCache *hc, struct Mailbox *m, notmuch_query_t *q, notmuch_thread_t *thread, bool dedup)
Add each top level reply in the thread.
Definition notmuch.c:922
static void query_window_reset(void)
Restore vfolder's search window to its original position.
Definition notmuch.c:252
const int NmUrlProtocolLen
Length of NmUrlProtocol string.
Definition notmuch.c:104
static int rename_filename(struct Mailbox *m, const char *old_file, const char *new_file, struct Email *e)
Rename the file.
Definition notmuch.c:1367
void nm_query_window_backward(void)
Function to move the current search window backward in time.
Definition notmuch.c:1714
static struct HeaderCache * nm_hcache_open(struct Mailbox *m)
Open a header cache.
Definition notmuch.c:111
static char * get_message_last_filename(notmuch_message_t *msg)
Get a message's last filename.
Definition notmuch.c:687
static notmuch_query_t * get_query(struct Mailbox *m, bool writable)
Create a new query.
Definition notmuch.c:449
static int update_message_path(struct Email *e, const char *path)
Set the path for a message.
Definition notmuch.c:536
static void append_replies(struct HeaderCache *hc, struct Mailbox *m, notmuch_query_t *q, notmuch_message_t *top, bool dedup, int depth)
Add all the replies to a given messages into the display.
Definition notmuch.c:889
static bool nm_message_has_tag(notmuch_message_t *msg, const char *tag)
Does a message have this tag?
Definition notmuch.c:1100
bool nm_query_window_available(void)
Are windowed queries enabled for use?
Definition notmuch.c:1677
static int update_tags(notmuch_message_t *msg, const char *tag_str)
Update the tags on a message.
Definition notmuch.c:1141
static bool read_mesgs_query(struct Mailbox *m, notmuch_query_t *q, bool dedup)
Search for matching messages.
Definition notmuch.c:974
char * nm_url_from_query(struct Mailbox *m, char *buf, size_t buflen)
Turn a query into a URL.
Definition notmuch.c:1615
static char * get_folder_from_path(const char *path)
Find an email's folder from its path.
Definition notmuch.c:600
static char * nm2mutt_message_id(const char *id)
Converts notmuch message Id to neomutt message Id.
Definition notmuch.c:625
char * nm_email_get_folder(struct Email *e)
Get the folder for a Email.
Definition notmuch.c:1505
static void append_message(struct HeaderCache *hc, struct Mailbox *m, notmuch_message_t *msg, bool dedup)
Associate a message.
Definition notmuch.c:774
static int update_email_tags(struct Email *e, notmuch_message_t *msg)
Update the Email's tags from Notmuch.
Definition notmuch.c:481
bool nm_message_is_still_queried(struct Mailbox *m, struct Email *e)
Is a message still visible in the query?
Definition notmuch.c:1737
static int update_email_flags(struct Mailbox *m, struct Email *e, const char *tag_str)
Update the Email's flags.
Definition notmuch.c:1195
static int remove_filename(struct Mailbox *m, const char *path)
Delete a file.
Definition notmuch.c:1303
static void progress_setup(struct Mailbox *m)
Set up the Progress Bar.
Definition notmuch.c:704
static void nm_hcache_close(struct HeaderCache **ptr)
Close the header cache.
Definition notmuch.c:125
void nm_query_window_forward(void)
Function to move the current search window forward in time.
Definition notmuch.c:1694
static notmuch_messages_t * get_messages(notmuch_query_t *query)
Load messages for a query.
Definition notmuch.c:946
const char NmUrlProtocol[]
Protocol string for Notmuch URLs.
Definition notmuch.c:102
static void nm_progress_update(struct Mailbox *m)
Update the progress counter.
Definition notmuch.c:723
static struct Email * get_mutt_email(struct Mailbox *m, notmuch_message_t *msg)
Get the Email of a Notmuch message.
Definition notmuch.c:740
struct Buffer * buf_pool_get(void)
Get a Buffer from the pool.
Definition pool.c:91
void buf_pool_release(struct Buffer **ptr)
Return a Buffer to the pool.
Definition pool.c:111
Progress Bar.
@ MUTT_PROGRESS_READ
Progress tracks elements, according to $read_inc
Definition lib.h:84
@ MUTT_PROGRESS_WRITE
Progress tracks elements, according to $write_inc
Definition lib.h:85
struct Progress * progress_new(enum ProgressType type, size_t size)
Create a new Progress Bar.
Definition progress.c:139
void progress_free(struct Progress **ptr)
Free a Progress Bar.
Definition progress.c:110
void progress_set_message(struct Progress *progress, const char *fmt,...) __attribute__((__format__(__printf__
bool progress_update(struct Progress *progress, size_t pos, int percent)
Update the state of the progress bar.
Definition progress.c:80
enum NmQueryType nm_string_to_query_type(const char *str)
Lookup a query type.
Definition query.c:150
enum NmQueryType nm_parse_type_from_query(char *buf, enum NmQueryType fallback)
Parse a query type out of a query.
Definition query.c:90
const char * nm_query_type_to_string(enum NmQueryType query_type)
Turn a query type into a string.
Definition query.c:137
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 enum NmTimebase timebase, const char *or_terms)
Windows buf with notmuch date: search term.
Definition query.c:220
Notmuch query functions.
NmWindowQueryRc
Return codes for nm_windowed_query_from_query()
Definition query.h:58
@ NM_WINDOW_QUERY_SUCCESS
Query was successful.
Definition query.h:59
@ NM_WINDOW_QUERY_INVALID_DURATION
Invalid duration.
Definition query.h:61
@ NM_WINDOW_QUERY_INVALID_TIMEBASE
Invalid timebase.
Definition query.h:60
NmQueryType
Notmuch Query Types.
Definition query.h:35
@ NM_QUERY_TYPE_UNKNOWN
Unknown query type. Error in notmuch query.
Definition query.h:36
@ NM_QUERY_TYPE_THREADS
Whole threads.
Definition query.h:38
@ NM_QUERY_TYPE_MESSAGES
Default: Messages only.
Definition query.h:37
NmTimebase
Time periods for notmuch windowed queries.
Definition query.h:45
#define STAILQ_FOREACH(var, head, field)
Definition queue.h:390
#define ASSERT(COND)
Definition signal2.h:59
volatile sig_atomic_t SigInt
true after SIGINT is received
Definition signal.c:68
A group of associated Mailboxes.
Definition account.h:36
void(* adata_free)(void **ptr)
Definition account.h:53
void * adata
Private data (for Mailbox backends)
Definition account.h:42
String manipulation buffer.
Definition buffer.h:36
The envelope/body of an email.
Definition email.h:39
bool read
Email is read.
Definition email.h:50
struct Envelope * env
Envelope information.
Definition email.h:68
void * edata
Driver-specific data.
Definition email.h:74
struct Body * body
List of MIME parts.
Definition email.h:69
bool active
Message is not to be removed.
Definition email.h:76
void * nm_edata
Notmuch private data.
Definition email.h:93
bool old
Email is seen, but unread.
Definition email.h:49
bool changed
Email has been edited.
Definition email.h:77
struct TagList tags
For drivers that support server tagging.
Definition email.h:72
char * path
Path of Email (for local Mailboxes)
Definition email.h:70
bool deleted
Email is deleted.
Definition email.h:78
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:103
Header Cache.
Definition lib.h:87
A mailbox.
Definition mailbox.h:81
void(* mdata_free)(void **ptr)
Definition mailbox.h:145
int msg_count
Total number of messages.
Definition mailbox.h:90
enum MailboxType type
Mailbox type.
Definition mailbox.h:104
void * mdata
Driver specific data.
Definition mailbox.h:134
struct Email ** emails
Array of Emails.
Definition mailbox.h:98
struct HashTable * id_hash
Hash Table: "Message-ID" -> Email.
Definition mailbox.h:125
struct Buffer pathbuf
Path of the Mailbox.
Definition mailbox.h:82
int msg_flagged
Number of flagged messages.
Definition mailbox.h:92
bool verbose
Display status messages?
Definition mailbox.h:119
int msg_unread
Number of unread messages.
Definition mailbox.h:91
A local copy of an email.
Definition message.h:34
FILE * fp
pointer to the message data
Definition message.h:35
Definition mxapi.h:98
Container for Accounts, Notifications.
Definition neomutt.h:41
struct ConfigSubset * sub
Inherited config items.
Definition neomutt.h:49
Notmuch-specific Account data -.
Definition adata.h:35
Notmuch-specific Email data -.
Definition edata.h:34
char * folder
Location of the Email.
Definition edata.h:35
char * virtual_id
Unique Notmuch Id.
Definition edata.h:37
Notmuch-specific Mailbox data -.
Definition mdata.h:35
struct Url * db_url
Parsed view url of the Notmuch database.
Definition mdata.h:36
int oldmsgcount
Old message count.
Definition mdata.h:42
struct Progress * progress
A progress bar.
Definition mdata.h:41
struct timespec mtime
Time Mailbox was last changed.
Definition mdata.h:44
enum NmQueryType query_type
Messages or Threads.
Definition mdata.h:39
int db_limit
Maximum number of results to return.
Definition mdata.h:38
char * db_query
Previous query.
Definition mdata.h:37
int ignmsgcount
Ignored messages.
Definition mdata.h:43
Array of Notmuch tags.
Definition tag.h:36
struct StringArray tags
Tags.
Definition tag.h:37
char * tag_str
Source string.
Definition tag.h:38
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
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:303
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:392
void nm_tag_array_free(struct NmTags *tags)
Free all memory of a NmTags.
Definition tag.c:40
struct NmTags 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:51
Notmuch tag functions.
bool driver_tags_replace(struct TagList *tl, const char *tags)
Replace all tags.
Definition tags.c:202
void driver_tags_get(struct TagList *tl, struct Buffer *tags)
Get tags all tags separated by space.
Definition tags.c:165
void driver_tags_get_transformed(struct TagList *tl, struct Buffer *tags)
Get transformed tags separated by space.
Definition tags.c:153
struct Url * url_parse(const char *src)
Fill in Url.
Definition url.c:242
void url_free(struct Url **ptr)
Free the contents of a URL.
Definition url.c:124
void url_pct_encode(char *buf, size_t buflen, const char *src)
Percent-encode a string.
Definition url.c:152