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
}
}
-/**
- * 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
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
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
}
/**
- * 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;
}
}
/**
- * 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);
}
}
-/**
- * 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
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;
}
/**
}
/**
- * 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;
}
/**
*/
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;
}
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);
}
/**
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