6 * Copyright (C) 1996-2002,2010,2013 Michael R. Elkins <me@mutt.org>
7 * Copyright (C) 1999-2003 Thomas Roessler <roessler@does-not-exist.org>
8 * Copyright (C) 2016-2018 Richard Russon <rich@flatcap.org>
9 * Copyright (C) 2019 Pietro Cerutti <gahr@gahr.ch>
12 * This program is free software: you can redistribute it and/or modify it under
13 * the terms of the GNU General Public License as published by the Free Software
14 * Foundation, either version 2 of the License, or (at your option) any later
17 * This program is distributed in the hope that it will be useful, but WITHOUT
18 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
19 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
22 * You should have received a copy of the GNU General Public License along with
23 * this program. If not, see <http://www.gnu.org/licenses/>.
27 * @page mx Mailbox multiplexor
39 #include "mutt/mutt.h"
40 #include "address/lib.h"
41 #include "email/lib.h"
51 #include "maildir/lib.h"
52 #include "mbox/mbox.h"
53 #include "mutt_header.h"
54 #include "mutt_logging.h"
55 #include "mutt_mailbox.h"
65 #include "imap/imap.h"
71 #include "nntp/nntp.h"
74 #include "notmuch/mutt_notmuch.h"
80 /* These Config Variables are only used in mx.c */
81 unsigned char C_CatchupNewsgroup; ///< Config: (nntp) Mark all articles as read when leaving a newsgroup
82 bool C_KeepFlagged; ///< Config: Don't move flagged messages from #C_Spoolfile to #C_Mbox
83 unsigned char C_MboxType; ///< Config: Default type for creating new mailboxes
84 unsigned char C_Move; ///< Config: Move emails from #C_Spoolfile to #C_Mbox when read
85 char *C_Trash; ///< Config: Folder to put deleted emails
88 static struct Mapping MagicMap[] = {
89 { "mbox", MUTT_MBOX, },
90 { "MMDF", MUTT_MMDF, },
92 { "Maildir", MUTT_MAILDIR, },
97 struct EnumDef MagicDef = {
100 (struct Mapping *) &MagicMap,
104 * mx_ops - All the Mailbox backends
106 static const struct MxOps *mx_ops[] = {
107 /* These mailboxes can be recognised by their Url scheme */
121 /* Local mailboxes */
127 /* If everything else fails... */
128 #ifdef USE_COMPRESSED
135 * mx_get_ops - Get mailbox operations
136 * @param magic Mailbox magic number
137 * @retval ptr Mailbox function
140 const struct MxOps *mx_get_ops(enum MailboxType magic)
142 for (const struct MxOps **ops = mx_ops; *ops; ops++)
143 if ((*ops)->magic == magic)
150 * mutt_is_spool - Is this the spoolfile?
151 * @param str Name to check
152 * @retval true It is the spoolfile
154 static bool mutt_is_spool(const char *str)
156 return mutt_str_strcmp(C_Spoolfile, str) == 0;
160 * mx_access - Wrapper for access, checks permissions on a given mailbox
161 * @param path Path of mailbox
162 * @param flags Flags, e.g. W_OK
163 * @retval 0 Success, allowed
164 * @retval <0 Failure, not allowed
166 * We may be interested in using ACL-style flags at some point, currently we
167 * use the normal access() flags.
169 int mx_access(const char *path, int flags)
172 if (imap_path_probe(path, NULL) == MUTT_IMAP)
173 return imap_access(path);
176 return access(path, flags);
180 * mx_open_mailbox_append - Open a mailbox for appending
182 * @param flags Flags, see #OpenMailboxFlags
186 static int mx_open_mailbox_append(struct Mailbox *m, OpenMailboxFlags flags)
194 if ((m->magic == MUTT_UNKNOWN) || (m->magic == MUTT_MAILBOX_ERROR))
196 m->magic = mx_path_probe(mailbox_path(m), NULL);
198 if (m->magic == MUTT_UNKNOWN)
200 if (flags & (MUTT_APPEND | MUTT_NEWFOLDER))
202 m->magic = MUTT_MAILBOX_ERROR;
206 mutt_error(_("%s is not a mailbox"), mailbox_path(m));
211 if (m->magic == MUTT_MAILBOX_ERROR)
213 if (stat(mailbox_path(m), &sb) == -1)
217 #ifdef USE_COMPRESSED
218 if (mutt_comp_can_append(m))
219 m->magic = MUTT_COMPRESSED;
222 m->magic = C_MboxType;
223 flags |= MUTT_APPENDNEW;
227 mutt_perror(mailbox_path(m));
235 m->mx_ops = mx_get_ops(m->magic);
238 if (!m->mx_ops || !m->mx_ops->mbox_open_append)
241 int rc = m->mx_ops->mbox_open_append(m, flags);
247 * mx_mbox_open - Open a mailbox and parse it
248 * @param m Mailbox to open
249 * @param flags Flags, see #OpenMailboxFlags
250 * @retval ptr Mailbox context
253 struct Context *mx_mbox_open(struct Mailbox *m, OpenMailboxFlags flags)
258 struct Context *ctx = ctx_new();
261 struct EventContext ev_ctx = { ctx };
262 notify_send(ctx->notify, NT_CONTEXT, NT_CONTEXT_OPEN, IP & ev_ctx);
264 // If the Mailbox is closed, Context->mailbox must be set to NULL
265 notify_observer_add(m->notify, NT_MAILBOX, 0, ctx_mailbox_observer, IP ctx);
267 if ((m->magic == MUTT_UNKNOWN) && (flags & (MUTT_NEWFOLDER | MUTT_APPEND)))
269 m->magic = C_MboxType;
270 m->mx_ops = mx_get_ops(m->magic);
275 struct Account *a = mx_ac_find(m);
276 bool new_account = false;
279 a = account_new(NULL, NeoMutt->sub);
283 if (mx_ac_add(a, m) < 0)
294 neomutt_account_add(NeoMutt, a);
298 ctx->msg_not_read_yet = -1;
299 ctx->collapsed = false;
304 m->rights = MUTT_ACL_ALL;
306 m->quiet = (flags & MUTT_QUIET);
307 if (flags & MUTT_READONLY)
309 m->peekonly = (flags & MUTT_PEEK);
311 if (flags & (MUTT_APPEND | MUTT_NEWFOLDER))
313 if (mx_open_mailbox_append(ctx->mailbox, flags) != 0)
315 mx_fastclose_mailbox(m);
322 if (m->magic == MUTT_UNKNOWN)
324 m->magic = mx_path_probe(mailbox_path(m), NULL);
325 m->mx_ops = mx_get_ops(m->magic);
328 if ((m->magic == MUTT_UNKNOWN) || (m->magic == MUTT_MAILBOX_ERROR) || !m->mx_ops)
330 if (m->magic == MUTT_MAILBOX_ERROR)
331 mutt_perror(mailbox_path(m));
332 else if ((m->magic == MUTT_UNKNOWN) || !m->mx_ops)
333 mutt_error(_("%s is not a mailbox"), mailbox_path(m));
335 mx_fastclose_mailbox(m);
340 mutt_make_label_hash(m);
342 /* if the user has a 'push' command in their .neomuttrc, or in a folder-hook,
343 * it will cause the progress messages not to be displayed because
344 * mutt_refresh() will think we are in the middle of a macro. so set a
345 * flag to indicate that we should really refresh the screen. */
346 OptForceRefresh = true;
349 mutt_message(_("Reading %s..."), mailbox_path(m));
351 int rc = m->mx_ops->mbox_open(ctx->mailbox);
356 if ((rc == 0) || (rc == -2))
358 if ((flags & MUTT_NOSORT) == 0)
360 /* avoid unnecessary work since the mailbox is completely unthreaded
362 OptSortSubthreads = false;
363 OptNeedRescore = false;
369 mutt_error(_("Reading from %s interrupted..."), mailbox_path(m));
370 mutt_sort_headers(ctx, true);
375 mx_fastclose_mailbox(m);
380 OptForceRefresh = false;
386 * mx_fastclose_mailbox - free up memory associated with the Mailbox
389 void mx_fastclose_mailbox(struct Mailbox *m)
398 /* never announce that a mailbox we've just left has new mail. #3290
399 * TODO: really belongs in mx_mbox_close, but this is a nice hook point */
401 mutt_mailbox_set_notified(m);
404 m->mx_ops->mbox_close(m);
406 mailbox_changed(m, MBN_CLOSED);
408 mutt_hash_free(&m->subj_hash);
409 mutt_hash_free(&m->id_hash);
410 mutt_hash_free(&m->label_hash);
414 for (int i = 0; i < m->msg_count; i++)
415 email_free(&m->emails[i]);
422 * sync_mailbox - save changes to disk
424 * @param index_hint Current email
428 static int sync_mailbox(struct Mailbox *m, int *index_hint)
430 if (!m || !m->mx_ops || !m->mx_ops->mbox_sync)
435 /* L10N: Displayed before/as a mailbox is being synced */
436 mutt_message(_("Writing %s..."), mailbox_path(m));
439 int rc = m->mx_ops->mbox_sync(m, index_hint);
440 if ((rc != 0) && !m->quiet)
442 /* L10N: Displayed if a mailbox sync fails */
443 mutt_error(_("Unable to write %s"), mailbox_path(m));
450 * trash_append - move deleted mails to the trash folder
455 static int trash_append(struct Mailbox *m)
461 int opt_confappend, rc;
463 if (!C_Trash || (m->msg_deleted == 0) || ((m->magic == MUTT_MAILDIR) && C_MaildirTrash))
470 for (int i = 0; i < m->msg_count; i++)
472 if (m->emails[i]->deleted && (!m->emails[i]->purge))
480 if (delmsgcount == 0)
481 return 0; /* nothing to be done */
483 /* avoid the "append messages" prompt */
484 opt_confappend = C_Confirmappend;
486 C_Confirmappend = false;
487 rc = mutt_save_confirm(C_Trash, &st);
489 C_Confirmappend = true;
492 /* L10N: Although we know the precise number of messages, we do not show it to the user.
493 So feel free to use a "generic plural" as plural translation if your language has one. */
494 mutt_error(ngettext("message not deleted", "messages not deleted", delmsgcount));
498 if ((lstat(mailbox_path(m), &stc) == 0) && (stc.st_ino == st.st_ino) &&
499 (stc.st_dev == st.st_dev) && (stc.st_rdev == st.st_rdev))
501 return 0; /* we are in the trash folder: simple sync */
505 if ((m->magic == MUTT_IMAP) && (imap_path_probe(C_Trash, NULL) == MUTT_IMAP))
507 if (imap_fast_trash(m, C_Trash) == 0)
512 struct Mailbox *m_trash = mx_path_resolve(C_Trash);
513 struct Context *ctx_trash = mx_mbox_open(m_trash, MUTT_OPEN_NO_FLAGS);
516 bool old_append = m_trash->append;
517 m_trash->append = true;
519 /* continue from initial scan above */
520 for (int i = first_del; i < m->msg_count; i++)
522 if (m->emails[i]->deleted && (!m->emails[i]->purge))
524 if (mutt_append_message(ctx_trash->mailbox, m, m->emails[i],
525 MUTT_CM_NO_FLAGS, CH_NO_FLAGS) == -1)
527 m_trash->append = old_append;
528 mx_mbox_close(&ctx_trash);
534 m_trash->append = old_append;
535 mx_mbox_close(&ctx_trash);
539 mutt_error(_("Can't open trash folder"));
540 mailbox_free(&m_trash);
548 * mx_mbox_close - Save changes and close mailbox
549 * @param[out] ptr Mailbox
553 * @note Context will be freed after it's closed
555 int mx_mbox_close(struct Context **ptr)
560 struct Context *ctx = *ptr;
561 if (!ctx || !ctx->mailbox)
564 struct Mailbox *m = ctx->mailbox;
566 int i, read_msgs = 0;
567 enum QuadOption move_messages = MUTT_NO;
568 enum QuadOption purge = MUTT_YES;
570 char buf[PATH_MAX + 64];
572 if (m->readonly || m->dontwrite || m->append)
574 mx_fastclose_mailbox(m);
580 if ((m->msg_unread != 0) && (m->magic == MUTT_NNTP))
582 struct NntpMboxData *mdata = m->mdata;
584 if (mdata && mdata->adata && mdata->group)
586 enum QuadOption ans =
587 query_quadoption(C_CatchupNewsgroup, _("Mark all articles read?"));
588 if (ans == MUTT_ABORT)
590 else if (ans == MUTT_YES)
591 mutt_newsgroup_catchup(m, mdata->adata, mdata->group);
596 for (i = 0; i < m->msg_count; i++)
600 if (!m->emails[i]->deleted && m->emails[i]->read && !(m->emails[i]->flagged && C_KeepFlagged))
607 /* don't need to move articles from newsgroup */
608 if (m->magic == MUTT_NNTP)
612 if ((read_msgs != 0) && (C_Move != MUTT_NO))
615 char *p = mutt_find_hook(MUTT_MBOX_HOOK, mailbox_path(m));
619 mutt_str_strfcpy(mbox, p, sizeof(mbox));
623 mutt_str_strfcpy(mbox, C_Mbox, sizeof(mbox));
624 is_spool = mutt_is_spool(mailbox_path(m)) && !mutt_is_spool(mbox);
627 if (is_spool && (mbox[0] != '\0'))
629 mutt_expand_path(mbox, sizeof(mbox));
630 snprintf(buf, sizeof(buf),
631 /* L10N: The first argument is the number of read messages to be
632 moved, the second argument is the target mailbox. */
633 ngettext("Move %d read message to %s?", "Move %d read messages to %s?", read_msgs),
635 move_messages = query_quadoption(C_Move, buf);
636 if (move_messages == MUTT_ABORT)
641 /* There is no point in asking whether or not to purge if we are
642 * just marking messages as "trash". */
643 if ((m->msg_deleted != 0) && !((m->magic == MUTT_MAILDIR) && C_MaildirTrash))
645 snprintf(buf, sizeof(buf),
646 ngettext("Purge %d deleted message?", "Purge %d deleted messages?", m->msg_deleted),
648 purge = query_quadoption(C_Delete, buf);
649 if (purge == MUTT_ABORT)
655 for (i = 0; i < m->msg_count; i++)
657 if (!m->emails[i]->deleted && !m->emails[i]->old && !m->emails[i]->read)
658 mutt_set_flag(m, m->emails[i], MUTT_OLD, true);
665 mutt_message(_("Moving read messages to %s..."), mbox);
668 /* try to use server-side copy first */
671 if ((m->magic == MUTT_IMAP) && (imap_path_probe(mbox, NULL) == MUTT_IMAP))
673 /* tag messages for moving, and clear old tags, if any */
674 for (i = 0; i < m->msg_count; i++)
676 if (m->emails[i]->read && !m->emails[i]->deleted &&
677 !(m->emails[i]->flagged && C_KeepFlagged))
679 m->emails[i]->tagged = true;
683 m->emails[i]->tagged = false;
687 i = imap_copy_messages(ctx->mailbox, NULL, mbox, true);
690 if (i == 0) /* success */
692 else if (i == -1) /* horrible error, bail */
694 else /* use regular append-copy mode */
697 struct Mailbox *m_read = mx_path_resolve(mbox);
698 struct Context *ctx_read = mx_mbox_open(m_read, MUTT_APPEND);
701 mailbox_free(&m_read);
705 for (i = 0; i < m->msg_count; i++)
707 if (m->emails[i]->read && !m->emails[i]->deleted &&
708 !(m->emails[i]->flagged && C_KeepFlagged))
710 if (mutt_append_message(ctx_read->mailbox, ctx->mailbox, m->emails[i],
711 MUTT_CM_NO_FLAGS, CH_UPDATE_LEN) == 0)
713 mutt_set_flag(m, m->emails[i], MUTT_DELETE, true);
714 mutt_set_flag(m, m->emails[i], MUTT_PURGE, true);
718 mx_mbox_close(&ctx_read);
724 mx_mbox_close(&ctx_read);
727 else if (!m->changed && (m->msg_deleted == 0))
730 mutt_message(_("Mailbox is unchanged"));
731 if ((m->magic == MUTT_MBOX) || (m->magic == MUTT_MMDF))
732 mbox_reset_atime(m, NULL);
733 mx_fastclose_mailbox(m);
738 /* copy mails to the trash before expunging */
739 if (purge && (m->msg_deleted != 0) && (mutt_str_strcmp(mailbox_path(m), C_Trash) != 0))
741 if (trash_append(ctx->mailbox) != 0)
746 /* allow IMAP to preserve the deleted flag across sessions */
747 if (m->magic == MUTT_IMAP)
749 int check = imap_sync_mailbox(ctx->mailbox, (purge != MUTT_NO), true);
756 if (purge == MUTT_NO)
758 for (i = 0; i < m->msg_count; i++)
760 m->emails[i]->deleted = false;
761 m->emails[i]->purge = false;
766 if (m->changed || (m->msg_deleted != 0))
768 int check = sync_mailbox(ctx->mailbox, NULL);
778 mutt_message(_("%d kept, %d moved, %d deleted"),
779 m->msg_count - m->msg_deleted, read_msgs, m->msg_deleted);
782 mutt_message(_("%d kept, %d deleted"), m->msg_count - m->msg_deleted, m->msg_deleted);
785 if ((m->msg_count == m->msg_deleted) &&
786 ((m->magic == MUTT_MMDF) || (m->magic == MUTT_MBOX)) &&
787 !mutt_is_spool(mailbox_path(m)) && !C_SaveEmpty)
789 mutt_file_unlink_empty(mailbox_path(m));
793 if ((purge == MUTT_YES) && (m->msg_deleted != 0))
795 for (i = 0; i < m->msg_count; i++)
797 if (m->emails[i]->deleted && !m->emails[i]->read)
800 if (!m->emails[i]->old)
803 if (m->emails[i]->deleted && m->emails[i]->flagged)
809 mx_fastclose_mailbox(m);
816 * mx_mbox_sync - Save changes to mailbox
817 * @param[in] m Mailbox
818 * @param[out] index_hint Currently selected Email
822 int mx_mbox_sync(struct Mailbox *m, int *index_hint)
829 int msgcount, deleted;
833 char buf[256], tmp[256];
834 if (km_expand_key(buf, sizeof(buf), km_find_func(MENU_MAIN, OP_TOGGLE_WRITE)))
835 snprintf(tmp, sizeof(tmp), _(" Press '%s' to toggle write"), buf);
837 mutt_str_strfcpy(tmp, _("Use 'toggle-write' to re-enable write"), sizeof(tmp));
839 mutt_error(_("Mailbox is marked unwritable. %s"), tmp);
842 else if (m->readonly)
844 mutt_error(_("Mailbox is read-only"));
848 if (!m->changed && (m->msg_deleted == 0))
851 mutt_message(_("Mailbox is unchanged"));
855 if (m->msg_deleted != 0)
859 snprintf(buf, sizeof(buf),
860 ngettext("Purge %d deleted message?", "Purge %d deleted messages?", m->msg_deleted),
862 purge = query_quadoption(C_Delete, buf);
863 if (purge == MUTT_ABORT)
865 else if (purge == MUTT_NO)
868 return 0; /* nothing to do! */
869 /* let IMAP servers hold on to D flags */
870 if (m->magic != MUTT_IMAP)
872 for (int i = 0; i < m->msg_count; i++)
874 m->emails[i]->deleted = false;
875 m->emails[i]->purge = false;
880 mailbox_changed(m, MBN_UNTAG);
883 /* really only for IMAP - imap_sync_mailbox results in a call to
884 * ctx_update_tables, so m->msg_deleted is 0 when it comes back */
885 msgcount = m->msg_count;
886 deleted = m->msg_deleted;
888 if (purge && (m->msg_deleted != 0) && (mutt_str_strcmp(mailbox_path(m), C_Trash) != 0))
890 if (trash_append(m) != 0)
895 if (m->magic == MUTT_IMAP)
896 rc = imap_sync_mailbox(m, purge, false);
899 rc = sync_mailbox(m, index_hint);
903 if ((m->magic == MUTT_IMAP) && !purge)
906 mutt_message(_("Mailbox checkpointed"));
912 mutt_message(_("%d kept, %d deleted"), msgcount - deleted, deleted);
917 if ((m->msg_count == m->msg_deleted) &&
918 ((m->magic == MUTT_MBOX) || (m->magic == MUTT_MMDF)) &&
919 !mutt_is_spool(mailbox_path(m)) && !C_SaveEmpty)
921 unlink(mailbox_path(m));
922 mx_fastclose_mailbox(m);
926 /* if we haven't deleted any messages, we don't need to resort */
927 /* ... except for certain folder formats which need "unsorted"
928 * sort order in order to synchronize folders.
930 * MH and maildir are safe. mbox-style seems to need re-sorting,
931 * at least with the new threading code. */
932 if (purge || ((m->magic != MUTT_MAILDIR) && (m->magic != MUTT_MH)))
934 /* IMAP does this automatically after handling EXPUNGE */
935 if (m->magic != MUTT_IMAP)
937 mailbox_changed(m, MBN_UPDATE);
938 mailbox_changed(m, MBN_RESORT);
947 * mx_msg_open_new - Open a new message
948 * @param m Destination mailbox
949 * @param e Message being copied (required for maildir support, because the filename depends on the message flags)
950 * @param flags Flags, see #MsgOpenFlags
951 * @retval ptr New Message
953 struct Message *mx_msg_open_new(struct Mailbox *m, struct Email *e, MsgOpenFlags flags)
958 struct Address *p = NULL;
959 struct Message *msg = NULL;
961 if (!m->mx_ops || !m->mx_ops->msg_open_new)
963 mutt_debug(LL_DEBUG1, "function unimplemented for mailbox type %d\n", m->magic);
967 msg = mutt_mem_calloc(1, sizeof(struct Message));
972 msg->flags.flagged = e->flagged;
973 msg->flags.replied = e->replied;
974 msg->flags.read = e->read;
975 msg->flags.draft = (flags & MUTT_SET_DRAFT);
976 msg->received = e->received;
979 if (msg->received == 0)
980 msg->received = mutt_date_epoch();
982 if (m->mx_ops->msg_open_new(m, msg, e) == 0)
984 if (m->magic == MUTT_MMDF)
985 fputs(MMDF_SEP, msg->fp);
987 if (((m->magic == MUTT_MBOX) || (m->magic == MUTT_MMDF)) && flags & MUTT_ADD_FROM)
991 p = TAILQ_FIRST(&e->env->return_path);
993 p = TAILQ_FIRST(&e->env->sender);
995 p = TAILQ_FIRST(&e->env->from);
998 char buf[64] = { 0 };
999 mutt_date_localtime_format(buf, sizeof(buf), "%a %b %e %H:%M:%S %Y\n", msg->received);
1000 fprintf(msg->fp, "From %s %s", p ? p->mailbox : NONULL(Username), buf);
1010 * mx_mbox_check - Check for new mail - Wrapper for MxOps::mbox_check()
1012 * @param index_hint Current email
1013 * @retval >0 Success, e.g. #MUTT_NEW_MAIL
1014 * @retval 0 Success, no change
1015 * @retval -1 Failure
1017 int mx_mbox_check(struct Mailbox *m, int *index_hint)
1019 if (!m || !m->mx_ops)
1022 int rc = m->mx_ops->mbox_check(m, index_hint);
1023 if ((rc == MUTT_NEW_MAIL) || (rc == MUTT_REOPENED))
1024 mailbox_changed(m, MBN_INVALID);
1030 * mx_msg_open - return a stream pointer for a message
1032 * @param msgno Message number
1033 * @retval ptr Message
1034 * @retval NULL Error
1036 struct Message *mx_msg_open(struct Mailbox *m, int msgno)
1041 struct Message *msg = NULL;
1043 if (!m->mx_ops || !m->mx_ops->msg_open)
1045 mutt_debug(LL_DEBUG1, "function not implemented for mailbox type %d\n", m->magic);
1049 msg = mutt_mem_calloc(1, sizeof(struct Message));
1050 if (m->mx_ops->msg_open(m, msg, msgno) < 0)
1057 * mx_msg_commit - Commit a message to a folder - Wrapper for MxOps::msg_commit()
1059 * @param msg Message to commit
1061 * @retval -1 Failure
1063 int mx_msg_commit(struct Mailbox *m, struct Message *msg)
1065 if (!m || !m->mx_ops || !m->mx_ops->msg_commit)
1068 if (!(msg->write && m->append))
1070 mutt_debug(LL_DEBUG1, "msg->write = %d, m->append = %d\n", msg->write, m->append);
1074 return m->mx_ops->msg_commit(m, msg);
1078 * mx_msg_close - Close a message
1079 * @param[in] m Mailbox
1080 * @param[out] msg Message to close
1082 * @retval -1 Failure
1084 int mx_msg_close(struct Mailbox *m, struct Message **msg)
1086 if (!m || !msg || !*msg)
1091 if (m->mx_ops && m->mx_ops->msg_close)
1092 rc = m->mx_ops->msg_close(m, *msg);
1096 mutt_debug(LL_DEBUG1, "unlinking %s\n", (*msg)->path);
1097 unlink((*msg)->path);
1098 FREE(&(*msg)->path);
1101 FREE(&(*msg)->committed_path);
1107 * mx_alloc_memory - Create storage for the emails
1110 void mx_alloc_memory(struct Mailbox *m)
1112 size_t s = MAX(sizeof(struct Email *), sizeof(int));
1114 if ((m->email_max + 25) * s < m->email_max * s)
1116 mutt_error(_("Out of memory"));
1123 mutt_mem_realloc(&m->emails, sizeof(struct Email *) * m->email_max);
1124 mutt_mem_realloc(&m->v2r, sizeof(int) * m->email_max);
1128 m->emails = mutt_mem_calloc(m->email_max, sizeof(struct Email *));
1129 m->v2r = mutt_mem_calloc(m->email_max, sizeof(int));
1131 for (int i = m->msg_count; i < m->email_max; i++)
1133 m->emails[i] = NULL;
1139 * mx_check_empty - Is the mailbox empty
1140 * @param path Mailbox to check
1141 * @retval 1 Mailbox is empty
1142 * @retval 0 Mailbox contains mail
1145 int mx_check_empty(const char *path)
1147 switch (mx_path_probe(path, NULL))
1151 return mutt_file_check_empty(path);
1153 return mh_check_empty(path);
1155 return maildir_check_empty(path);
1159 int rc = imap_path_status(path, false);
1176 * mx_tags_edit - start the tag editor of the mailbox
1178 * @param tags Existing tags
1179 * @param buf Buffer for the results
1180 * @param buflen Length of the buffer
1182 * @retval 0 No valid user input
1183 * @retval 1 Buffer set
1185 int mx_tags_edit(struct Mailbox *m, const char *tags, char *buf, size_t buflen)
1190 if (m->mx_ops->tags_edit)
1191 return m->mx_ops->tags_edit(m, tags, buf, buflen);
1193 mutt_message(_("Folder doesn't support tagging, aborting"));
1198 * mx_tags_commit - Save tags to the Mailbox - Wrapper for MxOps::tags_commit()
1201 * @param tags Tags to save
1203 * @retval -1 Failure
1205 int mx_tags_commit(struct Mailbox *m, struct Email *e, char *tags)
1210 if (m->mx_ops->tags_commit)
1211 return m->mx_ops->tags_commit(m, e, tags);
1213 mutt_message(_("Folder doesn't support tagging, aborting"));
1218 * mx_tags_is_supported - return true if mailbox support tagging
1220 * @retval true Tagging is supported
1222 bool mx_tags_is_supported(struct Mailbox *m)
1224 return m && m->mx_ops->tags_commit && m->mx_ops->tags_edit;
1228 * mx_path_probe - Find a mailbox that understands a path
1229 * @param[in] path Path to examine
1230 * @param[out] st stat buffer (OPTIONAL, for local mailboxes)
1231 * @retval num Type, e.g. #MUTT_IMAP
1233 enum MailboxType mx_path_probe(const char *path, struct stat *st)
1236 return MUTT_UNKNOWN;
1238 static const struct MxOps *no_stat[] = {
1253 static const struct MxOps *with_stat[] = {
1254 &MxMaildirOps, &MxMboxOps, &MxMhOps, &MxMmdfOps,
1255 #ifdef USE_COMPRESSED
1260 enum MailboxType rc;
1262 for (size_t i = 0; i < mutt_array_size(no_stat); i++)
1264 rc = no_stat[i]->path_probe(path, NULL);
1265 if (rc != MUTT_UNKNOWN)
1269 struct stat st2 = { 0 };
1273 if (stat(path, st) != 0)
1275 mutt_debug(LL_DEBUG1, "unable to stat %s: %s (errno %d)\n", path, strerror(errno), errno);
1276 return MUTT_UNKNOWN;
1279 for (size_t i = 0; i < mutt_array_size(with_stat); i++)
1281 rc = with_stat[i]->path_probe(path, st);
1282 if (rc != MUTT_UNKNOWN)
1290 * mx_path_canon - Canonicalise a mailbox path - Wrapper for MxOps::path_canon()
1292 int mx_path_canon(char *buf, size_t buflen, const char *folder, enum MailboxType *magic)
1297 for (size_t i = 0; i < 3; i++)
1299 /* Look for !! ! - < > or ^ followed by / or NUL */
1300 if ((buf[0] == '!') && (buf[1] == '!'))
1302 if (((buf[2] == '/') || (buf[2] == '\0')))
1304 mutt_str_inline_replace(buf, buflen, 2, LastFolder);
1307 else if ((buf[0] == '+') || (buf[0] == '='))
1309 size_t folder_len = mutt_str_strlen(folder);
1310 if ((folder_len > 0) && (folder[folder_len - 1] != '/'))
1313 mutt_str_inline_replace(buf, buflen, 0, folder);
1317 mutt_str_inline_replace(buf, buflen, 1, folder);
1320 else if ((buf[1] == '/') || (buf[1] == '\0'))
1324 mutt_str_inline_replace(buf, buflen, 1, C_Spoolfile);
1326 else if (buf[0] == '-')
1328 mutt_str_inline_replace(buf, buflen, 1, LastFolder);
1330 else if (buf[0] == '<')
1332 mutt_str_inline_replace(buf, buflen, 1, C_Record);
1334 else if (buf[0] == '>')
1336 mutt_str_inline_replace(buf, buflen, 1, C_Mbox);
1338 else if (buf[0] == '^')
1340 mutt_str_inline_replace(buf, buflen, 1, CurrentFolder);
1342 else if (buf[0] == '~')
1344 mutt_str_inline_replace(buf, buflen, 1, HomeDir);
1347 else if (buf[0] == '@')
1349 /* elm compatibility, @ expands alias to user name */
1350 struct AddressList *al = mutt_alias_lookup(buf + 1);
1351 if (TAILQ_EMPTY(al))
1354 struct Email *e = email_new();
1355 e->env = mutt_env_new();
1356 mutt_addrlist_copy(&e->env->from, al, false);
1357 mutt_addrlist_copy(&e->env->to, al, false);
1358 mutt_default_save(buf, buflen, e);
1368 // if (!folder) //XXX - use inherited version, or pass NULL to backend?
1371 enum MailboxType magic2 = mx_path_probe(buf, NULL);
1374 const struct MxOps *ops = mx_get_ops(magic2);
1375 if (!ops || !ops->path_canon)
1378 if (ops->path_canon(buf, buflen) < 0)
1380 mutt_path_canon(buf, buflen, HomeDir);
1387 * mx_path_canon2 - XXX
1388 * canonicalise the path to realpath
1390 int mx_path_canon2(struct Mailbox *m, const char *folder)
1398 mutt_str_strfcpy(buf, m->realpath, sizeof(buf));
1400 mutt_str_strfcpy(buf, mailbox_path(m), sizeof(buf));
1402 int rc = mx_path_canon(buf, sizeof(buf), folder, &m->magic);
1404 mutt_str_replace(&m->realpath, buf);
1408 m->mx_ops = mx_get_ops(m->magic);
1409 mutt_buffer_strcpy(&m->pathbuf, m->realpath);
1416 * mx_path_pretty - Abbreviate a mailbox path - Wrapper for MxOps::path_pretty()
1418 int mx_path_pretty(char *buf, size_t buflen, const char *folder)
1420 enum MailboxType magic = mx_path_probe(buf, NULL);
1421 const struct MxOps *ops = mx_get_ops(magic);
1425 if (!ops->path_canon)
1428 if (ops->path_canon(buf, buflen) < 0)
1431 if (!ops->path_pretty)
1434 if (ops->path_pretty(buf, buflen, folder) < 0)
1441 * mx_path_parent - Find the parent of a mailbox path - Wrapper for MxOps::path_parent()
1443 int mx_path_parent(char *buf, size_t buflen)
1452 * mx_msg_padding_size - Bytes of padding between messages - Wrapper for MxOps::msg_padding_size()
1454 * @retval num Number of bytes of padding
1456 * mmdf and mbox add separators, which leads a small discrepancy when computing
1457 * vsize for a limited view.
1459 int mx_msg_padding_size(struct Mailbox *m)
1461 if (!m || !m->mx_ops || !m->mx_ops->msg_padding_size)
1464 return m->mx_ops->msg_padding_size(m);
1470 struct Account *mx_ac_find(struct Mailbox *m)
1472 if (!m || !m->mx_ops)
1475 struct Account *np = NULL;
1476 TAILQ_FOREACH(np, &NeoMutt->accounts, entries)
1478 if (np->magic != m->magic)
1481 if (m->mx_ops->ac_find(np, m->realpath))
1489 * mx_mbox_find - XXX
1491 * find a mailbox on an account
1493 struct Mailbox *mx_mbox_find(struct Account *a, const char *path)
1498 struct MailboxNode *np = NULL;
1499 STAILQ_FOREACH(np, &a->mailboxes, entries)
1501 if (mutt_str_strcmp(np->mailbox->realpath, path) == 0)
1509 * mx_mbox_find2 - XXX
1511 * find a mailbox on an account
1513 struct Mailbox *mx_mbox_find2(const char *path)
1519 mutt_str_strfcpy(buf, path, sizeof(buf));
1520 mx_path_canon(buf, sizeof(buf), C_Folder, NULL);
1522 struct Account *np = NULL;
1523 TAILQ_FOREACH(np, &NeoMutt->accounts, entries)
1525 struct Mailbox *m = mx_mbox_find(np, buf);
1534 * mx_path_resolve - XXX
1536 struct Mailbox *mx_path_resolve(const char *path)
1541 struct Mailbox *m = mx_mbox_find2(path);
1546 m->flags = MB_HIDDEN;
1547 mutt_buffer_strcpy(&m->pathbuf, path);
1548 mx_path_canon2(m, C_Folder);
1554 * mx_ac_add - Add a Mailbox to an Account - Wrapper for MxOps::ac_add()
1556 int mx_ac_add(struct Account *a, struct Mailbox *m)
1558 if (!a || !m || !m->mx_ops || !m->mx_ops->ac_add)
1561 if (m->mx_ops->ac_add(a, m) < 0)
1564 account_mailbox_add(a, m);
1569 * mx_ac_remove - Remove a Mailbox from an Account and delete Account if empty
1570 * @param m Mailbox to remove
1572 int mx_ac_remove(struct Mailbox *m)
1574 if (!m || !m->account)
1577 account_mailbox_remove(m->account, m);
1578 if (STAILQ_EMPTY(&m->account->mailboxes))
1580 neomutt_account_remove(NeoMutt, m->account);
1586 * mx_mbox_check_stats - Check the statistics for a mailbox - Wrapper for MxOps::mbox_check_stats()
1588 int mx_mbox_check_stats(struct Mailbox *m, int flags)
1593 return m->mx_ops->mbox_check_stats(m, flags);
1597 * mx_save_hcache - Save message to the header cache - Wrapper for MxOps::msg_save_hcache()
1601 * @retval -1 Failure
1603 * Write a single header out to the header cache.
1605 int mx_save_hcache(struct Mailbox *m, struct Email *e)
1607 if (!m->mx_ops || !m->mx_ops->msg_save_hcache)
1610 return m->mx_ops->msg_save_hcache(m, e);