NeoMutt  2025-01-09-117-gace867
Teaching an old dog new tricks
DOXYGEN
Loading...
Searching...
No Matches
notmuch.c
Go to the documentation of this file.
1
52#include "config.h"
53#include <errno.h>
54#include <limits.h>
55#include <notmuch.h>
56#include <stdbool.h>
57#include <stdint.h>
58#include <stdio.h>
59#include <string.h>
60#include <time.h>
61#include <unistd.h>
62#include "private.h"
63#include "mutt/lib.h"
64#include "config/lib.h"
65#include "email/lib.h"
66#include "core/lib.h"
67#include "mutt.h"
68#include "lib.h"
69#include "editor/lib.h"
70#include "hcache/lib.h"
71#include "history/lib.h"
72#include "index/lib.h"
73#include "progress/lib.h"
74#include "adata.h"
75#include "commands.h"
76#include "edata.h"
77#include "maildir/shared.h"
78#include "mdata.h"
79#include "mutt_thread.h"
80#include "mx.h"
81#include "protos.h"
82#include "query.h"
83#include "tag.h"
84#ifdef ENABLE_NLS
85#include <libintl.h>
86#endif
87
88struct stat;
89
93static const struct Command NmCommands[] = {
94 // clang-format off
95 { "unvirtual-mailboxes", parse_unmailboxes, 0 },
96 { "virtual-mailboxes", parse_mailboxes, MUTT_NAMED },
97 { NULL, NULL, 0 },
98 // clang-format on
99};
100
102const char NmUrlProtocol[] = "notmuch://";
104const int NmUrlProtocolLen = sizeof(NmUrlProtocol) - 1;
105
109void nm_init(void)
110{
112}
113
119static struct HeaderCache *nm_hcache_open(struct Mailbox *m)
120{
121#ifdef USE_HCACHE
122 const char *const c_header_cache = cs_subset_path(NeoMutt->sub, "header_cache");
123 return hcache_open(c_header_cache, mailbox_path(m), NULL, true);
124#else
125 return NULL;
126#endif
127}
128
133static void nm_hcache_close(struct HeaderCache **ptr)
134{
135#ifdef USE_HCACHE
136 hcache_close(ptr);
137#endif
138}
139
145static char *nm_get_default_url(void)
146{
147 // path to DB + query + url "decoration"
148 size_t len = PATH_MAX + 1024 + 32;
149 char *url = MUTT_MEM_MALLOC(len, char);
150
151 // Try to use `$nm_default_url` or `$folder`.
152 // If neither are set, it is impossible to create a Notmuch URL.
153 const char *const c_nm_default_url = cs_subset_string(NeoMutt->sub, "nm_default_url");
154 const char *const c_folder = cs_subset_string(NeoMutt->sub, "folder");
155 if (c_nm_default_url)
156 {
157 snprintf(url, len, "%s", c_nm_default_url);
158 }
159 else if (c_folder)
160 {
161 snprintf(url, len, "notmuch://%s", c_folder);
162 }
163 else
164 {
165 FREE(&url);
166 return NULL;
167 }
168
169 return url;
170}
171
177static struct NmMboxData *nm_get_default_data(void)
178{
179 // path to DB + query + url "decoration"
180 char *url = nm_get_default_url();
181 if (!url)
182 return NULL;
183
184 struct NmMboxData *default_data = nm_mdata_new(url);
185 FREE(&url);
186
187 return default_data;
188}
189
200static int init_mailbox(struct Mailbox *m)
201{
202 if (!m || (m->type != MUTT_NOTMUCH))
203 return -1;
204
205 if (m->mdata)
206 return 0;
207
209 if (!m->mdata)
210 return -1;
211
213 return 0;
214}
215
222static char *email_get_id(struct Email *e)
223{
224 struct NmEmailData *edata = nm_edata_get(e);
225 if (!edata)
226 return NULL;
227
228 return edata->virtual_id;
229}
230
238static char *email_get_fullpath(struct Email *e, char *buf, size_t buflen)
239{
240 snprintf(buf, buflen, "%s/%s", nm_email_get_folder(e), e->path);
241 return buf;
242}
243
253static void query_window_reset(void)
254{
255 mutt_debug(LL_DEBUG2, "entering\n");
256 cs_subset_str_native_set(NeoMutt->sub, "nm_query_window_current_position", 0, NULL);
257}
258
283static bool windowed_query_from_query(const char *query, char *buf, size_t buflen)
284{
285 mutt_debug(LL_DEBUG2, "nm: %s\n", query);
286
287 const bool c_nm_query_window_enable = cs_subset_bool(NeoMutt->sub, "nm_query_window_enable");
288 const short c_nm_query_window_duration = cs_subset_number(NeoMutt->sub, "nm_query_window_duration");
289 const short c_nm_query_window_current_position = cs_subset_number(NeoMutt->sub, "nm_query_window_current_position");
290 const char *const c_nm_query_window_current_search = cs_subset_string(NeoMutt->sub, "nm_query_window_current_search");
291 const char *const c_nm_query_window_timebase = cs_subset_string(NeoMutt->sub, "nm_query_window_timebase");
292 const char *const c_nm_query_window_or_terms = cs_subset_string(NeoMutt->sub, "nm_query_window_or_terms");
293
294 /* if the query has changed, reset the window position */
295 if (!c_nm_query_window_current_search || !mutt_str_equal(query, c_nm_query_window_current_search))
296 {
298 }
299
301 buf, buflen, c_nm_query_window_enable, c_nm_query_window_duration,
302 c_nm_query_window_current_position, c_nm_query_window_current_search,
303 c_nm_query_window_timebase, c_nm_query_window_or_terms);
304
305 switch (rc)
306 {
308 {
309 mutt_debug(LL_DEBUG2, "nm: %s -> %s\n", query, buf);
310 break;
311 }
313 {
315 return false;
316 }
318 {
320 // L10N: The values 'hour', 'day', 'week', 'month', 'year' are literal.
321 // They should not be translated.
322 _("Invalid nm_query_window_timebase value (valid values are: hour, day, week, month, year)"));
323 mutt_debug(LL_DEBUG2, "Invalid nm_query_window_timebase value\n");
324 return false;
325 }
326 }
327
328 return true;
329}
330
347static char *get_query_string(struct NmMboxData *mdata, bool window)
348{
349 mutt_debug(LL_DEBUG2, "nm: %s\n", window ? "true" : "false");
350
351 if (!mdata)
352 return NULL;
353 if (mdata->db_query && !window)
354 return mdata->db_query;
355
356 const char *const c_nm_query_type = cs_subset_string(NeoMutt->sub, "nm_query_type");
357 mdata->query_type = nm_string_to_query_type(c_nm_query_type); /* user's default */
358
359 struct UrlQuery *item = NULL;
360 STAILQ_FOREACH(item, &mdata->db_url->query_strings, entries)
361 {
362 if (!item->value || !item->name)
363 continue;
364
365 if (mutt_str_equal(item->name, "limit"))
366 {
367 if (!mutt_str_atoi_full(item->value, &mdata->db_limit))
368 {
369 mutt_error(_("failed to parse notmuch limit: %s"), item->value);
370 }
371 }
372 else if (mutt_str_equal(item->name, "type"))
373 {
375 }
376 else if (mutt_str_equal(item->name, "query"))
377 {
378 mutt_str_replace(&mdata->db_query, item->value);
379 }
380 }
381
382 if (!mdata->db_query)
383 return NULL;
384
385 if (window)
386 {
387 char buf[1024] = { 0 };
388 cs_subset_str_string_set(NeoMutt->sub, "nm_query_window_current_search",
389 mdata->db_query, NULL);
390
391 /* if a date part is defined, do not apply windows (to avoid the risk of
392 * having a non-intersected date frame). A good improvement would be to
393 * accept if they intersect */
394 if (!strstr(mdata->db_query, "date:") &&
395 windowed_query_from_query(mdata->db_query, buf, sizeof(buf)))
396 {
397 mutt_str_replace(&mdata->db_query, buf);
398 }
399
400 mutt_debug(LL_DEBUG2, "nm: query (windowed) '%s'\n", mdata->db_query);
401 }
402 else
403 {
404 mutt_debug(LL_DEBUG2, "nm: query '%s'\n", mdata->db_query);
405 }
406
407 return mdata->db_query;
408}
409
415static int get_limit(struct NmMboxData *mdata)
416{
417 return mdata ? mdata->db_limit : 0;
418}
419
424static void apply_exclude_tags(notmuch_query_t *query)
425{
426 const char *const c_nm_exclude_tags = cs_subset_string(NeoMutt->sub, "nm_exclude_tags");
427 if (!c_nm_exclude_tags || !query)
428 return;
429
430 struct NmTags tags = nm_tag_str_to_tags(c_nm_exclude_tags);
431
432 char **tag = NULL;
433 ARRAY_FOREACH(tag, &tags.tags)
434 {
435 mutt_debug(LL_DEBUG2, "nm: query exclude tag '%s'\n", *tag);
436 notmuch_query_add_tag_exclude(query, *tag);
437 }
438
439 notmuch_query_set_omit_excluded(query, 1);
441}
442
450static notmuch_query_t *get_query(struct Mailbox *m, bool writable)
451{
452 struct NmMboxData *mdata = nm_mdata_get(m);
453 if (!mdata)
454 return NULL;
455
456 notmuch_database_t *db = nm_db_get(m, writable);
457 const char *str = get_query_string(mdata, true);
458
459 if (!db || !str)
460 goto err;
461
462 notmuch_query_t *q = notmuch_query_create(db, str);
463 if (!q)
464 goto err;
465
467 notmuch_query_set_sort(q, NOTMUCH_SORT_NEWEST_FIRST);
468 mutt_debug(LL_DEBUG2, "nm: query successfully initialized (%s)\n", str);
469 return q;
470err:
471 nm_db_release(m);
472 return NULL;
473}
474
482static int update_email_tags(struct Email *e, notmuch_message_t *msg)
483{
484 struct NmEmailData *edata = nm_edata_get(e);
485 struct Buffer *new_tags = buf_pool_get();
486 struct Buffer *old_tags = buf_pool_get();
487
488 mutt_debug(LL_DEBUG2, "nm: tags update requested (%s)\n", edata->virtual_id);
489
490 for (notmuch_tags_t *tags = notmuch_message_get_tags(msg);
491 tags && notmuch_tags_valid(tags); notmuch_tags_move_to_next(tags))
492 {
493 const char *t = notmuch_tags_get(tags);
494 if (!t || (*t == '\0'))
495 continue;
496
497 buf_join_str(new_tags, t, ' ');
498 }
499
500 driver_tags_get(&e->tags, old_tags);
501
502 if (!buf_is_empty(new_tags) && !buf_is_empty(old_tags) &&
503 (buf_str_equal(old_tags, new_tags)))
504 {
505 buf_pool_release(&new_tags);
506 buf_pool_release(&old_tags);
507 mutt_debug(LL_DEBUG2, "nm: tags unchanged\n");
508 return 1;
509 }
510 buf_pool_release(&old_tags);
511
512 /* new version */
513 driver_tags_replace(&e->tags, buf_string(new_tags));
514 buf_reset(new_tags);
515
516 driver_tags_get_transformed(&e->tags, new_tags);
517 mutt_debug(LL_DEBUG2, "nm: new tags transformed: '%s'\n", buf_string(new_tags));
518 buf_reset(new_tags);
519
520 driver_tags_get(&e->tags, new_tags);
521 mutt_debug(LL_DEBUG2, "nm: new tag: '%s'\n", buf_string(new_tags));
522 buf_pool_release(&new_tags);
523
524 return 0;
525}
526
534static int update_message_path(struct Email *e, const char *path)
535{
536 struct NmEmailData *edata = nm_edata_get(e);
537
538 mutt_debug(LL_DEBUG2, "nm: path update requested path=%s, (%s)\n", path, edata->virtual_id);
539
540 char *p = strrchr(path, '/');
541 if (p && ((p - path) > 3) &&
542 (mutt_strn_equal(p - 3, "cur", 3) || mutt_strn_equal(p - 3, "new", 3) ||
543 mutt_strn_equal(p - 3, "tmp", 3)))
544 {
545 edata->type = MUTT_MAILDIR;
546
547 FREE(&e->path);
548 FREE(&edata->folder);
549
550 p -= 3; /* skip subfolder (e.g. "new") */
551 if (cs_subset_bool(NeoMutt->sub, "mark_old"))
552 {
553 e->old = mutt_str_startswith(p, "cur");
554 }
555 e->path = mutt_str_dup(p);
556
557 for (; (p > path) && (*(p - 1) == '/'); p--)
558 ; // do nothing
559
560 edata->folder = mutt_strn_dup(path, p - path);
561
562 mutt_debug(LL_DEBUG2, "nm: folder='%s', file='%s'\n", edata->folder, e->path);
563
564 // We _might_ be looking at a different file (with the same message-id)
565 // so reparse it from scratch.
566
567 // Preserve the message-id as it's used in the Email HashTable
568 mutt_debug(LL_DEBUG1, "nm: reparse the message\n");
569 char *message_id = e->env->message_id;
570 e->env->message_id = NULL;
571
572 mutt_body_free(&e->body);
573 mutt_env_free(&e->env);
575 ASSERT(e->body);
576 ASSERT(e->env);
577
578 FREE(&e->env->message_id);
579 e->env->message_id = message_id;
580 message_id = NULL;
581
582 return 0;
583 }
584
585 return 1;
586}
587
594static char *get_folder_from_path(const char *path)
595{
596 char *p = strrchr(path, '/');
597
598 if (p && ((p - path) > 3) &&
599 (mutt_strn_equal(p - 3, "cur", 3) || mutt_strn_equal(p - 3, "new", 3) ||
600 mutt_strn_equal(p - 3, "tmp", 3)))
601 {
602 p -= 3;
603 for (; (p > path) && (*(p - 1) == '/'); p--)
604 ; // do nothing
605
606 return mutt_strn_dup(path, p - path);
607 }
608
609 return NULL;
610}
611
619static char *nm2mutt_message_id(const char *id)
620{
621 if (!id)
622 return NULL;
623
624 char *mid = NULL;
625 mutt_str_asprintf(&mid, "<%s>", id);
626 return mid;
627}
628
637static int init_email(struct Email *e, const char *path, notmuch_message_t *msg)
638{
639 if (nm_edata_get(e))
640 return 0;
641
642 struct NmEmailData *edata = nm_edata_new();
643 e->nm_edata = edata;
644
645 /* Notmuch ensures that message Id exists (if not notmuch Notmuch will
646 * generate an ID), so it's more safe than use neomutt Email->env->id */
647 const char *id = notmuch_message_get_message_id(msg);
648 edata->virtual_id = mutt_str_dup(id);
649
650 mutt_debug(LL_DEBUG2, "nm: [e=%p, edata=%p] (%s)\n", (void *) e, (void *) edata, id);
651
652 char *nm_msg_id = nm2mutt_message_id(id);
653 if (!e->env->message_id)
654 {
655 e->env->message_id = nm_msg_id;
656 }
657 else if (!mutt_str_equal(e->env->message_id, nm_msg_id))
658 {
659 FREE(&e->env->message_id);
660 e->env->message_id = nm_msg_id;
661 }
662 else
663 {
664 FREE(&nm_msg_id);
665 }
666
667 if (update_message_path(e, path) != 0)
668 return -1;
669
670 update_email_tags(e, msg);
671
672 return 0;
673}
674
681static const char *get_message_last_filename(notmuch_message_t *msg)
682{
683 const char *name = NULL;
684
685 for (notmuch_filenames_t *ls = notmuch_message_get_filenames(msg);
686 ls && notmuch_filenames_valid(ls); notmuch_filenames_move_to_next(ls))
687 {
688 name = notmuch_filenames_get(ls);
689 }
690
691 return name;
692}
693
698static void progress_setup(struct Mailbox *m)
699{
700 if (!m->verbose)
701 return;
702
703 struct NmMboxData *mdata = nm_mdata_get(m);
704 if (!mdata)
705 return;
706
707 mdata->oldmsgcount = m->msg_count;
708 mdata->ignmsgcount = 0;
709 mdata->progress = progress_new(MUTT_PROGRESS_READ, mdata->oldmsgcount);
710 progress_set_message(mdata->progress, _("Reading messages..."));
711}
712
717static void nm_progress_update(struct Mailbox *m)
718{
719 struct NmMboxData *mdata = nm_mdata_get(m);
720
721 if (!m->verbose || !mdata || !mdata->progress)
722 return;
723
724 progress_update(mdata->progress, m->msg_count + mdata->ignmsgcount, -1);
725}
726
734static struct Email *get_mutt_email(struct Mailbox *m, notmuch_message_t *msg)
735{
736 if (!m || !msg)
737 return NULL;
738
739 const char *id = notmuch_message_get_message_id(msg);
740 if (!id)
741 return NULL;
742
743 mutt_debug(LL_DEBUG2, "nm: neomutt email, id='%s'\n", id);
744
745 if (!m->id_hash)
746 {
747 mutt_debug(LL_DEBUG2, "nm: init hash\n");
749 if (!m->id_hash)
750 return NULL;
751 }
752
753 char *mid = nm2mutt_message_id(id);
754 mutt_debug(LL_DEBUG2, "nm: neomutt id='%s'\n", mid);
755
756 struct Email *e = mutt_hash_find(m->id_hash, mid);
757 FREE(&mid);
758 return e;
759}
760
768static void append_message(struct HeaderCache *hc, struct Mailbox *m,
769 notmuch_message_t *msg, bool dedup)
770{
771 struct NmMboxData *mdata = nm_mdata_get(m);
772 if (!mdata)
773 return;
774
775 char *newpath = NULL;
776 struct Email *e = NULL;
777
778 /* deduplicate */
779 if (dedup && get_mutt_email(m, msg))
780 {
781 mdata->ignmsgcount++;
783 mutt_debug(LL_DEBUG2, "nm: ignore id=%s, already in the m\n",
784 notmuch_message_get_message_id(msg));
785 return;
786 }
787
788 const char *path = get_message_last_filename(msg);
789 if (!path)
790 return;
791
792 mutt_debug(LL_DEBUG2, "nm: appending message, i=%d, id=%s, path=%s\n",
793 m->msg_count, notmuch_message_get_message_id(msg), path);
794
796
797#ifdef USE_HCACHE
799 if (!e)
800#endif
801 {
802 if (access(path, F_OK) == 0)
803 {
804 /* We pass is_old=false as argument here, but e->old will be updated later
805 * by update_message_path() (called by init_email() below). */
806 e = maildir_email_new();
807 if (!maildir_parse_message(path, false, e))
808 email_free(&e);
809 }
810 else
811 {
812 /* maybe moved try find it... */
813 char *folder = get_folder_from_path(path);
814
815 if (folder)
816 {
817 FILE *fp = maildir_open_find_message(folder, path, &newpath);
818 if (fp)
819 {
820 e = maildir_email_new();
821 if (!maildir_parse_stream(fp, newpath, false, e))
822 email_free(&e);
823 mutt_file_fclose(&fp);
824
825 mutt_debug(LL_DEBUG1, "nm: not up-to-date: %s -> %s\n", path, newpath);
826 }
827 }
828 FREE(&folder);
829 }
830
831 if (!e)
832 {
833 mutt_debug(LL_DEBUG1, "nm: failed to parse message: %s\n", path);
834 goto done;
835 }
836
837#ifdef USE_HCACHE
838 hcache_store_email(hc, newpath ? newpath : path,
839 mutt_str_len(newpath ? newpath : path), e, 0);
840#endif
841 }
842
843 if (init_email(e, newpath ? newpath : path, msg) != 0)
844 {
845 email_free(&e);
846 mutt_debug(LL_DEBUG1, "nm: failed to append email!\n");
847 goto done;
848 }
849
850 e->active = true;
851 e->index = m->msg_count;
852 mailbox_size_add(m, e);
853 m->emails[m->msg_count] = e;
854 m->msg_count++;
855
856 if (newpath)
857 {
858 /* remember that file has been moved -- nm_mbox_sync() will update the DB */
859 struct NmEmailData *edata = nm_edata_get(e);
860 if (edata)
861 {
862 mutt_debug(LL_DEBUG1, "nm: remember obsolete path: %s\n", path);
863 edata->oldpath = mutt_str_dup(path);
864 }
865 }
867done:
868 FREE(&newpath);
869}
870
881static void append_replies(struct HeaderCache *hc, struct Mailbox *m,
882 notmuch_query_t *q, notmuch_message_t *top, bool dedup)
883{
884 notmuch_messages_t *msgs = NULL;
885
886 for (msgs = notmuch_message_get_replies(top); notmuch_messages_valid(msgs);
887 notmuch_messages_move_to_next(msgs))
888 {
889 notmuch_message_t *nm = notmuch_messages_get(msgs);
890 append_message(hc, m, nm, dedup);
891 /* recurse through all the replies to this message too */
892 append_replies(hc, m, q, nm, dedup);
893 notmuch_message_destroy(nm);
894 }
895}
896
908static void append_thread(struct HeaderCache *hc, struct Mailbox *m,
909 notmuch_query_t *q, notmuch_thread_t *thread, bool dedup)
910{
911 notmuch_messages_t *msgs = NULL;
912
913 for (msgs = notmuch_thread_get_toplevel_messages(thread);
914 notmuch_messages_valid(msgs); notmuch_messages_move_to_next(msgs))
915 {
916 notmuch_message_t *nm = notmuch_messages_get(msgs);
917 append_message(hc, m, nm, dedup);
918 append_replies(hc, m, q, nm, dedup);
919 notmuch_message_destroy(nm);
920 }
921}
922
932static notmuch_messages_t *get_messages(notmuch_query_t *query)
933{
934 if (!query)
935 return NULL;
936
937 notmuch_messages_t *msgs = NULL;
938
939#if LIBNOTMUCH_CHECK_VERSION(5, 0, 0)
940 if (notmuch_query_search_messages(query, &msgs) != NOTMUCH_STATUS_SUCCESS)
941 return NULL;
942#elif LIBNOTMUCH_CHECK_VERSION(4, 3, 0)
943 if (notmuch_query_search_messages_st(query, &msgs) != NOTMUCH_STATUS_SUCCESS)
944 return NULL;
945#else
946 msgs = notmuch_query_search_messages(query);
947#endif
948
949 return msgs;
950}
951
960static bool read_mesgs_query(struct Mailbox *m, notmuch_query_t *q, bool dedup)
961{
962 struct NmMboxData *mdata = nm_mdata_get(m);
963 if (!mdata)
964 return false;
965
966 int limit = get_limit(mdata);
967
968 notmuch_messages_t *msgs = get_messages(q);
969
970 if (!msgs)
971 return false;
972
973 struct HeaderCache *hc = nm_hcache_open(m);
974
975 for (; notmuch_messages_valid(msgs) && ((limit == 0) || (m->msg_count < limit));
976 notmuch_messages_move_to_next(msgs))
977 {
978 if (SigInt)
979 {
980 nm_hcache_close(&hc);
981 SigInt = false;
982 return false;
983 }
984 notmuch_message_t *nm = notmuch_messages_get(msgs);
985 append_message(hc, m, nm, dedup);
986 notmuch_message_destroy(nm);
987 }
988
989 nm_hcache_close(&hc);
990 return true;
991}
992
1002static notmuch_threads_t *get_threads(notmuch_query_t *query)
1003{
1004 if (!query)
1005 return NULL;
1006
1007 notmuch_threads_t *threads = NULL;
1008#if LIBNOTMUCH_CHECK_VERSION(5, 0, 0)
1009 if (notmuch_query_search_threads(query, &threads) != NOTMUCH_STATUS_SUCCESS)
1010 return NULL;
1011#elif LIBNOTMUCH_CHECK_VERSION(4, 3, 0)
1012 if (notmuch_query_search_threads_st(query, &threads) != NOTMUCH_STATUS_SUCCESS)
1013 return NULL;
1014#else
1015 threads = notmuch_query_search_threads(query);
1016#endif
1017
1018 return threads;
1019}
1020
1030static bool read_threads_query(struct Mailbox *m, notmuch_query_t *q, bool dedup, int limit)
1031{
1032 struct NmMboxData *mdata = nm_mdata_get(m);
1033 if (!mdata)
1034 return false;
1035
1036 notmuch_threads_t *threads = get_threads(q);
1037 if (!threads)
1038 return false;
1039
1040 struct HeaderCache *hc = nm_hcache_open(m);
1041
1042 for (; notmuch_threads_valid(threads) && ((limit == 0) || (m->msg_count < limit));
1043 notmuch_threads_move_to_next(threads))
1044 {
1045 if (SigInt)
1046 {
1047 nm_hcache_close(&hc);
1048 SigInt = false;
1049 return false;
1050 }
1051 notmuch_thread_t *thread = notmuch_threads_get(threads);
1052 append_thread(hc, m, q, thread, dedup);
1053 notmuch_thread_destroy(thread);
1054 }
1055
1056 nm_hcache_close(&hc);
1057 return true;
1058}
1059
1067static notmuch_message_t *get_nm_message(notmuch_database_t *db, struct Email *e)
1068{
1069 notmuch_message_t *msg = NULL;
1070 char *id = email_get_id(e);
1071
1072 mutt_debug(LL_DEBUG2, "nm: find message (%s)\n", id);
1073
1074 if (id && db)
1075 notmuch_database_find_message(db, id, &msg);
1076
1077 return msg;
1078}
1079
1086static bool nm_message_has_tag(notmuch_message_t *msg, char *tag)
1087{
1088 const char *possible_match_tag = NULL;
1089 notmuch_tags_t *tags = NULL;
1090
1091 for (tags = notmuch_message_get_tags(msg); notmuch_tags_valid(tags);
1092 notmuch_tags_move_to_next(tags))
1093 {
1094 possible_match_tag = notmuch_tags_get(tags);
1095 if (mutt_str_equal(possible_match_tag, tag))
1096 {
1097 return true;
1098 }
1099 }
1100 return false;
1101}
1102
1108static void sync_email_path_with_nm(struct Email *e, notmuch_message_t *msg)
1109{
1110 const char *new_file = get_message_last_filename(msg);
1111 char old_file[PATH_MAX] = { 0 };
1112 email_get_fullpath(e, old_file, sizeof(old_file));
1113
1114 if (!mutt_str_equal(old_file, new_file))
1115 update_message_path(e, new_file);
1116}
1117
1125static int update_tags(notmuch_message_t *msg, const char *tag_str)
1126{
1127 if (!tag_str)
1128 return -1;
1129
1130 notmuch_message_freeze(msg);
1131
1133 char **tag_elem = NULL;
1134 ARRAY_FOREACH(tag_elem, &tags.tags)
1135 {
1136 char *tag = *tag_elem;
1137
1138 if (tag[0] == '-')
1139 {
1140 mutt_debug(LL_DEBUG1, "nm: remove tag: '%s'\n", tag + 1);
1141 notmuch_message_remove_tag(msg, tag + 1);
1142 }
1143 else if (tag[0] == '!')
1144 {
1145 mutt_debug(LL_DEBUG1, "nm: toggle tag: '%s'\n", tag + 1);
1146 if (nm_message_has_tag(msg, tag + 1))
1147 {
1148 notmuch_message_remove_tag(msg, tag + 1);
1149 }
1150 else
1151 {
1152 notmuch_message_add_tag(msg, tag + 1);
1153 }
1154 }
1155 else
1156 {
1157 mutt_debug(LL_DEBUG1, "nm: add tag: '%s'\n", (tag[0] == '+') ? tag + 1 : tag);
1158 notmuch_message_add_tag(msg, (tag[0] == '+') ? tag + 1 : tag);
1159 }
1160 }
1161
1162 notmuch_message_thaw(msg);
1164
1165 return 0;
1166}
1167
1179static int update_email_flags(struct Mailbox *m, struct Email *e, const char *tag_str)
1180{
1181 if (!tag_str)
1182 return -1;
1183
1184 const char *const c_nm_unread_tag = cs_subset_string(NeoMutt->sub, "nm_unread_tag");
1185 const char *const c_nm_replied_tag = cs_subset_string(NeoMutt->sub, "nm_replied_tag");
1186 const char *const c_nm_flagged_tag = cs_subset_string(NeoMutt->sub, "nm_flagged_tag");
1187
1189 char **tag_elem = NULL;
1190 ARRAY_FOREACH(tag_elem, &tags.tags)
1191 {
1192 char *tag = *tag_elem;
1193
1194 if (tag[0] == '-')
1195 {
1196 tag++;
1197 if (mutt_str_equal(tag, c_nm_unread_tag))
1198 mutt_set_flag(m, e, MUTT_READ, true, true);
1199 else if (mutt_str_equal(tag, c_nm_replied_tag))
1200 mutt_set_flag(m, e, MUTT_REPLIED, false, true);
1201 else if (mutt_str_equal(tag, c_nm_flagged_tag))
1202 mutt_set_flag(m, e, MUTT_FLAG, false, true);
1203 }
1204 else
1205 {
1206 tag = (tag[0] == '+') ? tag + 1 : tag;
1207 if (mutt_str_equal(tag, c_nm_unread_tag))
1208 mutt_set_flag(m, e, MUTT_READ, false, true);
1209 else if (mutt_str_equal(tag, c_nm_replied_tag))
1210 mutt_set_flag(m, e, MUTT_REPLIED, true, true);
1211 else if (mutt_str_equal(tag, c_nm_flagged_tag))
1212 mutt_set_flag(m, e, MUTT_FLAG, true, true);
1213 }
1214 }
1215
1217
1218 return 0;
1219}
1220
1231static int rename_maildir_filename(const char *old, char *buf, size_t buflen, struct Email *e)
1232{
1233 char filename[PATH_MAX] = { 0 };
1234 char suffix[PATH_MAX] = { 0 };
1235 char folder[PATH_MAX] = { 0 };
1236
1237 mutt_str_copy(folder, old, sizeof(folder));
1238 char *p = strrchr(folder, '/');
1239 if (p)
1240 {
1241 *p = '\0';
1242 p++;
1243 }
1244 else
1245 {
1246 p = folder;
1247 }
1248
1249 mutt_str_copy(filename, p, sizeof(filename));
1250
1251 /* remove (new,cur,...) from folder path */
1252 p = strrchr(folder, '/');
1253 if (p)
1254 *p = '\0';
1255
1256 /* remove old flags from filename */
1257 const char c_maildir_field_delimiter = *cc_maildir_field_delimiter();
1258 p = strchr(filename, c_maildir_field_delimiter);
1259 if (p)
1260 *p = '\0';
1261
1262 /* compose new flags */
1263 maildir_gen_flags(suffix, sizeof(suffix), e);
1264
1265 snprintf(buf, buflen, "%s/%s/%s%s", folder,
1266 (e->read || e->old) ? "cur" : "new", filename, suffix);
1267
1268 if (mutt_str_equal(old, buf))
1269 return 1;
1270
1271 if (rename(old, buf) != 0)
1272 {
1273 mutt_debug(LL_DEBUG1, "nm: rename(2) failed %s -> %s\n", old, buf);
1274 return -1;
1275 }
1276
1277 return 0;
1278}
1279
1287static int remove_filename(struct Mailbox *m, const char *path)
1288{
1289 struct NmMboxData *mdata = nm_mdata_get(m);
1290 if (!mdata)
1291 return -1;
1292
1293 mutt_debug(LL_DEBUG2, "nm: remove filename '%s'\n", path);
1294
1295 notmuch_database_t *db = nm_db_get(m, true);
1296 if (!db)
1297 return -1;
1298
1299 notmuch_message_t *msg = NULL;
1300 notmuch_status_t st = notmuch_database_find_message_by_filename(db, path, &msg);
1301 if (st || !msg)
1302 return -1;
1303
1304 int trans = nm_db_trans_begin(m);
1305 if (trans < 0)
1306 return -1;
1307
1308 /* note that unlink() is probably unnecessary here, it's already removed
1309 * by mh_sync_mailbox_message(), but for sure... */
1310 notmuch_filenames_t *ls = NULL;
1311 st = notmuch_database_remove_message(db, path);
1312 switch (st)
1313 {
1314 case NOTMUCH_STATUS_SUCCESS:
1315 mutt_debug(LL_DEBUG2, "nm: remove success, call unlink\n");
1316 unlink(path);
1317 break;
1318 case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:
1319 mutt_debug(LL_DEBUG2, "nm: remove success (duplicate), call unlink\n");
1320 unlink(path);
1321 for (ls = notmuch_message_get_filenames(msg);
1322 ls && notmuch_filenames_valid(ls); notmuch_filenames_move_to_next(ls))
1323 {
1324 path = notmuch_filenames_get(ls);
1325
1326 mutt_debug(LL_DEBUG2, "nm: remove duplicate: '%s'\n", path);
1327 unlink(path);
1328 notmuch_database_remove_message(db, path);
1329 }
1330 break;
1331 default:
1332 mutt_debug(LL_DEBUG1, "nm: failed to remove '%s' [st=%d]\n", path, (int) st);
1333 break;
1334 }
1335
1336 notmuch_message_destroy(msg);
1337 if (trans)
1338 nm_db_trans_end(m);
1339 return 0;
1340}
1341
1351static int rename_filename(struct Mailbox *m, const char *old_file,
1352 const char *new_file, struct Email *e)
1353{
1354 struct NmMboxData *mdata = nm_mdata_get(m);
1355 if (!mdata)
1356 return -1;
1357
1358 notmuch_database_t *db = nm_db_get(m, true);
1359 if (!db || !new_file || !old_file || (access(new_file, F_OK) != 0))
1360 return -1;
1361
1362 int rc = -1;
1363 notmuch_status_t st;
1364 notmuch_filenames_t *ls = NULL;
1365 notmuch_message_t *msg = NULL;
1366
1367 mutt_debug(LL_DEBUG1, "nm: rename filename, %s -> %s\n", old_file, new_file);
1368 int trans = nm_db_trans_begin(m);
1369 if (trans < 0)
1370 return -1;
1371
1372 mutt_debug(LL_DEBUG2, "nm: rename: add '%s'\n", new_file);
1373#if LIBNOTMUCH_CHECK_VERSION(5, 1, 0)
1374 st = notmuch_database_index_file(db, new_file, NULL, &msg);
1375#else
1376 st = notmuch_database_add_message(db, new_file, &msg);
1377#endif
1378
1379 if ((st != NOTMUCH_STATUS_SUCCESS) && (st != NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID))
1380 {
1381 mutt_debug(LL_DEBUG1, "nm: failed to add '%s' [st=%d]\n", new_file, (int) st);
1382 goto done;
1383 }
1384
1385 mutt_debug(LL_DEBUG2, "nm: rename: rem '%s'\n", old_file);
1386 st = notmuch_database_remove_message(db, old_file);
1387 switch (st)
1388 {
1389 case NOTMUCH_STATUS_SUCCESS:
1390 break;
1391 case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:
1392 mutt_debug(LL_DEBUG2, "nm: rename: syncing duplicate filename\n");
1393 notmuch_message_destroy(msg);
1394 msg = NULL;
1395 notmuch_database_find_message_by_filename(db, new_file, &msg);
1396
1397 for (ls = notmuch_message_get_filenames(msg);
1398 msg && ls && notmuch_filenames_valid(ls); notmuch_filenames_move_to_next(ls))
1399 {
1400 const char *path = notmuch_filenames_get(ls);
1401 char newpath[PATH_MAX] = { 0 };
1402
1403 if (mutt_str_equal(new_file, path))
1404 continue;
1405
1406 mutt_debug(LL_DEBUG2, "nm: rename: syncing duplicate: %s\n", path);
1407
1408 if (rename_maildir_filename(path, newpath, sizeof(newpath), e) == 0)
1409 {
1410 mutt_debug(LL_DEBUG2, "nm: rename dup %s -> %s\n", path, newpath);
1411 notmuch_database_remove_message(db, path);
1412#if LIBNOTMUCH_CHECK_VERSION(5, 1, 0)
1413 notmuch_database_index_file(db, newpath, NULL, NULL);
1414#else
1415 notmuch_database_add_message(db, newpath, NULL);
1416#endif
1417 }
1418 }
1419 notmuch_message_destroy(msg);
1420 msg = NULL;
1421 notmuch_database_find_message_by_filename(db, new_file, &msg);
1422 st = NOTMUCH_STATUS_SUCCESS;
1423 break;
1424 default:
1425 mutt_debug(LL_DEBUG1, "nm: failed to remove '%s' [st=%d]\n", old_file, (int) st);
1426 break;
1427 }
1428
1429 if ((st == NOTMUCH_STATUS_SUCCESS) && e && msg)
1430 {
1431 notmuch_message_maildir_flags_to_tags(msg);
1432 update_email_tags(e, msg);
1433
1434 struct Buffer *tags = buf_pool_get();
1435 driver_tags_get(&e->tags, tags);
1436 update_tags(msg, buf_string(tags));
1437 buf_pool_release(&tags);
1438 }
1439
1440 rc = 0;
1441done:
1442 if (msg)
1443 notmuch_message_destroy(msg);
1444 if (trans)
1445 nm_db_trans_end(m);
1446 return rc;
1447}
1448
1456static unsigned int count_query(notmuch_database_t *db, const char *qstr, int limit)
1457{
1458 notmuch_query_t *q = notmuch_query_create(db, qstr);
1459 if (!q)
1460 return 0;
1461
1462 unsigned int res = 0;
1463
1465#if LIBNOTMUCH_CHECK_VERSION(5, 0, 0)
1466 if (notmuch_query_count_messages(q, &res) != NOTMUCH_STATUS_SUCCESS)
1467 res = 0; /* may not be defined on error */
1468#elif LIBNOTMUCH_CHECK_VERSION(4, 3, 0)
1469 if (notmuch_query_count_messages_st(q, &res) != NOTMUCH_STATUS_SUCCESS)
1470 res = 0; /* may not be defined on error */
1471#else
1472 res = notmuch_query_count_messages(q);
1473#endif
1474 notmuch_query_destroy(q);
1475 mutt_debug(LL_DEBUG1, "nm: count '%s', result=%d\n", qstr, res);
1476
1477 if ((limit > 0) && (res > limit))
1478 res = limit;
1479
1480 return res;
1481}
1482
1490{
1491 struct NmEmailData *edata = nm_edata_get(e);
1492 if (!edata)
1493 return NULL;
1494
1495 return edata->folder;
1496}
1497
1508char *nm_email_get_folder_rel_db(struct Mailbox *m, struct Email *e)
1509{
1510 char *full_folder = nm_email_get_folder(e);
1511 if (!full_folder)
1512 return NULL;
1513
1514 const char *db_path = nm_db_get_filename(m);
1515 if (!db_path)
1516 return NULL;
1517
1518 size_t prefix = mutt_str_startswith(full_folder, db_path);
1519
1520 char *path = full_folder + prefix;
1521 if (*path == '/')
1522 path++;
1523
1524 return path;
1525}
1526
1534int nm_read_entire_thread(struct Mailbox *m, struct Email *e)
1535{
1536 if (!m)
1537 return -1;
1538
1539 struct NmMboxData *mdata = nm_mdata_get(m);
1540 if (!mdata)
1541 return -1;
1542
1543 notmuch_query_t *q = NULL;
1544 notmuch_database_t *db = NULL;
1545 notmuch_message_t *msg = NULL;
1546 int rc = -1;
1547
1548 if (!(db = nm_db_get(m, false)) || !(msg = get_nm_message(db, e)))
1549 goto done;
1550
1551 mutt_debug(LL_DEBUG1, "nm: reading entire-thread messages...[current count=%d]\n",
1552 m->msg_count);
1553
1554 progress_setup(m);
1555 const char *id = notmuch_message_get_thread_id(msg);
1556 if (!id)
1557 goto done;
1558
1559 struct Buffer *qstr = buf_pool_get();
1560 buf_printf(qstr, "thread:%s", id);
1561 q = notmuch_query_create(db, buf_string(qstr));
1562 buf_pool_release(&qstr);
1563 if (!q)
1564 goto done;
1566 notmuch_query_set_sort(q, NOTMUCH_SORT_NEWEST_FIRST);
1567
1568 read_threads_query(m, q, true, 0);
1569 mdata->mtime.tv_sec = mutt_date_now();
1570 mdata->mtime.tv_nsec = 0;
1571 rc = 0;
1572
1573 if (m->msg_count > mdata->oldmsgcount)
1575done:
1576 if (q)
1577 notmuch_query_destroy(q);
1578
1579 nm_db_release(m);
1580
1581 if (m->msg_count == mdata->oldmsgcount)
1582 mutt_message(_("No more messages in the thread"));
1583
1584 mdata->oldmsgcount = 0;
1585 mutt_debug(LL_DEBUG1, "nm: reading entire-thread messages... done [rc=%d, count=%d]\n",
1586 rc, m->msg_count);
1587 progress_free(&mdata->progress);
1588 return rc;
1589}
1590
1599char *nm_url_from_query(struct Mailbox *m, char *buf, size_t buflen)
1600{
1601 mutt_debug(LL_DEBUG2, "(%s)\n", buf);
1602 struct NmMboxData *mdata = nm_mdata_get(m);
1603 char url[PATH_MAX + 1024 + 32]; /* path to DB + query + URL "decoration" */
1604 int added;
1605 bool using_default_data = false;
1606
1607 // No existing data. Try to get a default NmMboxData.
1608 if (!mdata)
1609 {
1611
1612 // Failed to get default data.
1613 if (!mdata)
1614 return NULL;
1615
1616 using_default_data = true;
1617 }
1618
1620 cs_subset_string(NeoMutt->sub, "nm_query_type"));
1621 mdata->query_type = nm_parse_type_from_query(buf, query_type);
1622
1623 const short c_nm_db_limit = cs_subset_number(NeoMutt->sub, "nm_db_limit");
1624 if (get_limit(mdata) == c_nm_db_limit)
1625 {
1626 added = snprintf(url, sizeof(url), "%s%s?type=%s&query=", NmUrlProtocol,
1628 }
1629 else
1630 {
1631 added = snprintf(url, sizeof(url), "%s%s?type=%s&limit=%d&query=", NmUrlProtocol,
1634 }
1635
1636 if (added >= sizeof(url))
1637 {
1638 // snprintf output was truncated, so can't create URL
1639 return NULL;
1640 }
1641
1642 url_pct_encode(&url[added], sizeof(url) - added, buf);
1643
1644 mutt_str_copy(buf, url, buflen);
1645 buf[buflen - 1] = '\0';
1646
1647 if (using_default_data)
1648 nm_mdata_free((void **) &mdata);
1649
1650 mutt_debug(LL_DEBUG1, "nm: url from query '%s'\n", buf);
1651 return buf;
1652}
1653
1659{
1660 const short c_nm_query_window_duration = cs_subset_number(NeoMutt->sub, "nm_query_window_duration");
1661 const bool c_nm_query_window_enable = cs_subset_bool(NeoMutt->sub, "nm_query_window_enable");
1662
1663 return c_nm_query_window_enable || (c_nm_query_window_duration > 0);
1664}
1665
1676{
1677 const short c_nm_query_window_current_position = cs_subset_number(NeoMutt->sub, "nm_query_window_current_position");
1678 if (c_nm_query_window_current_position != 0)
1679 {
1680 cs_subset_str_native_set(NeoMutt->sub, "nm_query_window_current_position",
1681 c_nm_query_window_current_position - 1, NULL);
1682 }
1683
1684 mutt_debug(LL_DEBUG2, "(%d)\n", c_nm_query_window_current_position - 1);
1685}
1686
1696{
1697 const short c_nm_query_window_current_position = cs_subset_number(NeoMutt->sub, "nm_query_window_current_position");
1698 cs_subset_str_native_set(NeoMutt->sub, "nm_query_window_current_position",
1699 c_nm_query_window_current_position + 1, NULL);
1700 mutt_debug(LL_DEBUG2, "(%d)\n", c_nm_query_window_current_position + 1);
1701}
1702
1707{
1708 cs_subset_str_native_set(NeoMutt->sub, "nm_query_window_current_position", 0, NULL);
1709 mutt_debug(LL_DEBUG2, "Reset nm_query_window_current_position to 0\n");
1710}
1711
1719{
1720 struct NmMboxData *mdata = nm_mdata_get(m);
1721 if (!mdata)
1722 return false;
1723
1724 notmuch_database_t *db = nm_db_get(m, false);
1725 char *orig_str = get_query_string(mdata, true);
1726
1727 if (!db || !orig_str)
1728 return false;
1729
1730 char *new_str = NULL;
1731 bool rc = false;
1732 if (mutt_str_asprintf(&new_str, "id:%s and (%s)", email_get_id(e), orig_str) < 0)
1733 return false;
1734
1735 mutt_debug(LL_DEBUG2, "nm: checking if message is still queried: %s\n", new_str);
1736
1737 notmuch_query_t *q = notmuch_query_create(db, new_str);
1738
1739 switch (mdata->query_type)
1740 {
1741 case NM_QUERY_TYPE_UNKNOWN: // UNKNOWN should never occur, but MESGS is default
1743 {
1744 notmuch_messages_t *messages = get_messages(q);
1745
1746 if (!messages)
1747 return false;
1748
1749 rc = notmuch_messages_valid(messages);
1750 notmuch_messages_destroy(messages);
1751 break;
1752 }
1754 {
1755 notmuch_threads_t *threads = get_threads(q);
1756
1757 if (!threads)
1758 return false;
1759
1760 rc = notmuch_threads_valid(threads);
1761 notmuch_threads_destroy(threads);
1762 break;
1763 }
1764 }
1765
1766 notmuch_query_destroy(q);
1767
1768 mutt_debug(LL_DEBUG2, "nm: checking if message is still queried: %s = %s\n",
1769 new_str, rc ? "true" : "false");
1770
1771 return rc;
1772}
1773
1783int nm_update_filename(struct Mailbox *m, const char *old_file,
1784 const char *new_file, struct Email *e)
1785{
1786 char buf[PATH_MAX] = { 0 };
1787 struct NmMboxData *mdata = nm_mdata_get(m);
1788 if (!mdata || !new_file)
1789 return -1;
1790
1791 if (!old_file && nm_edata_get(e))
1792 {
1793 email_get_fullpath(e, buf, sizeof(buf));
1794 old_file = buf;
1795 }
1796
1797 int rc = rename_filename(m, old_file, new_file, e);
1798
1799 nm_db_release(m);
1800 mdata->mtime.tv_sec = mutt_date_now();
1801 mdata->mtime.tv_nsec = 0;
1802 return rc;
1803}
1804
1808static enum MxStatus nm_mbox_check_stats(struct Mailbox *m, uint8_t flags)
1809{
1810 struct UrlQuery *item = NULL;
1811 struct Url *url = NULL;
1812 const char *db_filename = NULL;
1813 char *db_query = NULL;
1814 notmuch_database_t *db = NULL;
1815 enum MxStatus rc = MX_STATUS_ERROR;
1816 const short c_nm_db_limit = cs_subset_number(NeoMutt->sub, "nm_db_limit");
1817 int limit = c_nm_db_limit;
1818 mutt_debug(LL_DEBUG1, "nm: count\n");
1819
1820 url = url_parse(mailbox_path(m));
1821 if (!url)
1822 {
1823 mutt_error(_("failed to parse notmuch url: %s"), mailbox_path(m));
1824 goto done;
1825 }
1826
1827 STAILQ_FOREACH(item, &url->query_strings, entries)
1828 {
1829 if (item->value && (mutt_str_equal(item->name, "query")))
1830 {
1831 db_query = item->value;
1832 }
1833 else if (item->value && (mutt_str_equal(item->name, "limit")))
1834 {
1835 // Try to parse the limit
1836 if (!mutt_str_atoi_full(item->value, &limit))
1837 {
1838 mutt_error(_("failed to parse limit: %s"), item->value);
1839 goto done;
1840 }
1841 }
1842 }
1843
1844 if (!db_query)
1845 goto done;
1846
1847 db_filename = url->path;
1848 if (!db_filename)
1849 db_filename = nm_db_get_filename(m);
1850
1851 /* don't be verbose about connection, as we're called from
1852 * sidebar/mailbox very often */
1853 db = nm_db_do_open(db_filename, false, false);
1854 if (!db)
1855 goto done;
1856
1857 /* all emails */
1858 m->msg_count = count_query(db, db_query, limit);
1860
1861 // holder variable for extending query to unread/flagged
1862 char *qstr = NULL;
1863
1864 // unread messages
1865 const char *const c_nm_unread_tag = cs_subset_string(NeoMutt->sub, "nm_unread_tag");
1866 mutt_str_asprintf(&qstr, "( %s ) tag:%s", db_query, c_nm_unread_tag);
1867 m->msg_unread = count_query(db, qstr, limit);
1868 FREE(&qstr);
1869
1870 // flagged messages
1871 const char *const c_nm_flagged_tag = cs_subset_string(NeoMutt->sub, "nm_flagged_tag");
1872 mutt_str_asprintf(&qstr, "( %s ) tag:%s", db_query, c_nm_flagged_tag);
1873 m->msg_flagged = count_query(db, qstr, limit);
1874 FREE(&qstr);
1875
1876 rc = (m->msg_new > 0) ? MX_STATUS_NEW_MAIL : MX_STATUS_OK;
1877done:
1878 if (db)
1879 {
1880 nm_db_free(db);
1881 mutt_debug(LL_DEBUG1, "nm: count close DB\n");
1882 }
1883 url_free(&url);
1884
1885 mutt_debug(LL_DEBUG1, "nm: count done [rc=%d]\n", rc);
1886 return rc;
1887}
1888
1893static struct Mailbox *get_default_mailbox(void)
1894{
1895 // Create a new notmuch mailbox from scratch and add plumbing for DB access.
1896 char *default_url = nm_get_default_url();
1897 struct Mailbox *m = mx_path_resolve(default_url);
1898
1899 FREE(&default_url);
1900
1901 // These are no-ops for an initialized mailbox.
1902 init_mailbox(m);
1903 mx_mbox_ac_link(m);
1904
1905 return m;
1906}
1907
1916int nm_record_message(struct Mailbox *m, char *path, struct Email *e)
1917{
1918 notmuch_database_t *db = NULL;
1919 notmuch_status_t st;
1920 notmuch_message_t *msg = NULL;
1921 int rc = -1;
1922
1923 struct NmMboxData *mdata = nm_mdata_get(m);
1924
1925 // If no notmuch data, fall back to the default mailbox.
1926 //
1927 // IMPORTANT: DO NOT FREE THIS MAILBOX. Two reasons:
1928 // 1) If user has default mailbox in config, we'll be removing it. That's not
1929 // good program behavior!
1930 // 2) If not in user's config, keep mailbox around for future nm_record calls.
1931 // It saves NeoMutt from allocating/deallocating repeatedly.
1932 if (!mdata)
1933 {
1934 mutt_debug(LL_DEBUG1, "nm: non-nm mailbox. trying the default nm mailbox.\n");
1935 m = get_default_mailbox();
1936 mdata = nm_mdata_get(m);
1937 }
1938
1939 if (!path || !mdata || (access(path, F_OK) != 0))
1940 return 0;
1941 db = nm_db_get(m, true);
1942 if (!db)
1943 return -1;
1944
1945 mutt_debug(LL_DEBUG1, "nm: record message: %s\n", path);
1946 int trans = nm_db_trans_begin(m);
1947 if (trans < 0)
1948 goto done;
1949
1950#if LIBNOTMUCH_CHECK_VERSION(5, 1, 0)
1951 st = notmuch_database_index_file(db, path, NULL, &msg);
1952#else
1953 st = notmuch_database_add_message(db, path, &msg);
1954#endif
1955
1956 if ((st != NOTMUCH_STATUS_SUCCESS) && (st != NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID))
1957 {
1958 mutt_debug(LL_DEBUG1, "nm: failed to add '%s' [st=%d]\n", path, (int) st);
1959 goto done;
1960 }
1961
1962 if ((st == NOTMUCH_STATUS_SUCCESS) && msg)
1963 {
1964 notmuch_message_maildir_flags_to_tags(msg);
1965 if (e)
1966 {
1967 struct Buffer *tags = buf_pool_get();
1968 driver_tags_get(&e->tags, tags);
1969 update_tags(msg, buf_string(tags));
1970 buf_pool_release(&tags);
1971 }
1972 const char *const c_nm_record_tags = cs_subset_string(NeoMutt->sub, "nm_record_tags");
1973 if (c_nm_record_tags)
1974 update_tags(msg, c_nm_record_tags);
1975 }
1976
1977 rc = 0;
1978done:
1979 if (msg)
1980 notmuch_message_destroy(msg);
1981 if (trans == 1)
1982 nm_db_trans_end(m);
1983
1984 nm_db_release(m);
1985
1986 return rc;
1987}
1988
1999int nm_get_all_tags(struct Mailbox *m, const char **tag_list, int *tag_count)
2000{
2001 struct NmMboxData *mdata = nm_mdata_get(m);
2002 if (!mdata)
2003 return -1;
2004
2005 notmuch_database_t *db = NULL;
2006 notmuch_tags_t *tags = NULL;
2007 const char *tag = NULL;
2008 int rc = -1;
2009
2010 if (!(db = nm_db_get(m, false)) || !(tags = notmuch_database_get_all_tags(db)))
2011 goto done;
2012
2013 *tag_count = 0;
2014 mutt_debug(LL_DEBUG1, "nm: get all tags\n");
2015
2016 while (notmuch_tags_valid(tags))
2017 {
2018 tag = notmuch_tags_get(tags);
2019 /* Skip empty string */
2020 if (*tag)
2021 {
2022 if (tag_list)
2023 tag_list[*tag_count] = mutt_str_dup(tag);
2024 (*tag_count)++;
2025 }
2026 notmuch_tags_move_to_next(tags);
2027 }
2028
2029 rc = 0;
2030done:
2031 if (tags)
2032 notmuch_tags_destroy(tags);
2033
2034 nm_db_release(m);
2035
2036 mutt_debug(LL_DEBUG1, "nm: get all tags done [rc=%d tag_count=%u]\n", rc, *tag_count);
2037 return rc;
2038}
2039
2043static bool nm_ac_owns_path(struct Account *a, const char *path)
2044{
2045 return true;
2046}
2047
2051static bool nm_ac_add(struct Account *a, struct Mailbox *m)
2052{
2053 if (a->adata)
2054 return true;
2055
2056 struct NmAccountData *adata = nm_adata_new();
2057 a->adata = adata;
2059
2060 return true;
2061}
2062
2066static enum MxOpenReturns nm_mbox_open(struct Mailbox *m)
2067{
2068 if (init_mailbox(m) != 0)
2069 return MX_OPEN_ERROR;
2070
2071 struct NmMboxData *mdata = nm_mdata_get(m);
2072 if (!mdata)
2073 return MX_OPEN_ERROR;
2074
2075 mutt_debug(LL_DEBUG1, "nm: reading messages...[current count=%d]\n", m->msg_count);
2076
2077 progress_setup(m);
2078 enum MxOpenReturns rc = MX_OPEN_ERROR;
2079
2080 notmuch_query_t *q = get_query(m, false);
2081 if (q)
2082 {
2083 rc = MX_OPEN_OK;
2084 switch (mdata->query_type)
2085 {
2086 case NM_QUERY_TYPE_UNKNOWN: // UNKNOWN should never occur, but MESGS is default
2088 if (!read_mesgs_query(m, q, false))
2089 rc = MX_OPEN_ABORT;
2090 break;
2092 if (!read_threads_query(m, q, false, get_limit(mdata)))
2093 rc = MX_OPEN_ABORT;
2094 break;
2095 }
2096 notmuch_query_destroy(q);
2097 }
2098
2099 nm_db_release(m);
2100
2101 mdata->mtime.tv_sec = mutt_date_now();
2102 mdata->mtime.tv_nsec = 0;
2103
2104 mdata->oldmsgcount = 0;
2105
2106 mutt_debug(LL_DEBUG1, "nm: reading messages... done [rc=%d, count=%d]\n", rc, m->msg_count);
2107 progress_free(&mdata->progress);
2108 return rc;
2109}
2110
2116static enum MxStatus nm_mbox_check(struct Mailbox *m)
2117{
2118 struct NmMboxData *mdata = nm_mdata_get(m);
2119 time_t mtime = 0;
2120 if (!mdata || (nm_db_get_mtime(m, &mtime) != 0))
2121 return MX_STATUS_ERROR;
2122
2123 int new_flags = 0;
2124 bool occult = false;
2125
2126 if (mdata->mtime.tv_sec >= mtime)
2127 {
2128 mutt_debug(LL_DEBUG2, "nm: check unnecessary (db=%llu mailbox=%llu)\n",
2129 (unsigned long long) mtime, (unsigned long long) mdata->mtime.tv_sec);
2130 return MX_STATUS_OK;
2131 }
2132
2133 mutt_debug(LL_DEBUG1, "nm: checking (db=%llu mailbox=%llu)\n",
2134 (unsigned long long) mtime, (unsigned long long) mdata->mtime.tv_sec);
2135
2136 notmuch_query_t *q = get_query(m, false);
2137 if (!q)
2138 goto done;
2139
2140 mutt_debug(LL_DEBUG1, "nm: start checking (count=%d)\n", m->msg_count);
2141 mdata->oldmsgcount = m->msg_count;
2142
2143 for (int i = 0; i < m->msg_count; i++)
2144 {
2145 struct Email *e = m->emails[i];
2146 if (!e)
2147 break;
2148
2149 e->active = false;
2150 }
2151
2152 int limit = get_limit(mdata);
2153
2154 notmuch_messages_t *msgs = get_messages(q);
2155
2156 // TODO: Analyze impact of removing this version guard.
2157#if LIBNOTMUCH_CHECK_VERSION(5, 0, 0)
2158 if (!msgs)
2159 return MX_STATUS_OK;
2160#elif LIBNOTMUCH_CHECK_VERSION(4, 3, 0)
2161 if (!msgs)
2162 goto done;
2163#endif
2164
2165 struct HeaderCache *hc = nm_hcache_open(m);
2166
2167 for (int i = 0; notmuch_messages_valid(msgs) && ((limit == 0) || (i < limit));
2168 notmuch_messages_move_to_next(msgs), i++)
2169 {
2170 notmuch_message_t *msg = notmuch_messages_get(msgs);
2171 struct Email *e = get_mutt_email(m, msg);
2172
2173 if (!e)
2174 {
2175 /* new email */
2176 append_message(hc, m, msg, false);
2177 notmuch_message_destroy(msg);
2178 continue;
2179 }
2180
2181 /* message already exists, merge flags */
2182 e->active = true;
2183
2184 /* Check to see if the message has moved to a different subdirectory.
2185 * If so, update the associated filename. */
2186 const char *new_file = get_message_last_filename(msg);
2187 char old_file[PATH_MAX] = { 0 };
2188 email_get_fullpath(e, old_file, sizeof(old_file));
2189
2190 if (!mutt_str_equal(old_file, new_file))
2191 update_message_path(e, new_file);
2192
2193 if (!e->changed)
2194 {
2195 /* if the user hasn't modified the flags on this message, update the
2196 * flags we just detected. */
2197 struct Email *e_tmp = maildir_email_new();
2198 maildir_parse_flags(e_tmp, new_file);
2199 e_tmp->old = e->old;
2200 maildir_update_flags(m, e, e_tmp);
2201 email_free(&e_tmp);
2202 }
2203
2204 if (update_email_tags(e, msg) == 0)
2205 new_flags++;
2206
2207 notmuch_message_destroy(msg);
2208 }
2209
2210 nm_hcache_close(&hc);
2211
2212 for (int i = 0; i < m->msg_count; i++)
2213 {
2214 struct Email *e = m->emails[i];
2215 if (!e)
2216 break;
2217
2218 if (!e->active)
2219 {
2220 occult = true;
2221 break;
2222 }
2223 }
2224
2225 if (m->msg_count > mdata->oldmsgcount)
2227done:
2228 if (q)
2229 notmuch_query_destroy(q);
2230
2231 nm_db_release(m);
2232
2233 mdata->mtime.tv_sec = mutt_date_now();
2234 mdata->mtime.tv_nsec = 0;
2235
2236 mutt_debug(LL_DEBUG1, "nm: ... check done [count=%d, new_flags=%d, occult=%d]\n",
2237 m->msg_count, new_flags, occult);
2238
2239 if (occult)
2240 return MX_STATUS_REOPENED;
2241 if (m->msg_count > mdata->oldmsgcount)
2242 return MX_STATUS_NEW_MAIL;
2243 if (new_flags)
2244 return MX_STATUS_FLAGS;
2245 return MX_STATUS_OK;
2246}
2247
2251static enum MxStatus nm_mbox_sync(struct Mailbox *m)
2252{
2253 struct NmMboxData *mdata = nm_mdata_get(m);
2254 if (!mdata)
2255 return MX_STATUS_ERROR;
2256
2257 enum MxStatus rc = MX_STATUS_OK;
2258 struct Progress *progress = NULL;
2259 char *url = mutt_str_dup(mailbox_path(m));
2260 bool changed = false;
2261
2262 mutt_debug(LL_DEBUG1, "nm: sync start\n");
2263
2264 if (m->verbose)
2265 {
2266 /* all is in this function so we don't use data->progress here */
2268 progress_set_message(progress, _("Writing %s..."), mailbox_path(m));
2269 }
2270
2271 struct HeaderCache *hc = nm_hcache_open(m);
2272
2273 int mh_sync_errors = 0;
2274 for (int i = 0; i < m->msg_count; i++)
2275 {
2276 char old_file[PATH_MAX], new_file[PATH_MAX];
2277 struct Email *e = m->emails[i];
2278 if (!e)
2279 break;
2280
2281 struct NmEmailData *edata = nm_edata_get(e);
2282
2283 progress_update(progress, i, -1);
2284
2285 *old_file = '\0';
2286 *new_file = '\0';
2287
2288 if (edata->oldpath)
2289 {
2290 mutt_str_copy(old_file, edata->oldpath, sizeof(old_file));
2291 old_file[sizeof(old_file) - 1] = '\0';
2292 mutt_debug(LL_DEBUG2, "nm: fixing obsolete path '%s'\n", old_file);
2293 }
2294 else
2295 {
2296 email_get_fullpath(e, old_file, sizeof(old_file));
2297 }
2298
2299 buf_strcpy(&m->pathbuf, edata->folder);
2300 m->type = edata->type;
2301
2302 bool ok = maildir_sync_mailbox_message(m, e, hc);
2303 if (!ok)
2304 {
2305 // Syncing file failed, query notmuch for new filepath.
2306 m->type = MUTT_NOTMUCH;
2307 notmuch_database_t *db = nm_db_get(m, true);
2308 if (db)
2309 {
2310 notmuch_message_t *msg = get_nm_message(db, e);
2311
2313
2314 buf_strcpy(&m->pathbuf, edata->folder);
2315 m->type = edata->type;
2316 ok = maildir_sync_mailbox_message(m, e, hc);
2317 m->type = MUTT_NOTMUCH;
2318 }
2319 nm_db_release(m);
2320 m->type = edata->type;
2321 }
2322
2323 buf_strcpy(&m->pathbuf, url);
2324 m->type = MUTT_NOTMUCH;
2325
2326 if (!ok)
2327 {
2328 mh_sync_errors += 1;
2329 continue;
2330 }
2331
2332 if (!e->deleted)
2333 email_get_fullpath(e, new_file, sizeof(new_file));
2334
2335 if (e->deleted || !mutt_str_equal(old_file, new_file))
2336 {
2337 if (e->deleted && (remove_filename(m, old_file) == 0))
2338 changed = true;
2339 else if (*new_file && *old_file && (rename_filename(m, old_file, new_file, e) == 0))
2340 changed = true;
2341 }
2342
2343 FREE(&edata->oldpath);
2344 }
2345
2346 if (mh_sync_errors > 0)
2347 {
2348 mutt_error(ngettext("Unable to sync %d message due to external mailbox modification",
2349 "Unable to sync %d messages due to external mailbox modification",
2350 mh_sync_errors),
2351 mh_sync_errors);
2352 }
2353
2354 buf_strcpy(&m->pathbuf, url);
2355 m->type = MUTT_NOTMUCH;
2356
2357 nm_db_release(m);
2358
2359 if (changed)
2360 {
2361 mdata->mtime.tv_sec = mutt_date_now();
2362 mdata->mtime.tv_nsec = 0;
2363 }
2364
2365 nm_hcache_close(&hc);
2366
2367 progress_free(&progress);
2368 FREE(&url);
2369 mutt_debug(LL_DEBUG1, "nm: .... sync done [rc=%d]\n", rc);
2370 return rc;
2371}
2372
2378static enum MxStatus nm_mbox_close(struct Mailbox *m)
2379{
2380 return MX_STATUS_OK;
2381}
2382
2386static bool nm_msg_open(struct Mailbox *m, struct Message *msg, struct Email *e)
2387{
2388 char path[PATH_MAX] = { 0 };
2389 char *folder = nm_email_get_folder(e);
2390
2391 snprintf(path, sizeof(path), "%s/%s", folder, e->path);
2392
2393 msg->fp = mutt_file_fopen(path, "r");
2394 if (!msg->fp && (errno == ENOENT) && ((m->type == MUTT_MAILDIR) || (m->type == MUTT_NOTMUCH)))
2395 {
2396 msg->fp = maildir_open_find_message(folder, e->path, NULL);
2397 }
2398
2399 return msg->fp != NULL;
2400}
2401
2406static int nm_msg_commit(struct Mailbox *m, struct Message *msg)
2407{
2408 mutt_error(_("Can't write to virtual folder"));
2409 return -1;
2410}
2411
2415static int nm_msg_close(struct Mailbox *m, struct Message *msg)
2416{
2417 mutt_file_fclose(&(msg->fp));
2418 return 0;
2419}
2420
2424static int nm_tags_edit(struct Mailbox *m, const char *tags, struct Buffer *buf)
2425{
2426 buf_reset(buf);
2427 if (mw_get_field("Add/remove labels: ", buf, MUTT_COMP_NO_FLAGS, HC_OTHER,
2428 &CompleteNmTagOps, NULL) != 0)
2429 {
2430 return -1;
2431 }
2432 return 1;
2433}
2434
2438static int nm_tags_commit(struct Mailbox *m, struct Email *e, const char *buf)
2439{
2440 if (*buf == '\0')
2441 return 0; /* no tag change, so nothing to do */
2442
2443 struct NmMboxData *mdata = nm_mdata_get(m);
2444 if (!mdata)
2445 return -1;
2446
2447 notmuch_database_t *db = NULL;
2448 notmuch_message_t *msg = NULL;
2449 int rc = -1;
2450
2451 if (!(db = nm_db_get(m, true)) || !(msg = get_nm_message(db, e)))
2452 goto done;
2453
2454 mutt_debug(LL_DEBUG1, "nm: tags modify: '%s'\n", buf);
2455
2456 update_tags(msg, buf);
2457 update_email_flags(m, e, buf);
2458 update_email_tags(e, msg);
2459 email_set_color(m, e);
2460
2461 rc = 0;
2462 e->changed = true;
2463done:
2464 nm_db_release(m);
2465 if (e->changed)
2466 {
2467 mdata->mtime.tv_sec = mutt_date_now();
2468 mdata->mtime.tv_nsec = 0;
2469 }
2470 mutt_debug(LL_DEBUG1, "nm: tags modify done [rc=%d]\n", rc);
2471 return rc;
2472}
2473
2477enum MailboxType nm_path_probe(const char *path, const struct stat *st)
2478{
2480 return MUTT_UNKNOWN;
2481
2482 return MUTT_NOTMUCH;
2483}
2484
2488static int nm_path_canon(struct Buffer *path)
2489{
2490 return 0;
2491}
2492
2496const struct MxOps MxNotmuchOps = {
2497 // clang-format off
2498 .type = MUTT_NOTMUCH,
2499 .name = "notmuch",
2500 .is_local = false,
2501 .ac_owns_path = nm_ac_owns_path,
2502 .ac_add = nm_ac_add,
2503 .mbox_open = nm_mbox_open,
2504 .mbox_open_append = NULL,
2505 .mbox_check = nm_mbox_check,
2506 .mbox_check_stats = nm_mbox_check_stats,
2507 .mbox_sync = nm_mbox_sync,
2508 .mbox_close = nm_mbox_close,
2509 .msg_open = nm_msg_open,
2510 .msg_open_new = maildir_msg_open_new,
2511 .msg_commit = nm_msg_commit,
2512 .msg_close = nm_msg_close,
2513 .msg_padding_size = NULL,
2514 .msg_save_hcache = NULL,
2515 .tags_edit = nm_tags_edit,
2516 .tags_commit = nm_tags_commit,
2517 .path_probe = nm_path_probe,
2518 .path_canon = nm_path_canon,
2519 .path_is_empty = NULL,
2520 // clang-format on
2521};
#define ARRAY_FOREACH(elem, head)
Iterate over all elements of the array.
Definition: array.h:214
int buf_printf(struct Buffer *buf, const char *fmt,...)
Format a string overwriting a Buffer.
Definition: buffer.c:161
void buf_reset(struct Buffer *buf)
Reset an existing Buffer.
Definition: buffer.c:76
bool buf_is_empty(const struct Buffer *buf)
Is the Buffer empty?
Definition: buffer.c:291
void buf_join_str(struct Buffer *buf, const char *str, char sep)
Join a buffer with a string separated by sep.
Definition: buffer.c:748
bool buf_str_equal(const struct Buffer *a, const struct Buffer *b)
Return if two buffers are equal.
Definition: buffer.c:683
size_t buf_strcpy(struct Buffer *buf, const char *s)
Copy a string into a Buffer.
Definition: buffer.c:395
static const char * buf_string(const struct Buffer *buf)
Convert a buffer to a const char * "string".
Definition: buffer.h:96
Functions to parse commands in a config file.
#define MUTT_NAMED
Definition: commands.h:36
const char * cs_subset_string(const struct ConfigSubset *sub, const char *name)
Get a string config item by name.
Definition: helpers.c:291
short cs_subset_number(const struct ConfigSubset *sub, const char *name)
Get a number config item by name.
Definition: helpers.c:143
const char * cs_subset_path(const struct ConfigSubset *sub, const char *name)
Get a path config item by name.
Definition: helpers.c:168
bool cs_subset_bool(const struct ConfigSubset *sub, const char *name)
Get a boolean config item by name.
Definition: helpers.c:47
Convenience wrapper for the config headers.
const char * cc_maildir_field_delimiter(void)
Get the cached value of $maildir_field_delimiter.
Definition: config_cache.c:131
bool commands_register(struct CommandArray *ca, const struct Command *cmds)
Add commands to Commands array.
Definition: command.c:51
Convenience wrapper for the core headers.
void mailbox_size_add(struct Mailbox *m, const struct Email *e)
Add an email's size to the total size of a Mailbox.
Definition: mailbox.c:249
void mailbox_changed(struct Mailbox *m, enum NotifyMailbox action)
Notify observers of a change to a Mailbox.
Definition: mailbox.c:233
@ NT_MAILBOX_INVALID
Email list was changed.
Definition: mailbox.h:189
static const char * mailbox_path(const struct Mailbox *m)
Get the Mailbox's path string.
Definition: mailbox.h:223
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 email_set_color(struct Mailbox *m, struct Email *e)
Select an Index colour for an Email.
Definition: dlg_index.c:1406
Edit a string.
void mutt_body_free(struct Body **ptr)
Free a Body.
Definition: body.c:58
void email_free(struct Email **ptr)
Free an Email.
Definition: email.c:46
Structs that make up an email.
void mutt_env_free(struct Envelope **ptr)
Free an Envelope.
Definition: envelope.c:126
#define mutt_file_fclose(FP)
Definition: file.h:139
#define mutt_file_fopen(PATH, MODE)
Definition: file.h:138
void mutt_set_flag(struct Mailbox *m, struct Email *e, enum MessageType flag, bool bf, bool upd_mbox)
Set a flag on an email.
Definition: flags.c:57
void nm_adata_free(void **ptr)
Free the private Account data - Implements Account::adata_free() -.
Definition: adata.c:39
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:1465
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:753
int mw_get_field(const char *prompt, struct Buffer *buf, CompletionFlags complete, enum HistoryClass hclass, const struct CompleteOps *comp_api, void *cdata)
Ask the user for a string -.
Definition: window.c:273
#define mutt_error(...)
Definition: logging2.h:93
#define mutt_message(...)
Definition: logging2.h:92
#define mutt_debug(LEVEL,...)
Definition: logging2.h:90
void nm_mdata_free(void **ptr)
Free the private Mailbox data - Implements Mailbox::mdata_free() -.
Definition: mdata.c:45
static bool nm_ac_add(struct Account *a, struct Mailbox *m)
Add a Mailbox to an Account - Implements MxOps::ac_add() -.
Definition: notmuch.c:2051
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:2043
const struct MxOps MxNotmuchOps
Notmuch Mailbox - Implements MxOps -.
Definition: notmuch.c:2496
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:1808
static enum MxStatus nm_mbox_check(struct Mailbox *m)
Check for new mail - Implements MxOps::mbox_check() -.
Definition: notmuch.c:2116
static enum MxStatus nm_mbox_close(struct Mailbox *m)
Close a Mailbox - Implements MxOps::mbox_close() -.
Definition: notmuch.c:2378
static enum MxOpenReturns nm_mbox_open(struct Mailbox *m)
Open a Mailbox - Implements MxOps::mbox_open() -.
Definition: notmuch.c:2066
static enum MxStatus nm_mbox_sync(struct Mailbox *m)
Save changes to the Mailbox - Implements MxOps::mbox_sync() -.
Definition: notmuch.c:2251
static int nm_msg_close(struct Mailbox *m, struct Message *msg)
Close an email - Implements MxOps::msg_close() -.
Definition: notmuch.c:2415
static int nm_msg_commit(struct Mailbox *m, struct Message *msg)
Save changes to an email - Implements MxOps::msg_commit() -.
Definition: notmuch.c:2406
bool maildir_msg_open_new(struct Mailbox *m, struct Message *msg, const struct Email *e)
Open a new message in a Mailbox - Implements MxOps::msg_open_new() -.
Definition: message.c:531
static bool nm_msg_open(struct Mailbox *m, struct Message *msg, struct Email *e)
Open an email message in a Mailbox - Implements MxOps::msg_open() -.
Definition: notmuch.c:2386
static int nm_path_canon(struct Buffer *path)
Canonicalise a Mailbox path - Implements MxOps::path_canon() -.
Definition: notmuch.c:2488
enum MailboxType nm_path_probe(const char *path, const struct stat *st)
Is this a Notmuch Mailbox? - Implements MxOps::path_probe() -.
Definition: notmuch.c:2477
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:2438
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:2424
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
struct HeaderCache * hcache_open(const char *path, const char *folder, hcache_namer_t namer, bool create)
Multiplexor for StoreOps::open.
Definition: hcache.c:471
void hcache_close(struct HeaderCache **ptr)
Multiplexor for StoreOps::close.
Definition: hcache.c:542
struct HCacheEntry hcache_fetch_email(struct HeaderCache *hc, const char *key, size_t keylen, uint32_t uidvalidity)
Multiplexor for StoreOps::fetch.
Definition: hcache.c:562
int hcache_store_email(struct HeaderCache *hc, const char *key, size_t keylen, struct Email *e, uint32_t uidvalidity)
Multiplexor for StoreOps::store.
Definition: hcache.c:670
Header cache multiplexor.
Read/write command history from/to a file.
@ HC_OTHER
Miscellaneous strings.
Definition: lib.h:58
GUI manage the main index (list of emails)
@ LL_DEBUG2
Log at debug level 2.
Definition: logging2.h:45
@ LL_DEBUG1
Log at debug level 1.
Definition: logging2.h:44
bool maildir_parse_message(const char *fname, bool is_old, struct Email *e)
Actually parse a maildir message.
Definition: mailbox.c:194
struct Email * maildir_email_new(void)
Create a Maildir Email.
Definition: mailbox.c:67
bool maildir_parse_stream(FILE *fp, const char *fname, bool is_old, struct Email *e)
Parse a Maildir message.
Definition: mailbox.c:157
void maildir_parse_flags(struct Email *e, const char *path)
Parse Maildir file flags.
Definition: mailbox.c:81
FILE * maildir_open_find_message(const char *folder, const char *msg, char **newname)
Find a message by name.
Definition: message.c:169
bool maildir_sync_mailbox_message(struct Mailbox *m, struct Email *e, struct HeaderCache *hc)
Save changes to the mailbox.
Definition: message.c:311
void maildir_gen_flags(char *dest, size_t destlen, struct Email *e)
Generate the Maildir flags for an email.
Definition: message.c:72
bool maildir_update_flags(struct Mailbox *m, struct Email *e_old, struct Email *e_new)
Update the mailbox flags.
Definition: shared.c:105
Maildir shared functions.
#define FREE(x)
Definition: memory.h:55
#define MUTT_MEM_MALLOC(n, type)
Definition: memory.h:41
time_t mutt_date_now(void)
Return the number of seconds since the Unix epoch.
Definition: date.c:456
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:381
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:254
int mutt_str_asprintf(char **strp, const char *fmt,...)
Definition: string.c:804
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition: string.c:661
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:426
size_t mutt_str_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix.
Definition: string.c:231
size_t mutt_str_len(const char *a)
Calculate the length of a string, safely.
Definition: string.c:497
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:582
size_t mutt_istr_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix, ignoring case.
Definition: string.c:243
char * mutt_str_replace(char **p, const char *s)
Replace one string with another.
Definition: string.c:281
Many unsorted constants and some structs.
#define MUTT_COMP_NO_FLAGS
No flags are set.
Definition: mutt.h:56
@ MUTT_READ
Messages that have been read.
Definition: mutt.h:73
@ MUTT_FLAG
Flagged messages.
Definition: mutt.h:79
@ MUTT_REPLIED
Messages that have been replied to.
Definition: mutt.h:72
#define PATH_MAX
Definition: mutt.h:42
struct HashTable * mutt_make_id_hash(struct Mailbox *m)
Create a Hash Table for message-ids.
Definition: mutt_thread.c:1702
Create/manipulate threading in emails.
void mx_alloc_memory(struct Mailbox *m, int req_size)
Create storage for the emails.
Definition: mx.c:1211
bool mx_mbox_ac_link(struct Mailbox *m)
Link a Mailbox to an existing or new Account.
Definition: mx.c:251
struct Mailbox * mx_path_resolve(const char *path)
Get a Mailbox for a path.
Definition: mx.c:1641
API for mailboxes.
MxOpenReturns
Return values for mbox_open()
Definition: mxapi.h:73
@ MX_OPEN_ERROR
Open failed with an error.
Definition: mxapi.h:75
@ MX_OPEN_ABORT
Open was aborted.
Definition: mxapi.h:76
@ MX_OPEN_OK
Open succeeded.
Definition: mxapi.h:74
MxStatus
Return values from mbox_check(), mbox_check_stats(), mbox_sync(), and mbox_close()
Definition: mxapi.h:60
@ MX_STATUS_ERROR
An error occurred.
Definition: mxapi.h:61
@ MX_STATUS_OK
No changes.
Definition: mxapi.h:62
@ MX_STATUS_FLAGS
Nondestructive flags change (IMAP)
Definition: mxapi.h:66
@ MX_STATUS_REOPENED
Mailbox was reopened.
Definition: mxapi.h:65
@ MX_STATUS_NEW_MAIL
New mail received in Mailbox.
Definition: mxapi.h:63
struct NmAccountData * nm_adata_new(void)
Allocate and initialise a new NmAccountData structure.
Definition: adata.c:58
const struct CompleteOps CompleteNmTagOps
Auto-Completion of NmTags.
Definition: complete.c:254
notmuch_database_t * nm_db_get(struct Mailbox *m, bool writable)
Get the Notmuch database.
Definition: db.c:209
int nm_db_trans_begin(struct Mailbox *m)
Start a Notmuch database transaction.
Definition: db.c:266
notmuch_database_t * nm_db_do_open(const char *filename, bool writable, bool verbose)
Open a Notmuch database.
Definition: db.c:115
const char * nm_db_get_filename(struct Mailbox *m)
Get the filename of the Notmuch database.
Definition: db.c:58
int nm_db_get_mtime(struct Mailbox *m, time_t *mtime)
Get the database modification time.
Definition: db.c:315
int nm_db_release(struct Mailbox *m)
Close the Notmuch database.
Definition: db.c:233
void nm_db_free(notmuch_database_t *db)
Decoupled way to close a Notmuch database.
Definition: db.c:250
int nm_db_trans_end(struct Mailbox *m)
End a database transaction.
Definition: db.c:288
struct NmEmailData * nm_edata_get(struct Email *e)
Get the Notmuch Email data.
Definition: edata.c:72
struct NmEmailData * nm_edata_new(void)
Create a new NmEmailData for an email.
Definition: edata.c:61
struct NmMboxData * nm_mdata_new(const char *url)
Create a new NmMboxData object from a query.
Definition: mdata.c:68
struct NmMboxData * nm_mdata_get(struct Mailbox *m)
Get the Notmuch Mailbox data.
Definition: mdata.c:96
Notmuch-specific Mailbox data.
static notmuch_threads_t * get_threads(notmuch_query_t *query)
Load threads for a query.
Definition: notmuch.c:1002
static char * get_query_string(struct NmMboxData *mdata, bool window)
Builds the notmuch vfolder search string.
Definition: notmuch.c:347
static char * nm_get_default_url(void)
Create a Mailbox with default Notmuch settings.
Definition: notmuch.c:145
int nm_update_filename(struct Mailbox *m, const char *old_file, const char *new_file, struct Email *e)
Change the filename.
Definition: notmuch.c:1783
void nm_query_window_reset(void)
Resets the vfolder window position to the present.
Definition: notmuch.c:1706
static char * email_get_id(struct Email *e)
Get the unique Notmuch Id.
Definition: notmuch.c:222
static void apply_exclude_tags(notmuch_query_t *query)
Exclude the configured tags.
Definition: notmuch.c:424
static char * email_get_fullpath(struct Email *e, char *buf, size_t buflen)
Get the full path of an email.
Definition: notmuch.c:238
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:283
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:1999
static unsigned int count_query(notmuch_database_t *db, const char *qstr, int limit)
Count the results of a query.
Definition: notmuch.c:1456
static int init_mailbox(struct Mailbox *m)
Add Notmuch data to the Mailbox.
Definition: notmuch.c:200
static int rename_maildir_filename(const char *old, char *buf, size_t buflen, struct Email *e)
Rename a Maildir file.
Definition: notmuch.c:1231
static void sync_email_path_with_nm(struct Email *e, notmuch_message_t *msg)
Synchronize Neomutt's Email path with notmuch.
Definition: notmuch.c:1108
static notmuch_message_t * get_nm_message(notmuch_database_t *db, struct Email *e)
Find a Notmuch message.
Definition: notmuch.c:1067
static int init_email(struct Email *e, const char *path, notmuch_message_t *msg)
Set up an email's Notmuch data.
Definition: notmuch.c:637
static int get_limit(struct NmMboxData *mdata)
Get the database limit.
Definition: notmuch.c:415
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:1508
static bool read_threads_query(struct Mailbox *m, notmuch_query_t *q, bool dedup, int limit)
Perform a query with threads.
Definition: notmuch.c:1030
static struct Mailbox * get_default_mailbox(void)
Get Mailbox for notmuch without any parameters.
Definition: notmuch.c:1893
int nm_record_message(struct Mailbox *m, char *path, struct Email *e)
Add a message to the Notmuch database.
Definition: notmuch.c:1916
static const struct Command NmCommands[]
Notmuch Commands.
Definition: notmuch.c:93
static struct NmMboxData * nm_get_default_data(void)
Create a Mailbox with default Notmuch settings.
Definition: notmuch.c:177
int nm_read_entire_thread(struct Mailbox *m, struct Email *e)
Get the entire thread of an email.
Definition: notmuch.c:1534
static void append_thread(struct HeaderCache *hc, struct Mailbox *m, notmuch_query_t *q, notmuch_thread_t *thread, bool dedup)
Add each top level reply in the thread.
Definition: notmuch.c:908
static void query_window_reset(void)
Restore vfolder's search window to its original position.
Definition: notmuch.c:253
const int NmUrlProtocolLen
Length of NmUrlProtocol string.
Definition: notmuch.c:104
static int rename_filename(struct Mailbox *m, const char *old_file, const char *new_file, struct Email *e)
Rename the file.
Definition: notmuch.c:1351
void nm_query_window_backward(void)
Function to move the current search window backward in time.
Definition: notmuch.c:1695
static struct HeaderCache * nm_hcache_open(struct Mailbox *m)
Open a header cache.
Definition: notmuch.c:119
static bool nm_message_has_tag(notmuch_message_t *msg, char *tag)
Does a message have this tag?
Definition: notmuch.c:1086
static notmuch_query_t * get_query(struct Mailbox *m, bool writable)
Create a new query.
Definition: notmuch.c:450
static int update_message_path(struct Email *e, const char *path)
Set the path for a message.
Definition: notmuch.c:534
bool nm_query_window_available(void)
Are windowed queries enabled for use?
Definition: notmuch.c:1658
static int update_tags(notmuch_message_t *msg, const char *tag_str)
Update the tags on a message.
Definition: notmuch.c:1125
static bool read_mesgs_query(struct Mailbox *m, notmuch_query_t *q, bool dedup)
Search for matching messages.
Definition: notmuch.c:960
static const char * get_message_last_filename(notmuch_message_t *msg)
Get a message's last filename.
Definition: notmuch.c:681
static void append_replies(struct HeaderCache *hc, struct Mailbox *m, notmuch_query_t *q, notmuch_message_t *top, bool dedup)
Add all the replies to a given messages into the display.
Definition: notmuch.c:881
char * nm_url_from_query(struct Mailbox *m, char *buf, size_t buflen)
Turn a query into a URL.
Definition: notmuch.c:1599
static char * get_folder_from_path(const char *path)
Find an email's folder from its path.
Definition: notmuch.c:594
void nm_init(void)
Setup feature commands.
Definition: notmuch.c:109
static char * nm2mutt_message_id(const char *id)
Converts notmuch message Id to neomutt message Id.
Definition: notmuch.c:619
char * nm_email_get_folder(struct Email *e)
Get the folder for a Email.
Definition: notmuch.c:1489
static void append_message(struct HeaderCache *hc, struct Mailbox *m, notmuch_message_t *msg, bool dedup)
Associate a message.
Definition: notmuch.c:768
static int update_email_tags(struct Email *e, notmuch_message_t *msg)
Update the Email's tags from Notmuch.
Definition: notmuch.c:482
bool nm_message_is_still_queried(struct Mailbox *m, struct Email *e)
Is a message still visible in the query?
Definition: notmuch.c:1718
static int update_email_flags(struct Mailbox *m, struct Email *e, const char *tag_str)
Update the Email's flags.
Definition: notmuch.c:1179
static int remove_filename(struct Mailbox *m, const char *path)
Delete a file.
Definition: notmuch.c:1287
static void progress_setup(struct Mailbox *m)
Set up the Progress Bar.
Definition: notmuch.c:698
static void nm_hcache_close(struct HeaderCache **ptr)
Close the header cache.
Definition: notmuch.c:133
void nm_query_window_forward(void)
Function to move the current search window forward in time.
Definition: notmuch.c:1675
static notmuch_messages_t * get_messages(notmuch_query_t *query)
Load messages for a query.
Definition: notmuch.c:932
const char NmUrlProtocol[]
Protocol string for Notmuch URLs.
Definition: notmuch.c:102
static void nm_progress_update(struct Mailbox *m)
Update the progress counter.
Definition: notmuch.c:717
static struct Email * get_mutt_email(struct Mailbox *m, notmuch_message_t *msg)
Get the Email of a Notmuch message.
Definition: notmuch.c:734
struct Buffer * buf_pool_get(void)
Get a Buffer from the pool.
Definition: pool.c:82
void buf_pool_release(struct Buffer **ptr)
Return a Buffer to the pool.
Definition: pool.c:96
Pop-specific Account data.
Pop-specific Email data.
Progress Bar.
@ MUTT_PROGRESS_READ
Progress tracks elements, according to $read_inc
Definition: lib.h:83
@ MUTT_PROGRESS_WRITE
Progress tracks elements, according to $write_inc
Definition: lib.h:84
struct Progress * progress_new(enum ProgressType type, size_t size)
Create a new Progress Bar.
Definition: progress.c:139
void progress_free(struct Progress **ptr)
Free a Progress Bar.
Definition: progress.c:110
void progress_set_message(struct Progress *progress, const char *fmt,...) __attribute__((__format__(__printf__
bool progress_update(struct Progress *progress, size_t pos, int percent)
Update the state of the progress bar.
Definition: progress.c:80
Prototypes for many functions.
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:206
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:390
GUI display the mailboxes in a side panel.
#define ASSERT(COND)
Definition: signal2.h:60
volatile sig_atomic_t SigInt
true after SIGINT is received
Definition: signal.c:69
Key value store.
A group of associated Mailboxes.
Definition: account.h:36
void(* adata_free)(void **ptr)
Definition: account.h:53
void * adata
Private data (for Mailbox backends)
Definition: account.h:42
String manipulation buffer.
Definition: buffer.h:36
The envelope/body of an email.
Definition: email.h:39
bool read
Email is read.
Definition: email.h:50
struct Envelope * env
Envelope information.
Definition: email.h:68
void * edata
Driver-specific data.
Definition: email.h:74
struct Body * body
List of MIME parts.
Definition: email.h:69
bool active
Message is not to be removed.
Definition: email.h:76
void * nm_edata
Notmuch private data.
Definition: email.h:93
bool old
Email is seen, but unread.
Definition: email.h:49
bool changed
Email has been edited.
Definition: email.h:77
struct TagList tags
For drivers that support server tagging.
Definition: email.h:72
char * path
Path of Email (for local Mailboxes)
Definition: email.h:70
bool deleted
Email is deleted.
Definition: email.h:78
int index
The absolute (unsorted) message number.
Definition: email.h:110
struct MuttThread * thread
Thread of Emails.
Definition: email.h:119
char * message_id
Message ID.
Definition: envelope.h:73
struct Email * email
Retrieved email.
Definition: lib.h:102
Header Cache.
Definition: lib.h:86
A mailbox.
Definition: mailbox.h:79
void(* mdata_free)(void **ptr)
Definition: mailbox.h:143
int msg_new
Number of new messages.
Definition: mailbox.h:92
int msg_count
Total number of messages.
Definition: mailbox.h:88
enum MailboxType type
Mailbox type.
Definition: mailbox.h:102
void * mdata
Driver specific data.
Definition: mailbox.h:132
struct Email ** emails
Array of Emails.
Definition: mailbox.h:96
struct HashTable * id_hash
Hash Table: "message-id" -> Email.
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:117
int msg_unread
Number of unread messages.
Definition: mailbox.h:89
A local copy of an email.
Definition: message.h:34
FILE * fp
pointer to the message data
Definition: message.h:35
Definition: mxapi.h:88
enum MailboxType type
Mailbox type, e.g. MUTT_IMAP.
Definition: mxapi.h:89
Container for Accounts, Notifications.
Definition: neomutt.h:43
struct CommandArray commands
NeoMutt commands.
Definition: neomutt.h:51
struct ConfigSubset * sub
Inherited config items.
Definition: neomutt.h:47
Notmuch-specific Account data -.
Definition: adata.h:35
Notmuch-specific Email data -.
Definition: edata.h:34
char * folder
Location of the Email.
Definition: edata.h:35
char * virtual_id
Unique Notmuch Id.
Definition: edata.h:37
Notmuch-specific Mailbox data -.
Definition: mdata.h:35
struct Url * db_url
Parsed view url of the Notmuch database.
Definition: mdata.h:36
int oldmsgcount
Definition: mdata.h:42
struct Progress * progress
A progress bar.
Definition: mdata.h:41
struct timespec mtime
Time Mailbox was last changed.
Definition: mdata.h:44
enum NmQueryType query_type
Messages or Threads.
Definition: mdata.h:39
int db_limit
Maximum number of results to return.
Definition: mdata.h:38
char * db_query
Previous query.
Definition: mdata.h:37
int ignmsgcount
Ignored messages.
Definition: mdata.h:43
Array of Notmuch tags.
Definition: tag.h:38
struct TagArray tags
Tags.
Definition: tag.h:39
char * tag_str
Source string.
Definition: tag.h:40
Parsed Query String.
Definition: url.h:58
char * name
Query name.
Definition: url.h:59
char * value
Query value.
Definition: url.h:60
A parsed URL proto://user:password@host:port/path?a=1&b=2
Definition: url.h:69
struct UrlQueryList query_strings
List of query strings.
Definition: url.h:76
char * path
Path.
Definition: url.h:75
int cs_subset_str_native_set(const struct ConfigSubset *sub, const char *name, intptr_t value, struct Buffer *err)
Natively set the value of a string config item.
Definition: subset.c:299
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:388
void nm_tag_array_free(struct NmTags *tags)
Free all memory of a NmTags.
Definition: tag.c:40
struct NmTags nm_tag_str_to_tags(const char *tag_str)
Converts a comma and/or space-delimited string of tags into an array.
Definition: tag.c:51
Notmuch tag functions.
bool driver_tags_replace(struct TagList *tl, const char *tags)
Replace all tags.
Definition: tags.c:201
void driver_tags_get(struct TagList *tl, struct Buffer *tags)
Get tags all tags separated by space.
Definition: tags.c:164
void driver_tags_get_transformed(struct TagList *tl, struct Buffer *tags)
Get transformed tags separated by space.
Definition: tags.c:152
struct Url * url_parse(const char *src)
Fill in Url.
Definition: url.c:239
void url_free(struct Url **ptr)
Free the contents of a URL.
Definition: url.c:124
void url_pct_encode(char *buf, size_t buflen, const char *src)
Percent-encode a string.
Definition: url.c:152