/* Read a MH/maildir style mailbox. * * args: * ctx [IN/OUT] context for this mailbox * subdir [IN] NULL for MH mailboxes, otherwise the subdir of the * maildir mailbox to read from */ int mh_read_dir (CONTEXT *ctx, const char *subdir) { struct maildir *md; struct mh_sequences mhs; struct maildir **last; int count; md = NULL; last = &md; count = 0; memset (&mhs, 0, sizeof (mhs)); maildir_update_mtime(ctx); if(maildir_parse_dir(ctx, &last, subdir, &count) == -1) return -1; if (ctx->magic == M_MH) { mh_read_sequences (&mhs, ctx->path); mh_update_maildir (md, &mhs); mhs_free_sequences (&mhs); } maildir_move_to_context(ctx, &md); return 0; }
int mh_buffy (const char *path) { int i, r = 0; struct mh_sequences mhs; memset (&mhs, 0, sizeof (mhs)); mh_read_sequences (&mhs, path); for (i = 0; !r && i <= mhs.max; i++) if (mhs_check (&mhs, i) & MH_SEQ_UNSEEN) r = 1; mhs_free_sequences (&mhs); return r; }
/** * mh_read_sequences - Read a set of MH sequences * @param mhs Existing sequences * @param path File to read from * @retval 0 Success * @retval -1 Error */ int mh_read_sequences(struct MhSequences *mhs, const char *path) { int line = 1; char *buf = NULL; size_t sz = 0; MhSeqFlags flags; int first, last, rc = 0; char pathname[PATH_MAX]; snprintf(pathname, sizeof(pathname), "%s/.mh_sequences", path); FILE *fp = fopen(pathname, "r"); if (!fp) return 0; /* yes, ask callers to silently ignore the error */ while ((buf = mutt_file_read_line(buf, &sz, fp, &line, 0))) { char *t = strtok(buf, " \t:"); if (!t) continue; if (mutt_str_strcmp(t, C_MhSeqUnseen) == 0) flags = MH_SEQ_UNSEEN; else if (mutt_str_strcmp(t, C_MhSeqFlagged) == 0) flags = MH_SEQ_FLAGGED; else if (mutt_str_strcmp(t, C_MhSeqReplied) == 0) flags = MH_SEQ_REPLIED; else /* unknown sequence */ continue; while ((t = strtok(NULL, " \t:"))) { if (mh_read_token(t, &first, &last) < 0) { mhs_free_sequences(mhs); rc = -1; goto out; } for (; first <= last; first++) mhs_set(mhs, first, flags); } } rc = 0; out: FREE(&buf); mutt_file_fclose(&fp); return rc; }
void mh_update_sequences (CONTEXT *ctx) { FILE *ofp, *nfp; char sequences[_POSIX_PATH_MAX]; char *tmpfname; char *buff = NULL; char *p; size_t s; int l = 0; int i; int unseen = 0; int flagged = 0; int replied = 0; char seq_unseen[STRING]; char seq_replied[STRING]; char seq_flagged[STRING]; struct mh_sequences mhs; memset (&mhs, 0, sizeof (mhs)); snprintf (seq_unseen, sizeof (seq_unseen), "%s:", NONULL (MhUnseen)); snprintf (seq_replied, sizeof (seq_replied), "%s:", NONULL (MhReplied)); snprintf (seq_flagged, sizeof (seq_flagged), "%s:", NONULL (MhFlagged)); if (mh_mkstemp (ctx, &nfp, &tmpfname) != 0) { /* error message? */ return; } snprintf (sequences, sizeof (sequences), "%s/.mh_sequences", ctx->path); /* first, copy unknown sequences */ if ((ofp = fopen (sequences, "r"))) { while ((buff = mutt_read_line (buff, &s, ofp, &l))) { if (!mutt_strncmp (buff, seq_unseen, mutt_strlen (seq_unseen))) continue; if (!mutt_strncmp (buff, seq_flagged, mutt_strlen (seq_flagged))) continue; if (!mutt_strncmp (buff, seq_replied, mutt_strlen (seq_replied))) continue; fprintf (nfp, "%s\n", buff); } } safe_fclose (&ofp); /* now, update our unseen, flagged, and replied sequences */ for (l = 0; l < ctx->msgcount; l++) { if (ctx->hdrs[l]->deleted) continue; if ((p = strrchr (ctx->hdrs[l]->path, '/'))) p++; else p = ctx->hdrs[l]->path; i = atoi (p); if (!ctx->hdrs[l]->read) { mhs_set (&mhs, i, MH_SEQ_UNSEEN); unseen++; } if (ctx->hdrs[l]->flagged) { mhs_set (&mhs, i, MH_SEQ_FLAGGED); flagged++; } if (ctx->hdrs[l]->replied) { mhs_set (&mhs, i, MH_SEQ_REPLIED); replied++; } } /* write out the new sequences */ if (unseen) mhs_write_one_sequence (nfp, &mhs, MH_SEQ_UNSEEN, NONULL (MhUnseen)); if (flagged) mhs_write_one_sequence (nfp, &mhs, MH_SEQ_FLAGGED, NONULL (MhFlagged)); if (replied) mhs_write_one_sequence (nfp, &mhs, MH_SEQ_REPLIED, NONULL (MhReplied)); mhs_free_sequences (&mhs); /* try to commit the changes - no guarantee here */ safe_fclose (&nfp); unlink (sequences); if (safe_rename (tmpfname, sequences) != 0) { /* report an error? */ unlink (tmpfname); } safe_free ((void **) &tmpfname); }
int mh_check_mailbox(CONTEXT *ctx, int *index_hint) { char buf[_POSIX_PATH_MAX], b1[LONG_STRING], b2[LONG_STRING]; struct stat st, st_cur; short modified = 0, have_new = 0, occult = 0; struct maildir *md, *p; struct maildir **last; HASH *fnames; int i, j; if(!option (OPTCHECKNEW)) return 0; if(ctx->magic == M_MH) { strfcpy(buf, ctx->path, sizeof(buf)); if(stat(buf, &st) == -1) return -1; /* create .mh_sequences when there isn't one. */ snprintf (buf, sizeof (buf), "%s/.mh_sequences", ctx->path); if (stat (buf, &st_cur) == -1) { if (errno == ENOENT) { char *tmp; FILE *fp = NULL; if (mh_mkstemp (ctx, &fp, &tmp) == 0) { safe_fclose (&fp); if (safe_rename (tmp, buf) == -1) unlink (tmp); safe_free ((void **) &tmp); } if (stat (buf, &st_cur) == -1) modified = 1; } else modified = 1; } } else if(ctx->magic == M_MAILDIR) { snprintf(buf, sizeof(buf), "%s/new", ctx->path); if(stat(buf, &st) == -1) return -1; snprintf(buf, sizeof(buf), "%s/cur", ctx->path); if(stat(buf, &st_cur) == -1) /* XXX - name is bad. */ modified = 1; } if(!modified && ctx->magic == M_MAILDIR && st_cur.st_mtime > ctx->mtime_cur) modified = 1; if(!modified && ctx->magic == M_MH && (st.st_mtime > ctx->mtime || st_cur.st_mtime > ctx->mtime_cur)) modified = 1; if(modified || (ctx->magic == M_MAILDIR && st.st_mtime > ctx->mtime)) have_new = 1; if(!modified && !have_new) return 0; ctx->mtime_cur = st_cur.st_mtime; ctx->mtime = st.st_mtime; #if 0 if(Sort != SORT_ORDER) { short old_sort; old_sort = Sort; Sort = SORT_ORDER; mutt_sort_headers(ctx, 1); Sort = old_sort; } #endif md = NULL; last = &md; if(ctx->magic == M_MAILDIR) { if(have_new) maildir_parse_dir(ctx, &last, "new", NULL); if(modified) maildir_parse_dir(ctx, &last, "cur", NULL); } else if(ctx->magic == M_MH) { struct mh_sequences mhs; memset (&mhs, 0, sizeof (mhs)); maildir_parse_dir (ctx, &last, NULL, NULL); mh_read_sequences (&mhs, ctx->path); mh_update_maildir (md, &mhs); mhs_free_sequences (&mhs); } /* check for modifications and adjust flags */ fnames = hash_create (1031); for(p = md; p; p = p->next) { if(ctx->magic == M_MAILDIR) { maildir_canon_filename(b2, p->h->path, sizeof(b2)); p->canon_fname = safe_strdup(b2); } else p->canon_fname = safe_strdup(p->h->path); hash_insert(fnames, p->canon_fname, p, 0); } for(i = 0; i < ctx->msgcount; i++) { ctx->hdrs[i]->active = 0; if(ctx->magic == M_MAILDIR) maildir_canon_filename(b1, ctx->hdrs[i]->path, sizeof(b1)); else strfcpy(b1, ctx->hdrs[i]->path, sizeof(b1)); dprint(2, (debugfile, "%s:%d: mh_check_mailbox(): Looking for %s.\n", __FILE__, __LINE__, b1)); if((p = hash_find(fnames, b1)) && p->h && mbox_strict_cmp_headers(ctx->hdrs[i], p->h)) { /* found the right message */ dprint(2, (debugfile, "%s:%d: Found. Flags before: %s%s%s%s%s\n", __FILE__, __LINE__, ctx->hdrs[i]->flagged ? "f" : "", ctx->hdrs[i]->deleted ? "D" : "", ctx->hdrs[i]->replied ? "r" : "", ctx->hdrs[i]->old ? "O" : "", ctx->hdrs[i]->read ? "R" : "")); if(mutt_strcmp(ctx->hdrs[i]->path, p->h->path)) mutt_str_replace (&ctx->hdrs[i]->path, p->h->path); if(modified) { if(!ctx->hdrs[i]->changed) { mutt_set_flag (ctx, ctx->hdrs[i], M_FLAG, p->h->flagged); mutt_set_flag (ctx, ctx->hdrs[i], M_REPLIED, p->h->replied); mutt_set_flag (ctx, ctx->hdrs[i], M_READ, p->h->read); } mutt_set_flag(ctx, ctx->hdrs[i], M_OLD, p->h->old); } ctx->hdrs[i]->active = 1; dprint(2, (debugfile, "%s:%d: Flags after: %s%s%s%s%s\n", __FILE__, __LINE__, ctx->hdrs[i]->flagged ? "f" : "", ctx->hdrs[i]->deleted ? "D" : "", ctx->hdrs[i]->replied ? "r" : "", ctx->hdrs[i]->old ? "O" : "", ctx->hdrs[i]->read ? "R" : "")); mutt_free_header(&p->h); } else if (ctx->magic == M_MAILDIR && !modified && !strncmp("cur/", ctx->hdrs[i]->path, 4)) { /* If the cur/ part wasn't externally modified for a maildir * type folder, assume the message is still active. Actually, * we simply don't know. */ ctx->hdrs[i]->active = 1; } else if (modified || (ctx->magic == M_MAILDIR && !strncmp("new/", ctx->hdrs[i]->path, 4))) { /* Mailbox was modified, or a new message vanished. */ /* Note: This code will _not_ apply for a new message which * is just moved to cur/, as this would modify cur's time * stamp and lead to modified == 1. Thus, we'd have parsed * the complete folder above, and the message would have * been found in the look-up table. */ dprint(2, (debugfile, "%s:%d: Not found. Flags were: %s%s%s%s%s\n", __FILE__, __LINE__, ctx->hdrs[i]->flagged ? "f" : "", ctx->hdrs[i]->deleted ? "D" : "", ctx->hdrs[i]->replied ? "r" : "", ctx->hdrs[i]->old ? "O" : "", ctx->hdrs[i]->read ? "R" : "")); occult = 1; } } /* destroy the file name hash */ hash_destroy(&fnames, NULL); /* If we didn't just get new mail, update the tables. */ if(modified || occult) { short old_sort; int old_count; #ifndef LIBMUTT if (Sort != SORT_ORDER) { old_sort = Sort; Sort = SORT_ORDER; mutt_sort_headers (ctx, 1); Sort = old_sort; } #endif old_count = ctx->msgcount; for (i = 0, j = 0; i < old_count; i++) { if (ctx->hdrs[i]->active && index_hint && *index_hint == i) *index_hint = j; if (ctx->hdrs[i]->active) ctx->hdrs[i]->index = j++; } mx_update_tables(ctx, 0); } /* Incorporate new messages */ maildir_move_to_context(ctx, &md); return (modified || occult) ? M_REOPENED : have_new ? M_NEW_MAIL : 0; }
/** * mh_mbox_check - Implements MxOps::mbox_check() * * This function handles arrival of new mail and reopening of mh/maildir * folders. Things are getting rather complex because we don't have a * well-defined "mailbox order", so the tricks from mbox.c and mx.c won't work * here. * * Don't change this code unless you _really_ understand what happens. */ int mh_mbox_check(struct Mailbox *m, int *index_hint) { if (!m) return -1; char buf[PATH_MAX]; struct stat st, st_cur; bool modified = false, occult = false, flags_changed = false; int num_new = 0; struct Maildir *md = NULL, *p = NULL; struct Maildir **last = NULL; struct MhSequences mhs = { 0 }; int count = 0; struct Hash *fnames = NULL; struct MaildirMboxData *mdata = maildir_mdata_get(m); if (!C_CheckNew) return 0; mutt_str_strfcpy(buf, m->path, sizeof(buf)); if (stat(buf, &st) == -1) return -1; /* create .mh_sequences when there isn't one. */ snprintf(buf, sizeof(buf), "%s/.mh_sequences", m->path); int i = stat(buf, &st_cur); if ((i == -1) && (errno == ENOENT)) { char *tmp = NULL; FILE *fp = NULL; if (mh_mkstemp(m, &fp, &tmp) == 0) { mutt_file_fclose(&fp); if (mutt_file_safe_rename(tmp, buf) == -1) unlink(tmp); FREE(&tmp); } } if ((i == -1) && (stat(buf, &st_cur) == -1)) modified = true; if ((mutt_file_stat_timespec_compare(&st, MUTT_STAT_MTIME, &m->mtime) > 0) || (mutt_file_stat_timespec_compare(&st_cur, MUTT_STAT_MTIME, &mdata->mtime_cur) > 0)) { modified = true; } if (!modified) return 0; /* Update the modification times on the mailbox. * * The monitor code notices changes in the open mailbox too quickly. * In practice, this sometimes leads to all the new messages not being * noticed during the SAME group of mtime stat updates. To work around * the problem, don't update the stat times for a monitor caused check. */ #ifdef USE_INOTIFY if (MonitorContextChanged) MonitorContextChanged = 0; else #endif { mutt_file_get_stat_timespec(&mdata->mtime_cur, &st_cur, MUTT_STAT_MTIME); mutt_file_get_stat_timespec(&m->mtime, &st, MUTT_STAT_MTIME); } md = NULL; last = &md; maildir_parse_dir(m, &last, NULL, &count, NULL); maildir_delayed_parsing(m, &md, NULL); if (mh_read_sequences(&mhs, m->path) < 0) return -1; mh_update_maildir(md, &mhs); mhs_free_sequences(&mhs); /* check for modifications and adjust flags */ fnames = mutt_hash_new(count, MUTT_HASH_NO_FLAGS); for (p = md; p; p = p->next) { /* the hash key must survive past the header, which is freed below. */ p->canon_fname = mutt_str_strdup(p->email->path); mutt_hash_insert(fnames, p->canon_fname, p); } for (i = 0; i < m->msg_count; i++) { m->emails[i]->active = false; p = mutt_hash_find(fnames, m->emails[i]->path); if (p && p->email && mutt_email_cmp_strict(m->emails[i], p->email)) { m->emails[i]->active = true; /* found the right message */ if (!m->emails[i]->changed) if (maildir_update_flags(m, m->emails[i], p->email)) flags_changed = true; mutt_email_free(&p->email); } else /* message has disappeared */ occult = true; } /* destroy the file name hash */ mutt_hash_free(&fnames); /* If we didn't just get new mail, update the tables. */ if (occult) mutt_mailbox_changed(m, MBN_RESORT); /* Incorporate new messages */ num_new = maildir_move_to_mailbox(m, &md); if (num_new > 0) { mutt_mailbox_changed(m, MBN_INVALID); m->changed = true; } if (occult) return MUTT_REOPENED; if (num_new > 0) return MUTT_NEW_MAIL; if (flags_changed) return MUTT_FLAGS; return 0; }
/** * mh_mbox_check_stats - Implements MxOps::check_stats */ static int mh_mbox_check_stats(struct Mailbox *m, int flags) { 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 m visit, there is no "new mail" */ if (C_MailCheckRecent && (mh_sequences_changed(m) <= 0)) { rc = false; check_new = false; } if (!check_new) return 0; if (mh_read_sequences(&mhs, m->path) < 0) return false; m->msg_count = 0; m->msg_unread = 0; m->msg_flagged = 0; for (int i = mhs.max; i > 0; i--) { if ((mhs_check(&mhs, i) & MH_SEQ_FLAGGED)) m->msg_flagged++; if (mhs_check(&mhs, i) & MH_SEQ_UNSEEN) { m->msg_unread++; if (check_new) { /* if the first unseen message we encounter was in the m during the * last visit, don't notify about it */ if (!C_MailCheckRecent || (mh_already_notified(m, i) == 0)) { m->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; } } } mhs_free_sequences(&mhs); dirp = opendir(m->path); if (dirp) { while ((de = readdir(dirp))) { if (*de->d_name == '.') continue; if (mh_valid_message(de->d_name)) m->msg_count++; } closedir(dirp); } return rc; }
/** * mh_update_sequences - Update sequence numbers * @param m Mailbox * * XXX we don't currently remove deleted messages from sequences we don't know. * Should we? */ void mh_update_sequences(struct Mailbox *m) { char sequences[PATH_MAX]; char *tmpfname = NULL; char *buf = NULL; char *p = NULL; size_t s; int l = 0; int i; int unseen = 0; int flagged = 0; int replied = 0; char seq_unseen[256]; char seq_replied[256]; char seq_flagged[256]; struct MhSequences mhs = { 0 }; snprintf(seq_unseen, sizeof(seq_unseen), "%s:", NONULL(C_MhSeqUnseen)); snprintf(seq_replied, sizeof(seq_replied), "%s:", NONULL(C_MhSeqReplied)); snprintf(seq_flagged, sizeof(seq_flagged), "%s:", NONULL(C_MhSeqFlagged)); FILE *fp_new = NULL; if (mh_mkstemp(m, &fp_new, &tmpfname) != 0) { /* error message? */ return; } snprintf(sequences, sizeof(sequences), "%s/.mh_sequences", m->path); /* first, copy unknown sequences */ FILE *fp_old = fopen(sequences, "r"); if (fp_old) { while ((buf = mutt_file_read_line(buf, &s, fp_old, &l, 0))) { if (mutt_str_startswith(buf, seq_unseen, CASE_MATCH) || mutt_str_startswith(buf, seq_flagged, CASE_MATCH) || mutt_str_startswith(buf, seq_replied, CASE_MATCH)) continue; fprintf(fp_new, "%s\n", buf); } } mutt_file_fclose(&fp_old); /* now, update our unseen, flagged, and replied sequences */ for (l = 0; l < m->msg_count; l++) { if (m->emails[l]->deleted) continue; p = strrchr(m->emails[l]->path, '/'); if (p) p++; else p = m->emails[l]->path; if (mutt_str_atoi(p, &i) < 0) continue; if (!m->emails[l]->read) { mhs_set(&mhs, i, MH_SEQ_UNSEEN); unseen++; } if (m->emails[l]->flagged) { mhs_set(&mhs, i, MH_SEQ_FLAGGED); flagged++; } if (m->emails[l]->replied) { mhs_set(&mhs, i, MH_SEQ_REPLIED); replied++; } } /* write out the new sequences */ if (unseen) mhs_write_one_sequence(fp_new, &mhs, MH_SEQ_UNSEEN, NONULL(C_MhSeqUnseen)); if (flagged) mhs_write_one_sequence(fp_new, &mhs, MH_SEQ_FLAGGED, NONULL(C_MhSeqFlagged)); if (replied) mhs_write_one_sequence(fp_new, &mhs, MH_SEQ_REPLIED, NONULL(C_MhSeqReplied)); mhs_free_sequences(&mhs); /* try to commit the changes - no guarantee here */ mutt_file_fclose(&fp_new); unlink(sequences); if (mutt_file_safe_rename(tmpfname, sequences) != 0) { /* report an error? */ unlink(tmpfname); } FREE(&tmpfname); }