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