NeoMutt
Teaching an old dog new tricks
DOXYGEN
Loading...
Searching...
No Matches
notmuch.c
Go to the documentation of this file.
1
43#include "config.h"
44#include <errno.h>
45#include <limits.h>
46#include <notmuch.h>
47#include <stdbool.h>
48#include <stdint.h>
49#include <stdio.h>
50#include <string.h>
51#include <time.h>
52#include <unistd.h>
53#include "private.h"
54#include "mutt/lib.h"
55#include "config/lib.h"
56#include "email/lib.h"
57#include "core/lib.h"
58#include "mutt.h"
59#include "lib.h"
60#include "editor/lib.h"
61#include "hcache/lib.h"
62#include "history/lib.h"
63#include "index/lib.h"
64#include "maildir/lib.h"
65#include "progress/lib.h"
66#include "adata.h"
67#include "commands.h"
68#include "edata.h"
69#include "globals.h" // IWYU pragma: keep
70#include "mdata.h"
71#include "mutt_thread.h"
72#include "mx.h"
73#include "protos.h"
74#include "query.h"
75#include "tag.h"
76#ifdef ENABLE_NLS
77#include <libintl.h>
78#endif
79
80struct stat;
81
85static const struct Command NmCommands[] = {
86 // clang-format off
87 { "unvirtual-mailboxes", parse_unmailboxes, 0 },
88 { "virtual-mailboxes", parse_mailboxes, MUTT_NAMED },
89 // clang-format on
90};
91
93const char NmUrlProtocol[] = "notmuch://";
95const int NmUrlProtocolLen = sizeof(NmUrlProtocol) - 1;
96
100void nm_init(void)
101{
103}
104
110static struct HeaderCache *nm_hcache_open(struct Mailbox *m)
111{
112#ifdef USE_HCACHE
113 const char *const c_header_cache = cs_subset_path(NeoMutt->sub, "header_cache");
114 return hcache_open(c_header_cache, mailbox_path(m), NULL);
115#else
116 return NULL;
117#endif
118}
119
124static void nm_hcache_close(struct HeaderCache **ptr)
125{
126#ifdef USE_HCACHE
127 hcache_close(ptr);
128#endif
129}
130
136static char *nm_get_default_url(void)
137{
138 // path to DB + query + url "decoration"
139 size_t len = PATH_MAX + 1024 + 32;
140 char *url = mutt_mem_malloc(len);
141
142 // Try to use `$nm_default_url` or `$folder`.
143 // If neither are set, it is impossible to create a Notmuch URL.
144 const char *const c_nm_default_url = cs_subset_string(NeoMutt->sub, "nm_default_url");
145 const char *const c_folder = cs_subset_string(NeoMutt->sub, "folder");
146 if (c_nm_default_url)
147 {
148 snprintf(url, len, "%s", c_nm_default_url);
149 }
150 else if (c_folder)
151 {
152 snprintf(url, len, "notmuch://%s", c_folder);
153 }
154 else
155 {
156 FREE(&url);
157 return NULL;
158 }
159
160 return url;
161}
162
168static struct NmMboxData *nm_get_default_data(void)
169{
170 // path to DB + query + url "decoration"
171 char *url = nm_get_default_url();
172 if (!url)
173 return NULL;
174
175 struct NmMboxData *default_data = nm_mdata_new(url);
176 FREE(&url);
177
178 return default_data;
179}
180
191static int init_mailbox(struct Mailbox *m)
192{
193 if (!m || (m->type != MUTT_NOTMUCH))
194 return -1;
195
196 if (m->mdata)
197 return 0;
198
200 if (!m->mdata)
201 return -1;
202
204 return 0;
205}
206
213static char *email_get_id(struct Email *e)
214{
215 struct NmEmailData *edata = nm_edata_get(e);
216 if (!edata)
217 return NULL;
218
219 return edata->virtual_id;
220}
221
229static char *email_get_fullpath(struct Email *e, char *buf, size_t buflen)
230{
231 snprintf(buf, buflen, "%s/%s", nm_email_get_folder(e), e->path);
232 return buf;
233}
234
244static void query_window_reset(void)
245{
246 mutt_debug(LL_DEBUG2, "entering\n");
247 cs_subset_str_native_set(NeoMutt->sub, "nm_query_window_current_position", 0, NULL);
248}
249
274static bool windowed_query_from_query(const char *query, char *buf, size_t buflen)
275{
276 mutt_debug(LL_DEBUG2, "nm: %s\n", query);
277
278 const bool c_nm_query_window_enable = cs_subset_bool(NeoMutt->sub, "nm_query_window_enable");
279 const short c_nm_query_window_duration = cs_subset_number(NeoMutt->sub, "nm_query_window_duration");
280 const short c_nm_query_window_current_position = cs_subset_number(NeoMutt->sub, "nm_query_window_current_position");
281 const char *const c_nm_query_window_current_search = cs_subset_string(NeoMutt->sub, "nm_query_window_current_search");
282 const char *const c_nm_query_window_timebase = cs_subset_string(NeoMutt->sub, "nm_query_window_timebase");
283 const char *const c_nm_query_window_or_terms = cs_subset_string(NeoMutt->sub, "nm_query_window_or_terms");
284
285 /* if the query has changed, reset the window position */
286 if (!c_nm_query_window_current_search || !mutt_str_equal(query, c_nm_query_window_current_search))
287 {
289 }
290
292 buf, buflen, c_nm_query_window_enable, c_nm_query_window_duration,
293 c_nm_query_window_current_position, c_nm_query_window_current_search,
294 c_nm_query_window_timebase, c_nm_query_window_or_terms);
295
296 switch (rc)
297 {
299 {
300 mutt_debug(LL_DEBUG2, "nm: %s -> %s\n", query, buf);
301 break;
302 }
304 {
306 return false;
307 }
309 {
311 // L10N: The values 'hour', 'day', 'week', 'month', 'year' are literal.
312 // They should not be translated.
313 _("Invalid nm_query_window_timebase value (valid values are: hour, day, week, month, year)"));
314 mutt_debug(LL_DEBUG2, "Invalid nm_query_window_timebase value\n");
315 return false;
316 }
317 }
318
319 return true;
320}
321
338static char *get_query_string(struct NmMboxData *mdata, bool window)
339{
340 mutt_debug(LL_DEBUG2, "nm: %s\n", window ? "true" : "false");
341
342 if (!mdata)
343 return NULL;
344 if (mdata->db_query && !window)
345 return mdata->db_query;
346
347 const char *const c_nm_query_type = cs_subset_string(NeoMutt->sub, "nm_query_type");
348 mdata->query_type = nm_string_to_query_type(c_nm_query_type); /* user's default */
349
350 struct UrlQuery *item = NULL;
351 STAILQ_FOREACH(item, &mdata->db_url->query_strings, entries)
352 {
353 if (!item->value || !item->name)
354 continue;
355
356 if (mutt_str_equal(item->name, "limit"))
357 {
358 if (!mutt_str_atoi_full(item->value, &mdata->db_limit))
359 {
360 mutt_error(_("failed to parse notmuch limit: %s"), item->value);
361 }
362 }
363 else if (mutt_str_equal(item->name, "type"))
364 {
366 }
367 else if (mutt_str_equal(item->name, "query"))
368 {
369 mutt_str_replace(&mdata->db_query, item->value);
370 }
371 }
372
373 if (!mdata->db_query)
374 return NULL;
375
376 if (window)
377 {
378 char buf[1024] = { 0 };
379 cs_subset_str_string_set(NeoMutt->sub, "nm_query_window_current_search",
380 mdata->db_query, NULL);
381
382 /* if a date part is defined, do not apply windows (to avoid the risk of
383 * having a non-intersected date frame). A good improvement would be to
384 * accept if they intersect */
385 if (!strstr(mdata->db_query, "date:") &&
386 windowed_query_from_query(mdata->db_query, buf, sizeof(buf)))
387 {
388 mutt_str_replace(&mdata->db_query, buf);
389 }
390
391 mutt_debug(LL_DEBUG2, "nm: query (windowed) '%s'\n", mdata->db_query);
392 }
393 else
394 {
395 mutt_debug(LL_DEBUG2, "nm: query '%s'\n", mdata->db_query);
396 }
397
398 return mdata->db_query;
399}
400
406static int get_limit(struct NmMboxData *mdata)
407{
408 return mdata ? mdata->db_limit : 0;
409}
410
415static void apply_exclude_tags(notmuch_query_t *query)
416{
417 const char *const c_nm_exclude_tags = cs_subset_string(NeoMutt->sub, "nm_exclude_tags");
418 if (!c_nm_exclude_tags || !query)
419 return;
420
421 struct NmTags tags = nm_tag_str_to_tags(c_nm_exclude_tags);
422
423 char **tag = NULL;
424 ARRAY_FOREACH(tag, &tags.tags)
425 {
426 mutt_debug(LL_DEBUG2, "nm: query exclude tag '%s'\n", *tag);
427 notmuch_query_add_tag_exclude(query, *tag);
428 }
429
430 notmuch_query_set_omit_excluded(query, 1);
432}
433
441static notmuch_query_t *get_query(struct Mailbox *m, bool writable)
442{
443 struct NmMboxData *mdata = nm_mdata_get(m);
444 if (!mdata)
445 return NULL;
446
447 notmuch_database_t *db = nm_db_get(m, writable);
448 const char *str = get_query_string(mdata, true);
449
450 if (!db || !str)
451 goto err;
452
453 notmuch_query_t *q = notmuch_query_create(db, str);
454 if (!q)
455 goto err;
456
458 notmuch_query_set_sort(q, NOTMUCH_SORT_NEWEST_FIRST);
459 mutt_debug(LL_DEBUG2, "nm: query successfully initialized (%s)\n", str);
460 return q;
461err:
462 nm_db_release(m);
463 return NULL;
464}
465
473static int update_email_tags(struct Email *e, notmuch_message_t *msg)
474{
475 struct NmEmailData *edata = nm_edata_get(e);
476 char *new_tags = NULL;
477 char *old_tags = NULL;
478
479 mutt_debug(LL_DEBUG2, "nm: tags update requested (%s)\n", edata->virtual_id);
480
481 for (notmuch_tags_t *tags = notmuch_message_get_tags(msg);
482 tags && notmuch_tags_valid(tags); notmuch_tags_move_to_next(tags))
483 {
484 const char *t = notmuch_tags_get(tags);
485 if (!t || (*t == '\0'))
486 continue;
487
488 mutt_str_append_item(&new_tags, t, ' ');
489 }
490
491 old_tags = driver_tags_get(&e->tags);
492
493 if (new_tags && old_tags && (mutt_str_equal(old_tags, new_tags)))
494 {
495 FREE(&old_tags);
496 FREE(&new_tags);
497 mutt_debug(LL_DEBUG2, "nm: tags unchanged\n");
498 return 1;
499 }
500 FREE(&old_tags);
501
502 /* new version */
503 driver_tags_replace(&e->tags, new_tags);
504 FREE(&new_tags);
505
506 new_tags = driver_tags_get_transformed(&e->tags);
507 mutt_debug(LL_DEBUG2, "nm: new tags: '%s'\n", new_tags);
508 FREE(&new_tags);
509
510 new_tags = driver_tags_get(&e->tags);
511 mutt_debug(LL_DEBUG2, "nm: new tag transforms: '%s'\n", new_tags);
512 FREE(&new_tags);
513
514 return 0;
515}
516
524static int update_message_path(struct Email *e, const char *path)
525{
526 struct NmEmailData *edata = nm_edata_get(e);
527
528 mutt_debug(LL_DEBUG2, "nm: path update requested path=%s, (%s)\n", path, edata->virtual_id);
529
530 char *p = strrchr(path, '/');
531 if (p && ((p - path) > 3) &&
532 (mutt_strn_equal(p - 3, "cur", 3) || mutt_strn_equal(p - 3, "new", 3) ||
533 mutt_strn_equal(p - 3, "tmp", 3)))
534 {
535 edata->type = MUTT_MAILDIR;
536
537 FREE(&e->path);
538 FREE(&edata->folder);
539
540 p -= 3; /* skip subfolder (e.g. "new") */
541 if (cs_subset_bool(NeoMutt->sub, "mark_old"))
542 {
543 e->old = mutt_str_startswith(p, "cur");
544 }
545 e->path = mutt_str_dup(p);
546
547 for (; (p > path) && (*(p - 1) == '/'); p--)
548 ; // do nothing
549
550 edata->folder = mutt_strn_dup(path, p - path);
551
552 mutt_debug(LL_DEBUG2, "nm: folder='%s', file='%s'\n", edata->folder, e->path);
553 return 0;
554 }
555
556 return 1;
557}
558
565static char *get_folder_from_path(const char *path)
566{
567 char *p = strrchr(path, '/');
568
569 if (p && ((p - path) > 3) &&
570 (mutt_strn_equal(p - 3, "cur", 3) || mutt_strn_equal(p - 3, "new", 3) ||
571 mutt_strn_equal(p - 3, "tmp", 3)))
572 {
573 p -= 3;
574 for (; (p > path) && (*(p - 1) == '/'); p--)
575 ; // do nothing
576
577 return mutt_strn_dup(path, p - path);
578 }
579
580 return NULL;
581}
582
590static char *nm2mutt_message_id(const char *id)
591{
592 if (!id)
593 return NULL;
594
595 char *mid = NULL;
596 mutt_str_asprintf(&mid, "<%s>", id);
597 return mid;
598}
599
608static int init_email(struct Email *e, const char *path, notmuch_message_t *msg)
609{
610 if (nm_edata_get(e))
611 return 0;
612
613 struct NmEmailData *edata = nm_edata_new();
614 e->nm_edata = edata;
615
616 /* Notmuch ensures that message Id exists (if not notmuch Notmuch will
617 * generate an ID), so it's more safe than use neomutt Email->env->id */
618 const char *id = notmuch_message_get_message_id(msg);
619 edata->virtual_id = mutt_str_dup(id);
620
621 mutt_debug(LL_DEBUG2, "nm: [e=%p, edata=%p] (%s)\n", (void *) e, (void *) edata, id);
622
623 char *nm_msg_id = nm2mutt_message_id(id);
624 if (!e->env->message_id)
625 {
626 e->env->message_id = nm_msg_id;
627 }
628 else if (!mutt_str_equal(e->env->message_id, nm_msg_id))
629 {
630 FREE(&e->env->message_id);
631 e->env->message_id = nm_msg_id;
632 }
633 else
634 {
635 FREE(&nm_msg_id);
636 }
637
638 if (update_message_path(e, path) != 0)
639 return -1;
640
641 update_email_tags(e, msg);
642
643 return 0;
644}
645
652static const char *get_message_last_filename(notmuch_message_t *msg)
653{
654 const char *name = NULL;
655
656 for (notmuch_filenames_t *ls = notmuch_message_get_filenames(msg);
657 ls && notmuch_filenames_valid(ls); notmuch_filenames_move_to_next(ls))
658 {
659 name = notmuch_filenames_get(ls);
660 }
661
662 return name;
663}
664
669static void progress_setup(struct Mailbox *m)
670{
671 if (!m->verbose)
672 return;
673
674 struct NmMboxData *mdata = nm_mdata_get(m);
675 if (!mdata)
676 return;
677
678 mdata->oldmsgcount = m->msg_count;
679 mdata->ignmsgcount = 0;
680 mdata->progress = progress_new(_("Reading messages..."), MUTT_PROGRESS_READ,
681 mdata->oldmsgcount);
682}
683
688static void nm_progress_update(struct Mailbox *m)
689{
690 struct NmMboxData *mdata = nm_mdata_get(m);
691
692 if (!m->verbose || !mdata || !mdata->progress)
693 return;
694
695 progress_update(mdata->progress, m->msg_count + mdata->ignmsgcount, -1);
696}
697
705static struct Email *get_mutt_email(struct Mailbox *m, notmuch_message_t *msg)
706{
707 if (!m || !msg)
708 return NULL;
709
710 const char *id = notmuch_message_get_message_id(msg);
711 if (!id)
712 return NULL;
713
714 mutt_debug(LL_DEBUG2, "nm: neomutt email, id='%s'\n", id);
715
716 if (!m->id_hash)
717 {
718 mutt_debug(LL_DEBUG2, "nm: init hash\n");
720 if (!m->id_hash)
721 return NULL;
722 }
723
724 char *mid = nm2mutt_message_id(id);
725 mutt_debug(LL_DEBUG2, "nm: neomutt id='%s'\n", mid);
726
727 struct Email *e = mutt_hash_find(m->id_hash, mid);
728 FREE(&mid);
729 return e;
730}
731
739static void append_message(struct HeaderCache *hc, struct Mailbox *m,
740 notmuch_message_t *msg, bool dedup)
741{
742 struct NmMboxData *mdata = nm_mdata_get(m);
743 if (!mdata)
744 return;
745
746 char *newpath = NULL;
747 struct Email *e = NULL;
748
749 /* deduplicate */
750 if (dedup && get_mutt_email(m, msg))
751 {
752 mdata->ignmsgcount++;
754 mutt_debug(LL_DEBUG2, "nm: ignore id=%s, already in the m\n",
755 notmuch_message_get_message_id(msg));
756 return;
757 }
758
759 const char *path = get_message_last_filename(msg);
760 if (!path)
761 return;
762
763 mutt_debug(LL_DEBUG2, "nm: appending message, i=%d, id=%s, path=%s\n",
764 m->msg_count, notmuch_message_get_message_id(msg), path);
765
767
768#ifdef USE_HCACHE
770 if (!e)
771#endif
772 {
773 if (access(path, F_OK) == 0)
774 {
775 /* We pass is_old=false as argument here, but e->old will be updated later
776 * by update_message_path() (called by init_email() below). */
777 e = maildir_email_new();
778 if (!maildir_parse_message(MUTT_MAILDIR, path, false, e))
779 email_free(&e);
780 }
781 else
782 {
783 /* maybe moved try find it... */
784 char *folder = get_folder_from_path(path);
785
786 if (folder)
787 {
788 FILE *fp = maildir_open_find_message(folder, path, &newpath);
789 if (fp)
790 {
791 e = maildir_email_new();
792 if (!maildir_parse_stream(MUTT_MAILDIR, fp, newpath, false, e))
793 email_free(&e);
794 mutt_file_fclose(&fp);
795
796 mutt_debug(LL_DEBUG1, "nm: not up-to-date: %s -> %s\n", path, newpath);
797 }
798 }
799 FREE(&folder);
800 }
801
802 if (!e)
803 {
804 mutt_debug(LL_DEBUG1, "nm: failed to parse message: %s\n", path);
805 goto done;
806 }
807
808#ifdef USE_HCACHE
809 hcache_store(hc, newpath ? newpath : path,
810 mutt_str_len(newpath ? newpath : path), e, 0);
811#endif
812 }
813
814 if (init_email(e, newpath ? newpath : path, msg) != 0)
815 {
816 email_free(&e);
817 mutt_debug(LL_DEBUG1, "nm: failed to append email!\n");
818 goto done;
819 }
820
821 e->active = true;
822 e->index = m->msg_count;
823 mailbox_size_add(m, e);
824 m->emails[m->msg_count] = e;
825 m->msg_count++;
826
827 if (newpath)
828 {
829 /* remember that file has been moved -- nm_mbox_sync() will update the DB */
830 struct NmEmailData *edata = nm_edata_get(e);
831 if (edata)
832 {
833 mutt_debug(LL_DEBUG1, "nm: remember obsolete path: %s\n", path);
834 edata->oldpath = mutt_str_dup(path);
835 }
836 }
838done:
839 FREE(&newpath);
840}
841
852static void append_replies(struct HeaderCache *hc, struct Mailbox *m,
853 notmuch_query_t *q, notmuch_message_t *top, bool dedup)
854{
855 notmuch_messages_t *msgs = NULL;
856
857 for (msgs = notmuch_message_get_replies(top); notmuch_messages_valid(msgs);
858 notmuch_messages_move_to_next(msgs))
859 {
860 notmuch_message_t *nm = notmuch_messages_get(msgs);
861 append_message(hc, m, nm, dedup);
862 /* recurse through all the replies to this message too */
863 append_replies(hc, m, q, nm, dedup);
864 notmuch_message_destroy(nm);
865 }
866}
867
879static void append_thread(struct HeaderCache *hc, struct Mailbox *m,
880 notmuch_query_t *q, notmuch_thread_t *thread, bool dedup)
881{
882 notmuch_messages_t *msgs = NULL;
883
884 for (msgs = notmuch_thread_get_toplevel_messages(thread);
885 notmuch_messages_valid(msgs); notmuch_messages_move_to_next(msgs))
886 {
887 notmuch_message_t *nm = notmuch_messages_get(msgs);
888 append_message(hc, m, nm, dedup);
889 append_replies(hc, m, q, nm, dedup);
890 notmuch_message_destroy(nm);
891 }
892}
893
903static notmuch_messages_t *get_messages(notmuch_query_t *query)
904{
905 if (!query)
906 return NULL;
907
908 notmuch_messages_t *msgs = NULL;
909
910#if LIBNOTMUCH_CHECK_VERSION(5, 0, 0)
911 if (notmuch_query_search_messages(query, &msgs) != NOTMUCH_STATUS_SUCCESS)
912 return NULL;
913#elif LIBNOTMUCH_CHECK_VERSION(4, 3, 0)
914 if (notmuch_query_search_messages_st(query, &msgs) != NOTMUCH_STATUS_SUCCESS)
915 return NULL;
916#else
917 msgs = notmuch_query_search_messages(query);
918#endif
919
920 return msgs;
921}
922
931static bool read_mesgs_query(struct Mailbox *m, notmuch_query_t *q, bool dedup)
932{
933 struct NmMboxData *mdata = nm_mdata_get(m);
934 if (!mdata)
935 return false;
936
937 int limit = get_limit(mdata);
938
939 notmuch_messages_t *msgs = get_messages(q);
940
941 if (!msgs)
942 return false;
943
944 struct HeaderCache *hc = nm_hcache_open(m);
945
946 for (; notmuch_messages_valid(msgs) && ((limit == 0) || (m->msg_count < limit));
947 notmuch_messages_move_to_next(msgs))
948 {
949 if (SigInt)
950 {
951 nm_hcache_close(&hc);
952 SigInt = false;
953 return false;
954 }
955 notmuch_message_t *nm = notmuch_messages_get(msgs);
956 append_message(hc, m, nm, dedup);
957 notmuch_message_destroy(nm);
958 }
959
960 nm_hcache_close(&hc);
961 return true;
962}
963
973static notmuch_threads_t *get_threads(notmuch_query_t *query)
974{
975 if (!query)
976 return NULL;
977
978 notmuch_threads_t *threads = NULL;
979#if LIBNOTMUCH_CHECK_VERSION(5, 0, 0)
980 if (notmuch_query_search_threads(query, &threads) != NOTMUCH_STATUS_SUCCESS)
981 return false;
982#elif LIBNOTMUCH_CHECK_VERSION(4, 3, 0)
983 if (notmuch_query_search_threads_st(query, &threads) != NOTMUCH_STATUS_SUCCESS)
984 return false;
985#else
986 threads = notmuch_query_search_threads(query);
987#endif
988
989 return threads;
990}
991
1001static bool read_threads_query(struct Mailbox *m, notmuch_query_t *q, bool dedup, int limit)
1002{
1003 struct NmMboxData *mdata = nm_mdata_get(m);
1004 if (!mdata)
1005 return false;
1006
1007 notmuch_threads_t *threads = get_threads(q);
1008 if (!threads)
1009 return false;
1010
1011 struct HeaderCache *hc = nm_hcache_open(m);
1012
1013 for (; notmuch_threads_valid(threads) && ((limit == 0) || (m->msg_count < limit));
1014 notmuch_threads_move_to_next(threads))
1015 {
1016 if (SigInt)
1017 {
1018 nm_hcache_close(&hc);
1019 SigInt = false;
1020 return false;
1021 }
1022 notmuch_thread_t *thread = notmuch_threads_get(threads);
1023 append_thread(hc, m, q, thread, dedup);
1024 notmuch_thread_destroy(thread);
1025 }
1026
1027 nm_hcache_close(&hc);
1028 return true;
1029}
1030
1038static notmuch_message_t *get_nm_message(notmuch_database_t *db, struct Email *e)
1039{
1040 notmuch_message_t *msg = NULL;
1041 char *id = email_get_id(e);
1042
1043 mutt_debug(LL_DEBUG2, "nm: find message (%s)\n", id);
1044
1045 if (id && db)
1046 notmuch_database_find_message(db, id, &msg);
1047
1048 return msg;
1049}
1050
1057static bool nm_message_has_tag(notmuch_message_t *msg, char *tag)
1058{
1059 const char *possible_match_tag = NULL;
1060 notmuch_tags_t *tags = NULL;
1061
1062 for (tags = notmuch_message_get_tags(msg); notmuch_tags_valid(tags);
1063 notmuch_tags_move_to_next(tags))
1064 {
1065 possible_match_tag = notmuch_tags_get(tags);
1066 if (mutt_str_equal(possible_match_tag, tag))
1067 {
1068 return true;
1069 }
1070 }
1071 return false;
1072}
1073
1079static void sync_email_path_with_nm(struct Email *e, notmuch_message_t *msg)
1080{
1081 const char *new_file = get_message_last_filename(msg);
1082 char old_file[PATH_MAX] = { 0 };
1083 email_get_fullpath(e, old_file, sizeof(old_file));
1084
1085 if (!mutt_str_equal(old_file, new_file))
1086 update_message_path(e, new_file);
1087}
1088
1096static int update_tags(notmuch_message_t *msg, const char *tag_str)
1097{
1098 if (!tag_str)
1099 return -1;
1100
1101 notmuch_message_freeze(msg);
1102
1104 char **tag_elem = NULL;
1105 ARRAY_FOREACH(tag_elem, &tags.tags)
1106 {
1107 char *tag = *tag_elem;
1108
1109 if (tag[0] == '-')
1110 {
1111 mutt_debug(LL_DEBUG1, "nm: remove tag: '%s'\n", tag + 1);
1112 notmuch_message_remove_tag(msg, tag + 1);
1113 }
1114 else if (tag[0] == '!')
1115 {
1116 mutt_debug(LL_DEBUG1, "nm: toggle tag: '%s'\n", tag + 1);
1117 if (nm_message_has_tag(msg, tag + 1))
1118 {
1119 notmuch_message_remove_tag(msg, tag + 1);
1120 }
1121 else
1122 {
1123 notmuch_message_add_tag(msg, tag + 1);
1124 }
1125 }
1126 else
1127 {
1128 mutt_debug(LL_DEBUG1, "nm: add tag: '%s'\n", (tag[0] == '+') ? tag + 1 : tag);
1129 notmuch_message_add_tag(msg, (tag[0] == '+') ? tag + 1 : tag);
1130 }
1131 }
1132
1133 notmuch_message_thaw(msg);
1135
1136 return 0;
1137}
1138
1150static int update_email_flags(struct Mailbox *m, struct Email *e, const char *tag_str)
1151{
1152 if (!tag_str)
1153 return -1;
1154
1155 const char *const c_nm_unread_tag = cs_subset_string(NeoMutt->sub, "nm_unread_tag");
1156 const char *const c_nm_replied_tag = cs_subset_string(NeoMutt->sub, "nm_replied_tag");
1157 const char *const c_nm_flagged_tag = cs_subset_string(NeoMutt->sub, "nm_flagged_tag");
1158
1160 char **tag_elem = NULL;
1161 ARRAY_FOREACH(tag_elem, &tags.tags)
1162 {
1163 char *tag = *tag_elem;
1164
1165 if (tag[0] == '-')
1166 {
1167 tag++;
1168 if (mutt_str_equal(tag, c_nm_unread_tag))
1169 mutt_set_flag(m, e, MUTT_READ, true, true);
1170 else if (mutt_str_equal(tag, c_nm_replied_tag))
1171 mutt_set_flag(m, e, MUTT_REPLIED, false, true);
1172 else if (mutt_str_equal(tag, c_nm_flagged_tag))
1173 mutt_set_flag(m, e, MUTT_FLAG, false, true);
1174 }
1175 else
1176 {
1177 tag = (tag[0] == '+') ? tag + 1 : tag;
1178 if (mutt_str_equal(tag, c_nm_unread_tag))
1179 mutt_set_flag(m, e, MUTT_READ, false, true);
1180 else if (mutt_str_equal(tag, c_nm_replied_tag))
1181 mutt_set_flag(m, e, MUTT_REPLIED, true, true);
1182 else if (mutt_str_equal(tag, c_nm_flagged_tag))
1183 mutt_set_flag(m, e, MUTT_FLAG, true, true);
1184 }
1185 }
1186
1188
1189 return 0;
1190}
1191
1202static int rename_maildir_filename(const char *old, char *buf, size_t buflen, struct Email *e)
1203{
1204 char filename[PATH_MAX] = { 0 };
1205 char suffix[PATH_MAX] = { 0 };
1206 char folder[PATH_MAX] = { 0 };
1207
1208 mutt_str_copy(folder, old, sizeof(folder));
1209 char *p = strrchr(folder, '/');
1210 if (p)
1211 {
1212 *p = '\0';
1213 p++;
1214 }
1215 else
1216 {
1217 p = folder;
1218 }
1219
1220 mutt_str_copy(filename, p, sizeof(filename));
1221
1222 /* remove (new,cur,...) from folder path */
1223 p = strrchr(folder, '/');
1224 if (p)
1225 *p = '\0';
1226
1227 /* remove old flags from filename */
1228 const char c_maildir_field_delimiter = *cc_maildir_field_delimiter();
1229 p = strchr(filename, c_maildir_field_delimiter);
1230 if (p)
1231 *p = '\0';
1232
1233 /* compose new flags */
1234 maildir_gen_flags(suffix, sizeof(suffix), e);
1235
1236 snprintf(buf, buflen, "%s/%s/%s%s", folder,
1237 (e->read || e->old) ? "cur" : "new", filename, suffix);
1238
1239 if (mutt_str_equal(old, buf))
1240 return 1;
1241
1242 if (rename(old, buf) != 0)
1243 {
1244 mutt_debug(LL_DEBUG1, "nm: rename(2) failed %s -> %s\n", old, buf);
1245 return -1;
1246 }
1247
1248 return 0;
1249}
1250
1258static int remove_filename(struct Mailbox *m, const char *path)
1259{
1260 struct NmMboxData *mdata = nm_mdata_get(m);
1261 if (!mdata)
1262 return -1;
1263
1264 mutt_debug(LL_DEBUG2, "nm: remove filename '%s'\n", path);
1265
1266 notmuch_database_t *db = nm_db_get(m, true);
1267 if (!db)
1268 return -1;
1269
1270 notmuch_message_t *msg = NULL;
1271 notmuch_status_t st = notmuch_database_find_message_by_filename(db, path, &msg);
1272 if (st || !msg)
1273 return -1;
1274
1275 int trans = nm_db_trans_begin(m);
1276 if (trans < 0)
1277 return -1;
1278
1279 /* note that unlink() is probably unnecessary here, it's already removed
1280 * by mh_sync_mailbox_message(), but for sure... */
1281 notmuch_filenames_t *ls = NULL;
1282 st = notmuch_database_remove_message(db, path);
1283 switch (st)
1284 {
1285 case NOTMUCH_STATUS_SUCCESS:
1286 mutt_debug(LL_DEBUG2, "nm: remove success, call unlink\n");
1287 unlink(path);
1288 break;
1289 case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:
1290 mutt_debug(LL_DEBUG2, "nm: remove success (duplicate), call unlink\n");
1291 unlink(path);
1292 for (ls = notmuch_message_get_filenames(msg);
1293 ls && notmuch_filenames_valid(ls); notmuch_filenames_move_to_next(ls))
1294 {
1295 path = notmuch_filenames_get(ls);
1296
1297 mutt_debug(LL_DEBUG2, "nm: remove duplicate: '%s'\n", path);
1298 unlink(path);
1299 notmuch_database_remove_message(db, path);
1300 }
1301 break;
1302 default:
1303 mutt_debug(LL_DEBUG1, "nm: failed to remove '%s' [st=%d]\n", path, (int) st);
1304 break;
1305 }
1306
1307 notmuch_message_destroy(msg);
1308 if (trans)
1309 nm_db_trans_end(m);
1310 return 0;
1311}
1312
1322static int rename_filename(struct Mailbox *m, const char *old_file,
1323 const char *new_file, struct Email *e)
1324{
1325 struct NmMboxData *mdata = nm_mdata_get(m);
1326 if (!mdata)
1327 return -1;
1328
1329 notmuch_database_t *db = nm_db_get(m, true);
1330 if (!db || !new_file || !old_file || (access(new_file, F_OK) != 0))
1331 return -1;
1332
1333 int rc = -1;
1334 notmuch_status_t st;
1335 notmuch_filenames_t *ls = NULL;
1336 notmuch_message_t *msg = NULL;
1337
1338 mutt_debug(LL_DEBUG1, "nm: rename filename, %s -> %s\n", old_file, new_file);
1339 int trans = nm_db_trans_begin(m);
1340 if (trans < 0)
1341 return -1;
1342
1343 mutt_debug(LL_DEBUG2, "nm: rename: add '%s'\n", new_file);
1344#if LIBNOTMUCH_CHECK_VERSION(5, 1, 0)
1345 st = notmuch_database_index_file(db, new_file, NULL, &msg);
1346#else
1347 st = notmuch_database_add_message(db, new_file, &msg);
1348#endif
1349
1350 if ((st != NOTMUCH_STATUS_SUCCESS) && (st != NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID))
1351 {
1352 mutt_debug(LL_DEBUG1, "nm: failed to add '%s' [st=%d]\n", new_file, (int) st);
1353 goto done;
1354 }
1355
1356 mutt_debug(LL_DEBUG2, "nm: rename: rem '%s'\n", old_file);
1357 st = notmuch_database_remove_message(db, old_file);
1358 switch (st)
1359 {
1360 case NOTMUCH_STATUS_SUCCESS:
1361 break;
1362 case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:
1363 mutt_debug(LL_DEBUG2, "nm: rename: syncing duplicate filename\n");
1364 notmuch_message_destroy(msg);
1365 msg = NULL;
1366 notmuch_database_find_message_by_filename(db, new_file, &msg);
1367
1368 for (ls = notmuch_message_get_filenames(msg);
1369 msg && ls && notmuch_filenames_valid(ls); notmuch_filenames_move_to_next(ls))
1370 {
1371 const char *path = notmuch_filenames_get(ls);
1372 char newpath[PATH_MAX] = { 0 };
1373
1374 if (mutt_str_equal(new_file, path))
1375 continue;
1376
1377 mutt_debug(LL_DEBUG2, "nm: rename: syncing duplicate: %s\n", path);
1378
1379 if (rename_maildir_filename(path, newpath, sizeof(newpath), e) == 0)
1380 {
1381 mutt_debug(LL_DEBUG2, "nm: rename dup %s -> %s\n", path, newpath);
1382 notmuch_database_remove_message(db, path);
1383#if LIBNOTMUCH_CHECK_VERSION(5, 1, 0)
1384 notmuch_database_index_file(db, newpath, NULL, NULL);
1385#else
1386 notmuch_database_add_message(db, newpath, NULL);
1387#endif
1388 }
1389 }
1390 notmuch_message_destroy(msg);
1391 msg = NULL;
1392 notmuch_database_find_message_by_filename(db, new_file, &msg);
1393 st = NOTMUCH_STATUS_SUCCESS;
1394 break;
1395 default:
1396 mutt_debug(LL_DEBUG1, "nm: failed to remove '%s' [st=%d]\n", old_file, (int) st);
1397 break;
1398 }
1399
1400 if ((st == NOTMUCH_STATUS_SUCCESS) && e && msg)
1401 {
1402 notmuch_message_maildir_flags_to_tags(msg);
1403 update_email_tags(e, msg);
1404
1405 char *tags = driver_tags_get(&e->tags);
1406 update_tags(msg, tags);
1407 FREE(&tags);
1408 }
1409
1410 rc = 0;
1411done:
1412 if (msg)
1413 notmuch_message_destroy(msg);
1414 if (trans)
1415 nm_db_trans_end(m);
1416 return rc;
1417}
1418
1426static unsigned int count_query(notmuch_database_t *db, const char *qstr, int limit)
1427{
1428 notmuch_query_t *q = notmuch_query_create(db, qstr);
1429 if (!q)
1430 return 0;
1431
1432 unsigned int res = 0;
1433
1435#if LIBNOTMUCH_CHECK_VERSION(5, 0, 0)
1436 if (notmuch_query_count_messages(q, &res) != NOTMUCH_STATUS_SUCCESS)
1437 res = 0; /* may not be defined on error */
1438#elif LIBNOTMUCH_CHECK_VERSION(4, 3, 0)
1439 if (notmuch_query_count_messages_st(q, &res) != NOTMUCH_STATUS_SUCCESS)
1440 res = 0; /* may not be defined on error */
1441#else
1442 res = notmuch_query_count_messages(q);
1443#endif
1444 notmuch_query_destroy(q);
1445 mutt_debug(LL_DEBUG1, "nm: count '%s', result=%d\n", qstr, res);
1446
1447 if ((limit > 0) && (res > limit))
1448 res = limit;
1449
1450 return res;
1451}
1452
1460{
1461 struct NmEmailData *edata = nm_edata_get(e);
1462 if (!edata)
1463 return NULL;
1464
1465 return edata->folder;
1466}
1467
1478char *nm_email_get_folder_rel_db(struct Mailbox *m, struct Email *e)
1479{
1480 char *full_folder = nm_email_get_folder(e);
1481 if (!full_folder)
1482 return NULL;
1483
1484 const char *db_path = nm_db_get_filename(m);
1485 if (!db_path)
1486 return NULL;
1487
1488 return full_folder + strlen(db_path);
1489}
1490
1498int nm_read_entire_thread(struct Mailbox *m, struct Email *e)
1499{
1500 if (!m)
1501 return -1;
1502
1503 struct NmMboxData *mdata = nm_mdata_get(m);
1504 if (!mdata)
1505 return -1;
1506
1507 notmuch_query_t *q = NULL;
1508 notmuch_database_t *db = NULL;
1509 notmuch_message_t *msg = NULL;
1510 int rc = -1;
1511
1512 if (!(db = nm_db_get(m, false)) || !(msg = get_nm_message(db, e)))
1513 goto done;
1514
1515 mutt_debug(LL_DEBUG1, "nm: reading entire-thread messages...[current count=%d]\n",
1516 m->msg_count);
1517
1518 progress_setup(m);
1519 const char *id = notmuch_message_get_thread_id(msg);
1520 if (!id)
1521 goto done;
1522
1523 char *qstr = NULL;
1524 mutt_str_append_item(&qstr, "thread:", '\0');
1525 mutt_str_append_item(&qstr, id, '\0');
1526
1527 q = notmuch_query_create(db, qstr);
1528 FREE(&qstr);
1529 if (!q)
1530 goto done;
1532 notmuch_query_set_sort(q, NOTMUCH_SORT_NEWEST_FIRST);
1533
1534 read_threads_query(m, q, true, 0);
1535 mdata->mtime.tv_sec = mutt_date_now();
1536 mdata->mtime.tv_nsec = 0;
1537 rc = 0;
1538
1539 if (m->msg_count > mdata->oldmsgcount)
1541done:
1542 if (q)
1543 notmuch_query_destroy(q);
1544
1545 nm_db_release(m);
1546
1547 if (m->msg_count == mdata->oldmsgcount)
1548 mutt_message(_("No more messages in the thread"));
1549
1550 mdata->oldmsgcount = 0;
1551 mutt_debug(LL_DEBUG1, "nm: reading entire-thread messages... done [rc=%d, count=%d]\n",
1552 rc, m->msg_count);
1553 progress_free(&mdata->progress);
1554 return rc;
1555}
1556
1565char *nm_url_from_query(struct Mailbox *m, char *buf, size_t buflen)
1566{
1567 mutt_debug(LL_DEBUG2, "(%s)\n", buf);
1568 struct NmMboxData *mdata = nm_mdata_get(m);
1569 char url[PATH_MAX + 1024 + 32]; /* path to DB + query + URL "decoration" */
1570 int added;
1571 bool using_default_data = false;
1572
1573 // No existing data. Try to get a default NmMboxData.
1574 if (!mdata)
1575 {
1577
1578 // Failed to get default data.
1579 if (!mdata)
1580 return NULL;
1581
1582 using_default_data = true;
1583 }
1584
1586 cs_subset_string(NeoMutt->sub, "nm_query_type"));
1587 mdata->query_type = nm_parse_type_from_query(buf, query_type);
1588
1589 const short c_nm_db_limit = cs_subset_number(NeoMutt->sub, "nm_db_limit");
1590 if (get_limit(mdata) == c_nm_db_limit)
1591 {
1592 added = snprintf(url, sizeof(url), "%s%s?type=%s&query=", NmUrlProtocol,
1594 }
1595 else
1596 {
1597 added = snprintf(url, sizeof(url), "%s%s?type=%s&limit=%d&query=", NmUrlProtocol,
1600 }
1601
1602 if (added >= sizeof(url))
1603 {
1604 // snprintf output was truncated, so can't create URL
1605 return NULL;
1606 }
1607
1608 url_pct_encode(&url[added], sizeof(url) - added, buf);
1609
1610 mutt_str_copy(buf, url, buflen);
1611 buf[buflen - 1] = '\0';
1612
1613 if (using_default_data)
1614 nm_mdata_free((void **) &mdata);
1615
1616 mutt_debug(LL_DEBUG1, "nm: url from query '%s'\n", buf);
1617 return buf;
1618}
1619
1625{
1626 const short c_nm_query_window_duration = cs_subset_number(NeoMutt->sub, "nm_query_window_duration");
1627 const bool c_nm_query_window_enable = cs_subset_bool(NeoMutt->sub, "nm_query_window_enable");
1628
1629 return c_nm_query_window_enable || (c_nm_query_window_duration > 0);
1630}
1631
1642{
1643 const short c_nm_query_window_current_position = cs_subset_number(NeoMutt->sub, "nm_query_window_current_position");
1644 if (c_nm_query_window_current_position != 0)
1645 {
1646 cs_subset_str_native_set(NeoMutt->sub, "nm_query_window_current_position",
1647 c_nm_query_window_current_position - 1, NULL);
1648 }
1649
1650 mutt_debug(LL_DEBUG2, "(%d)\n", c_nm_query_window_current_position - 1);
1651}
1652
1662{
1663 const short c_nm_query_window_current_position = cs_subset_number(NeoMutt->sub, "nm_query_window_current_position");
1664 cs_subset_str_native_set(NeoMutt->sub, "nm_query_window_current_position",
1665 c_nm_query_window_current_position + 1, NULL);
1666 mutt_debug(LL_DEBUG2, "(%d)\n", c_nm_query_window_current_position + 1);
1667}
1668
1673{
1674 cs_subset_str_native_set(NeoMutt->sub, "nm_query_window_current_position", 0, NULL);
1675 mutt_debug(LL_DEBUG2, "Reset nm_query_window_current_position to 0\n");
1676}
1677
1685{
1686 struct NmMboxData *mdata = nm_mdata_get(m);
1687 if (!mdata)
1688 return false;
1689
1690 notmuch_database_t *db = nm_db_get(m, false);
1691 char *orig_str = get_query_string(mdata, true);
1692
1693 if (!db || !orig_str)
1694 return false;
1695
1696 char *new_str = NULL;
1697 bool rc = false;
1698 if (mutt_str_asprintf(&new_str, "id:%s and (%s)", email_get_id(e), orig_str) < 0)
1699 return false;
1700
1701 mutt_debug(LL_DEBUG2, "nm: checking if message is still queried: %s\n", new_str);
1702
1703 notmuch_query_t *q = notmuch_query_create(db, new_str);
1704
1705 switch (mdata->query_type)
1706 {
1707 case NM_QUERY_TYPE_UNKNOWN: // UNKNOWN should never occur, but MESGS is default
1709 {
1710 notmuch_messages_t *messages = get_messages(q);
1711
1712 if (!messages)
1713 return false;
1714
1715 rc = notmuch_messages_valid(messages);
1716 notmuch_messages_destroy(messages);
1717 break;
1718 }
1720 {
1721 notmuch_threads_t *threads = get_threads(q);
1722
1723 if (!threads)
1724 return false;
1725
1726 rc = notmuch_threads_valid(threads);
1727 notmuch_threads_destroy(threads);
1728 break;
1729 }
1730 }
1731
1732 notmuch_query_destroy(q);
1733
1734 mutt_debug(LL_DEBUG2, "nm: checking if message is still queried: %s = %s\n",
1735 new_str, rc ? "true" : "false");
1736
1737 return rc;
1738}
1739
1749int nm_update_filename(struct Mailbox *m, const char *old_file,
1750 const char *new_file, struct Email *e)
1751{
1752 char buf[PATH_MAX] = { 0 };
1753 struct NmMboxData *mdata = nm_mdata_get(m);
1754 if (!mdata || !new_file)
1755 return -1;
1756
1757 if (!old_file && nm_edata_get(e))
1758 {
1759 email_get_fullpath(e, buf, sizeof(buf));
1760 old_file = buf;
1761 }
1762
1763 int rc = rename_filename(m, old_file, new_file, e);
1764
1765 nm_db_release(m);
1766 mdata->mtime.tv_sec = mutt_date_now();
1767 mdata->mtime.tv_nsec = 0;
1768 return rc;
1769}
1770
1774static enum MxStatus nm_mbox_check_stats(struct Mailbox *m, uint8_t flags)
1775{
1776 struct UrlQuery *item = NULL;
1777 struct Url *url = NULL;
1778 const char *db_filename = NULL;
1779 char *db_query = NULL;
1780 notmuch_database_t *db = NULL;
1781 enum MxStatus rc = MX_STATUS_ERROR;
1782 const short c_nm_db_limit = cs_subset_number(NeoMutt->sub, "nm_db_limit");
1783 int limit = c_nm_db_limit;
1784 mutt_debug(LL_DEBUG1, "nm: count\n");
1785
1786 url = url_parse(mailbox_path(m));
1787 if (!url)
1788 {
1789 mutt_error(_("failed to parse notmuch url: %s"), mailbox_path(m));
1790 goto done;
1791 }
1792
1793 STAILQ_FOREACH(item, &url->query_strings, entries)
1794 {
1795 if (item->value && (mutt_str_equal(item->name, "query")))
1796 {
1797 db_query = item->value;
1798 }
1799 else if (item->value && (mutt_str_equal(item->name, "limit")))
1800 {
1801 // Try to parse the limit
1802 if (!mutt_str_atoi_full(item->value, &limit))
1803 {
1804 mutt_error(_("failed to parse limit: %s"), item->value);
1805 goto done;
1806 }
1807 }
1808 }
1809
1810 if (!db_query)
1811 goto done;
1812
1813 db_filename = url->path;
1814 if (!db_filename)
1815 db_filename = nm_db_get_filename(m);
1816
1817 /* don't be verbose about connection, as we're called from
1818 * sidebar/mailbox very often */
1819 db = nm_db_do_open(db_filename, false, false);
1820 if (!db)
1821 goto done;
1822
1823 /* all emails */
1824 m->msg_count = count_query(db, db_query, limit);
1826
1827 // holder variable for extending query to unread/flagged
1828 char *qstr = NULL;
1829
1830 // unread messages
1831 const char *const c_nm_unread_tag = cs_subset_string(NeoMutt->sub, "nm_unread_tag");
1832 mutt_str_asprintf(&qstr, "( %s ) tag:%s", db_query, c_nm_unread_tag);
1833 m->msg_unread = count_query(db, qstr, limit);
1834 FREE(&qstr);
1835
1836 // flagged messages
1837 const char *const c_nm_flagged_tag = cs_subset_string(NeoMutt->sub, "nm_flagged_tag");
1838 mutt_str_asprintf(&qstr, "( %s ) tag:%s", db_query, c_nm_flagged_tag);
1839 m->msg_flagged = count_query(db, qstr, limit);
1840 FREE(&qstr);
1841
1842 rc = (m->msg_new > 0) ? MX_STATUS_NEW_MAIL : MX_STATUS_OK;
1843done:
1844 if (db)
1845 {
1846 nm_db_free(db);
1847 mutt_debug(LL_DEBUG1, "nm: count close DB\n");
1848 }
1849 url_free(&url);
1850
1851 mutt_debug(LL_DEBUG1, "nm: count done [rc=%d]\n", rc);
1852 return rc;
1853}
1854
1859static struct Mailbox *get_default_mailbox(void)
1860{
1861 // Create a new notmuch mailbox from scratch and add plumbing for DB access.
1862 char *default_url = nm_get_default_url();
1863 struct Mailbox *m = mx_path_resolve(default_url);
1864
1865 FREE(&default_url);
1866
1867 // These are no-ops for an initialized mailbox.
1868 init_mailbox(m);
1869 mx_mbox_ac_link(m);
1870
1871 return m;
1872}
1873
1882int nm_record_message(struct Mailbox *m, char *path, struct Email *e)
1883{
1884 notmuch_database_t *db = NULL;
1885 notmuch_status_t st;
1886 notmuch_message_t *msg = NULL;
1887 int rc = -1;
1888
1889 struct NmMboxData *mdata = nm_mdata_get(m);
1890
1891 // If no notmuch data, fall back to the default mailbox.
1892 //
1893 // IMPORTANT: DO NOT FREE THIS MAILBOX. Two reasons:
1894 // 1) If user has default mailbox in config, we'll be removing it. That's not
1895 // good program behavior!
1896 // 2) If not in user's config, keep mailbox around for future nm_record calls.
1897 // It saves NeoMutt from allocating/deallocating repeatedly.
1898 if (!mdata)
1899 {
1900 mutt_debug(LL_DEBUG1, "nm: non-nm mailbox. trying the default nm mailbox.");
1901 m = get_default_mailbox();
1902 mdata = nm_mdata_get(m);
1903 }
1904
1905 if (!path || !mdata || (access(path, F_OK) != 0))
1906 return 0;
1907 db = nm_db_get(m, true);
1908 if (!db)
1909 return -1;
1910
1911 mutt_debug(LL_DEBUG1, "nm: record message: %s\n", path);
1912 int trans = nm_db_trans_begin(m);
1913 if (trans < 0)
1914 goto done;
1915
1916#if LIBNOTMUCH_CHECK_VERSION(5, 1, 0)
1917 st = notmuch_database_index_file(db, path, NULL, &msg);
1918#else
1919 st = notmuch_database_add_message(db, path, &msg);
1920#endif
1921
1922 if ((st != NOTMUCH_STATUS_SUCCESS) && (st != NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID))
1923 {
1924 mutt_debug(LL_DEBUG1, "nm: failed to add '%s' [st=%d]\n", path, (int) st);
1925 goto done;
1926 }
1927
1928 if ((st == NOTMUCH_STATUS_SUCCESS) && msg)
1929 {
1930 notmuch_message_maildir_flags_to_tags(msg);
1931 if (e)
1932 {
1933 char *tags = driver_tags_get(&e->tags);
1934 update_tags(msg, tags);
1935 FREE(&tags);
1936 }
1937 const char *const c_nm_record_tags = cs_subset_string(NeoMutt->sub, "nm_record_tags");
1938 if (c_nm_record_tags)
1939 update_tags(msg, c_nm_record_tags);
1940 }
1941
1942 rc = 0;
1943done:
1944 if (msg)
1945 notmuch_message_destroy(msg);
1946 if (trans == 1)
1947 nm_db_trans_end(m);
1948
1949 nm_db_release(m);
1950
1951 return rc;
1952}
1953
1964int nm_get_all_tags(struct Mailbox *m, const char **tag_list, int *tag_count)
1965{
1966 struct NmMboxData *mdata = nm_mdata_get(m);
1967 if (!mdata)
1968 return -1;
1969
1970 notmuch_database_t *db = NULL;
1971 notmuch_tags_t *tags = NULL;
1972 const char *tag = NULL;
1973 int rc = -1;
1974
1975 if (!(db = nm_db_get(m, false)) || !(tags = notmuch_database_get_all_tags(db)))
1976 goto done;
1977
1978 *tag_count = 0;
1979 mutt_debug(LL_DEBUG1, "nm: get all tags\n");
1980
1981 while (notmuch_tags_valid(tags))
1982 {
1983 tag = notmuch_tags_get(tags);
1984 /* Skip empty string */
1985 if (*tag)
1986 {
1987 if (tag_list)
1988 tag_list[*tag_count] = mutt_str_dup(tag);
1989 (*tag_count)++;
1990 }
1991 notmuch_tags_move_to_next(tags);
1992 }
1993
1994 rc = 0;
1995done:
1996 if (tags)
1997 notmuch_tags_destroy(tags);
1998
1999 nm_db_release(m);
2000
2001 mutt_debug(LL_DEBUG1, "nm: get all tags done [rc=%d tag_count=%u]\n", rc, *tag_count);
2002 return rc;
2003}
2004
2008static bool nm_ac_owns_path(struct Account *a, const char *path)
2009{
2010 return true;
2011}
2012
2016static bool nm_ac_add(struct Account *a, struct Mailbox *m)
2017{
2018 if (a->adata)
2019 return true;
2020
2021 struct NmAccountData *adata = nm_adata_new();
2022 a->adata = adata;
2024
2025 return true;
2026}
2027
2031static enum MxOpenReturns nm_mbox_open(struct Mailbox *m)
2032{
2033 if (init_mailbox(m) != 0)
2034 return MX_OPEN_ERROR;
2035
2036 struct NmMboxData *mdata = nm_mdata_get(m);
2037 if (!mdata)
2038 return MX_OPEN_ERROR;
2039
2040 mutt_debug(LL_DEBUG1, "nm: reading messages...[current count=%d]\n", m->msg_count);
2041
2042 progress_setup(m);
2043 enum MxOpenReturns rc = MX_OPEN_ERROR;
2044
2045 notmuch_query_t *q = get_query(m, false);
2046 if (q)
2047 {
2048 rc = MX_OPEN_OK;
2049 switch (mdata->query_type)
2050 {
2051 case NM_QUERY_TYPE_UNKNOWN: // UNKNOWN should never occur, but MESGS is default
2053 if (!read_mesgs_query(m, q, false))
2054 rc = MX_OPEN_ABORT;
2055 break;
2057 if (!read_threads_query(m, q, false, get_limit(mdata)))
2058 rc = MX_OPEN_ABORT;
2059 break;
2060 }
2061 notmuch_query_destroy(q);
2062 }
2063
2064 nm_db_release(m);
2065
2066 mdata->mtime.tv_sec = mutt_date_now();
2067 mdata->mtime.tv_nsec = 0;
2068
2069 mdata->oldmsgcount = 0;
2070
2071 mutt_debug(LL_DEBUG1, "nm: reading messages... done [rc=%d, count=%d]\n", rc, m->msg_count);
2072 progress_free(&mdata->progress);
2073 return rc;
2074}
2075
2081static enum MxStatus nm_mbox_check(struct Mailbox *m)
2082{
2083 struct NmMboxData *mdata = nm_mdata_get(m);
2084 time_t mtime = 0;
2085 if (!mdata || (nm_db_get_mtime(m, &mtime) != 0))
2086 return MX_STATUS_ERROR;
2087
2088 int new_flags = 0;
2089 bool occult = false;
2090
2091 if (mdata->mtime.tv_sec >= mtime)
2092 {
2093 mutt_debug(LL_DEBUG2, "nm: check unnecessary (db=%lu mailbox=%lu)\n", mtime,
2094 mdata->mtime.tv_sec);
2095 return MX_STATUS_OK;
2096 }
2097
2098 mutt_debug(LL_DEBUG1, "nm: checking (db=%lu mailbox=%lu)\n", mtime,
2099 mdata->mtime.tv_sec);
2100
2101 notmuch_query_t *q = get_query(m, false);
2102 if (!q)
2103 goto done;
2104
2105 mutt_debug(LL_DEBUG1, "nm: start checking (count=%d)\n", m->msg_count);
2106 mdata->oldmsgcount = m->msg_count;
2107
2108 for (int i = 0; i < m->msg_count; i++)
2109 {
2110 struct Email *e = m->emails[i];
2111 if (!e)
2112 break;
2113
2114 e->active = false;
2115 }
2116
2117 int limit = get_limit(mdata);
2118
2119 notmuch_messages_t *msgs = get_messages(q);
2120
2121 // TODO: Analyze impact of removing this version guard.
2122#if LIBNOTMUCH_CHECK_VERSION(5, 0, 0)
2123 if (!msgs)
2124 return MX_STATUS_OK;
2125#elif LIBNOTMUCH_CHECK_VERSION(4, 3, 0)
2126 if (!msgs)
2127 goto done;
2128#endif
2129
2130 struct HeaderCache *hc = nm_hcache_open(m);
2131
2132 for (int i = 0; notmuch_messages_valid(msgs) && ((limit == 0) || (i < limit));
2133 notmuch_messages_move_to_next(msgs), i++)
2134 {
2135 notmuch_message_t *msg = notmuch_messages_get(msgs);
2136 struct Email *e = get_mutt_email(m, msg);
2137
2138 if (!e)
2139 {
2140 /* new email */
2141 append_message(hc, m, msg, false);
2142 notmuch_message_destroy(msg);
2143 continue;
2144 }
2145
2146 /* message already exists, merge flags */
2147 e->active = true;
2148
2149 /* Check to see if the message has moved to a different subdirectory.
2150 * If so, update the associated filename. */
2151 const char *new_file = get_message_last_filename(msg);
2152 char old_file[PATH_MAX] = { 0 };
2153 email_get_fullpath(e, old_file, sizeof(old_file));
2154
2155 if (!mutt_str_equal(old_file, new_file))
2156 update_message_path(e, new_file);
2157
2158 if (!e->changed)
2159 {
2160 /* if the user hasn't modified the flags on this message, update the
2161 * flags we just detected. */
2162 struct Email *e_tmp = maildir_email_new();
2163 maildir_parse_flags(e_tmp, new_file);
2164 e_tmp->old = e->old;
2165 maildir_update_flags(m, e, e_tmp);
2166 email_free(&e_tmp);
2167 }
2168
2169 if (update_email_tags(e, msg) == 0)
2170 new_flags++;
2171
2172 notmuch_message_destroy(msg);
2173 }
2174
2175 nm_hcache_close(&hc);
2176
2177 for (int i = 0; i < m->msg_count; i++)
2178 {
2179 struct Email *e = m->emails[i];
2180 if (!e)
2181 break;
2182
2183 if (!e->active)
2184 {
2185 occult = true;
2186 break;
2187 }
2188 }
2189
2190 if (m->msg_count > mdata->oldmsgcount)
2192done:
2193 if (q)
2194 notmuch_query_destroy(q);
2195
2196 nm_db_release(m);
2197
2198 mdata->mtime.tv_sec = mutt_date_now();
2199 mdata->mtime.tv_nsec = 0;
2200
2201 mutt_debug(LL_DEBUG1, "nm: ... check done [count=%d, new_flags=%d, occult=%d]\n",
2202 m->msg_count, new_flags, occult);
2203
2204 if (occult)
2205 return MX_STATUS_REOPENED;
2206 if (m->msg_count > mdata->oldmsgcount)
2207 return MX_STATUS_NEW_MAIL;
2208 if (new_flags)
2209 return MX_STATUS_FLAGS;
2210 return MX_STATUS_OK;
2211}
2212
2216static enum MxStatus nm_mbox_sync(struct Mailbox *m)
2217{
2218 struct NmMboxData *mdata = nm_mdata_get(m);
2219 if (!mdata)
2220 return MX_STATUS_ERROR;
2221
2222 enum MxStatus rc = MX_STATUS_OK;
2223 struct Progress *progress = NULL;
2224 char *url = mutt_str_dup(mailbox_path(m));
2225 bool changed = false;
2226
2227 mutt_debug(LL_DEBUG1, "nm: sync start\n");
2228
2229 if (m->verbose)
2230 {
2231 /* all is in this function so we don't use data->progress here */
2232 char msg[PATH_MAX] = { 0 };
2233 snprintf(msg, sizeof(msg), _("Writing %s..."), mailbox_path(m));
2234 progress = progress_new(msg, MUTT_PROGRESS_WRITE, m->msg_count);
2235 }
2236
2237 struct HeaderCache *hc = nm_hcache_open(m);
2238
2239 int mh_sync_errors = 0;
2240 for (int i = 0; i < m->msg_count; i++)
2241 {
2242 char old_file[PATH_MAX], new_file[PATH_MAX];
2243 struct Email *e = m->emails[i];
2244 if (!e)
2245 break;
2246
2247 struct NmEmailData *edata = nm_edata_get(e);
2248
2249 progress_update(progress, i, -1);
2250
2251 *old_file = '\0';
2252 *new_file = '\0';
2253
2254 if (edata->oldpath)
2255 {
2256 mutt_str_copy(old_file, edata->oldpath, sizeof(old_file));
2257 old_file[sizeof(old_file) - 1] = '\0';
2258 mutt_debug(LL_DEBUG2, "nm: fixing obsolete path '%s'\n", old_file);
2259 }
2260 else
2261 {
2262 email_get_fullpath(e, old_file, sizeof(old_file));
2263 }
2264
2265 buf_strcpy(&m->pathbuf, edata->folder);
2266 m->type = edata->type;
2267
2268 bool ok = maildir_sync_mailbox_message(m, e, hc);
2269 if (!ok)
2270 {
2271 // Syncing file failed, query notmuch for new filepath.
2272 m->type = MUTT_NOTMUCH;
2273 notmuch_database_t *db = nm_db_get(m, true);
2274 if (db)
2275 {
2276 notmuch_message_t *msg = get_nm_message(db, e);
2277
2279
2280 buf_strcpy(&m->pathbuf, edata->folder);
2281 m->type = edata->type;
2282 ok = maildir_sync_mailbox_message(m, e, hc);
2283 m->type = MUTT_NOTMUCH;
2284 }
2285 nm_db_release(m);
2286 m->type = edata->type;
2287 }
2288
2289 buf_strcpy(&m->pathbuf, url);
2290 m->type = MUTT_NOTMUCH;
2291
2292 if (!ok)
2293 {
2294 mh_sync_errors += 1;
2295 continue;
2296 }
2297
2298 if (!e->deleted)
2299 email_get_fullpath(e, new_file, sizeof(new_file));
2300
2301 if (e->deleted || !mutt_str_equal(old_file, new_file))
2302 {
2303 if (e->deleted && (remove_filename(m, old_file) == 0))
2304 changed = true;
2305 else if (*new_file && *old_file && (rename_filename(m, old_file, new_file, e) == 0))
2306 changed = true;
2307 }
2308
2309 FREE(&edata->oldpath);
2310 }
2311
2312 if (mh_sync_errors > 0)
2313 {
2314 mutt_error(ngettext("Unable to sync %d message due to external mailbox modification",
2315 "Unable to sync %d messages due to external mailbox modification",
2316 mh_sync_errors),
2317 mh_sync_errors);
2318 }
2319
2320 buf_strcpy(&m->pathbuf, url);
2321 m->type = MUTT_NOTMUCH;
2322
2323 nm_db_release(m);
2324
2325 if (changed)
2326 {
2327 mdata->mtime.tv_sec = mutt_date_now();
2328 mdata->mtime.tv_nsec = 0;
2329 }
2330
2331 nm_hcache_close(&hc);
2332
2333 progress_free(&progress);
2334 FREE(&url);
2335 mutt_debug(LL_DEBUG1, "nm: .... sync done [rc=%d]\n", rc);
2336 return rc;
2337}
2338
2344static enum MxStatus nm_mbox_close(struct Mailbox *m)
2345{
2346 return MX_STATUS_OK;
2347}
2348
2352static bool nm_msg_open(struct Mailbox *m, struct Message *msg, struct Email *e)
2353{
2354 char path[PATH_MAX] = { 0 };
2355 char *folder = nm_email_get_folder(e);
2356
2357 snprintf(path, sizeof(path), "%s/%s", folder, e->path);
2358
2359 msg->fp = fopen(path, "r");
2360 if (!msg->fp && (errno == ENOENT) && ((m->type == MUTT_MAILDIR) || (m->type == MUTT_NOTMUCH)))
2361 {
2362 msg->fp = maildir_open_find_message(folder, e->path, NULL);
2363 }
2364
2365 return msg->fp != NULL;
2366}
2367
2372static int nm_msg_commit(struct Mailbox *m, struct Message *msg)
2373{
2374 mutt_error(_("Can't write to virtual folder"));
2375 return -1;
2376}
2377
2381static int nm_msg_close(struct Mailbox *m, struct Message *msg)
2382{
2383 mutt_file_fclose(&(msg->fp));
2384 return 0;
2385}
2386
2390static int nm_tags_edit(struct Mailbox *m, const char *tags, struct Buffer *buf)
2391{
2392 buf_reset(buf);
2393 if (mw_get_field("Add/remove labels: ", buf, MUTT_COMP_NO_FLAGS, HC_OTHER,
2394 &CompleteNmTagOps, NULL) != 0)
2395 {
2396 return -1;
2397 }
2398 return 1;
2399}
2400
2404static int nm_tags_commit(struct Mailbox *m, struct Email *e, const char *buf)
2405{
2406 if (*buf == '\0')
2407 return 0; /* no tag change, so nothing to do */
2408
2409 struct NmMboxData *mdata = nm_mdata_get(m);
2410 if (!mdata)
2411 return -1;
2412
2413 notmuch_database_t *db = NULL;
2414 notmuch_message_t *msg = NULL;
2415 int rc = -1;
2416
2417 if (!(db = nm_db_get(m, true)) || !(msg = get_nm_message(db, e)))
2418 goto done;
2419
2420 mutt_debug(LL_DEBUG1, "nm: tags modify: '%s'\n", buf);
2421
2422 update_tags(msg, buf);
2423 update_email_flags(m, e, buf);
2424 update_email_tags(e, msg);
2426
2427 rc = 0;
2428 e->changed = true;
2429done:
2430 nm_db_release(m);
2431 if (e->changed)
2432 {
2433 mdata->mtime.tv_sec = mutt_date_now();
2434 mdata->mtime.tv_nsec = 0;
2435 }
2436 mutt_debug(LL_DEBUG1, "nm: tags modify done [rc=%d]\n", rc);
2437 return rc;
2438}
2439
2443enum MailboxType nm_path_probe(const char *path, const struct stat *st)
2444{
2446 return MUTT_UNKNOWN;
2447
2448 return MUTT_NOTMUCH;
2449}
2450
2454static int nm_path_canon(struct Buffer *path)
2455{
2456 return 0;
2457}
2458
2462static int nm_path_pretty(struct Buffer *path, const char *folder)
2463{
2464 /* Succeed, but don't do anything, for now */
2465 return 0;
2466}
2467
2471static int nm_path_parent(struct Buffer *path)
2472{
2473 /* Succeed, but don't do anything, for now */
2474 return 0;
2475}
2476
2480const struct MxOps MxNotmuchOps = {
2481 // clang-format off
2482 .type = MUTT_NOTMUCH,
2483 .name = "notmuch",
2484 .is_local = false,
2485 .ac_owns_path = nm_ac_owns_path,
2486 .ac_add = nm_ac_add,
2487 .mbox_open = nm_mbox_open,
2488 .mbox_open_append = NULL,
2489 .mbox_check = nm_mbox_check,
2490 .mbox_check_stats = nm_mbox_check_stats,
2491 .mbox_sync = nm_mbox_sync,
2492 .mbox_close = nm_mbox_close,
2493 .msg_open = nm_msg_open,
2494 .msg_open_new = maildir_msg_open_new,
2495 .msg_commit = nm_msg_commit,
2496 .msg_close = nm_msg_close,
2497 .msg_padding_size = NULL,
2498 .msg_save_hcache = NULL,
2499 .tags_edit = nm_tags_edit,
2500 .tags_commit = nm_tags_commit,
2501 .path_probe = nm_path_probe,
2502 .path_canon = nm_path_canon,
2503 .path_pretty = nm_path_pretty,
2504 .path_parent = nm_path_parent,
2505 .path_is_empty = NULL,
2506 // clang-format on
2507};
#define ARRAY_FOREACH(elem, head)
Iterate over all elements of the array.
Definition: array.h:211
void buf_reset(struct Buffer *buf)
Reset an existing Buffer.
Definition: buffer.c:88
size_t buf_strcpy(struct Buffer *buf, const char *s)
Copy a string into a Buffer.
Definition: buffer.c:407
Functions to parse commands in a config file.
#define MUTT_NAMED
Definition: commands.h:36
const char * cs_subset_string(const struct ConfigSubset *sub, const char *name)
Get a string config item by name.
Definition: helpers.c:292
short cs_subset_number(const struct ConfigSubset *sub, const char *name)
Get a number config item by name.
Definition: helpers.c:144
const char * cs_subset_path(const struct ConfigSubset *sub, const char *name)
Get a path config item by name.
Definition: helpers.c:169
bool cs_subset_bool(const struct ConfigSubset *sub, const char *name)
Get a boolean config item by name.
Definition: helpers.c:48
Convenience wrapper for the config headers.
const char * cc_maildir_field_delimiter(void)
Get the cached value of $maildir_field_delimiter.
Definition: config_cache.c:130
void commands_register(const struct Command *cmds, const size_t num_cmds)
Add commands to Commands array.
Definition: command.c:53
Convenience wrapper for the core headers.
void mutt_set_header_color(struct Mailbox *m, struct Email *e)
Select a colour for a message.
Definition: dlg_index.c:1374
Enter a string.
void email_free(struct Email **ptr)
Free an Email.
Definition: email.c:44
Structs that make up an email.
int mutt_file_fclose(FILE **fp)
Close a FILE handle (and NULL the pointer)
Definition: file.c:152
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:53
SIG_ATOMIC_VOLATILE_T SigInt
true after SIGINT is received
Definition: globals.c:59
enum CommandResult parse_unmailboxes(struct Buffer *buf, struct Buffer *s, intptr_t data, struct Buffer *err)
Parse the 'unmailboxes' command - Implements Command::parse() -.
Definition: commands.c:1419
enum CommandResult parse_mailboxes(struct Buffer *buf, struct Buffer *s, intptr_t data, struct Buffer *err)
Parse the 'mailboxes' command - Implements Command::parse() -.
Definition: commands.c:713
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:275
#define mutt_error(...)
Definition: logging2.h:92
#define mutt_message(...)
Definition: logging2.h:91
#define mutt_debug(LEVEL,...)
Definition: logging2.h:89
static bool nm_ac_add(struct Account *a, struct Mailbox *m)
Add a Mailbox to an Account - Implements MxOps::ac_add() -.
Definition: notmuch.c:2016
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:2008
const struct MxOps MxNotmuchOps
Notmuch Mailbox - Implements MxOps -.
Definition: notmuch.c:2480
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:1774
static enum MxStatus nm_mbox_check(struct Mailbox *m)
Check for new mail - Implements MxOps::mbox_check() -.
Definition: notmuch.c:2081
static enum MxStatus nm_mbox_close(struct Mailbox *m)
Close a Mailbox - Implements MxOps::mbox_close() -.
Definition: notmuch.c:2344
static enum MxOpenReturns nm_mbox_open(struct Mailbox *m)
Open a Mailbox - Implements MxOps::mbox_open() -.
Definition: notmuch.c:2031
static enum MxStatus nm_mbox_sync(struct Mailbox *m)
Save changes to the Mailbox - Implements MxOps::mbox_sync() -.
Definition: notmuch.c:2216
static int nm_msg_close(struct Mailbox *m, struct Message *msg)
Close an email - Implements MxOps::msg_close() -.
Definition: notmuch.c:2381
static int nm_msg_commit(struct Mailbox *m, struct Message *msg)
Save changes to an email - Implements MxOps::msg_commit() -.
Definition: notmuch.c:2372
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:1522
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:2352
static int nm_path_canon(struct Buffer *path)
Canonicalise a Mailbox path - Implements MxOps::path_canon() -.
Definition: notmuch.c:2454
static int nm_path_parent(struct Buffer *path)
Find the parent of a Mailbox path - Implements MxOps::path_parent() -.
Definition: notmuch.c:2471
static int nm_path_pretty(struct Buffer *path, const char *folder)
Abbreviate a Mailbox path - Implements MxOps::path_pretty() -.
Definition: notmuch.c:2462
enum MailboxType nm_path_probe(const char *path, const struct stat *st)
Is this a Notmuch Mailbox? - Implements MxOps::path_probe() -.
Definition: notmuch.c:2443
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:2404
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:2390
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 hcache_store(struct HeaderCache *hc, const char *key, size_t keylen, struct Email *e, uint32_t uidvalidity)
Multiplexor for StoreOps::store.
Definition: hcache.c:686
struct HCacheEntry hcache_fetch(struct HeaderCache *hc, const char *key, size_t keylen, uint32_t uidvalidity)
Multiplexor for StoreOps::fetch.
Definition: hcache.c:583
struct HeaderCache * hcache_open(const char *path, const char *folder, hcache_namer_t namer)
Multiplexor for StoreOps::open.
Definition: hcache.c:494
void hcache_close(struct HeaderCache **ptr)
Multiplexor for StoreOps::close.
Definition: hcache.c:563
Read/write command history from/to a file.
@ HC_OTHER
Miscellaneous strings.
Definition: lib.h:54
GUI manage the main index (list of emails)
@ LL_DEBUG2
Log at debug level 2.
Definition: logging2.h:44
@ LL_DEBUG1
Log at debug level 1.
Definition: logging2.h:43
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:242
void mailbox_changed(struct Mailbox *m, enum NotifyMailbox action)
Notify observers of a change to a Mailbox.
Definition: mailbox.c:226
@ NT_MAILBOX_INVALID
Email list was changed.
Definition: mailbox.h:176
static const char * mailbox_path(const struct Mailbox *m)
Get the Mailbox's path string.
Definition: mailbox.h:210
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
Maildir local mailbox type.
bool maildir_parse_stream(enum MailboxType type, FILE *fp, const char *fname, bool is_old, struct Email *e)
Parse a Maildir message.
Definition: maildir.c:912
FILE * maildir_open_find_message(const char *folder, const char *msg, char **newname)
Find a message by name.
Definition: maildir.c:1023
bool maildir_update_flags(struct Mailbox *m, struct Email *e_old, struct Email *e_new)
Update the mailbox flags.
Definition: shared.c:120
bool maildir_parse_message(enum MailboxType type, const char *fname, bool is_old, struct Email *e)
Actually parse a maildir message.
Definition: maildir.c:954
struct Email * maildir_email_new(void)
Create a Maildir Email.
Definition: maildir.c:85
bool maildir_sync_mailbox_message(struct Mailbox *m, struct Email *e, struct HeaderCache *hc)
Save changes to the mailbox.
Definition: maildir.c:977
void maildir_gen_flags(char *dest, size_t destlen, struct Email *e)
Generate the Maildir flags for an email.
Definition: maildir.c:206
void maildir_parse_flags(struct Email *e, const char *path)
Parse Maildir file flags.
Definition: maildir.c:840
void * mutt_mem_malloc(size_t size)
Allocate memory on the heap.
Definition: memory.c:90
#define FREE(x)
Definition: memory.h:45
#define mutt_array_size(x)
Definition: memory.h:38
time_t mutt_date_now(void)
Return the number of seconds since the Unix epoch.
Definition: date.c:446
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:452
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:251
int mutt_str_asprintf(char **strp, const char *fmt,...)
Definition: string.c:1022
void mutt_str_append_item(char **str, const char *item, char sep)
Add string to another separated by sep.
Definition: string.c:347
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition: string.c:798
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:497
size_t mutt_str_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix.
Definition: string.c:228
size_t mutt_str_len(const char *a)
Calculate the length of a string, safely.
Definition: string.c:568
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:653
size_t mutt_istr_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix, ignoring case.
Definition: string.c:240
char * mutt_str_replace(char **p, const char *s)
Replace one string with another.
Definition: string.c:327
Many unsorted constants and some structs.
#define MUTT_COMP_NO_FLAGS
No flags are set.
Definition: mutt.h:55
@ MUTT_READ
Messages that have been read.
Definition: mutt.h:72
@ MUTT_FLAG
Flagged messages.
Definition: mutt.h:78
@ MUTT_REPLIED
Messages that have been replied to.
Definition: mutt.h:71
#define PATH_MAX
Definition: mutt.h:41
struct HashTable * mutt_make_id_hash(struct Mailbox *m)
Create a Hash Table for message-ids.
Definition: mutt_thread.c:1699
Create/manipulate threading in emails.
void mx_alloc_memory(struct Mailbox *m, int req_size)
Create storage for the emails.
Definition: mx.c:1232
bool mx_mbox_ac_link(struct Mailbox *m)
Link a Mailbox to an existing or new Account.
Definition: mx.c:267
struct Mailbox * mx_path_resolve(const char *path)
Get a Mailbox for a path.
Definition: mx.c:1697
API for mailboxes.
MxOpenReturns
Return values for mbox_open()
Definition: mxapi.h:76
@ MX_OPEN_ERROR
Open failed with an error.
Definition: mxapi.h:78
@ MX_OPEN_ABORT
Open was aborted.
Definition: mxapi.h:79
@ MX_OPEN_OK
Open succeeded.
Definition: mxapi.h:77
MxStatus
Return values from mbox_check(), mbox_check_stats(), mbox_snc(), and mbox_close()
Definition: mxapi.h:63
@ MX_STATUS_ERROR
An error occurred.
Definition: mxapi.h:64
@ MX_STATUS_OK
No changes.
Definition: mxapi.h:65
@ MX_STATUS_FLAGS
Nondestructive flags change (IMAP)
Definition: mxapi.h:69
@ MX_STATUS_REOPENED
Mailbox was reopened.
Definition: mxapi.h:68
@ MX_STATUS_NEW_MAIL
New mail received in Mailbox.
Definition: mxapi.h:66
struct NmAccountData * nm_adata_new(void)
Allocate and initialise a new NmAccountData structure.
Definition: adata.c:58
void nm_adata_free(void **ptr)
Free the private Account data - Implements Account::adata_free()
Definition: adata.c:39
const struct CompleteOps CompleteNmTagOps
Auto-Completion of NmTags.
Definition: complete.c:256
notmuch_database_t * nm_db_get(struct Mailbox *m, bool writable)
Get the Notmuch database.
Definition: db.c:200
int nm_db_trans_begin(struct Mailbox *m)
Start a Notmuch database transaction.
Definition: db.c:257
notmuch_database_t * nm_db_do_open(const char *filename, bool writable, bool verbose)
Open a Notmuch database.
Definition: db.c:111
const char * nm_db_get_filename(struct Mailbox *m)
Get the filename of the Notmuch database.
Definition: db.c:54
int nm_db_get_mtime(struct Mailbox *m, time_t *mtime)
Get the database modification time.
Definition: db.c:306
int nm_db_release(struct Mailbox *m)
Close the Notmuch database.
Definition: db.c:224
void nm_db_free(notmuch_database_t *db)
Decoupled way to close a Notmuch database.
Definition: db.c:241
int nm_db_trans_end(struct Mailbox *m)
End a database transaction.
Definition: db.c:279
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
struct NmMboxData * nm_mdata_new(const char *url)
Create a new NmMboxData object from a query.
Definition: mdata.c:68
void nm_mdata_free(void **ptr)
Free the private Mailbox data - Implements Mailbox::mdata_free()
Definition: mdata.c:45
struct NmMboxData * nm_mdata_get(struct Mailbox *m)
Get the Notmuch Mailbox data.
Definition: mdata.c:96
Notmuch-specific Mailbox data.
static notmuch_threads_t * get_threads(notmuch_query_t *query)
Load threads for a query.
Definition: notmuch.c:973
static char * get_query_string(struct NmMboxData *mdata, bool window)
Builds the notmuch vfolder search string.
Definition: notmuch.c:338
static char * nm_get_default_url(void)
Create a Mailbox with default Notmuch settings.
Definition: notmuch.c:136
int nm_update_filename(struct Mailbox *m, const char *old_file, const char *new_file, struct Email *e)
Change the filename.
Definition: notmuch.c:1749
void nm_query_window_reset(void)
Resets the vfolder window position to the present.
Definition: notmuch.c:1672
static char * email_get_id(struct Email *e)
Get the unique Notmuch Id.
Definition: notmuch.c:213
static void apply_exclude_tags(notmuch_query_t *query)
Exclude the configured tags.
Definition: notmuch.c:415
static char * email_get_fullpath(struct Email *e, char *buf, size_t buflen)
Get the full path of an email.
Definition: notmuch.c:229
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:274
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:1964
static unsigned int count_query(notmuch_database_t *db, const char *qstr, int limit)
Count the results of a query.
Definition: notmuch.c:1426
static int init_mailbox(struct Mailbox *m)
Add Notmuch data to the Mailbox.
Definition: notmuch.c:191
static int rename_maildir_filename(const char *old, char *buf, size_t buflen, struct Email *e)
Rename a Maildir file.
Definition: notmuch.c:1202
static void sync_email_path_with_nm(struct Email *e, notmuch_message_t *msg)
Synchronize Neomutt's Email path with notmuch.
Definition: notmuch.c:1079
static notmuch_message_t * get_nm_message(notmuch_database_t *db, struct Email *e)
Find a Notmuch message.
Definition: notmuch.c:1038
static int init_email(struct Email *e, const char *path, notmuch_message_t *msg)
Set up an email's Notmuch data.
Definition: notmuch.c:608
static int get_limit(struct NmMboxData *mdata)
Get the database limit.
Definition: notmuch.c:406
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:1478
static bool read_threads_query(struct Mailbox *m, notmuch_query_t *q, bool dedup, int limit)
Perform a query with threads.
Definition: notmuch.c:1001
static struct Mailbox * get_default_mailbox(void)
Get Mailbox for notmuch without any parameters.
Definition: notmuch.c:1859
int nm_record_message(struct Mailbox *m, char *path, struct Email *e)
Add a message to the Notmuch database.
Definition: notmuch.c:1882
static const struct Command NmCommands[]
Notmuch Commands.
Definition: notmuch.c:85
static struct NmMboxData * nm_get_default_data(void)
Create a Mailbox with default Notmuch settings.
Definition: notmuch.c:168
int nm_read_entire_thread(struct Mailbox *m, struct Email *e)
Get the entire thread of an email.
Definition: notmuch.c:1498
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:879
static void query_window_reset(void)
Restore vfolder's search window to its original position.
Definition: notmuch.c:244
const int NmUrlProtocolLen
Length of NmUrlProtocol string.
Definition: notmuch.c:95
static int rename_filename(struct Mailbox *m, const char *old_file, const char *new_file, struct Email *e)
Rename the file.
Definition: notmuch.c:1322
void nm_query_window_backward(void)
Function to move the current search window backward in time.
Definition: notmuch.c:1661
static struct HeaderCache * nm_hcache_open(struct Mailbox *m)
Open a header cache.
Definition: notmuch.c:110
static bool nm_message_has_tag(notmuch_message_t *msg, char *tag)
Does a message have this tag?
Definition: notmuch.c:1057
static notmuch_query_t * get_query(struct Mailbox *m, bool writable)
Create a new query.
Definition: notmuch.c:441
static int update_message_path(struct Email *e, const char *path)
Set the path for a message.
Definition: notmuch.c:524
bool nm_query_window_available(void)
Are windowed queries enabled for use?
Definition: notmuch.c:1624
static int update_tags(notmuch_message_t *msg, const char *tag_str)
Update the tags on a message.
Definition: notmuch.c:1096
static bool read_mesgs_query(struct Mailbox *m, notmuch_query_t *q, bool dedup)
Search for matching messages.
Definition: notmuch.c:931
static const char * get_message_last_filename(notmuch_message_t *msg)
Get a message's last filename.
Definition: notmuch.c:652
static void append_replies(struct HeaderCache *hc, 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:852
char * nm_url_from_query(struct Mailbox *m, char *buf, size_t buflen)
Turn a query into a URL.
Definition: notmuch.c:1565
static char * get_folder_from_path(const char *path)
Find an email's folder from its path.
Definition: notmuch.c:565
void nm_init(void)
Setup feature commands.
Definition: notmuch.c:100
static char * nm2mutt_message_id(const char *id)
Converts notmuch message Id to neomutt message Id.
Definition: notmuch.c:590
char * nm_email_get_folder(struct Email *e)
Get the folder for a Email.
Definition: notmuch.c:1459
static void append_message(struct HeaderCache *hc, struct Mailbox *m, notmuch_message_t *msg, bool dedup)
Associate a message.
Definition: notmuch.c:739
static int update_email_tags(struct Email *e, notmuch_message_t *msg)
Update the Email's tags from Notmuch.
Definition: notmuch.c:473
bool nm_message_is_still_queried(struct Mailbox *m, struct Email *e)
Is a message still visible in the query?
Definition: notmuch.c:1684
static int update_email_flags(struct Mailbox *m, struct Email *e, const char *tag_str)
Update the Email's flags.
Definition: notmuch.c:1150
static int remove_filename(struct Mailbox *m, const char *path)
Delete a file.
Definition: notmuch.c:1258
static void progress_setup(struct Mailbox *m)
Set up the Progress Bar.
Definition: notmuch.c:669
static void nm_hcache_close(struct HeaderCache **ptr)
Close the header cache.
Definition: notmuch.c:124
void nm_query_window_forward(void)
Function to move the current search window forward in time.
Definition: notmuch.c:1641
static notmuch_messages_t * get_messages(notmuch_query_t *query)
Load messages for a query.
Definition: notmuch.c:903
const char NmUrlProtocol[]
Protocol string for Notmuch URLs.
Definition: notmuch.c:93
static void nm_progress_update(struct Mailbox *m)
Update the progress counter.
Definition: notmuch.c:688
static struct Email * get_mutt_email(struct Mailbox *m, notmuch_message_t *msg)
Get the Email of a Notmuch message.
Definition: notmuch.c:705
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:92
bool progress_update(struct Progress *progress, size_t pos, int percent)
Update the state of the progress bar.
Definition: progress.c:73
struct Progress * progress_new(const char *msg, enum ProgressType type, size_t size)
Create a new Progress Bar.
Definition: progress.c:124
Prototypes for many functions.
enum NmQueryType nm_string_to_query_type(const char *str)
Lookup a query type.
Definition: query.c:109
enum NmQueryType nm_parse_type_from_query(char *buf, enum NmQueryType fallback)
Parse a query type out of a query.
Definition: query.c:48
const char * nm_query_type_to_string(enum NmQueryType query_type)
Turn a query type into a string.
Definition: query.c:95
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:205
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:52
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:92
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
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:109
struct MuttThread * thread
Thread of Emails.
Definition: email.h:118
char * message_id
Message ID.
Definition: envelope.h:73
struct Email * email
Retrieved email.
Definition: lib.h:104
Header Cache.
Definition: lib.h:88
A mailbox.
Definition: mailbox.h:79
void(* mdata_free)(void **ptr)
Free the private data attached to the Mailbox.
Definition: mailbox.h:142
int msg_new
Number of new messages.
Definition: mailbox.h:92
int msg_count
Total number of messages.
Definition: mailbox.h:88
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: "message-id" -> Email.
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:116
int msg_unread
Number of unread messages.
Definition: mailbox.h:89
A local copy of an email.
Definition: message.h:34
FILE * fp
pointer to the message data
Definition: message.h:35
Definition: mxapi.h:91
enum MailboxType type
Mailbox type, e.g. MUTT_IMAP.
Definition: mxapi.h:92
Container for Accounts, Notifications.
Definition: neomutt.h:41
struct ConfigSubset * sub
Inherited config items.
Definition: neomutt.h:45
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
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
Definition: mdata.h:42
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:37
struct TagArray 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:52
time_t tv_sec
Number of seconds since the epoch.
Definition: file.h:51
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:304
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:407
void nm_tag_array_free(struct NmTags *tags)
Free all memory of a NmTags.
Definition: tag.c:39
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:50
Notmuch tag functions.
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
bool driver_tags_replace(struct TagList *head, const char *tags)
Replace all tags.
Definition: tags.c:186
struct Url * url_parse(const char *src)
Fill in Url.
Definition: url.c:238
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