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