NeoMutt  2024-11-14-138-ge5ca67
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 // clang-format on
98};
99
101const char NmUrlProtocol[] = "notmuch://";
103const int NmUrlProtocolLen = sizeof(NmUrlProtocol) - 1;
104
108void nm_init(void)
109{
111}
112
118static struct HeaderCache *nm_hcache_open(struct Mailbox *m)
119{
120#ifdef USE_HCACHE
121 const char *const c_header_cache = cs_subset_path(NeoMutt->sub, "header_cache");
122 return hcache_open(c_header_cache, mailbox_path(m), NULL, true);
123#else
124 return NULL;
125#endif
126}
127
132static void nm_hcache_close(struct HeaderCache **ptr)
133{
134#ifdef USE_HCACHE
135 hcache_close(ptr);
136#endif
137}
138
144static char *nm_get_default_url(void)
145{
146 // path to DB + query + url "decoration"
147 size_t len = PATH_MAX + 1024 + 32;
148 char *url = MUTT_MEM_MALLOC(len, char);
149
150 // Try to use `$nm_default_url` or `$folder`.
151 // If neither are set, it is impossible to create a Notmuch URL.
152 const char *const c_nm_default_url = cs_subset_string(NeoMutt->sub, "nm_default_url");
153 const char *const c_folder = cs_subset_string(NeoMutt->sub, "folder");
154 if (c_nm_default_url)
155 {
156 snprintf(url, len, "%s", c_nm_default_url);
157 }
158 else if (c_folder)
159 {
160 snprintf(url, len, "notmuch://%s", c_folder);
161 }
162 else
163 {
164 FREE(&url);
165 return NULL;
166 }
167
168 return url;
169}
170
176static struct NmMboxData *nm_get_default_data(void)
177{
178 // path to DB + query + url "decoration"
179 char *url = nm_get_default_url();
180 if (!url)
181 return NULL;
182
183 struct NmMboxData *default_data = nm_mdata_new(url);
184 FREE(&url);
185
186 return default_data;
187}
188
199static int init_mailbox(struct Mailbox *m)
200{
201 if (!m || (m->type != MUTT_NOTMUCH))
202 return -1;
203
204 if (m->mdata)
205 return 0;
206
208 if (!m->mdata)
209 return -1;
210
212 return 0;
213}
214
221static char *email_get_id(struct Email *e)
222{
223 struct NmEmailData *edata = nm_edata_get(e);
224 if (!edata)
225 return NULL;
226
227 return edata->virtual_id;
228}
229
237static char *email_get_fullpath(struct Email *e, char *buf, size_t buflen)
238{
239 snprintf(buf, buflen, "%s/%s", nm_email_get_folder(e), e->path);
240 return buf;
241}
242
252static void query_window_reset(void)
253{
254 mutt_debug(LL_DEBUG2, "entering\n");
255 cs_subset_str_native_set(NeoMutt->sub, "nm_query_window_current_position", 0, NULL);
256}
257
282static bool windowed_query_from_query(const char *query, char *buf, size_t buflen)
283{
284 mutt_debug(LL_DEBUG2, "nm: %s\n", query);
285
286 const bool c_nm_query_window_enable = cs_subset_bool(NeoMutt->sub, "nm_query_window_enable");
287 const short c_nm_query_window_duration = cs_subset_number(NeoMutt->sub, "nm_query_window_duration");
288 const short c_nm_query_window_current_position = cs_subset_number(NeoMutt->sub, "nm_query_window_current_position");
289 const char *const c_nm_query_window_current_search = cs_subset_string(NeoMutt->sub, "nm_query_window_current_search");
290 const char *const c_nm_query_window_timebase = cs_subset_string(NeoMutt->sub, "nm_query_window_timebase");
291 const char *const c_nm_query_window_or_terms = cs_subset_string(NeoMutt->sub, "nm_query_window_or_terms");
292
293 /* if the query has changed, reset the window position */
294 if (!c_nm_query_window_current_search || !mutt_str_equal(query, c_nm_query_window_current_search))
295 {
297 }
298
300 buf, buflen, c_nm_query_window_enable, c_nm_query_window_duration,
301 c_nm_query_window_current_position, c_nm_query_window_current_search,
302 c_nm_query_window_timebase, c_nm_query_window_or_terms);
303
304 switch (rc)
305 {
307 {
308 mutt_debug(LL_DEBUG2, "nm: %s -> %s\n", query, buf);
309 break;
310 }
312 {
314 return false;
315 }
317 {
319 // L10N: The values 'hour', 'day', 'week', 'month', 'year' are literal.
320 // They should not be translated.
321 _("Invalid nm_query_window_timebase value (valid values are: hour, day, week, month, year)"));
322 mutt_debug(LL_DEBUG2, "Invalid nm_query_window_timebase value\n");
323 return false;
324 }
325 }
326
327 return true;
328}
329
346static char *get_query_string(struct NmMboxData *mdata, bool window)
347{
348 mutt_debug(LL_DEBUG2, "nm: %s\n", window ? "true" : "false");
349
350 if (!mdata)
351 return NULL;
352 if (mdata->db_query && !window)
353 return mdata->db_query;
354
355 const char *const c_nm_query_type = cs_subset_string(NeoMutt->sub, "nm_query_type");
356 mdata->query_type = nm_string_to_query_type(c_nm_query_type); /* user's default */
357
358 struct UrlQuery *item = NULL;
359 STAILQ_FOREACH(item, &mdata->db_url->query_strings, entries)
360 {
361 if (!item->value || !item->name)
362 continue;
363
364 if (mutt_str_equal(item->name, "limit"))
365 {
366 if (!mutt_str_atoi_full(item->value, &mdata->db_limit))
367 {
368 mutt_error(_("failed to parse notmuch limit: %s"), item->value);
369 }
370 }
371 else if (mutt_str_equal(item->name, "type"))
372 {
374 }
375 else if (mutt_str_equal(item->name, "query"))
376 {
377 mutt_str_replace(&mdata->db_query, item->value);
378 }
379 }
380
381 if (!mdata->db_query)
382 return NULL;
383
384 if (window)
385 {
386 char buf[1024] = { 0 };
387 cs_subset_str_string_set(NeoMutt->sub, "nm_query_window_current_search",
388 mdata->db_query, NULL);
389
390 /* if a date part is defined, do not apply windows (to avoid the risk of
391 * having a non-intersected date frame). A good improvement would be to
392 * accept if they intersect */
393 if (!strstr(mdata->db_query, "date:") &&
394 windowed_query_from_query(mdata->db_query, buf, sizeof(buf)))
395 {
396 mutt_str_replace(&mdata->db_query, buf);
397 }
398
399 mutt_debug(LL_DEBUG2, "nm: query (windowed) '%s'\n", mdata->db_query);
400 }
401 else
402 {
403 mutt_debug(LL_DEBUG2, "nm: query '%s'\n", mdata->db_query);
404 }
405
406 return mdata->db_query;
407}
408
414static int get_limit(struct NmMboxData *mdata)
415{
416 return mdata ? mdata->db_limit : 0;
417}
418
423static void apply_exclude_tags(notmuch_query_t *query)
424{
425 const char *const c_nm_exclude_tags = cs_subset_string(NeoMutt->sub, "nm_exclude_tags");
426 if (!c_nm_exclude_tags || !query)
427 return;
428
429 struct NmTags tags = nm_tag_str_to_tags(c_nm_exclude_tags);
430
431 char **tag = NULL;
432 ARRAY_FOREACH(tag, &tags.tags)
433 {
434 mutt_debug(LL_DEBUG2, "nm: query exclude tag '%s'\n", *tag);
435 notmuch_query_add_tag_exclude(query, *tag);
436 }
437
438 notmuch_query_set_omit_excluded(query, 1);
440}
441
449static notmuch_query_t *get_query(struct Mailbox *m, bool writable)
450{
451 struct NmMboxData *mdata = nm_mdata_get(m);
452 if (!mdata)
453 return NULL;
454
455 notmuch_database_t *db = nm_db_get(m, writable);
456 const char *str = get_query_string(mdata, true);
457
458 if (!db || !str)
459 goto err;
460
461 notmuch_query_t *q = notmuch_query_create(db, str);
462 if (!q)
463 goto err;
464
466 notmuch_query_set_sort(q, NOTMUCH_SORT_NEWEST_FIRST);
467 mutt_debug(LL_DEBUG2, "nm: query successfully initialized (%s)\n", str);
468 return q;
469err:
470 nm_db_release(m);
471 return NULL;
472}
473
481static int update_email_tags(struct Email *e, notmuch_message_t *msg)
482{
483 struct NmEmailData *edata = nm_edata_get(e);
484 struct Buffer *new_tags = buf_pool_get();
485 struct Buffer *old_tags = buf_pool_get();
486
487 mutt_debug(LL_DEBUG2, "nm: tags update requested (%s)\n", edata->virtual_id);
488
489 for (notmuch_tags_t *tags = notmuch_message_get_tags(msg);
490 tags && notmuch_tags_valid(tags); notmuch_tags_move_to_next(tags))
491 {
492 const char *t = notmuch_tags_get(tags);
493 if (!t || (*t == '\0'))
494 continue;
495
496 buf_join_str(new_tags, t, ' ');
497 }
498
499 driver_tags_get(&e->tags, old_tags);
500
501 if (!buf_is_empty(new_tags) && !buf_is_empty(old_tags) &&
502 (buf_str_equal(old_tags, new_tags)))
503 {
504 buf_pool_release(&new_tags);
505 buf_pool_release(&old_tags);
506 mutt_debug(LL_DEBUG2, "nm: tags unchanged\n");
507 return 1;
508 }
509 buf_pool_release(&old_tags);
510
511 /* new version */
512 driver_tags_replace(&e->tags, buf_string(new_tags));
513 buf_reset(new_tags);
514
515 driver_tags_get_transformed(&e->tags, new_tags);
516 mutt_debug(LL_DEBUG2, "nm: new tags transformed: '%s'\n", buf_string(new_tags));
517 buf_reset(new_tags);
518
519 driver_tags_get(&e->tags, new_tags);
520 mutt_debug(LL_DEBUG2, "nm: new tag: '%s'\n", buf_string(new_tags));
521 buf_pool_release(&new_tags);
522
523 return 0;
524}
525
533static int update_message_path(struct Email *e, const char *path)
534{
535 struct NmEmailData *edata = nm_edata_get(e);
536
537 mutt_debug(LL_DEBUG2, "nm: path update requested path=%s, (%s)\n", path, edata->virtual_id);
538
539 char *p = strrchr(path, '/');
540 if (p && ((p - path) > 3) &&
541 (mutt_strn_equal(p - 3, "cur", 3) || mutt_strn_equal(p - 3, "new", 3) ||
542 mutt_strn_equal(p - 3, "tmp", 3)))
543 {
544 edata->type = MUTT_MAILDIR;
545
546 FREE(&e->path);
547 FREE(&edata->folder);
548
549 p -= 3; /* skip subfolder (e.g. "new") */
550 if (cs_subset_bool(NeoMutt->sub, "mark_old"))
551 {
552 e->old = mutt_str_startswith(p, "cur");
553 }
554 e->path = mutt_str_dup(p);
555
556 for (; (p > path) && (*(p - 1) == '/'); p--)
557 ; // do nothing
558
559 edata->folder = mutt_strn_dup(path, p - path);
560
561 mutt_debug(LL_DEBUG2, "nm: folder='%s', file='%s'\n", edata->folder, e->path);
562 return 0;
563 }
564
565 return 1;
566}
567
574static char *get_folder_from_path(const char *path)
575{
576 char *p = strrchr(path, '/');
577
578 if (p && ((p - path) > 3) &&
579 (mutt_strn_equal(p - 3, "cur", 3) || mutt_strn_equal(p - 3, "new", 3) ||
580 mutt_strn_equal(p - 3, "tmp", 3)))
581 {
582 p -= 3;
583 for (; (p > path) && (*(p - 1) == '/'); p--)
584 ; // do nothing
585
586 return mutt_strn_dup(path, p - path);
587 }
588
589 return NULL;
590}
591
599static char *nm2mutt_message_id(const char *id)
600{
601 if (!id)
602 return NULL;
603
604 char *mid = NULL;
605 mutt_str_asprintf(&mid, "<%s>", id);
606 return mid;
607}
608
617static int init_email(struct Email *e, const char *path, notmuch_message_t *msg)
618{
619 if (nm_edata_get(e))
620 return 0;
621
622 struct NmEmailData *edata = nm_edata_new();
623 e->nm_edata = edata;
624
625 /* Notmuch ensures that message Id exists (if not notmuch Notmuch will
626 * generate an ID), so it's more safe than use neomutt Email->env->id */
627 const char *id = notmuch_message_get_message_id(msg);
628 edata->virtual_id = mutt_str_dup(id);
629
630 mutt_debug(LL_DEBUG2, "nm: [e=%p, edata=%p] (%s)\n", (void *) e, (void *) edata, id);
631
632 char *nm_msg_id = nm2mutt_message_id(id);
633 if (!e->env->message_id)
634 {
635 e->env->message_id = nm_msg_id;
636 }
637 else if (!mutt_str_equal(e->env->message_id, nm_msg_id))
638 {
639 FREE(&e->env->message_id);
640 e->env->message_id = nm_msg_id;
641 }
642 else
643 {
644 FREE(&nm_msg_id);
645 }
646
647 if (update_message_path(e, path) != 0)
648 return -1;
649
650 update_email_tags(e, msg);
651
652 return 0;
653}
654
661static const char *get_message_last_filename(notmuch_message_t *msg)
662{
663 const char *name = NULL;
664
665 for (notmuch_filenames_t *ls = notmuch_message_get_filenames(msg);
666 ls && notmuch_filenames_valid(ls); notmuch_filenames_move_to_next(ls))
667 {
668 name = notmuch_filenames_get(ls);
669 }
670
671 return name;
672}
673
678static void progress_setup(struct Mailbox *m)
679{
680 if (!m->verbose)
681 return;
682
683 struct NmMboxData *mdata = nm_mdata_get(m);
684 if (!mdata)
685 return;
686
687 mdata->oldmsgcount = m->msg_count;
688 mdata->ignmsgcount = 0;
689 mdata->progress = progress_new(MUTT_PROGRESS_READ, mdata->oldmsgcount);
690 progress_set_message(mdata->progress, _("Reading messages..."));
691}
692
697static void nm_progress_update(struct Mailbox *m)
698{
699 struct NmMboxData *mdata = nm_mdata_get(m);
700
701 if (!m->verbose || !mdata || !mdata->progress)
702 return;
703
704 progress_update(mdata->progress, m->msg_count + mdata->ignmsgcount, -1);
705}
706
714static struct Email *get_mutt_email(struct Mailbox *m, notmuch_message_t *msg)
715{
716 if (!m || !msg)
717 return NULL;
718
719 const char *id = notmuch_message_get_message_id(msg);
720 if (!id)
721 return NULL;
722
723 mutt_debug(LL_DEBUG2, "nm: neomutt email, id='%s'\n", id);
724
725 if (!m->id_hash)
726 {
727 mutt_debug(LL_DEBUG2, "nm: init hash\n");
729 if (!m->id_hash)
730 return NULL;
731 }
732
733 char *mid = nm2mutt_message_id(id);
734 mutt_debug(LL_DEBUG2, "nm: neomutt id='%s'\n", mid);
735
736 struct Email *e = mutt_hash_find(m->id_hash, mid);
737 FREE(&mid);
738 return e;
739}
740
748static void append_message(struct HeaderCache *hc, struct Mailbox *m,
749 notmuch_message_t *msg, bool dedup)
750{
751 struct NmMboxData *mdata = nm_mdata_get(m);
752 if (!mdata)
753 return;
754
755 char *newpath = NULL;
756 struct Email *e = NULL;
757
758 /* deduplicate */
759 if (dedup && get_mutt_email(m, msg))
760 {
761 mdata->ignmsgcount++;
763 mutt_debug(LL_DEBUG2, "nm: ignore id=%s, already in the m\n",
764 notmuch_message_get_message_id(msg));
765 return;
766 }
767
768 const char *path = get_message_last_filename(msg);
769 if (!path)
770 return;
771
772 mutt_debug(LL_DEBUG2, "nm: appending message, i=%d, id=%s, path=%s\n",
773 m->msg_count, notmuch_message_get_message_id(msg), path);
774
776
777#ifdef USE_HCACHE
779 if (!e)
780#endif
781 {
782 if (access(path, F_OK) == 0)
783 {
784 /* We pass is_old=false as argument here, but e->old will be updated later
785 * by update_message_path() (called by init_email() below). */
786 e = maildir_email_new();
787 if (!maildir_parse_message(path, false, e))
788 email_free(&e);
789 }
790 else
791 {
792 /* maybe moved try find it... */
793 char *folder = get_folder_from_path(path);
794
795 if (folder)
796 {
797 FILE *fp = maildir_open_find_message(folder, path, &newpath);
798 if (fp)
799 {
800 e = maildir_email_new();
801 if (!maildir_parse_stream(fp, newpath, false, e))
802 email_free(&e);
803 mutt_file_fclose(&fp);
804
805 mutt_debug(LL_DEBUG1, "nm: not up-to-date: %s -> %s\n", path, newpath);
806 }
807 }
808 FREE(&folder);
809 }
810
811 if (!e)
812 {
813 mutt_debug(LL_DEBUG1, "nm: failed to parse message: %s\n", path);
814 goto done;
815 }
816
817#ifdef USE_HCACHE
818 hcache_store_email(hc, newpath ? newpath : path,
819 mutt_str_len(newpath ? newpath : path), e, 0);
820#endif
821 }
822
823 if (init_email(e, newpath ? newpath : path, msg) != 0)
824 {
825 email_free(&e);
826 mutt_debug(LL_DEBUG1, "nm: failed to append email!\n");
827 goto done;
828 }
829
830 e->active = true;
831 e->index = m->msg_count;
832 mailbox_size_add(m, e);
833 m->emails[m->msg_count] = e;
834 m->msg_count++;
835
836 if (newpath)
837 {
838 /* remember that file has been moved -- nm_mbox_sync() will update the DB */
839 struct NmEmailData *edata = nm_edata_get(e);
840 if (edata)
841 {
842 mutt_debug(LL_DEBUG1, "nm: remember obsolete path: %s\n", path);
843 edata->oldpath = mutt_str_dup(path);
844 }
845 }
847done:
848 FREE(&newpath);
849}
850
861static void append_replies(struct HeaderCache *hc, struct Mailbox *m,
862 notmuch_query_t *q, notmuch_message_t *top, bool dedup)
863{
864 notmuch_messages_t *msgs = NULL;
865
866 for (msgs = notmuch_message_get_replies(top); notmuch_messages_valid(msgs);
867 notmuch_messages_move_to_next(msgs))
868 {
869 notmuch_message_t *nm = notmuch_messages_get(msgs);
870 append_message(hc, m, nm, dedup);
871 /* recurse through all the replies to this message too */
872 append_replies(hc, m, q, nm, dedup);
873 notmuch_message_destroy(nm);
874 }
875}
876
888static void append_thread(struct HeaderCache *hc, struct Mailbox *m,
889 notmuch_query_t *q, notmuch_thread_t *thread, bool dedup)
890{
891 notmuch_messages_t *msgs = NULL;
892
893 for (msgs = notmuch_thread_get_toplevel_messages(thread);
894 notmuch_messages_valid(msgs); notmuch_messages_move_to_next(msgs))
895 {
896 notmuch_message_t *nm = notmuch_messages_get(msgs);
897 append_message(hc, m, nm, dedup);
898 append_replies(hc, m, q, nm, dedup);
899 notmuch_message_destroy(nm);
900 }
901}
902
912static notmuch_messages_t *get_messages(notmuch_query_t *query)
913{
914 if (!query)
915 return NULL;
916
917 notmuch_messages_t *msgs = NULL;
918
919#if LIBNOTMUCH_CHECK_VERSION(5, 0, 0)
920 if (notmuch_query_search_messages(query, &msgs) != NOTMUCH_STATUS_SUCCESS)
921 return NULL;
922#elif LIBNOTMUCH_CHECK_VERSION(4, 3, 0)
923 if (notmuch_query_search_messages_st(query, &msgs) != NOTMUCH_STATUS_SUCCESS)
924 return NULL;
925#else
926 msgs = notmuch_query_search_messages(query);
927#endif
928
929 return msgs;
930}
931
940static bool read_mesgs_query(struct Mailbox *m, notmuch_query_t *q, bool dedup)
941{
942 struct NmMboxData *mdata = nm_mdata_get(m);
943 if (!mdata)
944 return false;
945
946 int limit = get_limit(mdata);
947
948 notmuch_messages_t *msgs = get_messages(q);
949
950 if (!msgs)
951 return false;
952
953 struct HeaderCache *hc = nm_hcache_open(m);
954
955 for (; notmuch_messages_valid(msgs) && ((limit == 0) || (m->msg_count < limit));
956 notmuch_messages_move_to_next(msgs))
957 {
958 if (SigInt)
959 {
960 nm_hcache_close(&hc);
961 SigInt = false;
962 return false;
963 }
964 notmuch_message_t *nm = notmuch_messages_get(msgs);
965 append_message(hc, m, nm, dedup);
966 notmuch_message_destroy(nm);
967 }
968
969 nm_hcache_close(&hc);
970 return true;
971}
972
982static notmuch_threads_t *get_threads(notmuch_query_t *query)
983{
984 if (!query)
985 return NULL;
986
987 notmuch_threads_t *threads = NULL;
988#if LIBNOTMUCH_CHECK_VERSION(5, 0, 0)
989 if (notmuch_query_search_threads(query, &threads) != NOTMUCH_STATUS_SUCCESS)
990 return NULL;
991#elif LIBNOTMUCH_CHECK_VERSION(4, 3, 0)
992 if (notmuch_query_search_threads_st(query, &threads) != NOTMUCH_STATUS_SUCCESS)
993 return NULL;
994#else
995 threads = notmuch_query_search_threads(query);
996#endif
997
998 return threads;
999}
1000
1010static bool read_threads_query(struct Mailbox *m, notmuch_query_t *q, bool dedup, int limit)
1011{
1012 struct NmMboxData *mdata = nm_mdata_get(m);
1013 if (!mdata)
1014 return false;
1015
1016 notmuch_threads_t *threads = get_threads(q);
1017 if (!threads)
1018 return false;
1019
1020 struct HeaderCache *hc = nm_hcache_open(m);
1021
1022 for (; notmuch_threads_valid(threads) && ((limit == 0) || (m->msg_count < limit));
1023 notmuch_threads_move_to_next(threads))
1024 {
1025 if (SigInt)
1026 {
1027 nm_hcache_close(&hc);
1028 SigInt = false;
1029 return false;
1030 }
1031 notmuch_thread_t *thread = notmuch_threads_get(threads);
1032 append_thread(hc, m, q, thread, dedup);
1033 notmuch_thread_destroy(thread);
1034 }
1035
1036 nm_hcache_close(&hc);
1037 return true;
1038}
1039
1047static notmuch_message_t *get_nm_message(notmuch_database_t *db, struct Email *e)
1048{
1049 notmuch_message_t *msg = NULL;
1050 char *id = email_get_id(e);
1051
1052 mutt_debug(LL_DEBUG2, "nm: find message (%s)\n", id);
1053
1054 if (id && db)
1055 notmuch_database_find_message(db, id, &msg);
1056
1057 return msg;
1058}
1059
1066static bool nm_message_has_tag(notmuch_message_t *msg, char *tag)
1067{
1068 const char *possible_match_tag = NULL;
1069 notmuch_tags_t *tags = NULL;
1070
1071 for (tags = notmuch_message_get_tags(msg); notmuch_tags_valid(tags);
1072 notmuch_tags_move_to_next(tags))
1073 {
1074 possible_match_tag = notmuch_tags_get(tags);
1075 if (mutt_str_equal(possible_match_tag, tag))
1076 {
1077 return true;
1078 }
1079 }
1080 return false;
1081}
1082
1088static void sync_email_path_with_nm(struct Email *e, notmuch_message_t *msg)
1089{
1090 const char *new_file = get_message_last_filename(msg);
1091 char old_file[PATH_MAX] = { 0 };
1092 email_get_fullpath(e, old_file, sizeof(old_file));
1093
1094 if (!mutt_str_equal(old_file, new_file))
1095 update_message_path(e, new_file);
1096}
1097
1105static int update_tags(notmuch_message_t *msg, const char *tag_str)
1106{
1107 if (!tag_str)
1108 return -1;
1109
1110 notmuch_message_freeze(msg);
1111
1113 char **tag_elem = NULL;
1114 ARRAY_FOREACH(tag_elem, &tags.tags)
1115 {
1116 char *tag = *tag_elem;
1117
1118 if (tag[0] == '-')
1119 {
1120 mutt_debug(LL_DEBUG1, "nm: remove tag: '%s'\n", tag + 1);
1121 notmuch_message_remove_tag(msg, tag + 1);
1122 }
1123 else if (tag[0] == '!')
1124 {
1125 mutt_debug(LL_DEBUG1, "nm: toggle tag: '%s'\n", tag + 1);
1126 if (nm_message_has_tag(msg, tag + 1))
1127 {
1128 notmuch_message_remove_tag(msg, tag + 1);
1129 }
1130 else
1131 {
1132 notmuch_message_add_tag(msg, tag + 1);
1133 }
1134 }
1135 else
1136 {
1137 mutt_debug(LL_DEBUG1, "nm: add tag: '%s'\n", (tag[0] == '+') ? tag + 1 : tag);
1138 notmuch_message_add_tag(msg, (tag[0] == '+') ? tag + 1 : tag);
1139 }
1140 }
1141
1142 notmuch_message_thaw(msg);
1144
1145 return 0;
1146}
1147
1159static int update_email_flags(struct Mailbox *m, struct Email *e, const char *tag_str)
1160{
1161 if (!tag_str)
1162 return -1;
1163
1164 const char *const c_nm_unread_tag = cs_subset_string(NeoMutt->sub, "nm_unread_tag");
1165 const char *const c_nm_replied_tag = cs_subset_string(NeoMutt->sub, "nm_replied_tag");
1166 const char *const c_nm_flagged_tag = cs_subset_string(NeoMutt->sub, "nm_flagged_tag");
1167
1169 char **tag_elem = NULL;
1170 ARRAY_FOREACH(tag_elem, &tags.tags)
1171 {
1172 char *tag = *tag_elem;
1173
1174 if (tag[0] == '-')
1175 {
1176 tag++;
1177 if (mutt_str_equal(tag, c_nm_unread_tag))
1178 mutt_set_flag(m, e, MUTT_READ, true, true);
1179 else if (mutt_str_equal(tag, c_nm_replied_tag))
1180 mutt_set_flag(m, e, MUTT_REPLIED, false, true);
1181 else if (mutt_str_equal(tag, c_nm_flagged_tag))
1182 mutt_set_flag(m, e, MUTT_FLAG, false, true);
1183 }
1184 else
1185 {
1186 tag = (tag[0] == '+') ? tag + 1 : tag;
1187 if (mutt_str_equal(tag, c_nm_unread_tag))
1188 mutt_set_flag(m, e, MUTT_READ, false, true);
1189 else if (mutt_str_equal(tag, c_nm_replied_tag))
1190 mutt_set_flag(m, e, MUTT_REPLIED, true, true);
1191 else if (mutt_str_equal(tag, c_nm_flagged_tag))
1192 mutt_set_flag(m, e, MUTT_FLAG, true, true);
1193 }
1194 }
1195
1197
1198 return 0;
1199}
1200
1211static int rename_maildir_filename(const char *old, char *buf, size_t buflen, struct Email *e)
1212{
1213 char filename[PATH_MAX] = { 0 };
1214 char suffix[PATH_MAX] = { 0 };
1215 char folder[PATH_MAX] = { 0 };
1216
1217 mutt_str_copy(folder, old, sizeof(folder));
1218 char *p = strrchr(folder, '/');
1219 if (p)
1220 {
1221 *p = '\0';
1222 p++;
1223 }
1224 else
1225 {
1226 p = folder;
1227 }
1228
1229 mutt_str_copy(filename, p, sizeof(filename));
1230
1231 /* remove (new,cur,...) from folder path */
1232 p = strrchr(folder, '/');
1233 if (p)
1234 *p = '\0';
1235
1236 /* remove old flags from filename */
1237 const char c_maildir_field_delimiter = *cc_maildir_field_delimiter();
1238 p = strchr(filename, c_maildir_field_delimiter);
1239 if (p)
1240 *p = '\0';
1241
1242 /* compose new flags */
1243 maildir_gen_flags(suffix, sizeof(suffix), e);
1244
1245 snprintf(buf, buflen, "%s/%s/%s%s", folder,
1246 (e->read || e->old) ? "cur" : "new", filename, suffix);
1247
1248 if (mutt_str_equal(old, buf))
1249 return 1;
1250
1251 if (rename(old, buf) != 0)
1252 {
1253 mutt_debug(LL_DEBUG1, "nm: rename(2) failed %s -> %s\n", old, buf);
1254 return -1;
1255 }
1256
1257 return 0;
1258}
1259
1267static int remove_filename(struct Mailbox *m, const char *path)
1268{
1269 struct NmMboxData *mdata = nm_mdata_get(m);
1270 if (!mdata)
1271 return -1;
1272
1273 mutt_debug(LL_DEBUG2, "nm: remove filename '%s'\n", path);
1274
1275 notmuch_database_t *db = nm_db_get(m, true);
1276 if (!db)
1277 return -1;
1278
1279 notmuch_message_t *msg = NULL;
1280 notmuch_status_t st = notmuch_database_find_message_by_filename(db, path, &msg);
1281 if (st || !msg)
1282 return -1;
1283
1284 int trans = nm_db_trans_begin(m);
1285 if (trans < 0)
1286 return -1;
1287
1288 /* note that unlink() is probably unnecessary here, it's already removed
1289 * by mh_sync_mailbox_message(), but for sure... */
1290 notmuch_filenames_t *ls = NULL;
1291 st = notmuch_database_remove_message(db, path);
1292 switch (st)
1293 {
1294 case NOTMUCH_STATUS_SUCCESS:
1295 mutt_debug(LL_DEBUG2, "nm: remove success, call unlink\n");
1296 unlink(path);
1297 break;
1298 case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:
1299 mutt_debug(LL_DEBUG2, "nm: remove success (duplicate), call unlink\n");
1300 unlink(path);
1301 for (ls = notmuch_message_get_filenames(msg);
1302 ls && notmuch_filenames_valid(ls); notmuch_filenames_move_to_next(ls))
1303 {
1304 path = notmuch_filenames_get(ls);
1305
1306 mutt_debug(LL_DEBUG2, "nm: remove duplicate: '%s'\n", path);
1307 unlink(path);
1308 notmuch_database_remove_message(db, path);
1309 }
1310 break;
1311 default:
1312 mutt_debug(LL_DEBUG1, "nm: failed to remove '%s' [st=%d]\n", path, (int) st);
1313 break;
1314 }
1315
1316 notmuch_message_destroy(msg);
1317 if (trans)
1318 nm_db_trans_end(m);
1319 return 0;
1320}
1321
1331static int rename_filename(struct Mailbox *m, const char *old_file,
1332 const char *new_file, struct Email *e)
1333{
1334 struct NmMboxData *mdata = nm_mdata_get(m);
1335 if (!mdata)
1336 return -1;
1337
1338 notmuch_database_t *db = nm_db_get(m, true);
1339 if (!db || !new_file || !old_file || (access(new_file, F_OK) != 0))
1340 return -1;
1341
1342 int rc = -1;
1343 notmuch_status_t st;
1344 notmuch_filenames_t *ls = NULL;
1345 notmuch_message_t *msg = NULL;
1346
1347 mutt_debug(LL_DEBUG1, "nm: rename filename, %s -> %s\n", old_file, new_file);
1348 int trans = nm_db_trans_begin(m);
1349 if (trans < 0)
1350 return -1;
1351
1352 mutt_debug(LL_DEBUG2, "nm: rename: add '%s'\n", new_file);
1353#if LIBNOTMUCH_CHECK_VERSION(5, 1, 0)
1354 st = notmuch_database_index_file(db, new_file, NULL, &msg);
1355#else
1356 st = notmuch_database_add_message(db, new_file, &msg);
1357#endif
1358
1359 if ((st != NOTMUCH_STATUS_SUCCESS) && (st != NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID))
1360 {
1361 mutt_debug(LL_DEBUG1, "nm: failed to add '%s' [st=%d]\n", new_file, (int) st);
1362 goto done;
1363 }
1364
1365 mutt_debug(LL_DEBUG2, "nm: rename: rem '%s'\n", old_file);
1366 st = notmuch_database_remove_message(db, old_file);
1367 switch (st)
1368 {
1369 case NOTMUCH_STATUS_SUCCESS:
1370 break;
1371 case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:
1372 mutt_debug(LL_DEBUG2, "nm: rename: syncing duplicate filename\n");
1373 notmuch_message_destroy(msg);
1374 msg = NULL;
1375 notmuch_database_find_message_by_filename(db, new_file, &msg);
1376
1377 for (ls = notmuch_message_get_filenames(msg);
1378 msg && ls && notmuch_filenames_valid(ls); notmuch_filenames_move_to_next(ls))
1379 {
1380 const char *path = notmuch_filenames_get(ls);
1381 char newpath[PATH_MAX] = { 0 };
1382
1383 if (mutt_str_equal(new_file, path))
1384 continue;
1385
1386 mutt_debug(LL_DEBUG2, "nm: rename: syncing duplicate: %s\n", path);
1387
1388 if (rename_maildir_filename(path, newpath, sizeof(newpath), e) == 0)
1389 {
1390 mutt_debug(LL_DEBUG2, "nm: rename dup %s -> %s\n", path, newpath);
1391 notmuch_database_remove_message(db, path);
1392#if LIBNOTMUCH_CHECK_VERSION(5, 1, 0)
1393 notmuch_database_index_file(db, newpath, NULL, NULL);
1394#else
1395 notmuch_database_add_message(db, newpath, NULL);
1396#endif
1397 }
1398 }
1399 notmuch_message_destroy(msg);
1400 msg = NULL;
1401 notmuch_database_find_message_by_filename(db, new_file, &msg);
1402 st = NOTMUCH_STATUS_SUCCESS;
1403 break;
1404 default:
1405 mutt_debug(LL_DEBUG1, "nm: failed to remove '%s' [st=%d]\n", old_file, (int) st);
1406 break;
1407 }
1408
1409 if ((st == NOTMUCH_STATUS_SUCCESS) && e && msg)
1410 {
1411 notmuch_message_maildir_flags_to_tags(msg);
1412 update_email_tags(e, msg);
1413
1414 struct Buffer *tags = buf_pool_get();
1415 driver_tags_get(&e->tags, tags);
1416 update_tags(msg, buf_string(tags));
1417 buf_pool_release(&tags);
1418 }
1419
1420 rc = 0;
1421done:
1422 if (msg)
1423 notmuch_message_destroy(msg);
1424 if (trans)
1425 nm_db_trans_end(m);
1426 return rc;
1427}
1428
1436static unsigned int count_query(notmuch_database_t *db, const char *qstr, int limit)
1437{
1438 notmuch_query_t *q = notmuch_query_create(db, qstr);
1439 if (!q)
1440 return 0;
1441
1442 unsigned int res = 0;
1443
1445#if LIBNOTMUCH_CHECK_VERSION(5, 0, 0)
1446 if (notmuch_query_count_messages(q, &res) != NOTMUCH_STATUS_SUCCESS)
1447 res = 0; /* may not be defined on error */
1448#elif LIBNOTMUCH_CHECK_VERSION(4, 3, 0)
1449 if (notmuch_query_count_messages_st(q, &res) != NOTMUCH_STATUS_SUCCESS)
1450 res = 0; /* may not be defined on error */
1451#else
1452 res = notmuch_query_count_messages(q);
1453#endif
1454 notmuch_query_destroy(q);
1455 mutt_debug(LL_DEBUG1, "nm: count '%s', result=%d\n", qstr, res);
1456
1457 if ((limit > 0) && (res > limit))
1458 res = limit;
1459
1460 return res;
1461}
1462
1470{
1471 struct NmEmailData *edata = nm_edata_get(e);
1472 if (!edata)
1473 return NULL;
1474
1475 return edata->folder;
1476}
1477
1488char *nm_email_get_folder_rel_db(struct Mailbox *m, struct Email *e)
1489{
1490 char *full_folder = nm_email_get_folder(e);
1491 if (!full_folder)
1492 return NULL;
1493
1494 const char *db_path = nm_db_get_filename(m);
1495 if (!db_path)
1496 return NULL;
1497
1498 size_t prefix = mutt_str_startswith(full_folder, db_path);
1499
1500 char *path = full_folder + prefix;
1501 if (*path == '/')
1502 path++;
1503
1504 return path;
1505}
1506
1514int nm_read_entire_thread(struct Mailbox *m, struct Email *e)
1515{
1516 if (!m)
1517 return -1;
1518
1519 struct NmMboxData *mdata = nm_mdata_get(m);
1520 if (!mdata)
1521 return -1;
1522
1523 notmuch_query_t *q = NULL;
1524 notmuch_database_t *db = NULL;
1525 notmuch_message_t *msg = NULL;
1526 int rc = -1;
1527
1528 if (!(db = nm_db_get(m, false)) || !(msg = get_nm_message(db, e)))
1529 goto done;
1530
1531 mutt_debug(LL_DEBUG1, "nm: reading entire-thread messages...[current count=%d]\n",
1532 m->msg_count);
1533
1534 progress_setup(m);
1535 const char *id = notmuch_message_get_thread_id(msg);
1536 if (!id)
1537 goto done;
1538
1539 struct Buffer *qstr = buf_pool_get();
1540 buf_printf(qstr, "thread:%s", id);
1541 q = notmuch_query_create(db, buf_string(qstr));
1542 buf_pool_release(&qstr);
1543 if (!q)
1544 goto done;
1546 notmuch_query_set_sort(q, NOTMUCH_SORT_NEWEST_FIRST);
1547
1548 read_threads_query(m, q, true, 0);
1549 mdata->mtime.tv_sec = mutt_date_now();
1550 mdata->mtime.tv_nsec = 0;
1551 rc = 0;
1552
1553 if (m->msg_count > mdata->oldmsgcount)
1555done:
1556 if (q)
1557 notmuch_query_destroy(q);
1558
1559 nm_db_release(m);
1560
1561 if (m->msg_count == mdata->oldmsgcount)
1562 mutt_message(_("No more messages in the thread"));
1563
1564 mdata->oldmsgcount = 0;
1565 mutt_debug(LL_DEBUG1, "nm: reading entire-thread messages... done [rc=%d, count=%d]\n",
1566 rc, m->msg_count);
1567 progress_free(&mdata->progress);
1568 return rc;
1569}
1570
1579char *nm_url_from_query(struct Mailbox *m, char *buf, size_t buflen)
1580{
1581 mutt_debug(LL_DEBUG2, "(%s)\n", buf);
1582 struct NmMboxData *mdata = nm_mdata_get(m);
1583 char url[PATH_MAX + 1024 + 32]; /* path to DB + query + URL "decoration" */
1584 int added;
1585 bool using_default_data = false;
1586
1587 // No existing data. Try to get a default NmMboxData.
1588 if (!mdata)
1589 {
1591
1592 // Failed to get default data.
1593 if (!mdata)
1594 return NULL;
1595
1596 using_default_data = true;
1597 }
1598
1600 cs_subset_string(NeoMutt->sub, "nm_query_type"));
1601 mdata->query_type = nm_parse_type_from_query(buf, query_type);
1602
1603 const short c_nm_db_limit = cs_subset_number(NeoMutt->sub, "nm_db_limit");
1604 if (get_limit(mdata) == c_nm_db_limit)
1605 {
1606 added = snprintf(url, sizeof(url), "%s%s?type=%s&query=", NmUrlProtocol,
1608 }
1609 else
1610 {
1611 added = snprintf(url, sizeof(url), "%s%s?type=%s&limit=%d&query=", NmUrlProtocol,
1614 }
1615
1616 if (added >= sizeof(url))
1617 {
1618 // snprintf output was truncated, so can't create URL
1619 return NULL;
1620 }
1621
1622 url_pct_encode(&url[added], sizeof(url) - added, buf);
1623
1624 mutt_str_copy(buf, url, buflen);
1625 buf[buflen - 1] = '\0';
1626
1627 if (using_default_data)
1628 nm_mdata_free((void **) &mdata);
1629
1630 mutt_debug(LL_DEBUG1, "nm: url from query '%s'\n", buf);
1631 return buf;
1632}
1633
1639{
1640 const short c_nm_query_window_duration = cs_subset_number(NeoMutt->sub, "nm_query_window_duration");
1641 const bool c_nm_query_window_enable = cs_subset_bool(NeoMutt->sub, "nm_query_window_enable");
1642
1643 return c_nm_query_window_enable || (c_nm_query_window_duration > 0);
1644}
1645
1656{
1657 const short c_nm_query_window_current_position = cs_subset_number(NeoMutt->sub, "nm_query_window_current_position");
1658 if (c_nm_query_window_current_position != 0)
1659 {
1660 cs_subset_str_native_set(NeoMutt->sub, "nm_query_window_current_position",
1661 c_nm_query_window_current_position - 1, NULL);
1662 }
1663
1664 mutt_debug(LL_DEBUG2, "(%d)\n", c_nm_query_window_current_position - 1);
1665}
1666
1676{
1677 const short c_nm_query_window_current_position = cs_subset_number(NeoMutt->sub, "nm_query_window_current_position");
1678 cs_subset_str_native_set(NeoMutt->sub, "nm_query_window_current_position",
1679 c_nm_query_window_current_position + 1, NULL);
1680 mutt_debug(LL_DEBUG2, "(%d)\n", c_nm_query_window_current_position + 1);
1681}
1682
1687{
1688 cs_subset_str_native_set(NeoMutt->sub, "nm_query_window_current_position", 0, NULL);
1689 mutt_debug(LL_DEBUG2, "Reset nm_query_window_current_position to 0\n");
1690}
1691
1699{
1700 struct NmMboxData *mdata = nm_mdata_get(m);
1701 if (!mdata)
1702 return false;
1703
1704 notmuch_database_t *db = nm_db_get(m, false);
1705 char *orig_str = get_query_string(mdata, true);
1706
1707 if (!db || !orig_str)
1708 return false;
1709
1710 char *new_str = NULL;
1711 bool rc = false;
1712 if (mutt_str_asprintf(&new_str, "id:%s and (%s)", email_get_id(e), orig_str) < 0)
1713 return false;
1714
1715 mutt_debug(LL_DEBUG2, "nm: checking if message is still queried: %s\n", new_str);
1716
1717 notmuch_query_t *q = notmuch_query_create(db, new_str);
1718
1719 switch (mdata->query_type)
1720 {
1721 case NM_QUERY_TYPE_UNKNOWN: // UNKNOWN should never occur, but MESGS is default
1723 {
1724 notmuch_messages_t *messages = get_messages(q);
1725
1726 if (!messages)
1727 return false;
1728
1729 rc = notmuch_messages_valid(messages);
1730 notmuch_messages_destroy(messages);
1731 break;
1732 }
1734 {
1735 notmuch_threads_t *threads = get_threads(q);
1736
1737 if (!threads)
1738 return false;
1739
1740 rc = notmuch_threads_valid(threads);
1741 notmuch_threads_destroy(threads);
1742 break;
1743 }
1744 }
1745
1746 notmuch_query_destroy(q);
1747
1748 mutt_debug(LL_DEBUG2, "nm: checking if message is still queried: %s = %s\n",
1749 new_str, rc ? "true" : "false");
1750
1751 return rc;
1752}
1753
1763int nm_update_filename(struct Mailbox *m, const char *old_file,
1764 const char *new_file, struct Email *e)
1765{
1766 char buf[PATH_MAX] = { 0 };
1767 struct NmMboxData *mdata = nm_mdata_get(m);
1768 if (!mdata || !new_file)
1769 return -1;
1770
1771 if (!old_file && nm_edata_get(e))
1772 {
1773 email_get_fullpath(e, buf, sizeof(buf));
1774 old_file = buf;
1775 }
1776
1777 int rc = rename_filename(m, old_file, new_file, e);
1778
1779 nm_db_release(m);
1780 mdata->mtime.tv_sec = mutt_date_now();
1781 mdata->mtime.tv_nsec = 0;
1782 return rc;
1783}
1784
1788static enum MxStatus nm_mbox_check_stats(struct Mailbox *m, uint8_t flags)
1789{
1790 struct UrlQuery *item = NULL;
1791 struct Url *url = NULL;
1792 const char *db_filename = NULL;
1793 char *db_query = NULL;
1794 notmuch_database_t *db = NULL;
1795 enum MxStatus rc = MX_STATUS_ERROR;
1796 const short c_nm_db_limit = cs_subset_number(NeoMutt->sub, "nm_db_limit");
1797 int limit = c_nm_db_limit;
1798 mutt_debug(LL_DEBUG1, "nm: count\n");
1799
1800 url = url_parse(mailbox_path(m));
1801 if (!url)
1802 {
1803 mutt_error(_("failed to parse notmuch url: %s"), mailbox_path(m));
1804 goto done;
1805 }
1806
1807 STAILQ_FOREACH(item, &url->query_strings, entries)
1808 {
1809 if (item->value && (mutt_str_equal(item->name, "query")))
1810 {
1811 db_query = item->value;
1812 }
1813 else if (item->value && (mutt_str_equal(item->name, "limit")))
1814 {
1815 // Try to parse the limit
1816 if (!mutt_str_atoi_full(item->value, &limit))
1817 {
1818 mutt_error(_("failed to parse limit: %s"), item->value);
1819 goto done;
1820 }
1821 }
1822 }
1823
1824 if (!db_query)
1825 goto done;
1826
1827 db_filename = url->path;
1828 if (!db_filename)
1829 db_filename = nm_db_get_filename(m);
1830
1831 /* don't be verbose about connection, as we're called from
1832 * sidebar/mailbox very often */
1833 db = nm_db_do_open(db_filename, false, false);
1834 if (!db)
1835 goto done;
1836
1837 /* all emails */
1838 m->msg_count = count_query(db, db_query, limit);
1840
1841 // holder variable for extending query to unread/flagged
1842 char *qstr = NULL;
1843
1844 // unread messages
1845 const char *const c_nm_unread_tag = cs_subset_string(NeoMutt->sub, "nm_unread_tag");
1846 mutt_str_asprintf(&qstr, "( %s ) tag:%s", db_query, c_nm_unread_tag);
1847 m->msg_unread = count_query(db, qstr, limit);
1848 FREE(&qstr);
1849
1850 // flagged messages
1851 const char *const c_nm_flagged_tag = cs_subset_string(NeoMutt->sub, "nm_flagged_tag");
1852 mutt_str_asprintf(&qstr, "( %s ) tag:%s", db_query, c_nm_flagged_tag);
1853 m->msg_flagged = count_query(db, qstr, limit);
1854 FREE(&qstr);
1855
1856 rc = (m->msg_new > 0) ? MX_STATUS_NEW_MAIL : MX_STATUS_OK;
1857done:
1858 if (db)
1859 {
1860 nm_db_free(db);
1861 mutt_debug(LL_DEBUG1, "nm: count close DB\n");
1862 }
1863 url_free(&url);
1864
1865 mutt_debug(LL_DEBUG1, "nm: count done [rc=%d]\n", rc);
1866 return rc;
1867}
1868
1873static struct Mailbox *get_default_mailbox(void)
1874{
1875 // Create a new notmuch mailbox from scratch and add plumbing for DB access.
1876 char *default_url = nm_get_default_url();
1877 struct Mailbox *m = mx_path_resolve(default_url);
1878
1879 FREE(&default_url);
1880
1881 // These are no-ops for an initialized mailbox.
1882 init_mailbox(m);
1883 mx_mbox_ac_link(m);
1884
1885 return m;
1886}
1887
1896int nm_record_message(struct Mailbox *m, char *path, struct Email *e)
1897{
1898 notmuch_database_t *db = NULL;
1899 notmuch_status_t st;
1900 notmuch_message_t *msg = NULL;
1901 int rc = -1;
1902
1903 struct NmMboxData *mdata = nm_mdata_get(m);
1904
1905 // If no notmuch data, fall back to the default mailbox.
1906 //
1907 // IMPORTANT: DO NOT FREE THIS MAILBOX. Two reasons:
1908 // 1) If user has default mailbox in config, we'll be removing it. That's not
1909 // good program behavior!
1910 // 2) If not in user's config, keep mailbox around for future nm_record calls.
1911 // It saves NeoMutt from allocating/deallocating repeatedly.
1912 if (!mdata)
1913 {
1914 mutt_debug(LL_DEBUG1, "nm: non-nm mailbox. trying the default nm mailbox.");
1915 m = get_default_mailbox();
1916 mdata = nm_mdata_get(m);
1917 }
1918
1919 if (!path || !mdata || (access(path, F_OK) != 0))
1920 return 0;
1921 db = nm_db_get(m, true);
1922 if (!db)
1923 return -1;
1924
1925 mutt_debug(LL_DEBUG1, "nm: record message: %s\n", path);
1926 int trans = nm_db_trans_begin(m);
1927 if (trans < 0)
1928 goto done;
1929
1930#if LIBNOTMUCH_CHECK_VERSION(5, 1, 0)
1931 st = notmuch_database_index_file(db, path, NULL, &msg);
1932#else
1933 st = notmuch_database_add_message(db, path, &msg);
1934#endif
1935
1936 if ((st != NOTMUCH_STATUS_SUCCESS) && (st != NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID))
1937 {
1938 mutt_debug(LL_DEBUG1, "nm: failed to add '%s' [st=%d]\n", path, (int) st);
1939 goto done;
1940 }
1941
1942 if ((st == NOTMUCH_STATUS_SUCCESS) && msg)
1943 {
1944 notmuch_message_maildir_flags_to_tags(msg);
1945 if (e)
1946 {
1947 struct Buffer *tags = buf_pool_get();
1948 driver_tags_get(&e->tags, tags);
1949 update_tags(msg, buf_string(tags));
1950 buf_pool_release(&tags);
1951 }
1952 const char *const c_nm_record_tags = cs_subset_string(NeoMutt->sub, "nm_record_tags");
1953 if (c_nm_record_tags)
1954 update_tags(msg, c_nm_record_tags);
1955 }
1956
1957 rc = 0;
1958done:
1959 if (msg)
1960 notmuch_message_destroy(msg);
1961 if (trans == 1)
1962 nm_db_trans_end(m);
1963
1964 nm_db_release(m);
1965
1966 return rc;
1967}
1968
1979int nm_get_all_tags(struct Mailbox *m, const char **tag_list, int *tag_count)
1980{
1981 struct NmMboxData *mdata = nm_mdata_get(m);
1982 if (!mdata)
1983 return -1;
1984
1985 notmuch_database_t *db = NULL;
1986 notmuch_tags_t *tags = NULL;
1987 const char *tag = NULL;
1988 int rc = -1;
1989
1990 if (!(db = nm_db_get(m, false)) || !(tags = notmuch_database_get_all_tags(db)))
1991 goto done;
1992
1993 *tag_count = 0;
1994 mutt_debug(LL_DEBUG1, "nm: get all tags\n");
1995
1996 while (notmuch_tags_valid(tags))
1997 {
1998 tag = notmuch_tags_get(tags);
1999 /* Skip empty string */
2000 if (*tag)
2001 {
2002 if (tag_list)
2003 tag_list[*tag_count] = mutt_str_dup(tag);
2004 (*tag_count)++;
2005 }
2006 notmuch_tags_move_to_next(tags);
2007 }
2008
2009 rc = 0;
2010done:
2011 if (tags)
2012 notmuch_tags_destroy(tags);
2013
2014 nm_db_release(m);
2015
2016 mutt_debug(LL_DEBUG1, "nm: get all tags done [rc=%d tag_count=%u]\n", rc, *tag_count);
2017 return rc;
2018}
2019
2023static bool nm_ac_owns_path(struct Account *a, const char *path)
2024{
2025 return true;
2026}
2027
2031static bool nm_ac_add(struct Account *a, struct Mailbox *m)
2032{
2033 if (a->adata)
2034 return true;
2035
2036 struct NmAccountData *adata = nm_adata_new();
2037 a->adata = adata;
2039
2040 return true;
2041}
2042
2046static enum MxOpenReturns nm_mbox_open(struct Mailbox *m)
2047{
2048 if (init_mailbox(m) != 0)
2049 return MX_OPEN_ERROR;
2050
2051 struct NmMboxData *mdata = nm_mdata_get(m);
2052 if (!mdata)
2053 return MX_OPEN_ERROR;
2054
2055 mutt_debug(LL_DEBUG1, "nm: reading messages...[current count=%d]\n", m->msg_count);
2056
2057 progress_setup(m);
2058 enum MxOpenReturns rc = MX_OPEN_ERROR;
2059
2060 notmuch_query_t *q = get_query(m, false);
2061 if (q)
2062 {
2063 rc = MX_OPEN_OK;
2064 switch (mdata->query_type)
2065 {
2066 case NM_QUERY_TYPE_UNKNOWN: // UNKNOWN should never occur, but MESGS is default
2068 if (!read_mesgs_query(m, q, false))
2069 rc = MX_OPEN_ABORT;
2070 break;
2072 if (!read_threads_query(m, q, false, get_limit(mdata)))
2073 rc = MX_OPEN_ABORT;
2074 break;
2075 }
2076 notmuch_query_destroy(q);
2077 }
2078
2079 nm_db_release(m);
2080
2081 mdata->mtime.tv_sec = mutt_date_now();
2082 mdata->mtime.tv_nsec = 0;
2083
2084 mdata->oldmsgcount = 0;
2085
2086 mutt_debug(LL_DEBUG1, "nm: reading messages... done [rc=%d, count=%d]\n", rc, m->msg_count);
2087 progress_free(&mdata->progress);
2088 return rc;
2089}
2090
2096static enum MxStatus nm_mbox_check(struct Mailbox *m)
2097{
2098 struct NmMboxData *mdata = nm_mdata_get(m);
2099 time_t mtime = 0;
2100 if (!mdata || (nm_db_get_mtime(m, &mtime) != 0))
2101 return MX_STATUS_ERROR;
2102
2103 int new_flags = 0;
2104 bool occult = false;
2105
2106 if (mdata->mtime.tv_sec >= mtime)
2107 {
2108 mutt_debug(LL_DEBUG2, "nm: check unnecessary (db=%llu mailbox=%llu)\n",
2109 (unsigned long long) mtime, (unsigned long long) mdata->mtime.tv_sec);
2110 return MX_STATUS_OK;
2111 }
2112
2113 mutt_debug(LL_DEBUG1, "nm: checking (db=%llu mailbox=%llu)\n",
2114 (unsigned long long) mtime, (unsigned long long) mdata->mtime.tv_sec);
2115
2116 notmuch_query_t *q = get_query(m, false);
2117 if (!q)
2118 goto done;
2119
2120 mutt_debug(LL_DEBUG1, "nm: start checking (count=%d)\n", m->msg_count);
2121 mdata->oldmsgcount = m->msg_count;
2122
2123 for (int i = 0; i < m->msg_count; i++)
2124 {
2125 struct Email *e = m->emails[i];
2126 if (!e)
2127 break;
2128
2129 e->active = false;
2130 }
2131
2132 int limit = get_limit(mdata);
2133
2134 notmuch_messages_t *msgs = get_messages(q);
2135
2136 // TODO: Analyze impact of removing this version guard.
2137#if LIBNOTMUCH_CHECK_VERSION(5, 0, 0)
2138 if (!msgs)
2139 return MX_STATUS_OK;
2140#elif LIBNOTMUCH_CHECK_VERSION(4, 3, 0)
2141 if (!msgs)
2142 goto done;
2143#endif
2144
2145 struct HeaderCache *hc = nm_hcache_open(m);
2146
2147 for (int i = 0; notmuch_messages_valid(msgs) && ((limit == 0) || (i < limit));
2148 notmuch_messages_move_to_next(msgs), i++)
2149 {
2150 notmuch_message_t *msg = notmuch_messages_get(msgs);
2151 struct Email *e = get_mutt_email(m, msg);
2152
2153 if (!e)
2154 {
2155 /* new email */
2156 append_message(hc, m, msg, false);
2157 notmuch_message_destroy(msg);
2158 continue;
2159 }
2160
2161 /* message already exists, merge flags */
2162 e->active = true;
2163
2164 /* Check to see if the message has moved to a different subdirectory.
2165 * If so, update the associated filename. */
2166 const char *new_file = get_message_last_filename(msg);
2167 char old_file[PATH_MAX] = { 0 };
2168 email_get_fullpath(e, old_file, sizeof(old_file));
2169
2170 if (!mutt_str_equal(old_file, new_file))
2171 update_message_path(e, new_file);
2172
2173 if (!e->changed)
2174 {
2175 /* if the user hasn't modified the flags on this message, update the
2176 * flags we just detected. */
2177 struct Email *e_tmp = maildir_email_new();
2178 maildir_parse_flags(e_tmp, new_file);
2179 e_tmp->old = e->old;
2180 maildir_update_flags(m, e, e_tmp);
2181 email_free(&e_tmp);
2182 }
2183
2184 if (update_email_tags(e, msg) == 0)
2185 new_flags++;
2186
2187 notmuch_message_destroy(msg);
2188 }
2189
2190 nm_hcache_close(&hc);
2191
2192 for (int i = 0; i < m->msg_count; i++)
2193 {
2194 struct Email *e = m->emails[i];
2195 if (!e)
2196 break;
2197
2198 if (!e->active)
2199 {
2200 occult = true;
2201 break;
2202 }
2203 }
2204
2205 if (m->msg_count > mdata->oldmsgcount)
2207done:
2208 if (q)
2209 notmuch_query_destroy(q);
2210
2211 nm_db_release(m);
2212
2213 mdata->mtime.tv_sec = mutt_date_now();
2214 mdata->mtime.tv_nsec = 0;
2215
2216 mutt_debug(LL_DEBUG1, "nm: ... check done [count=%d, new_flags=%d, occult=%d]\n",
2217 m->msg_count, new_flags, occult);
2218
2219 if (occult)
2220 return MX_STATUS_REOPENED;
2221 if (m->msg_count > mdata->oldmsgcount)
2222 return MX_STATUS_NEW_MAIL;
2223 if (new_flags)
2224 return MX_STATUS_FLAGS;
2225 return MX_STATUS_OK;
2226}
2227
2231static enum MxStatus nm_mbox_sync(struct Mailbox *m)
2232{
2233 struct NmMboxData *mdata = nm_mdata_get(m);
2234 if (!mdata)
2235 return MX_STATUS_ERROR;
2236
2237 enum MxStatus rc = MX_STATUS_OK;
2238 struct Progress *progress = NULL;
2239 char *url = mutt_str_dup(mailbox_path(m));
2240 bool changed = false;
2241
2242 mutt_debug(LL_DEBUG1, "nm: sync start\n");
2243
2244 if (m->verbose)
2245 {
2246 /* all is in this function so we don't use data->progress here */
2248 progress_set_message(progress, _("Writing %s..."), mailbox_path(m));
2249 }
2250
2251 struct HeaderCache *hc = nm_hcache_open(m);
2252
2253 int mh_sync_errors = 0;
2254 for (int i = 0; i < m->msg_count; i++)
2255 {
2256 char old_file[PATH_MAX], new_file[PATH_MAX];
2257 struct Email *e = m->emails[i];
2258 if (!e)
2259 break;
2260
2261 struct NmEmailData *edata = nm_edata_get(e);
2262
2263 progress_update(progress, i, -1);
2264
2265 *old_file = '\0';
2266 *new_file = '\0';
2267
2268 if (edata->oldpath)
2269 {
2270 mutt_str_copy(old_file, edata->oldpath, sizeof(old_file));
2271 old_file[sizeof(old_file) - 1] = '\0';
2272 mutt_debug(LL_DEBUG2, "nm: fixing obsolete path '%s'\n", old_file);
2273 }
2274 else
2275 {
2276 email_get_fullpath(e, old_file, sizeof(old_file));
2277 }
2278
2279 buf_strcpy(&m->pathbuf, edata->folder);
2280 m->type = edata->type;
2281
2282 bool ok = maildir_sync_mailbox_message(m, e, hc);
2283 if (!ok)
2284 {
2285 // Syncing file failed, query notmuch for new filepath.
2286 m->type = MUTT_NOTMUCH;
2287 notmuch_database_t *db = nm_db_get(m, true);
2288 if (db)
2289 {
2290 notmuch_message_t *msg = get_nm_message(db, e);
2291
2293
2294 buf_strcpy(&m->pathbuf, edata->folder);
2295 m->type = edata->type;
2296 ok = maildir_sync_mailbox_message(m, e, hc);
2297 m->type = MUTT_NOTMUCH;
2298 }
2299 nm_db_release(m);
2300 m->type = edata->type;
2301 }
2302
2303 buf_strcpy(&m->pathbuf, url);
2304 m->type = MUTT_NOTMUCH;
2305
2306 if (!ok)
2307 {
2308 mh_sync_errors += 1;
2309 continue;
2310 }
2311
2312 if (!e->deleted)
2313 email_get_fullpath(e, new_file, sizeof(new_file));
2314
2315 if (e->deleted || !mutt_str_equal(old_file, new_file))
2316 {
2317 if (e->deleted && (remove_filename(m, old_file) == 0))
2318 changed = true;
2319 else if (*new_file && *old_file && (rename_filename(m, old_file, new_file, e) == 0))
2320 changed = true;
2321 }
2322
2323 FREE(&edata->oldpath);
2324 }
2325
2326 if (mh_sync_errors > 0)
2327 {
2328 mutt_error(ngettext("Unable to sync %d message due to external mailbox modification",
2329 "Unable to sync %d messages due to external mailbox modification",
2330 mh_sync_errors),
2331 mh_sync_errors);
2332 }
2333
2334 buf_strcpy(&m->pathbuf, url);
2335 m->type = MUTT_NOTMUCH;
2336
2337 nm_db_release(m);
2338
2339 if (changed)
2340 {
2341 mdata->mtime.tv_sec = mutt_date_now();
2342 mdata->mtime.tv_nsec = 0;
2343 }
2344
2345 nm_hcache_close(&hc);
2346
2347 progress_free(&progress);
2348 FREE(&url);
2349 mutt_debug(LL_DEBUG1, "nm: .... sync done [rc=%d]\n", rc);
2350 return rc;
2351}
2352
2358static enum MxStatus nm_mbox_close(struct Mailbox *m)
2359{
2360 return MX_STATUS_OK;
2361}
2362
2366static bool nm_msg_open(struct Mailbox *m, struct Message *msg, struct Email *e)
2367{
2368 char path[PATH_MAX] = { 0 };
2369 char *folder = nm_email_get_folder(e);
2370
2371 snprintf(path, sizeof(path), "%s/%s", folder, e->path);
2372
2373 msg->fp = mutt_file_fopen(path, "r");
2374 if (!msg->fp && (errno == ENOENT) && ((m->type == MUTT_MAILDIR) || (m->type == MUTT_NOTMUCH)))
2375 {
2376 msg->fp = maildir_open_find_message(folder, e->path, NULL);
2377 }
2378
2379 return msg->fp != NULL;
2380}
2381
2386static int nm_msg_commit(struct Mailbox *m, struct Message *msg)
2387{
2388 mutt_error(_("Can't write to virtual folder"));
2389 return -1;
2390}
2391
2395static int nm_msg_close(struct Mailbox *m, struct Message *msg)
2396{
2397 mutt_file_fclose(&(msg->fp));
2398 return 0;
2399}
2400
2404static int nm_tags_edit(struct Mailbox *m, const char *tags, struct Buffer *buf)
2405{
2406 buf_reset(buf);
2407 if (mw_get_field("Add/remove labels: ", buf, MUTT_COMP_NO_FLAGS, HC_OTHER,
2408 &CompleteNmTagOps, NULL) != 0)
2409 {
2410 return -1;
2411 }
2412 return 1;
2413}
2414
2418static int nm_tags_commit(struct Mailbox *m, struct Email *e, const char *buf)
2419{
2420 if (*buf == '\0')
2421 return 0; /* no tag change, so nothing to do */
2422
2423 struct NmMboxData *mdata = nm_mdata_get(m);
2424 if (!mdata)
2425 return -1;
2426
2427 notmuch_database_t *db = NULL;
2428 notmuch_message_t *msg = NULL;
2429 int rc = -1;
2430
2431 if (!(db = nm_db_get(m, true)) || !(msg = get_nm_message(db, e)))
2432 goto done;
2433
2434 mutt_debug(LL_DEBUG1, "nm: tags modify: '%s'\n", buf);
2435
2436 update_tags(msg, buf);
2437 update_email_flags(m, e, buf);
2438 update_email_tags(e, msg);
2439 email_set_color(m, e);
2440
2441 rc = 0;
2442 e->changed = true;
2443done:
2444 nm_db_release(m);
2445 if (e->changed)
2446 {
2447 mdata->mtime.tv_sec = mutt_date_now();
2448 mdata->mtime.tv_nsec = 0;
2449 }
2450 mutt_debug(LL_DEBUG1, "nm: tags modify done [rc=%d]\n", rc);
2451 return rc;
2452}
2453
2457enum MailboxType nm_path_probe(const char *path, const struct stat *st)
2458{
2460 return MUTT_UNKNOWN;
2461
2462 return MUTT_NOTMUCH;
2463}
2464
2468static int nm_path_canon(struct Buffer *path)
2469{
2470 return 0;
2471}
2472
2476const struct MxOps MxNotmuchOps = {
2477 // clang-format off
2478 .type = MUTT_NOTMUCH,
2479 .name = "notmuch",
2480 .is_local = false,
2481 .ac_owns_path = nm_ac_owns_path,
2482 .ac_add = nm_ac_add,
2483 .mbox_open = nm_mbox_open,
2484 .mbox_open_append = NULL,
2485 .mbox_check = nm_mbox_check,
2486 .mbox_check_stats = nm_mbox_check_stats,
2487 .mbox_sync = nm_mbox_sync,
2488 .mbox_close = nm_mbox_close,
2489 .msg_open = nm_msg_open,
2490 .msg_open_new = maildir_msg_open_new,
2491 .msg_commit = nm_msg_commit,
2492 .msg_close = nm_msg_close,
2493 .msg_padding_size = NULL,
2494 .msg_save_hcache = NULL,
2495 .tags_edit = nm_tags_edit,
2496 .tags_commit = nm_tags_commit,
2497 .path_probe = nm_path_probe,
2498 .path_canon = nm_path_canon,
2499 .path_is_empty = NULL,
2500 // clang-format on
2501};
#define ARRAY_FOREACH(elem, head)
Iterate over all elements of the array.
Definition: array.h:212
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:750
bool buf_str_equal(const struct Buffer *a, const struct Buffer *b)
Return if two buffers are equal.
Definition: buffer.c:685
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
void commands_register(const struct Command *cmds, const size_t num_cmds)
Add commands to Commands array.
Definition: command.c:53
Convenience wrapper for the core headers.
void 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 email_free(struct Email **ptr)
Free an Email.
Definition: email.c:46
Structs that make up an email.
#define mutt_file_fclose(FP)
Definition: file.h:138
#define mutt_file_fopen(PATH, MODE)
Definition: file.h:137
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:1448
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:739
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:274
#define mutt_error(...)
Definition: logging2.h:92
#define mutt_message(...)
Definition: logging2.h:91
#define mutt_debug(LEVEL,...)
Definition: logging2.h:89
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:2031
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:2023
const struct MxOps MxNotmuchOps
Notmuch Mailbox - Implements MxOps -.
Definition: notmuch.c:2476
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:1788
static enum MxStatus nm_mbox_check(struct Mailbox *m)
Check for new mail - Implements MxOps::mbox_check() -.
Definition: notmuch.c:2096
static enum MxStatus nm_mbox_close(struct Mailbox *m)
Close a Mailbox - Implements MxOps::mbox_close() -.
Definition: notmuch.c:2358
static enum MxOpenReturns nm_mbox_open(struct Mailbox *m)
Open a Mailbox - Implements MxOps::mbox_open() -.
Definition: notmuch.c:2046
static enum MxStatus nm_mbox_sync(struct Mailbox *m)
Save changes to the Mailbox - Implements MxOps::mbox_sync() -.
Definition: notmuch.c:2231
static int nm_msg_close(struct Mailbox *m, struct Message *msg)
Close an email - Implements MxOps::msg_close() -.
Definition: notmuch.c:2395
static int nm_msg_commit(struct Mailbox *m, struct Message *msg)
Save changes to an email - Implements MxOps::msg_commit() -.
Definition: notmuch.c:2386
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:2366
static int nm_path_canon(struct Buffer *path)
Canonicalise a Mailbox path - Implements MxOps::path_canon() -.
Definition: notmuch.c:2468
enum MailboxType nm_path_probe(const char *path, const struct stat *st)
Is this a Notmuch Mailbox? - Implements MxOps::path_probe() -.
Definition: notmuch.c:2457
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:2418
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:2404
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:44
@ LL_DEBUG1
Log at debug level 1.
Definition: logging2.h:43
bool maildir_parse_message(const char *fname, bool is_old, struct Email *e)
Actually parse a maildir message.
Definition: mailbox.c:189
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:152
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
#define mutt_array_size(x)
Definition: memory.h:38
time_t mutt_date_now(void)
Return the number of seconds since the Unix epoch.
Definition: date.c: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:380
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:253
int mutt_str_asprintf(char **strp, const char *fmt,...)
Definition: string.c:803
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition: string.c:660
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:425
size_t mutt_str_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix.
Definition: string.c:230
size_t mutt_str_len(const char *a)
Calculate the length of a string, safely.
Definition: string.c:496
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:581
size_t mutt_istr_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix, ignoring case.
Definition: string.c:242
char * mutt_str_replace(char **p, const char *s)
Replace one string with another.
Definition: string.c:280
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:1704
Create/manipulate threading in emails.
void mx_alloc_memory(struct Mailbox *m, int req_size)
Create storage for the emails.
Definition: mx.c:1206
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:1636
API for mailboxes.
MxOpenReturns
Return values for mbox_open()
Definition: mxapi.h:76
@ MX_OPEN_ERROR
Open failed with an error.
Definition: mxapi.h:78
@ MX_OPEN_ABORT
Open was aborted.
Definition: mxapi.h:79
@ MX_OPEN_OK
Open succeeded.
Definition: mxapi.h:77
MxStatus
Return values from mbox_check(), mbox_check_stats(), mbox_sync(), and mbox_close()
Definition: mxapi.h:63
@ MX_STATUS_ERROR
An error occurred.
Definition: mxapi.h:64
@ MX_STATUS_OK
No changes.
Definition: mxapi.h:65
@ MX_STATUS_FLAGS
Nondestructive flags change (IMAP)
Definition: mxapi.h:69
@ MX_STATUS_REOPENED
Mailbox was reopened.
Definition: mxapi.h:68
@ MX_STATUS_NEW_MAIL
New mail received in Mailbox.
Definition: mxapi.h:66
struct NmAccountData * nm_adata_new(void)
Allocate and initialise a new NmAccountData structure.
Definition: adata.c:58
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:982
static char * get_query_string(struct NmMboxData *mdata, bool window)
Builds the notmuch vfolder search string.
Definition: notmuch.c:346
static char * nm_get_default_url(void)
Create a Mailbox with default Notmuch settings.
Definition: notmuch.c:144
int nm_update_filename(struct Mailbox *m, const char *old_file, const char *new_file, struct Email *e)
Change the filename.
Definition: notmuch.c:1763
void nm_query_window_reset(void)
Resets the vfolder window position to the present.
Definition: notmuch.c:1686
static char * email_get_id(struct Email *e)
Get the unique Notmuch Id.
Definition: notmuch.c:221
static void apply_exclude_tags(notmuch_query_t *query)
Exclude the configured tags.
Definition: notmuch.c:423
static char * email_get_fullpath(struct Email *e, char *buf, size_t buflen)
Get the full path of an email.
Definition: notmuch.c:237
static bool windowed_query_from_query(const char *query, char *buf, size_t buflen)
Transforms a vfolder search query into a windowed one.
Definition: notmuch.c:282
int nm_get_all_tags(struct Mailbox *m, const char **tag_list, int *tag_count)
Fill a list with all notmuch tags.
Definition: notmuch.c:1979
static unsigned int count_query(notmuch_database_t *db, const char *qstr, int limit)
Count the results of a query.
Definition: notmuch.c:1436
static int init_mailbox(struct Mailbox *m)
Add Notmuch data to the Mailbox.
Definition: notmuch.c:199
static int rename_maildir_filename(const char *old, char *buf, size_t buflen, struct Email *e)
Rename a Maildir file.
Definition: notmuch.c:1211
static void sync_email_path_with_nm(struct Email *e, notmuch_message_t *msg)
Synchronize Neomutt's Email path with notmuch.
Definition: notmuch.c:1088
static notmuch_message_t * get_nm_message(notmuch_database_t *db, struct Email *e)
Find a Notmuch message.
Definition: notmuch.c:1047
static int init_email(struct Email *e, const char *path, notmuch_message_t *msg)
Set up an email's Notmuch data.
Definition: notmuch.c:617
static int get_limit(struct NmMboxData *mdata)
Get the database limit.
Definition: notmuch.c:414
char * nm_email_get_folder_rel_db(struct Mailbox *m, struct Email *e)
Get the folder for a Email from the same level as the notmuch database.
Definition: notmuch.c:1488
static bool read_threads_query(struct Mailbox *m, notmuch_query_t *q, bool dedup, int limit)
Perform a query with threads.
Definition: notmuch.c:1010
static struct Mailbox * get_default_mailbox(void)
Get Mailbox for notmuch without any parameters.
Definition: notmuch.c:1873
int nm_record_message(struct Mailbox *m, char *path, struct Email *e)
Add a message to the Notmuch database.
Definition: notmuch.c:1896
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:176
int nm_read_entire_thread(struct Mailbox *m, struct Email *e)
Get the entire thread of an email.
Definition: notmuch.c:1514
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:888
static void query_window_reset(void)
Restore vfolder's search window to its original position.
Definition: notmuch.c:252
const int NmUrlProtocolLen
Length of NmUrlProtocol string.
Definition: notmuch.c:103
static int rename_filename(struct Mailbox *m, const char *old_file, const char *new_file, struct Email *e)
Rename the file.
Definition: notmuch.c:1331
void nm_query_window_backward(void)
Function to move the current search window backward in time.
Definition: notmuch.c:1675
static struct HeaderCache * nm_hcache_open(struct Mailbox *m)
Open a header cache.
Definition: notmuch.c:118
static bool nm_message_has_tag(notmuch_message_t *msg, char *tag)
Does a message have this tag?
Definition: notmuch.c:1066
static notmuch_query_t * get_query(struct Mailbox *m, bool writable)
Create a new query.
Definition: notmuch.c:449
static int update_message_path(struct Email *e, const char *path)
Set the path for a message.
Definition: notmuch.c:533
bool nm_query_window_available(void)
Are windowed queries enabled for use?
Definition: notmuch.c:1638
static int update_tags(notmuch_message_t *msg, const char *tag_str)
Update the tags on a message.
Definition: notmuch.c:1105
static bool read_mesgs_query(struct Mailbox *m, notmuch_query_t *q, bool dedup)
Search for matching messages.
Definition: notmuch.c:940
static const char * get_message_last_filename(notmuch_message_t *msg)
Get a message's last filename.
Definition: notmuch.c:661
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:861
char * nm_url_from_query(struct Mailbox *m, char *buf, size_t buflen)
Turn a query into a URL.
Definition: notmuch.c:1579
static char * get_folder_from_path(const char *path)
Find an email's folder from its path.
Definition: notmuch.c:574
void nm_init(void)
Setup feature commands.
Definition: notmuch.c:108
static char * nm2mutt_message_id(const char *id)
Converts notmuch message Id to neomutt message Id.
Definition: notmuch.c:599
char * nm_email_get_folder(struct Email *e)
Get the folder for a Email.
Definition: notmuch.c:1469
static void append_message(struct HeaderCache *hc, struct Mailbox *m, notmuch_message_t *msg, bool dedup)
Associate a message.
Definition: notmuch.c:748
static int update_email_tags(struct Email *e, notmuch_message_t *msg)
Update the Email's tags from Notmuch.
Definition: notmuch.c:481
bool nm_message_is_still_queried(struct Mailbox *m, struct Email *e)
Is a message still visible in the query?
Definition: notmuch.c:1698
static int update_email_flags(struct Mailbox *m, struct Email *e, const char *tag_str)
Update the Email's flags.
Definition: notmuch.c:1159
static int remove_filename(struct Mailbox *m, const char *path)
Delete a file.
Definition: notmuch.c:1267
static void progress_setup(struct Mailbox *m)
Set up the Progress Bar.
Definition: notmuch.c:678
static void nm_hcache_close(struct HeaderCache **ptr)
Close the header cache.
Definition: notmuch.c:132
void nm_query_window_forward(void)
Function to move the current search window forward in time.
Definition: notmuch.c:1655
static notmuch_messages_t * get_messages(notmuch_query_t *query)
Load messages for a query.
Definition: notmuch.c:912
const char NmUrlProtocol[]
Protocol string for Notmuch URLs.
Definition: notmuch.c:101
static void nm_progress_update(struct Mailbox *m)
Update the progress counter.
Definition: notmuch.c:697
static struct Email * get_mutt_email(struct Mailbox *m, notmuch_message_t *msg)
Get the Email of a Notmuch message.
Definition: notmuch.c:714
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:352
GUI display the mailboxes in a side panel.
volatile sig_atomic_t SigInt
true after SIGINT is received
Definition: signal.c:66
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
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:91
enum MailboxType type
Mailbox type, e.g. MUTT_IMAP.
Definition: mxapi.h:92
Container for Accounts, Notifications.
Definition: neomutt.h:42
struct ConfigSubset * sub
Inherited config items.
Definition: neomutt.h:46
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:297
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:386
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