]> granicus.if.org Git - neomutt/commitdiff
reorg maildir functions
authorRichard Russon <rich@flatcap.org>
Tue, 11 Sep 2018 16:49:34 +0000 (17:49 +0100)
committerRichard Russon <rich@flatcap.org>
Thu, 13 Sep 2018 01:03:09 +0000 (02:03 +0100)
maildir/mh.c

index 85920e92bc71c1ac4984848765d54feb0526e49e..781a93c74a1316807d313fc3ab17a4f0a8655d42 100644 (file)
@@ -349,88 +349,6 @@ static bool mh_valid_message(const char *s)
   return true;
 }
 
-/**
- * mh_mailbox - Check for new mail for a mh mailbox
- * @param mailbox     Mailbox to check
- * @param check_stats Also count total, new, and flagged messages
- * @retval true if the mailbox has new mail
- */
-bool mh_mailbox(struct Mailbox *mailbox, bool check_stats)
-{
-  struct MhSequences mhs = { 0 };
-  bool check_new = true;
-  bool rc = false;
-  DIR *dirp = NULL;
-  struct dirent *de = NULL;
-
-  /* when $mail_check_recent is set and the .mh_sequences file hasn't changed
-   * since the last mailbox visit, there is no "new mail" */
-  if (MailCheckRecent && mh_sequences_changed(mailbox) <= 0)
-  {
-    rc = false;
-    check_new = false;
-  }
-
-  if (!(check_new || check_stats))
-    return rc;
-
-  if (mh_read_sequences(&mhs, mailbox->path) < 0)
-    return false;
-
-  if (check_stats)
-  {
-    mailbox->msg_count = 0;
-    mailbox->msg_unread = 0;
-    mailbox->msg_flagged = 0;
-  }
-
-  for (int i = mhs.max; i > 0; i--)
-  {
-    if (check_stats && (mhs_check(&mhs, i) & MH_SEQ_FLAGGED))
-      mailbox->msg_flagged++;
-    if (mhs_check(&mhs, i) & MH_SEQ_UNSEEN)
-    {
-      if (check_stats)
-        mailbox->msg_unread++;
-      if (check_new)
-      {
-        /* if the first unseen message we encounter was in the mailbox during the
-           last visit, don't notify about it */
-        if (!MailCheckRecent || mh_already_notified(mailbox, i) == 0)
-        {
-          mailbox->has_new = true;
-          rc = true;
-        }
-        /* Because we are traversing from high to low, we can stop
-         * checking for new mail after the first unseen message.
-         * Whether it resulted in "new mail" or not. */
-        check_new = false;
-        if (!check_stats)
-          break;
-      }
-    }
-  }
-  mhs_free_sequences(&mhs);
-
-  if (check_stats)
-  {
-    dirp = opendir(mailbox->path);
-    if (dirp)
-    {
-      while ((de = readdir(dirp)))
-      {
-        if (*de->d_name == '.')
-          continue;
-        if (mh_valid_message(de->d_name))
-          mailbox->msg_count++;
-      }
-      closedir(dirp);
-    }
-  }
-
-  return rc;
-}
-
 /**
  * mh_mkstemp - Create a temporary file
  * @param[in]  mailbox Mailbox to create the file in
@@ -783,65 +701,6 @@ static void maildir_free_maildir(struct Maildir **md)
   }
 }
 
-/**
- * maildir_parse_flags - Parse Maildir file flags
- * @param h    Header of email
- * @param path Path to email file
- */
-void maildir_parse_flags(struct Header *h, const char *path)
-{
-  char *q = NULL;
-
-  h->flagged = false;
-  h->read = false;
-  h->replied = false;
-
-  char *p = strrchr(path, ':');
-  if (p && (mutt_str_strncmp(p + 1, "2,", 2) == 0))
-  {
-    p += 3;
-
-    mutt_str_replace(&h->maildir_flags, p);
-    q = h->maildir_flags;
-
-    while (*p)
-    {
-      switch (*p)
-      {
-        case 'F':
-          h->flagged = true;
-          break;
-
-        case 'R': /* replied */
-          h->replied = true;
-          break;
-
-        case 'S': /* seen */
-          h->read = true;
-          break;
-
-        case 'T': /* trashed */
-          if (!h->flagged || !FlagSafe)
-          {
-            h->trash = true;
-            h->deleted = true;
-          }
-          break;
-
-        default:
-          *q++ = *p;
-          break;
-      }
-      p++;
-    }
-  }
-
-  if (q == h->maildir_flags)
-    FREE(&h->maildir_flags);
-  else if (q)
-    *q = '\0';
-}
-
 /**
  * maildir_update_mtime - Update our record of the Maildir modification time
  * @param mailbox Mailbox
@@ -872,72 +731,6 @@ static void maildir_update_mtime(struct Mailbox *mailbox)
     mutt_get_stat_timespec(&mailbox->mtime, &st, MUTT_STAT_MTIME);
 }
 
-/**
- * maildir_parse_stream - Parse a Maildir message
- * @param magic  Mailbox type, e.g. #MUTT_MAILDIR
- * @param f      Mesage file handle
- * @param fname  Message filename
- * @param is_old true, if the email is old (read)
- * @param h      Email Header to populate (OPTIONAL)
- * @retval ptr Populated email Header
- *
- * Actually parse a maildir message.  This may also be used to fill
- * out a fake header structure generated by lazy maildir parsing.
- */
-struct Header *maildir_parse_stream(enum MailboxType magic, FILE *f,
-                                    const char *fname, bool is_old, struct Header *h)
-{
-  struct stat st;
-
-  if (!h)
-    h = mutt_header_new();
-  h->env = mutt_rfc822_read_header(f, h, false, false);
-
-  fstat(fileno(f), &st);
-
-  if (!h->received)
-    h->received = h->date_sent;
-
-  /* always update the length since we have fresh information available. */
-  h->content->length = st.st_size - h->content->offset;
-
-  h->index = -1;
-
-  if (magic == MUTT_MAILDIR)
-  {
-    /* maildir stores its flags in the filename, so ignore the
-     * flags in the header of the message
-     */
-
-    h->old = is_old;
-    maildir_parse_flags(h, fname);
-  }
-  return h;
-}
-
-/**
- * maildir_parse_message - Actually parse a maildir message
- * @param magic  Mailbox type, e.g. #MUTT_MAILDIR
- * @param fname  Message filename
- * @param is_old true, if the email is old (read)
- * @param h      Email Header to populate (OPTIONAL)
- * @retval ptr Populated email Header
- *
- * This may also be used to fill out a fake header structure generated by lazy
- * maildir parsing.
- */
-struct Header *maildir_parse_message(enum MailboxType magic, const char *fname,
-                                     bool is_old, struct Header *h)
-{
-  FILE *f = fopen(fname, "r");
-  if (!f)
-    return NULL;
-
-  h = maildir_parse_stream(magic, f, fname, is_old, h);
-  mutt_file_fclose(&f);
-  return h;
-}
-
 /**
  * maildir_parse_dir - Read a Maildir mailbox
  * @param mailbox  Mailbox
@@ -1418,17 +1211,6 @@ static void maildir_delayed_parsing(struct Mailbox *mailbox, struct Maildir **md
   mh_sort_natural(mailbox, md);
 }
 
-/**
- * mh_mbox_close - Implements MxOps::mbox_close()
- * @retval 0 Always
- */
-static int mh_mbox_close(struct Context *ctx)
-{
-  FREE(&ctx->mailbox->data);
-
-  return 0;
-}
-
 /**
  * mh_read_dir - Read a MH/maildir style mailbox
  * @param ctx    Mailbox
@@ -1511,60 +1293,43 @@ static int maildir_read_dir(struct Context *ctx)
 }
 
 /**
- * maildir_mbox_open - Open a Maildir mailbox
- * @param ctx Mailbox
- * @retval  0 Success
- * @retval -1 Failure
+ * ch_compare - qsort callback to sort characters
+ * @param a First  character to compare
+ * @param b Second character to compare
+ * @retval -1 a precedes b
+ * @retval  0 a and b are identical
+ * @retval  1 b precedes a
  */
-static int maildir_mbox_open(struct Context *ctx)
+static int ch_compare(const void *a, const void *b)
 {
-  return maildir_read_dir(ctx);
+  return (int) (*((const char *) a) - *((const char *) b));
 }
 
 /**
- * maildir_mbox_open_append - Implements MxOps::mbox_open_append()
+ * maildir_mh_open_message - Open a Maildir or MH message
+ * @param mailbox    Mailbox
+ * @param msg        Message to open
+ * @param msgno      Index number
+ * @param is_maildir true, if a Maildir
+ * @retval  0 Success
+ * @retval -1 Failure
  */
-static int maildir_mbox_open_append(struct Context *ctx, int flags)
+static int maildir_mh_open_message(struct Mailbox *mailbox, struct Message *msg,
+                                   int msgno, int is_maildir)
 {
-  if (!(flags & MUTT_APPENDNEW))
-  {
-    return 0;
-  }
+  struct Header *cur = mailbox->hdrs[msgno];
+  char path[PATH_MAX];
 
-  if (mkdir(ctx->mailbox->path, S_IRWXU))
-  {
-    mutt_perror(ctx->mailbox->path);
-    return -1;
-  }
+  snprintf(path, sizeof(path), "%s/%s", mailbox->path, cur->path);
 
-  char tmp[PATH_MAX];
-  snprintf(tmp, sizeof(tmp), "%s/cur", ctx->mailbox->path);
-  if (mkdir(tmp, S_IRWXU))
-  {
-    mutt_perror(tmp);
-    rmdir(ctx->mailbox->path);
-    return -1;
-  }
-
-  snprintf(tmp, sizeof(tmp), "%s/new", ctx->mailbox->path);
-  if (mkdir(tmp, S_IRWXU))
-  {
-    mutt_perror(tmp);
-    snprintf(tmp, sizeof(tmp), "%s/cur", ctx->mailbox->path);
-    rmdir(tmp);
-    rmdir(ctx->mailbox->path);
-    return -1;
-  }
+  msg->fp = fopen(path, "r");
+  if (!msg->fp && (errno == ENOENT) && is_maildir)
+    msg->fp = maildir_open_find_message(mailbox->path, cur->path, NULL);
 
-  snprintf(tmp, sizeof(tmp), "%s/tmp", ctx->mailbox->path);
-  if (mkdir(tmp, S_IRWXU))
+  if (!msg->fp)
   {
-    mutt_perror(tmp);
-    snprintf(tmp, sizeof(tmp), "%s/cur", ctx->mailbox->path);
-    rmdir(tmp);
-    snprintf(tmp, sizeof(tmp), "%s/new", ctx->mailbox->path);
-    rmdir(tmp);
-    rmdir(ctx->mailbox->path);
+    mutt_perror(path);
+    mutt_debug(1, "fopen: %s: %s (errno %d).\n", path, strerror(errno), errno);
     return -1;
   }
 
@@ -1572,285 +1337,146 @@ static int maildir_mbox_open_append(struct Context *ctx, int flags)
 }
 
 /**
- * mh_mbox_open - Implements MxOps::mbox_open()
+ * mh_commit_msg - Commit a message to an MH folder
+ * @param mailbox Mailbox
+ * @param msg     Message to commit
+ * @param hdr     Email Header
+ * @param updseq  If true, update the sequence number
+ * @retval  0 Success
+ * @retval -1 Failure
  */
-static int mh_mbox_open(struct Context *ctx)
+static int mh_commit_msg(struct Mailbox *mailbox, struct Message *msg,
+                         struct Header *hdr, bool updseq)
 {
-  return mh_read_dir(ctx, NULL);
-}
+  DIR *dirp = NULL;
+  struct dirent *de = NULL;
+  char *cp = NULL, *dep = NULL;
+  unsigned int n, hi = 0;
+  char path[PATH_MAX];
+  char tmp[16];
 
-/**
- * mh_mbox_open_append - Implements MxOps::mbox_open_append()
- */
-static int mh_mbox_open_append(struct Context *ctx, int flags)
-{
-  if (!(flags & MUTT_APPENDNEW))
+  if (mutt_file_fsync_close(&msg->fp))
   {
-    return 0;
+    mutt_perror(_("Could not flush message to disk"));
+    return -1;
   }
 
-  if (mkdir(ctx->mailbox->path, S_IRWXU))
+  dirp = opendir(mailbox->path);
+  if (!dirp)
   {
-    mutt_perror(ctx->mailbox->path);
+    mutt_perror(mailbox->path);
     return -1;
   }
 
-  char tmp[PATH_MAX];
-  snprintf(tmp, sizeof(tmp), "%s/.mh_sequences", ctx->mailbox->path);
-  const int i = creat(tmp, S_IRWXU);
-  if (i == -1)
+  /* figure out what the next message number is */
+  while ((de = readdir(dirp)))
   {
-    mutt_perror(tmp);
-    rmdir(ctx->mailbox->path);
-    return -1;
+    dep = de->d_name;
+    if (*dep == ',')
+      dep++;
+    cp = dep;
+    while (*cp)
+    {
+      if (!isdigit((unsigned char) *cp))
+        break;
+      cp++;
+    }
+    if (!*cp)
+    {
+      n = atoi(dep);
+      if (n > hi)
+        hi = n;
+    }
   }
-  close(i);
-
-  return 0;
-}
-
-/**
- * mh_msg_open_new - Implements MxOps::msg_open_new()
- *
- * Open a new (temporary) message in an MH folder.
- */
-static int mh_msg_open_new(struct Context *ctx, struct Message *msg, struct Header *hdr)
-{
-  return mh_mkstemp(ctx->mailbox, &msg->fp, &msg->path);
-}
-
-/**
- * ch_compare - qsort callback to sort characters
- * @param a First  character to compare
- * @param b Second character to compare
- * @retval -1 a precedes b
- * @retval  0 a and b are identical
- * @retval  1 b precedes a
- */
-static int ch_compare(const void *a, const void *b)
-{
-  return (int) (*((const char *) a) - *((const char *) b));
-}
-
-/**
- * maildir_flags - Generate the Maildir flags for an email
- * @param dest    Buffer for the result
- * @param destlen Length of buffer
- * @param hdr     Header of the email
- */
-void maildir_flags(char *dest, size_t destlen, struct Header *hdr)
-{
-  *dest = '\0';
+  closedir(dirp);
 
-  /* The maildir specification requires that all files in the cur
-   * subdirectory have the :unique string appended, regardless of whether
-   * or not there are any flags.  If .old is set, we know that this message
-   * will end up in the cur directory, so we include it in the following
-   * test even though there is no associated flag.
+  /* Now try to rename the file to the proper name.
+   *
+   * Note: We may have to try multiple times, until we find a free slot.
    */
 
-  if (hdr && (hdr->flagged || hdr->replied || hdr->read || hdr->deleted ||
-              hdr->old || hdr->maildir_flags))
+  while (true)
   {
-    char tmp[LONG_STRING];
-    snprintf(tmp, sizeof(tmp), "%s%s%s%s%s", hdr->flagged ? "F" : "",
-             hdr->replied ? "R" : "", hdr->read ? "S" : "",
-             hdr->deleted ? "T" : "", NONULL(hdr->maildir_flags));
-    if (hdr->maildir_flags)
-      qsort(tmp, strlen(tmp), 1, ch_compare);
-    snprintf(dest, destlen, ":2,%s", tmp);
+    hi++;
+    snprintf(tmp, sizeof(tmp), "%u", hi);
+    snprintf(path, sizeof(path), "%s/%s", mailbox->path, tmp);
+    if (mutt_file_safe_rename(msg->path, path) == 0)
+    {
+      if (hdr)
+        mutt_str_replace(&hdr->path, tmp);
+      mutt_str_replace(&msg->committed_path, path);
+      FREE(&msg->path);
+      break;
+    }
+    else if (errno != EEXIST)
+    {
+      mutt_perror(mailbox->path);
+      return -1;
+    }
+  }
+  if (updseq)
+  {
+    mh_sequences_add_one(mailbox, hi, !msg->flags.read, msg->flags.flagged,
+                         msg->flags.replied);
   }
+  return 0;
 }
 
 /**
- * maildir_mh_open_message - Open a Maildir or MH message
- * @param mailbox    Mailbox
- * @param msg        Message to open
- * @param msgno      Index number
- * @param is_maildir true, if a Maildir
+ * md_commit_message - Commit a message to a maildir folder
+ * @param mailbox Mailbox
+ * @param msg     Message to commit
+ * @param hdr     Header of the email
  * @retval  0 Success
  * @retval -1 Failure
+ *
+ * msg->path contains the file name of a file in tmp/. We take the
+ * flags from this file's name.
+ *
+ * mailbox is the mail folder we commit to.
+ *
+ * hdr is a header structure to which we write the message's new
+ * file name.  This is used in the mh and maildir folder synch
+ * routines.  When this routine is invoked from mx_msg_commit(),
+ * hdr is NULL.
+ *
+ * msg->path looks like this:
+ *
+ *    tmp/{cur,new}.neomutt-HOSTNAME-PID-COUNTER:flags
+ *
+ * See also maildir_msg_open_new().
  */
-static int maildir_mh_open_message(struct Mailbox *mailbox, struct Message *msg,
-                                   int msgno, int is_maildir)
+static int md_commit_message(struct Mailbox *mailbox, struct Message *msg, struct Header *hdr)
 {
-  struct Header *cur = mailbox->hdrs[msgno];
+  char subdir[4];
+  char suffix[16];
   char path[PATH_MAX];
+  char full[PATH_MAX];
+  char *s = NULL;
 
-  snprintf(path, sizeof(path), "%s/%s", mailbox->path, cur->path);
-
-  msg->fp = fopen(path, "r");
-  if (!msg->fp && (errno == ENOENT) && is_maildir)
-    msg->fp = maildir_open_find_message(mailbox->path, cur->path, NULL);
-
-  if (!msg->fp)
+  if (mutt_file_fsync_close(&msg->fp))
   {
-    mutt_perror(path);
-    mutt_debug(1, "fopen: %s: %s (errno %d).\n", path, strerror(errno), errno);
+    mutt_perror(_("Could not flush message to disk"));
     return -1;
   }
 
-  return 0;
-}
+  /* extract the subdir */
+  s = strrchr(msg->path, '/') + 1;
+  mutt_str_strfcpy(subdir, s, 4);
 
-/**
- * maildir_msg_open - Implements MxOps::msg_open()
- */
-static int maildir_msg_open(struct Context *ctx, struct Message *msg, int msgno)
-{
-  return maildir_mh_open_message(ctx->mailbox, msg, msgno, 1);
-}
+  /* extract the flags */
+  s = strchr(s, ':');
+  if (s)
+    mutt_str_strfcpy(suffix, s, sizeof(suffix));
+  else
+    suffix[0] = '\0';
 
-/**
- * mh_msg_open - Implements MxOps::msg_open()
- */
-static int mh_msg_open(struct Context *ctx, struct Message *msg, int msgno)
-{
-  return maildir_mh_open_message(ctx->mailbox, msg, msgno, 0);
-}
-
-/**
- * mh_msg_close - Close a message
- * @param ctx Mailbox
- * @param msg Message to close
- * @retval 0   Success
- * @retval EOF Error, see errno
- *
- * @note May also return EOF Failure, see errno
- */
-static int mh_msg_close(struct Context *ctx, struct Message *msg)
-{
-  return mutt_file_fclose(&msg->fp);
-}
-
-/**
- * maildir_msg_open_new - Implements MxOps::msg_open_new()
- *
- * Open a new (temporary) message in a maildir folder.
- *
- * @note This uses _almost_ the maildir file name format,
- * but with a {cur,new} prefix.
- */
-static int maildir_msg_open_new(struct Context *ctx, struct Message *msg, struct Header *hdr)
-{
-  int fd;
-  char path[PATH_MAX];
-  char suffix[16];
-  char subdir[16];
-  mode_t omask;
-
-  if (hdr)
-  {
-    bool deleted = hdr->deleted;
-    hdr->deleted = false;
-
-    maildir_flags(suffix, sizeof(suffix), hdr);
-
-    hdr->deleted = deleted;
-  }
-  else
-    *suffix = '\0';
-
-  if (hdr && (hdr->read || hdr->old))
-    mutt_str_strfcpy(subdir, "cur", sizeof(subdir));
-  else
-    mutt_str_strfcpy(subdir, "new", sizeof(subdir));
-
-  omask = umask(mh_umask(ctx->mailbox));
-  while (true)
-  {
-    snprintf(path, sizeof(path), "%s/tmp/%s.%lld.R%" PRIu64 ".%s%s",
-             ctx->mailbox->path, subdir, (long long) time(NULL), mutt_rand64(),
-             NONULL(ShortHostname), suffix);
-
-    mutt_debug(2, "Trying %s.\n", path);
-
-    fd = open(path, O_WRONLY | O_EXCL | O_CREAT, 0666);
-    if (fd == -1)
-    {
-      if (errno != EEXIST)
-      {
-        umask(omask);
-        mutt_perror(path);
-        return -1;
-      }
-    }
-    else
-    {
-      mutt_debug(2, "Success.\n");
-      msg->path = mutt_str_strdup(path);
-      break;
-    }
-  }
-  umask(omask);
-
-  msg->fp = fdopen(fd, "w");
-  if (!msg->fp)
-  {
-    FREE(&msg->path);
-    close(fd);
-    unlink(path);
-    return -1;
-  }
-
-  return 0;
-}
-
-/**
- * md_commit_message - Commit a message to a maildir folder
- * @param mailbox Mailbox
- * @param msg     Message to commit
- * @param hdr     Header of the email
- * @retval  0 Success
- * @retval -1 Failure
- *
- * msg->path contains the file name of a file in tmp/. We take the
- * flags from this file's name.
- *
- * mailbox is the mail folder we commit to.
- *
- * hdr is a header structure to which we write the message's new
- * file name.  This is used in the mh and maildir folder synch
- * routines.  When this routine is invoked from mx_msg_commit(),
- * hdr is NULL.
- *
- * msg->path looks like this:
- *
- *    tmp/{cur,new}.neomutt-HOSTNAME-PID-COUNTER:flags
- *
- * See also maildir_msg_open_new().
- */
-static int md_commit_message(struct Mailbox *mailbox, struct Message *msg, struct Header *hdr)
-{
-  char subdir[4];
-  char suffix[16];
-  char path[PATH_MAX];
-  char full[PATH_MAX];
-  char *s = NULL;
-
-  if (mutt_file_fsync_close(&msg->fp))
-  {
-    mutt_perror(_("Could not flush message to disk"));
-    return -1;
-  }
-
-  /* extract the subdir */
-  s = strrchr(msg->path, '/') + 1;
-  mutt_str_strfcpy(subdir, s, 4);
-
-  /* extract the flags */
-  s = strchr(s, ':');
-  if (s)
-    mutt_str_strfcpy(suffix, s, sizeof(suffix));
-  else
-    suffix[0] = '\0';
-
-  /* construct a new file name. */
-  while (true)
-  {
-    snprintf(path, sizeof(path), "%s/%lld.R%" PRIu64 ".%s%s", subdir,
-             (long long) time(NULL), mutt_rand64(), NONULL(ShortHostname), suffix);
-    snprintf(full, sizeof(full), "%s/%s", mailbox->path, path);
+  /* construct a new file name. */
+  while (true)
+  {
+    snprintf(path, sizeof(path), "%s/%lld.R%" PRIu64 ".%s%s", subdir,
+             (long long) time(NULL), mutt_rand64(), NONULL(ShortHostname), suffix);
+    snprintf(full, sizeof(full), "%s/%s", mailbox->path, path);
 
     mutt_debug(2, "renaming %s to %s.\n", msg->path, full);
 
@@ -1896,108 +1522,6 @@ static int md_commit_message(struct Mailbox *mailbox, struct Message *msg, struc
   }
 }
 
-/**
- * maildir_msg_commit - Implements MxOps::msg_commit()
- */
-static int maildir_msg_commit(struct Context *ctx, struct Message *msg)
-{
-  return md_commit_message(ctx->mailbox, msg, NULL);
-}
-
-/**
- * mh_commit_msg - Commit a message to an MH folder
- * @param mailbox Mailbox
- * @param msg     Message to commit
- * @param hdr     Email Header
- * @param updseq  If true, update the sequence number
- * @retval  0 Success
- * @retval -1 Failure
- */
-static int mh_commit_msg(struct Mailbox *mailbox, struct Message *msg,
-                         struct Header *hdr, bool updseq)
-{
-  DIR *dirp = NULL;
-  struct dirent *de = NULL;
-  char *cp = NULL, *dep = NULL;
-  unsigned int n, hi = 0;
-  char path[PATH_MAX];
-  char tmp[16];
-
-  if (mutt_file_fsync_close(&msg->fp))
-  {
-    mutt_perror(_("Could not flush message to disk"));
-    return -1;
-  }
-
-  dirp = opendir(mailbox->path);
-  if (!dirp)
-  {
-    mutt_perror(mailbox->path);
-    return -1;
-  }
-
-  /* figure out what the next message number is */
-  while ((de = readdir(dirp)))
-  {
-    dep = de->d_name;
-    if (*dep == ',')
-      dep++;
-    cp = dep;
-    while (*cp)
-    {
-      if (!isdigit((unsigned char) *cp))
-        break;
-      cp++;
-    }
-    if (!*cp)
-    {
-      n = atoi(dep);
-      if (n > hi)
-        hi = n;
-    }
-  }
-  closedir(dirp);
-
-  /* Now try to rename the file to the proper name.
-   *
-   * Note: We may have to try multiple times, until we find a free slot.
-   */
-
-  while (true)
-  {
-    hi++;
-    snprintf(tmp, sizeof(tmp), "%u", hi);
-    snprintf(path, sizeof(path), "%s/%s", mailbox->path, tmp);
-    if (mutt_file_safe_rename(msg->path, path) == 0)
-    {
-      if (hdr)
-        mutt_str_replace(&hdr->path, tmp);
-      mutt_str_replace(&msg->committed_path, path);
-      FREE(&msg->path);
-      break;
-    }
-    else if (errno != EEXIST)
-    {
-      mutt_perror(mailbox->path);
-      return -1;
-    }
-  }
-  if (updseq)
-  {
-    mh_sequences_add_one(mailbox, hi, !msg->flags.read, msg->flags.flagged,
-                         msg->flags.replied);
-  }
-  return 0;
-}
-
-/**
- * mh_msg_commit - Implements MxOps::msg_commit()
- */
-static int mh_msg_commit(struct Context *ctx, struct Message *msg)
-{
-  return mh_commit_msg(ctx->mailbox, msg, NULL, true);
-}
-
 /**
  * mh_rewrite_message - Sync a message in an MH folder
  * @param ctx   Mailbox
@@ -2148,23 +1672,365 @@ static int maildir_sync_message(struct Context *ctx, int msgno)
     snprintf(fullpath, sizeof(fullpath), "%s/%s", ctx->mailbox->path, partpath);
     snprintf(oldpath, sizeof(oldpath), "%s/%s", ctx->mailbox->path, h->path);
 
-    if (mutt_str_strcmp(fullpath, oldpath) == 0)
-    {
-      /* message hasn't really changed */
-      return 0;
-    }
+    if (mutt_str_strcmp(fullpath, oldpath) == 0)
+    {
+      /* message hasn't really changed */
+      return 0;
+    }
+
+    /* record that the message is possibly marked as trashed on disk */
+    h->trash = h->deleted;
+
+    if (rename(oldpath, fullpath) != 0)
+    {
+      mutt_perror("rename");
+      return -1;
+    }
+    mutt_str_replace(&h->path, partpath);
+  }
+  return 0;
+}
+
+/**
+ * maildir_canon_filename - Generate the canonical filename for a Maildir folder
+ * @param src    Buffer containing source filename
+ * @param buf    Buffer for the result
+ * @param buflen Length of buf buffer
+ * @retval ptr Buf buffer
+ */
+static char *maildir_canon_filename(const char *src, char *buf, size_t buflen)
+{
+  char *t = strrchr(src, '/');
+  if (t)
+    src = t + 1;
+
+  mutt_str_strfcpy(buf, src, buflen);
+  char *u = strrchr(buf, ':');
+  if (u)
+    *u = '\0';
+
+  return buf;
+}
+
+/**
+ * maildir_update_tables - Update the Header tables
+ * @param ctx        Mailbox
+ * @param index_hint Current email in index
+ */
+static void maildir_update_tables(struct Context *ctx, int *index_hint)
+{
+  if (Sort != SORT_ORDER)
+  {
+    const short old_sort = Sort;
+    Sort = SORT_ORDER;
+    mutt_sort_headers(ctx, true);
+    Sort = old_sort;
+  }
+
+  const int old_count = ctx->mailbox->msg_count;
+  for (int i = 0, j = 0; i < old_count; i++)
+  {
+    if (ctx->mailbox->hdrs[i]->active && index_hint && *index_hint == i)
+      *index_hint = j;
+
+    if (ctx->mailbox->hdrs[i]->active)
+      ctx->mailbox->hdrs[i]->index = j++;
+  }
+
+  mx_update_tables(ctx, false);
+  mutt_clear_threads(ctx);
+}
+
+/**
+ * md_open_find_message - Find a message in a maildir folder
+ * @param folder    Base folder
+ * @param unique    Unique part of filename
+ * @param subfolder Subfolder to search, e.g. 'cur'
+ * @param newname   File's new name
+ * @retval ptr File handle
+ *
+ * These functions try to find a message in a maildir folder when it
+ * has moved under our feet.  Note that this code is rather expensive, but
+ * then again, it's called rarely.
+ */
+static FILE *md_open_find_message(const char *folder, const char *unique,
+                                  const char *subfolder, char **newname)
+{
+  char dir[PATH_MAX];
+  char tunique[PATH_MAX];
+  char fname[PATH_MAX];
+
+  struct dirent *de = NULL;
+
+  FILE *fp = NULL;
+  int oe = ENOENT;
+
+  snprintf(dir, sizeof(dir), "%s/%s", folder, subfolder);
+
+  DIR *dp = opendir(dir);
+  if (!dp)
+  {
+    errno = ENOENT;
+    return NULL;
+  }
+
+  while ((de = readdir(dp)))
+  {
+    maildir_canon_filename(de->d_name, tunique, sizeof(tunique));
+
+    if (mutt_str_strcmp(tunique, unique) == 0)
+    {
+      snprintf(fname, sizeof(fname), "%s/%s/%s", folder, subfolder, de->d_name);
+      fp = fopen(fname, "r");
+      oe = errno;
+      break;
+    }
+  }
+
+  closedir(dp);
+
+  if (newname && fp)
+    *newname = mutt_str_strdup(fname);
+
+  errno = oe;
+  return fp;
+}
+
+/**
+ * mh_mailbox - Check for new mail for a mh mailbox
+ * @param mailbox     Mailbox to check
+ * @param check_stats Also count total, new, and flagged messages
+ * @retval true if the mailbox has new mail
+ */
+bool mh_mailbox(struct Mailbox *mailbox, bool check_stats)
+{
+  struct MhSequences mhs = { 0 };
+  bool check_new = true;
+  bool rc = false;
+  DIR *dirp = NULL;
+  struct dirent *de = NULL;
+
+  /* when $mail_check_recent is set and the .mh_sequences file hasn't changed
+   * since the last mailbox visit, there is no "new mail" */
+  if (MailCheckRecent && mh_sequences_changed(mailbox) <= 0)
+  {
+    rc = false;
+    check_new = false;
+  }
+
+  if (!(check_new || check_stats))
+    return rc;
+
+  if (mh_read_sequences(&mhs, mailbox->path) < 0)
+    return false;
+
+  if (check_stats)
+  {
+    mailbox->msg_count = 0;
+    mailbox->msg_unread = 0;
+    mailbox->msg_flagged = 0;
+  }
+
+  for (int i = mhs.max; i > 0; i--)
+  {
+    if (check_stats && (mhs_check(&mhs, i) & MH_SEQ_FLAGGED))
+      mailbox->msg_flagged++;
+    if (mhs_check(&mhs, i) & MH_SEQ_UNSEEN)
+    {
+      if (check_stats)
+        mailbox->msg_unread++;
+      if (check_new)
+      {
+        /* if the first unseen message we encounter was in the mailbox during the
+           last visit, don't notify about it */
+        if (!MailCheckRecent || mh_already_notified(mailbox, i) == 0)
+        {
+          mailbox->has_new = true;
+          rc = true;
+        }
+        /* Because we are traversing from high to low, we can stop
+         * checking for new mail after the first unseen message.
+         * Whether it resulted in "new mail" or not. */
+        check_new = false;
+        if (!check_stats)
+          break;
+      }
+    }
+  }
+  mhs_free_sequences(&mhs);
+
+  if (check_stats)
+  {
+    dirp = opendir(mailbox->path);
+    if (dirp)
+    {
+      while ((de = readdir(dirp)))
+      {
+        if (*de->d_name == '.')
+          continue;
+        if (mh_valid_message(de->d_name))
+          mailbox->msg_count++;
+      }
+      closedir(dirp);
+    }
+  }
+
+  return rc;
+}
+
+/**
+ * maildir_parse_flags - Parse Maildir file flags
+ * @param h    Header of email
+ * @param path Path to email file
+ */
+void maildir_parse_flags(struct Header *h, const char *path)
+{
+  char *q = NULL;
+
+  h->flagged = false;
+  h->read = false;
+  h->replied = false;
+
+  char *p = strrchr(path, ':');
+  if (p && (mutt_str_strncmp(p + 1, "2,", 2) == 0))
+  {
+    p += 3;
+
+    mutt_str_replace(&h->maildir_flags, p);
+    q = h->maildir_flags;
+
+    while (*p)
+    {
+      switch (*p)
+      {
+        case 'F':
+          h->flagged = true;
+          break;
+
+        case 'R': /* replied */
+          h->replied = true;
+          break;
+
+        case 'S': /* seen */
+          h->read = true;
+          break;
+
+        case 'T': /* trashed */
+          if (!h->flagged || !FlagSafe)
+          {
+            h->trash = true;
+            h->deleted = true;
+          }
+          break;
+
+        default:
+          *q++ = *p;
+          break;
+      }
+      p++;
+    }
+  }
+
+  if (q == h->maildir_flags)
+    FREE(&h->maildir_flags);
+  else if (q)
+    *q = '\0';
+}
+
+/**
+ * maildir_parse_stream - Parse a Maildir message
+ * @param magic  Mailbox type, e.g. #MUTT_MAILDIR
+ * @param f      Mesage file handle
+ * @param fname  Message filename
+ * @param is_old true, if the email is old (read)
+ * @param h      Email Header to populate (OPTIONAL)
+ * @retval ptr Populated email Header
+ *
+ * Actually parse a maildir message.  This may also be used to fill
+ * out a fake header structure generated by lazy maildir parsing.
+ */
+struct Header *maildir_parse_stream(enum MailboxType magic, FILE *f,
+                                    const char *fname, bool is_old, struct Header *h)
+{
+  struct stat st;
+
+  if (!h)
+    h = mutt_header_new();
+  h->env = mutt_rfc822_read_header(f, h, false, false);
+
+  fstat(fileno(f), &st);
+
+  if (!h->received)
+    h->received = h->date_sent;
+
+  /* always update the length since we have fresh information available. */
+  h->content->length = st.st_size - h->content->offset;
+
+  h->index = -1;
+
+  if (magic == MUTT_MAILDIR)
+  {
+    /* maildir stores its flags in the filename, so ignore the
+     * flags in the header of the message
+     */
+
+    h->old = is_old;
+    maildir_parse_flags(h, fname);
+  }
+  return h;
+}
+
+/**
+ * maildir_parse_message - Actually parse a maildir message
+ * @param magic  Mailbox type, e.g. #MUTT_MAILDIR
+ * @param fname  Message filename
+ * @param is_old true, if the email is old (read)
+ * @param h      Email Header to populate (OPTIONAL)
+ * @retval ptr Populated email Header
+ *
+ * This may also be used to fill out a fake header structure generated by lazy
+ * maildir parsing.
+ */
+struct Header *maildir_parse_message(enum MailboxType magic, const char *fname,
+                                     bool is_old, struct Header *h)
+{
+  FILE *f = fopen(fname, "r");
+  if (!f)
+    return NULL;
+
+  h = maildir_parse_stream(magic, f, fname, is_old, h);
+  mutt_file_fclose(&f);
+  return h;
+}
 
-    /* record that the message is possibly marked as trashed on disk */
-    h->trash = h->deleted;
+/**
+ * maildir_flags - Generate the Maildir flags for an email
+ * @param dest    Buffer for the result
+ * @param destlen Length of buffer
+ * @param hdr     Header of the email
+ */
+void maildir_flags(char *dest, size_t destlen, struct Header *hdr)
+{
+  *dest = '\0';
 
-    if (rename(oldpath, fullpath) != 0)
-    {
-      mutt_perror("rename");
-      return -1;
-    }
-    mutt_str_replace(&h->path, partpath);
+  /* The maildir specification requires that all files in the cur
+   * subdirectory have the :unique string appended, regardless of whether
+   * or not there are any flags.  If .old is set, we know that this message
+   * will end up in the cur directory, so we include it in the following
+   * test even though there is no associated flag.
+   */
+
+  if (hdr && (hdr->flagged || hdr->replied || hdr->read || hdr->deleted ||
+              hdr->old || hdr->maildir_flags))
+  {
+    char tmp[LONG_STRING];
+    snprintf(tmp, sizeof(tmp), "%s%s%s%s%s", hdr->flagged ? "F" : "",
+             hdr->replied ? "R" : "", hdr->read ? "S" : "",
+             hdr->deleted ? "T" : "", NONULL(hdr->maildir_flags));
+    if (hdr->maildir_flags)
+      qsort(tmp, strlen(tmp), 1, ch_compare);
+    snprintf(dest, destlen, ":2,%s", tmp);
   }
-  return 0;
 }
 
 /**
@@ -2260,53 +2126,222 @@ int mh_sync_mailbox_message(struct Context *ctx, int msgno)
 }
 
 /**
- * maildir_canon_filename - Generate the canonical filename for a Maildir folder
- * @param src    Buffer containing source filename
- * @param buf    Buffer for the result
- * @param buflen Length of buf buffer
- * @retval ptr Buf buffer
+ * maildir_update_flags - Update the mailbox flags
+ * @param ctx Mailbox
+ * @param o   Old email Header
+ * @param n   New email Header
+ * @retval true  If the flags changed
+ * @retval false Otherwise
  */
-static char *maildir_canon_filename(const char *src, char *buf, size_t buflen)
+bool maildir_update_flags(struct Context *ctx, struct Header *o, struct Header *n)
 {
-  char *t = strrchr(src, '/');
-  if (t)
-    src = t + 1;
+  /* save the global state here so we can reset it at the
+   * end of list block if required.
+   */
+  bool context_changed = ctx->mailbox->changed;
+  bool header_changed;
 
-  mutt_str_strfcpy(buf, src, buflen);
-  char *u = strrchr(buf, ':');
-  if (u)
-    *u = '\0';
+  /* user didn't modify this message.  alter the flags to match the
+   * current state on disk.  This may not actually do
+   * anything. mutt_set_flag() will just ignore the call if the status
+   * bits are already properly set, but it is still faster not to pass
+   * through it */
+  if (o->flagged != n->flagged)
+    mutt_set_flag(ctx, o, MUTT_FLAG, n->flagged);
+  if (o->replied != n->replied)
+    mutt_set_flag(ctx, o, MUTT_REPLIED, n->replied);
+  if (o->read != n->read)
+    mutt_set_flag(ctx, o, MUTT_READ, n->read);
+  if (o->old != n->old)
+    mutt_set_flag(ctx, o, MUTT_OLD, n->old);
 
-  return buf;
+  /* mutt_set_flag() will set this, but we don't need to
+   * sync the changes we made because we just updated the
+   * context to match the current on-disk state of the
+   * message.
+   */
+  header_changed = o->changed;
+  o->changed = false;
+
+  /* if the mailbox was not modified before we made these
+   * changes, unset the changed flag since nothing needs to
+   * be synchronized.
+   */
+  if (!context_changed)
+    ctx->mailbox->changed = false;
+
+  return header_changed;
 }
 
 /**
- * maildir_update_tables - Update the Header tables
- * @param ctx        Mailbox
- * @param index_hint Current email in index
+ * maildir_open_find_message - Find a new
+ * @param folder  Maildir path
+ * @param msg     Email path
+ * @param newname New name, if it has moved
+ * @retval ptr File handle
  */
-static void maildir_update_tables(struct Context *ctx, int *index_hint)
+FILE *maildir_open_find_message(const char *folder, const char *msg, char **newname)
 {
-  if (Sort != SORT_ORDER)
+  char unique[PATH_MAX];
+
+  static unsigned int new_hits = 0, cur_hits = 0; /* simple dynamic optimization */
+
+  maildir_canon_filename(msg, unique, sizeof(unique));
+
+  FILE *fp = md_open_find_message(folder, unique, new_hits > cur_hits ? "new" : "cur", newname);
+  if (fp || (errno != ENOENT))
   {
-    const short old_sort = Sort;
-    Sort = SORT_ORDER;
-    mutt_sort_headers(ctx, true);
-    Sort = old_sort;
+    if (new_hits < UINT_MAX && cur_hits < UINT_MAX)
+    {
+      new_hits += (new_hits > cur_hits ? 1 : 0);
+      cur_hits += (new_hits > cur_hits ? 0 : 1);
+    }
+
+    return fp;
+  }
+  fp = md_open_find_message(folder, unique, new_hits > cur_hits ? "cur" : "new", newname);
+  if (fp || (errno != ENOENT))
+  {
+    if (new_hits < UINT_MAX && cur_hits < UINT_MAX)
+    {
+      new_hits += (new_hits > cur_hits ? 0 : 1);
+      cur_hits += (new_hits > cur_hits ? 1 : 0);
+    }
+
+    return fp;
+  }
+
+  return NULL;
+}
+
+/**
+ * maildir_check_empty - Is the mailbox empty
+ * @param path Mailbox to check
+ * @retval 1 Mailbox is empty
+ * @retval 0 Mailbox contains mail
+ * @retval -1 Error
+ */
+int maildir_check_empty(const char *path)
+{
+  DIR *dp = NULL;
+  struct dirent *de = NULL;
+  int r = 1; /* assume empty until we find a message */
+  char realpath[PATH_MAX];
+  int iter = 0;
+
+  /* Strategy here is to look for any file not beginning with a period */
+
+  do
+  {
+    /* we do "cur" on the first iteration since it's more likely that we'll
+     * find old messages without having to scan both subdirs
+     */
+    snprintf(realpath, sizeof(realpath), "%s/%s", path, iter == 0 ? "cur" : "new");
+    dp = opendir(realpath);
+    if (!dp)
+      return -1;
+    while ((de = readdir(dp)))
+    {
+      if (*de->d_name != '.')
+      {
+        r = 0;
+        break;
+      }
+    }
+    closedir(dp);
+    iter++;
+  } while (r && iter < 2);
+
+  return r;
+}
+
+/**
+ * mh_check_empty - Is mailbox empty
+ * @param path Mailbox to check
+ * @retval 1 Mailbox is empty
+ * @retval 0 Mailbox contains mail
+ * @retval -1 Error
+ */
+int mh_check_empty(const char *path)
+{
+  struct dirent *de = NULL;
+  int r = 1; /* assume empty until we find a message */
+
+  DIR *dp = opendir(path);
+  if (!dp)
+    return -1;
+  while ((de = readdir(dp)))
+  {
+    if (mh_valid_message(de->d_name))
+    {
+      r = 0;
+      break;
+    }
+  }
+  closedir(dp);
+
+  return r;
+}
+
+/**
+ * maildir_mbox_open - Open a Maildir mailbox
+ * @param ctx Mailbox
+ * @retval  0 Success
+ * @retval -1 Failure
+ */
+static int maildir_mbox_open(struct Context *ctx)
+{
+  return maildir_read_dir(ctx);
+}
+
+/**
+ * maildir_mbox_open_append - Implements MxOps::mbox_open_append()
+ */
+static int maildir_mbox_open_append(struct Context *ctx, int flags)
+{
+  if (!(flags & MUTT_APPENDNEW))
+  {
+    return 0;
+  }
+
+  if (mkdir(ctx->mailbox->path, S_IRWXU))
+  {
+    mutt_perror(ctx->mailbox->path);
+    return -1;
+  }
+
+  char tmp[PATH_MAX];
+  snprintf(tmp, sizeof(tmp), "%s/cur", ctx->mailbox->path);
+  if (mkdir(tmp, S_IRWXU))
+  {
+    mutt_perror(tmp);
+    rmdir(ctx->mailbox->path);
+    return -1;
+  }
+
+  snprintf(tmp, sizeof(tmp), "%s/new", ctx->mailbox->path);
+  if (mkdir(tmp, S_IRWXU))
+  {
+    mutt_perror(tmp);
+    snprintf(tmp, sizeof(tmp), "%s/cur", ctx->mailbox->path);
+    rmdir(tmp);
+    rmdir(ctx->mailbox->path);
+    return -1;
   }
 
-  const int old_count = ctx->mailbox->msg_count;
-  for (int i = 0, j = 0; i < old_count; i++)
+  snprintf(tmp, sizeof(tmp), "%s/tmp", ctx->mailbox->path);
+  if (mkdir(tmp, S_IRWXU))
   {
-    if (ctx->mailbox->hdrs[i]->active && index_hint && *index_hint == i)
-      *index_hint = j;
-
-    if (ctx->mailbox->hdrs[i]->active)
-      ctx->mailbox->hdrs[i]->index = j++;
+    mutt_perror(tmp);
+    snprintf(tmp, sizeof(tmp), "%s/cur", ctx->mailbox->path);
+    rmdir(tmp);
+    snprintf(tmp, sizeof(tmp), "%s/new", ctx->mailbox->path);
+    rmdir(tmp);
+    rmdir(ctx->mailbox->path);
+    return -1;
   }
 
-  mx_update_tables(ctx, false);
-  mutt_clear_threads(ctx);
+  return 0;
 }
 
 /**
@@ -2389,92 +2424,298 @@ static int maildir_mbox_check(struct Context *ctx, int *index_hint)
    */
   fnames = mutt_hash_create(count, 0);
 
-  for (p = md; p; p = p->next)
-  {
-    maildir_canon_filename(p->h->path, buf, sizeof(buf));
-    p->canon_fname = mutt_str_strdup(buf);
-    mutt_hash_insert(fnames, p->canon_fname, p);
-  }
+  for (p = md; p; p = p->next)
+  {
+    maildir_canon_filename(p->h->path, buf, sizeof(buf));
+    p->canon_fname = mutt_str_strdup(buf);
+    mutt_hash_insert(fnames, p->canon_fname, p);
+  }
+
+  /* check for modifications and adjust flags */
+  for (int i = 0; i < ctx->mailbox->msg_count; i++)
+  {
+    ctx->mailbox->hdrs[i]->active = false;
+    maildir_canon_filename(ctx->mailbox->hdrs[i]->path, buf, sizeof(buf));
+    p = mutt_hash_find(fnames, buf);
+    if (p && p->h)
+    {
+      /* message already exists, merge flags */
+      ctx->mailbox->hdrs[i]->active = true;
+
+      /* check to see if the message has moved to a different
+       * subdirectory.  If so, update the associated filename.
+       */
+      if (mutt_str_strcmp(ctx->mailbox->hdrs[i]->path, p->h->path) != 0)
+        mutt_str_replace(&ctx->mailbox->hdrs[i]->path, p->h->path);
+
+      /* if the user hasn't modified the flags on this message, update
+       * the flags we just detected.
+       */
+      if (!ctx->mailbox->hdrs[i]->changed)
+        if (maildir_update_flags(ctx, ctx->mailbox->hdrs[i], p->h))
+          flags_changed = true;
+
+      if (ctx->mailbox->hdrs[i]->deleted == ctx->mailbox->hdrs[i]->trash)
+      {
+        if (ctx->mailbox->hdrs[i]->deleted != p->h->deleted)
+        {
+          ctx->mailbox->hdrs[i]->deleted = p->h->deleted;
+          flags_changed = true;
+        }
+      }
+      ctx->mailbox->hdrs[i]->trash = p->h->trash;
+
+      /* this is a duplicate of an existing header, so remove it */
+      mutt_header_free(&p->h);
+    }
+    /* This message was not in the list of messages we just scanned.
+     * Check to see if we have enough information to know if the
+     * message has disappeared out from underneath us.
+     */
+    else if (((changed & 1) && (strncmp(ctx->mailbox->hdrs[i]->path, "new/", 4) == 0)) ||
+             ((changed & 2) && (strncmp(ctx->mailbox->hdrs[i]->path, "cur/", 4) == 0)))
+    {
+      /* This message disappeared, so we need to simulate a "reopen"
+       * event.  We know it disappeared because we just scanned the
+       * subdirectory it used to reside in.
+       */
+      occult = true;
+    }
+    else
+    {
+      /* This message resides in a subdirectory which was not
+       * modified, so we assume that it is still present and
+       * unchanged.
+       */
+      ctx->mailbox->hdrs[i]->active = true;
+    }
+  }
+
+  /* destroy the file name hash */
+  mutt_hash_destroy(&fnames);
+
+  /* If we didn't just get new mail, update the tables. */
+  if (occult)
+    maildir_update_tables(ctx, index_hint);
+
+  /* do any delayed parsing we need to do. */
+  maildir_delayed_parsing(ctx->mailbox, &md, NULL);
+
+  /* Incorporate new messages */
+  have_new = maildir_move_to_context(ctx, &md);
+
+  if (occult)
+    return MUTT_REOPENED;
+  if (have_new)
+    return MUTT_NEW_MAIL;
+  if (flags_changed)
+    return MUTT_FLAGS;
+  return 0;
+}
+
+/**
+ * maildir_msg_open - Implements MxOps::msg_open()
+ */
+static int maildir_msg_open(struct Context *ctx, struct Message *msg, int msgno)
+{
+  return maildir_mh_open_message(ctx->mailbox, msg, msgno, 1);
+}
+
+/**
+ * maildir_msg_open_new - Implements MxOps::msg_open_new()
+ *
+ * Open a new (temporary) message in a maildir folder.
+ *
+ * @note This uses _almost_ the maildir file name format,
+ * but with a {cur,new} prefix.
+ */
+static int maildir_msg_open_new(struct Context *ctx, struct Message *msg, struct Header *hdr)
+{
+  int fd;
+  char path[PATH_MAX];
+  char suffix[16];
+  char subdir[16];
+  mode_t omask;
+
+  if (hdr)
+  {
+    bool deleted = hdr->deleted;
+    hdr->deleted = false;
+
+    maildir_flags(suffix, sizeof(suffix), hdr);
+
+    hdr->deleted = deleted;
+  }
+  else
+    *suffix = '\0';
+
+  if (hdr && (hdr->read || hdr->old))
+    mutt_str_strfcpy(subdir, "cur", sizeof(subdir));
+  else
+    mutt_str_strfcpy(subdir, "new", sizeof(subdir));
+
+  omask = umask(mh_umask(ctx->mailbox));
+  while (true)
+  {
+    snprintf(path, sizeof(path), "%s/tmp/%s.%lld.R%" PRIu64 ".%s%s",
+             ctx->mailbox->path, subdir, (long long) time(NULL), mutt_rand64(),
+             NONULL(ShortHostname), suffix);
+
+    mutt_debug(2, "Trying %s.\n", path);
+
+    fd = open(path, O_WRONLY | O_EXCL | O_CREAT, 0666);
+    if (fd == -1)
+    {
+      if (errno != EEXIST)
+      {
+        umask(omask);
+        mutt_perror(path);
+        return -1;
+      }
+    }
+    else
+    {
+      mutt_debug(2, "Success.\n");
+      msg->path = mutt_str_strdup(path);
+      break;
+    }
+  }
+  umask(omask);
+
+  msg->fp = fdopen(fd, "w");
+  if (!msg->fp)
+  {
+    FREE(&msg->path);
+    close(fd);
+    unlink(path);
+    return -1;
+  }
+
+  return 0;
+}
+
+/**
+ * maildir_msg_commit - Implements MxOps::msg_commit()
+ */
+static int maildir_msg_commit(struct Context *ctx, struct Message *msg)
+{
+  return md_commit_message(ctx->mailbox, msg, NULL);
+}
+
+/**
+ * maildir_path_probe - Is this a Maildir mailbox? - Implements MxOps::path_probe()
+ */
+int maildir_path_probe(const char *path, const struct stat *st)
+{
+  if (!path)
+    return MUTT_UNKNOWN;
+
+  if (!st || !S_ISDIR(st->st_mode))
+    return MUTT_UNKNOWN;
+
+  char cur[PATH_MAX];
+  snprintf(cur, sizeof(cur), "%s/cur", path);
+
+  struct stat stc;
+  if ((stat(cur, &stc) == 0) && S_ISDIR(stc.st_mode))
+    return MUTT_MAILDIR;
+
+  return MUTT_UNKNOWN;
+}
+
+/**
+ * maildir_path_canon - Canonicalise a mailbox path - Implements MxOps::path_canon()
+ */
+int maildir_path_canon(char *buf, size_t buflen, const char *folder)
+{
+  if (!buf)
+    return -1;
+
+  if ((buf[0] == '+') || (buf[0] == '='))
+  {
+    if (!folder)
+      return -1;
+
+    buf[0] = '/';
+    mutt_str_inline_replace(buf, buflen, 0, folder);
+  }
+
+  mutt_path_canon(buf, buflen, HomeDir);
+  return 0;
+}
+
+/**
+ * maildir_path_pretty - Implements MxOps::path_pretty()
+ */
+int maildir_path_pretty(char *buf, size_t buflen, const char *folder)
+{
+  if (!buf)
+    return -1;
+
+  if (mutt_path_abbr_folder(buf, buflen, folder))
+    return 0;
+
+  if (mutt_path_pretty(buf, buflen, HomeDir))
+    return 0;
+
+  return -1;
+}
 
-  /* check for modifications and adjust flags */
-  for (int i = 0; i < ctx->mailbox->msg_count; i++)
-  {
-    ctx->mailbox->hdrs[i]->active = false;
-    maildir_canon_filename(ctx->mailbox->hdrs[i]->path, buf, sizeof(buf));
-    p = mutt_hash_find(fnames, buf);
-    if (p && p->h)
-    {
-      /* message already exists, merge flags */
-      ctx->mailbox->hdrs[i]->active = true;
+/**
+ * maildir_path_parent - Implements MxOps::path_parent()
+ */
+int maildir_path_parent(char *buf, size_t buflen)
+{
+  if (!buf)
+    return -1;
 
-      /* check to see if the message has moved to a different
-       * subdirectory.  If so, update the associated filename.
-       */
-      if (mutt_str_strcmp(ctx->mailbox->hdrs[i]->path, p->h->path) != 0)
-        mutt_str_replace(&ctx->mailbox->hdrs[i]->path, p->h->path);
+  if (mutt_path_parent(buf, buflen))
+    return 0;
 
-      /* if the user hasn't modified the flags on this message, update
-       * the flags we just detected.
-       */
-      if (!ctx->mailbox->hdrs[i]->changed)
-        if (maildir_update_flags(ctx, ctx->mailbox->hdrs[i], p->h))
-          flags_changed = true;
+  if (buf[0] == '~')
+    mutt_path_canon(buf, buflen, HomeDir);
 
-      if (ctx->mailbox->hdrs[i]->deleted == ctx->mailbox->hdrs[i]->trash)
-      {
-        if (ctx->mailbox->hdrs[i]->deleted != p->h->deleted)
-        {
-          ctx->mailbox->hdrs[i]->deleted = p->h->deleted;
-          flags_changed = true;
-        }
-      }
-      ctx->mailbox->hdrs[i]->trash = p->h->trash;
+  if (mutt_path_parent(buf, buflen))
+    return 0;
 
-      /* this is a duplicate of an existing header, so remove it */
-      mutt_header_free(&p->h);
-    }
-    /* This message was not in the list of messages we just scanned.
-     * Check to see if we have enough information to know if the
-     * message has disappeared out from underneath us.
-     */
-    else if (((changed & 1) && (strncmp(ctx->mailbox->hdrs[i]->path, "new/", 4) == 0)) ||
-             ((changed & 2) && (strncmp(ctx->mailbox->hdrs[i]->path, "cur/", 4) == 0)))
-    {
-      /* This message disappeared, so we need to simulate a "reopen"
-       * event.  We know it disappeared because we just scanned the
-       * subdirectory it used to reside in.
-       */
-      occult = true;
-    }
-    else
-    {
-      /* This message resides in a subdirectory which was not
-       * modified, so we assume that it is still present and
-       * unchanged.
-       */
-      ctx->mailbox->hdrs[i]->active = true;
-    }
-  }
+  return -1;
+}
 
-  /* destroy the file name hash */
-  mutt_hash_destroy(&fnames);
+/**
+ * mh_mbox_open - Implements MxOps::mbox_open()
+ */
+static int mh_mbox_open(struct Context *ctx)
+{
+  return mh_read_dir(ctx, NULL);
+}
 
-  /* If we didn't just get new mail, update the tables. */
-  if (occult)
-    maildir_update_tables(ctx, index_hint);
+/**
+ * mh_mbox_open_append - Implements MxOps::mbox_open_append()
+ */
+static int mh_mbox_open_append(struct Context *ctx, int flags)
+{
+  if (!(flags & MUTT_APPENDNEW))
+  {
+    return 0;
+  }
 
-  /* do any delayed parsing we need to do. */
-  maildir_delayed_parsing(ctx->mailbox, &md, NULL);
+  if (mkdir(ctx->mailbox->path, S_IRWXU))
+  {
+    mutt_perror(ctx->mailbox->path);
+    return -1;
+  }
 
-  /* Incorporate new messages */
-  have_new = maildir_move_to_context(ctx, &md);
+  char tmp[PATH_MAX];
+  snprintf(tmp, sizeof(tmp), "%s/.mh_sequences", ctx->mailbox->path);
+  const int i = creat(tmp, S_IRWXU);
+  if (i == -1)
+  {
+    mutt_perror(tmp);
+    rmdir(ctx->mailbox->path);
+    return -1;
+  }
+  close(i);
 
-  if (occult)
-    return MUTT_REOPENED;
-  if (have_new)
-    return MUTT_NEW_MAIL;
-  if (flags_changed)
-    return MUTT_FLAGS;
   return 0;
 }
 
@@ -2630,300 +2871,117 @@ static int mh_mbox_sync(struct Context *ctx, int *index_hint)
   else
     i = maildir_mbox_check(ctx, index_hint);
 
-  if (i != 0)
-    return i;
-
-#ifdef USE_HCACHE
-  if (ctx->mailbox->magic == MUTT_MAILDIR || ctx->mailbox->magic == MUTT_MH)
-    hc = mutt_hcache_open(HeaderCache, ctx->mailbox->path, NULL);
-#endif
-
-  if (!ctx->mailbox->quiet)
-  {
-    snprintf(msgbuf, sizeof(msgbuf), _("Writing %s..."), ctx->mailbox->path);
-    mutt_progress_init(&progress, msgbuf, MUTT_PROGRESS_MSG, WriteInc,
-                       ctx->mailbox->msg_count);
-  }
-
-  for (i = 0; i < ctx->mailbox->msg_count; i++)
-  {
-    if (!ctx->mailbox->quiet)
-      mutt_progress_update(&progress, i, -1);
-
-#ifdef USE_HCACHE
-    if (mh_sync_mailbox_message(ctx, i, hc) == -1)
-      goto err;
-#else
-    if (mh_sync_mailbox_message(ctx, i) == -1)
-      goto err;
-#endif
-  }
-
-#ifdef USE_HCACHE
-  if (ctx->mailbox->magic == MUTT_MAILDIR || ctx->mailbox->magic == MUTT_MH)
-    mutt_hcache_close(hc);
-#endif
-
-  if (ctx->mailbox->magic == MUTT_MH)
-    mh_update_sequences(ctx->mailbox);
-
-  /* XXX race condition? */
-
-  maildir_update_mtime(ctx->mailbox);
-
-  /* adjust indices */
-
-  if (ctx->deleted)
-  {
-    for (i = 0, j = 0; i < ctx->mailbox->msg_count; i++)
-    {
-      if (!ctx->mailbox->hdrs[i]->deleted || (ctx->mailbox->magic == MUTT_MAILDIR && MaildirTrash))
-        ctx->mailbox->hdrs[i]->index = j++;
-    }
-  }
-
-  return 0;
-
-err:
-#ifdef USE_HCACHE
-  if (ctx->mailbox->magic == MUTT_MAILDIR || ctx->mailbox->magic == MUTT_MH)
-    mutt_hcache_close(hc);
-#endif
-  return -1;
-}
-
-/**
- * maildir_update_flags - Update the mailbox flags
- * @param ctx Mailbox
- * @param o   Old email Header
- * @param n   New email Header
- * @retval true  If the flags changed
- * @retval false Otherwise
- */
-bool maildir_update_flags(struct Context *ctx, struct Header *o, struct Header *n)
-{
-  /* save the global state here so we can reset it at the
-   * end of list block if required.
-   */
-  bool context_changed = ctx->mailbox->changed;
-  bool header_changed;
-
-  /* user didn't modify this message.  alter the flags to match the
-   * current state on disk.  This may not actually do
-   * anything. mutt_set_flag() will just ignore the call if the status
-   * bits are already properly set, but it is still faster not to pass
-   * through it */
-  if (o->flagged != n->flagged)
-    mutt_set_flag(ctx, o, MUTT_FLAG, n->flagged);
-  if (o->replied != n->replied)
-    mutt_set_flag(ctx, o, MUTT_REPLIED, n->replied);
-  if (o->read != n->read)
-    mutt_set_flag(ctx, o, MUTT_READ, n->read);
-  if (o->old != n->old)
-    mutt_set_flag(ctx, o, MUTT_OLD, n->old);
-
-  /* mutt_set_flag() will set this, but we don't need to
-   * sync the changes we made because we just updated the
-   * context to match the current on-disk state of the
-   * message.
-   */
-  header_changed = o->changed;
-  o->changed = false;
-
-  /* if the mailbox was not modified before we made these
-   * changes, unset the changed flag since nothing needs to
-   * be synchronized.
-   */
-  if (!context_changed)
-    ctx->mailbox->changed = false;
-
-  return header_changed;
-}
-
-/**
- * md_open_find_message - Find a message in a maildir folder
- * @param folder    Base folder
- * @param unique    Unique part of filename
- * @param subfolder Subfolder to search, e.g. 'cur'
- * @param newname   File's new name
- * @retval ptr File handle
- *
- * These functions try to find a message in a maildir folder when it
- * has moved under our feet.  Note that this code is rather expensive, but
- * then again, it's called rarely.
- */
-static FILE *md_open_find_message(const char *folder, const char *unique,
-                                  const char *subfolder, char **newname)
-{
-  char dir[PATH_MAX];
-  char tunique[PATH_MAX];
-  char fname[PATH_MAX];
-
-  struct dirent *de = NULL;
-
-  FILE *fp = NULL;
-  int oe = ENOENT;
+  if (i != 0)
+    return i;
 
-  snprintf(dir, sizeof(dir), "%s/%s", folder, subfolder);
+#ifdef USE_HCACHE
+  if (ctx->mailbox->magic == MUTT_MAILDIR || ctx->mailbox->magic == MUTT_MH)
+    hc = mutt_hcache_open(HeaderCache, ctx->mailbox->path, NULL);
+#endif
 
-  DIR *dp = opendir(dir);
-  if (!dp)
+  if (!ctx->mailbox->quiet)
   {
-    errno = ENOENT;
-    return NULL;
+    snprintf(msgbuf, sizeof(msgbuf), _("Writing %s..."), ctx->mailbox->path);
+    mutt_progress_init(&progress, msgbuf, MUTT_PROGRESS_MSG, WriteInc,
+                       ctx->mailbox->msg_count);
   }
 
-  while ((de = readdir(dp)))
+  for (i = 0; i < ctx->mailbox->msg_count; i++)
   {
-    maildir_canon_filename(de->d_name, tunique, sizeof(tunique));
+    if (!ctx->mailbox->quiet)
+      mutt_progress_update(&progress, i, -1);
 
-    if (mutt_str_strcmp(tunique, unique) == 0)
-    {
-      snprintf(fname, sizeof(fname), "%s/%s/%s", folder, subfolder, de->d_name);
-      fp = fopen(fname, "r");
-      oe = errno;
-      break;
-    }
+#ifdef USE_HCACHE
+    if (mh_sync_mailbox_message(ctx, i, hc) == -1)
+      goto err;
+#else
+    if (mh_sync_mailbox_message(ctx, i) == -1)
+      goto err;
+#endif
   }
 
-  closedir(dp);
-
-  if (newname && fp)
-    *newname = mutt_str_strdup(fname);
+#ifdef USE_HCACHE
+  if (ctx->mailbox->magic == MUTT_MAILDIR || ctx->mailbox->magic == MUTT_MH)
+    mutt_hcache_close(hc);
+#endif
 
-  errno = oe;
-  return fp;
-}
+  if (ctx->mailbox->magic == MUTT_MH)
+    mh_update_sequences(ctx->mailbox);
 
-/**
- * maildir_open_find_message - Find a new
- * @param folder  Maildir path
- * @param msg     Email path
- * @param newname New name, if it has moved
- * @retval ptr File handle
- */
-FILE *maildir_open_find_message(const char *folder, const char *msg, char **newname)
-{
-  char unique[PATH_MAX];
+  /* XXX race condition? */
 
-  static unsigned int new_hits = 0, cur_hits = 0; /* simple dynamic optimization */
+  maildir_update_mtime(ctx->mailbox);
 
-  maildir_canon_filename(msg, unique, sizeof(unique));
+  /* adjust indices */
 
-  FILE *fp = md_open_find_message(folder, unique, new_hits > cur_hits ? "new" : "cur", newname);
-  if (fp || (errno != ENOENT))
+  if (ctx->deleted)
   {
-    if (new_hits < UINT_MAX && cur_hits < UINT_MAX)
+    for (i = 0, j = 0; i < ctx->mailbox->msg_count; i++)
     {
-      new_hits += (new_hits > cur_hits ? 1 : 0);
-      cur_hits += (new_hits > cur_hits ? 0 : 1);
+      if (!ctx->mailbox->hdrs[i]->deleted || (ctx->mailbox->magic == MUTT_MAILDIR && MaildirTrash))
+        ctx->mailbox->hdrs[i]->index = j++;
     }
-
-    return fp;
   }
-  fp = md_open_find_message(folder, unique, new_hits > cur_hits ? "cur" : "new", newname);
-  if (fp || (errno != ENOENT))
-  {
-    if (new_hits < UINT_MAX && cur_hits < UINT_MAX)
-    {
-      new_hits += (new_hits > cur_hits ? 0 : 1);
-      cur_hits += (new_hits > cur_hits ? 1 : 0);
-    }
 
-    return fp;
-  }
+  return 0;
 
-  return NULL;
+err:
+#ifdef USE_HCACHE
+  if (ctx->mailbox->magic == MUTT_MAILDIR || ctx->mailbox->magic == MUTT_MH)
+    mutt_hcache_close(hc);
+#endif
+  return -1;
 }
 
 /**
- * maildir_check_empty - Is the mailbox empty
- * @param path Mailbox to check
- * @retval 1 Mailbox is empty
- * @retval 0 Mailbox contains mail
- * @retval -1 Error
+ * mh_mbox_close - Implements MxOps::mbox_close()
+ * @retval 0 Always
  */
-int maildir_check_empty(const char *path)
+static int mh_mbox_close(struct Context *ctx)
 {
-  DIR *dp = NULL;
-  struct dirent *de = NULL;
-  int r = 1; /* assume empty until we find a message */
-  char realpath[PATH_MAX];
-  int iter = 0;
-
-  /* Strategy here is to look for any file not beginning with a period */
-
-  do
-  {
-    /* we do "cur" on the first iteration since it's more likely that we'll
-     * find old messages without having to scan both subdirs
-     */
-    snprintf(realpath, sizeof(realpath), "%s/%s", path, iter == 0 ? "cur" : "new");
-    dp = opendir(realpath);
-    if (!dp)
-      return -1;
-    while ((de = readdir(dp)))
-    {
-      if (*de->d_name != '.')
-      {
-        r = 0;
-        break;
-      }
-    }
-    closedir(dp);
-    iter++;
-  } while (r && iter < 2);
+  FREE(&ctx->mailbox->data);
 
-  return r;
+  return 0;
 }
 
 /**
- * mh_check_empty - Is mailbox empty
- * @param path Mailbox to check
- * @retval 1 Mailbox is empty
- * @retval 0 Mailbox contains mail
- * @retval -1 Error
+ * mh_msg_open - Implements MxOps::msg_open()
  */
-int mh_check_empty(const char *path)
+static int mh_msg_open(struct Context *ctx, struct Message *msg, int msgno)
 {
-  struct dirent *de = NULL;
-  int r = 1; /* assume empty until we find a message */
-
-  DIR *dp = opendir(path);
-  if (!dp)
-    return -1;
-  while ((de = readdir(dp)))
-  {
-    if (mh_valid_message(de->d_name))
-    {
-      r = 0;
-      break;
-    }
-  }
-  closedir(dp);
-
-  return r;
+  return maildir_mh_open_message(ctx->mailbox, msg, msgno, 0);
 }
 
 /**
- * maildir_path_probe - Is this a Maildir mailbox? - Implements MxOps::path_probe()
+ * mh_msg_open_new - Implements MxOps::msg_open_new()
+ *
+ * Open a new (temporary) message in an MH folder.
  */
-int maildir_path_probe(const char *path, const struct stat *st)
+static int mh_msg_open_new(struct Context *ctx, struct Message *msg, struct Header *hdr)
 {
-  if (!path)
-    return MUTT_UNKNOWN;
-
-  if (!st || !S_ISDIR(st->st_mode))
-    return MUTT_UNKNOWN;
-
-  char cur[PATH_MAX];
-  snprintf(cur, sizeof(cur), "%s/cur", path);
+  return mh_mkstemp(ctx->mailbox, &msg->fp, &msg->path);
+}
 
-  struct stat stc;
-  if ((stat(cur, &stc) == 0) && S_ISDIR(stc.st_mode))
-    return MUTT_MAILDIR;
+/**
+ * mh_msg_commit - Implements MxOps::msg_commit()
+ */
+static int mh_msg_commit(struct Context *ctx, struct Message *msg)
+{
+  return mh_commit_msg(ctx->mailbox, msg, NULL, true);
+}
 
-  return MUTT_UNKNOWN;
+/**
+ * mh_msg_close - Close a message
+ * @param ctx Mailbox
+ * @param msg Message to close
+ * @retval 0   Success
+ * @retval EOF Error, see errno
+ *
+ * @note May also return EOF Failure, see errno
+ */
+static int mh_msg_close(struct Context *ctx, struct Message *msg)
+{
+  return mutt_file_fclose(&msg->fp);
 }
 
 /**
@@ -2969,64 +3027,6 @@ int mh_path_probe(const char *path, const struct stat *st)
   return MUTT_UNKNOWN;
 }
 
-/**
- * maildir_path_canon - Canonicalise a mailbox path - Implements MxOps::path_canon()
- */
-int maildir_path_canon(char *buf, size_t buflen, const char *folder)
-{
-  if (!buf)
-    return -1;
-
-  if ((buf[0] == '+') || (buf[0] == '='))
-  {
-    if (!folder)
-      return -1;
-
-    buf[0] = '/';
-    mutt_str_inline_replace(buf, buflen, 0, folder);
-  }
-
-  mutt_path_canon(buf, buflen, HomeDir);
-  return 0;
-}
-
-/**
- * maildir_path_pretty - Implements MxOps::path_pretty()
- */
-int maildir_path_pretty(char *buf, size_t buflen, const char *folder)
-{
-  if (!buf)
-    return -1;
-
-  if (mutt_path_abbr_folder(buf, buflen, folder))
-    return 0;
-
-  if (mutt_path_pretty(buf, buflen, HomeDir))
-    return 0;
-
-  return -1;
-}
-
-/**
- * maildir_path_parent - Implements MxOps::path_parent()
- */
-int maildir_path_parent(char *buf, size_t buflen)
-{
-  if (!buf)
-    return -1;
-
-  if (mutt_path_parent(buf, buflen))
-    return 0;
-
-  if (buf[0] == '~')
-    mutt_path_canon(buf, buflen, HomeDir);
-
-  if (mutt_path_parent(buf, buflen))
-    return 0;
-
-  return -1;
-}
-
 // clang-format off
 /**
  * struct mx_maildir_ops - Maildir mailbox - Implements ::MxOps