NeoMutt  2021-02-05-666-ge300cd
Teaching an old dog new tricks
DOXYGEN
sequence.c
Go to the documentation of this file.
1 
29 #include "config.h"
30 #include <limits.h>
31 #include <stdio.h>
32 #include <string.h>
33 #include <sys/stat.h>
34 #include <unistd.h>
35 #include "private.h"
36 #include "mutt/lib.h"
37 #include "config/lib.h"
38 #include "email/lib.h"
39 #include "core/lib.h"
40 #include "sequence.h"
41 
49 static void mh_seq_alloc(struct MhSequences *mhs, int i)
50 {
51  if ((i <= mhs->max) && mhs->flags)
52  return;
53 
54  const int newmax = i + 128;
55  int j = mhs->flags ? mhs->max + 1 : 0;
56  mutt_mem_realloc(&mhs->flags, sizeof(mhs->flags[0]) * (newmax + 1));
57  while (j <= newmax)
58  mhs->flags[j++] = 0;
59 
60  mhs->max = newmax;
61 }
62 
67 void mh_seq_free(struct MhSequences *mhs)
68 {
69  FREE(&mhs->flags);
70 }
71 
78 MhSeqFlags mh_seq_check(struct MhSequences *mhs, int i)
79 {
80  if (!mhs->flags || (i > mhs->max))
81  return 0;
82  return mhs->flags[i];
83 }
84 
93 {
94  mh_seq_alloc(mhs, i);
95  mhs->flags[i] |= f;
96  return mhs->flags[i];
97 }
98 
107 void mh_seq_add_one(struct Mailbox *m, int n, bool unseen, bool flagged, bool replied)
108 {
109  bool unseen_done = false;
110  bool flagged_done = false;
111  bool replied_done = false;
112 
113  char *tmpfname = NULL;
114  char sequences[PATH_MAX];
115 
116  char seq_unseen[256];
117  char seq_replied[256];
118  char seq_flagged[256];
119 
120  char *buf = NULL;
121  size_t sz;
122 
123  FILE *fp_new = NULL;
124  if (!mh_mkstemp(m, &fp_new, &tmpfname))
125  return;
126 
127  const char *const c_mh_seq_unseen =
128  cs_subset_string(NeoMutt->sub, "mh_seq_unseen");
129  const char *const c_mh_seq_replied =
130  cs_subset_string(NeoMutt->sub, "mh_seq_replied");
131  const char *const c_mh_seq_flagged =
132  cs_subset_string(NeoMutt->sub, "mh_seq_flagged");
133  snprintf(seq_unseen, sizeof(seq_unseen), "%s:", NONULL(c_mh_seq_unseen));
134  snprintf(seq_replied, sizeof(seq_replied), "%s:", NONULL(c_mh_seq_replied));
135  snprintf(seq_flagged, sizeof(seq_flagged), "%s:", NONULL(c_mh_seq_flagged));
136 
137  snprintf(sequences, sizeof(sequences), "%s/.mh_sequences", mailbox_path(m));
138  FILE *fp_old = fopen(sequences, "r");
139  if (fp_old)
140  {
141  while ((buf = mutt_file_read_line(buf, &sz, fp_old, NULL, MUTT_RL_NO_FLAGS)))
142  {
143  if (unseen && mutt_strn_equal(buf, seq_unseen, mutt_str_len(seq_unseen)))
144  {
145  fprintf(fp_new, "%s %d\n", buf, n);
146  unseen_done = true;
147  }
148  else if (flagged && mutt_strn_equal(buf, seq_flagged, mutt_str_len(seq_flagged)))
149  {
150  fprintf(fp_new, "%s %d\n", buf, n);
151  flagged_done = true;
152  }
153  else if (replied && mutt_strn_equal(buf, seq_replied, mutt_str_len(seq_replied)))
154  {
155  fprintf(fp_new, "%s %d\n", buf, n);
156  replied_done = true;
157  }
158  else
159  fprintf(fp_new, "%s\n", buf);
160  }
161  }
162  mutt_file_fclose(&fp_old);
163  FREE(&buf);
164 
165  if (!unseen_done && unseen)
166  fprintf(fp_new, "%s: %d\n", NONULL(c_mh_seq_unseen), n);
167  if (!flagged_done && flagged)
168  fprintf(fp_new, "%s: %d\n", NONULL(c_mh_seq_flagged), n);
169  if (!replied_done && replied)
170  fprintf(fp_new, "%s: %d\n", NONULL(c_mh_seq_replied), n);
171 
172  mutt_file_fclose(&fp_new);
173 
174  unlink(sequences);
175  if (mutt_file_safe_rename(tmpfname, sequences) != 0)
176  unlink(tmpfname);
177 
178  FREE(&tmpfname);
179 }
180 
188 static void mh_seq_write_one(FILE *fp, struct MhSequences *mhs, MhSeqFlags f, const char *tag)
189 {
190  fprintf(fp, "%s:", tag);
191 
192  int first = -1;
193  int last = -1;
194 
195  for (int i = 0; i <= mhs->max; i++)
196  {
197  if ((mh_seq_check(mhs, i) & f))
198  {
199  if (first < 0)
200  first = i;
201  else
202  last = i;
203  }
204  else if (first >= 0)
205  {
206  if (last < 0)
207  fprintf(fp, " %d", first);
208  else
209  fprintf(fp, " %d-%d", first, last);
210 
211  first = -1;
212  last = -1;
213  }
214  }
215 
216  if (first >= 0)
217  {
218  if (last < 0)
219  fprintf(fp, " %d", first);
220  else
221  fprintf(fp, " %d-%d", first, last);
222  }
223 
224  fputc('\n', fp);
225 }
226 
234 void mh_seq_update(struct Mailbox *m)
235 {
236  char sequences[PATH_MAX];
237  char *tmpfname = NULL;
238  char *buf = NULL;
239  char *p = NULL;
240  size_t s;
241  int seq_num = 0;
242 
243  int unseen = 0;
244  int flagged = 0;
245  int replied = 0;
246 
247  char seq_unseen[256];
248  char seq_replied[256];
249  char seq_flagged[256];
250 
251  struct MhSequences mhs = { 0 };
252 
253  const char *const c_mh_seq_unseen =
254  cs_subset_string(NeoMutt->sub, "mh_seq_unseen");
255  const char *const c_mh_seq_replied =
256  cs_subset_string(NeoMutt->sub, "mh_seq_replied");
257  const char *const c_mh_seq_flagged =
258  cs_subset_string(NeoMutt->sub, "mh_seq_flagged");
259  snprintf(seq_unseen, sizeof(seq_unseen), "%s:", NONULL(c_mh_seq_unseen));
260  snprintf(seq_replied, sizeof(seq_replied), "%s:", NONULL(c_mh_seq_replied));
261  snprintf(seq_flagged, sizeof(seq_flagged), "%s:", NONULL(c_mh_seq_flagged));
262 
263  FILE *fp_new = NULL;
264  if (!mh_mkstemp(m, &fp_new, &tmpfname))
265  {
266  /* error message? */
267  return;
268  }
269 
270  snprintf(sequences, sizeof(sequences), "%s/.mh_sequences", mailbox_path(m));
271 
272  /* first, copy unknown sequences */
273  FILE *fp_old = fopen(sequences, "r");
274  if (fp_old)
275  {
276  while ((buf = mutt_file_read_line(buf, &s, fp_old, NULL, MUTT_RL_NO_FLAGS)))
277  {
278  if (mutt_str_startswith(buf, seq_unseen) || mutt_str_startswith(buf, seq_flagged) ||
279  mutt_str_startswith(buf, seq_replied))
280  {
281  continue;
282  }
283 
284  fprintf(fp_new, "%s\n", buf);
285  }
286  }
287  mutt_file_fclose(&fp_old);
288 
289  /* now, update our unseen, flagged, and replied sequences */
290  for (int i = 0; i < m->msg_count; i++)
291  {
292  struct Email *e = m->emails[i];
293  if (!e)
294  break;
295 
296  if (e->deleted)
297  continue;
298 
299  p = strrchr(e->path, '/');
300  if (p)
301  p++;
302  else
303  p = e->path;
304 
305  if (mutt_str_atoi(p, &seq_num) < 0)
306  continue;
307 
308  if (!e->read)
309  {
310  mh_seq_set(&mhs, seq_num, MH_SEQ_UNSEEN);
311  unseen++;
312  }
313  if (e->flagged)
314  {
315  mh_seq_set(&mhs, seq_num, MH_SEQ_FLAGGED);
316  flagged++;
317  }
318  if (e->replied)
319  {
320  mh_seq_set(&mhs, seq_num, MH_SEQ_REPLIED);
321  replied++;
322  }
323  }
324 
325  /* write out the new sequences */
326  if (unseen)
327  mh_seq_write_one(fp_new, &mhs, MH_SEQ_UNSEEN, NONULL(c_mh_seq_unseen));
328  if (flagged)
329  mh_seq_write_one(fp_new, &mhs, MH_SEQ_FLAGGED, NONULL(c_mh_seq_flagged));
330  if (replied)
331  mh_seq_write_one(fp_new, &mhs, MH_SEQ_REPLIED, NONULL(c_mh_seq_replied));
332 
333  mh_seq_free(&mhs);
334 
335  /* try to commit the changes - no guarantee here */
336  mutt_file_fclose(&fp_new);
337 
338  unlink(sequences);
339  if (mutt_file_safe_rename(tmpfname, sequences) != 0)
340  {
341  /* report an error? */
342  unlink(tmpfname);
343  }
344 
345  FREE(&tmpfname);
346 }
347 
356 static int mh_seq_read_token(char *t, int *first, int *last)
357 {
358  char *p = strchr(t, '-');
359  if (p)
360  {
361  *p++ = '\0';
362  if ((mutt_str_atoi(t, first) < 0) || (mutt_str_atoi(p, last) < 0))
363  return -1;
364  }
365  else
366  {
367  if (mutt_str_atoi(t, first) < 0)
368  return -1;
369  *last = *first;
370  }
371  return 0;
372 }
373 
381 int mh_seq_read(struct MhSequences *mhs, const char *path)
382 {
383  char *buf = NULL;
384  size_t sz = 0;
385 
386  MhSeqFlags flags;
387  int first, last, rc = 0;
388 
389  char pathname[PATH_MAX];
390  snprintf(pathname, sizeof(pathname), "%s/.mh_sequences", path);
391 
392  FILE *fp = fopen(pathname, "r");
393  if (!fp)
394  return 0; /* yes, ask callers to silently ignore the error */
395 
396  while ((buf = mutt_file_read_line(buf, &sz, fp, NULL, MUTT_RL_NO_FLAGS)))
397  {
398  char *t = strtok(buf, " \t:");
399  if (!t)
400  continue;
401 
402  const char *const c_mh_seq_unseen =
403  cs_subset_string(NeoMutt->sub, "mh_seq_unseen");
404  const char *const c_mh_seq_flagged =
405  cs_subset_string(NeoMutt->sub, "mh_seq_flagged");
406  const char *const c_mh_seq_replied =
407  cs_subset_string(NeoMutt->sub, "mh_seq_replied");
408  if (mutt_str_equal(t, c_mh_seq_unseen))
409  flags = MH_SEQ_UNSEEN;
410  else if (mutt_str_equal(t, c_mh_seq_flagged))
411  flags = MH_SEQ_FLAGGED;
412  else if (mutt_str_equal(t, c_mh_seq_replied))
413  flags = MH_SEQ_REPLIED;
414  else /* unknown sequence */
415  continue;
416 
417  while ((t = strtok(NULL, " \t:")))
418  {
419  if (mh_seq_read_token(t, &first, &last) < 0)
420  {
421  mh_seq_free(mhs);
422  rc = -1;
423  goto out;
424  }
425  for (; first <= last; first++)
426  mh_seq_set(mhs, first, flags);
427  }
428  }
429 
430  rc = 0;
431 
432 out:
433  FREE(&buf);
434  mutt_file_fclose(&fp);
435  return rc;
436 }
437 
445 int mh_seq_changed(struct Mailbox *m)
446 {
447  char path[PATH_MAX];
448  struct stat sb;
449 
450  if ((snprintf(path, sizeof(path), "%s/.mh_sequences", mailbox_path(m)) < sizeof(path)) &&
451  (stat(path, &sb) == 0))
452  {
454  }
455  return -1;
456 }
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition: string.c:904
struct Email ** emails
Array of Emails.
Definition: mailbox.h:99
MhSeqFlags mh_seq_check(struct MhSequences *mhs, int i)
Get the flags for a given sequence.
Definition: sequence.c:78
static const char * mailbox_path(const struct Mailbox *m)
Get the Mailbox&#39;s path string.
Definition: mailbox.h:215
void mh_seq_add_one(struct Mailbox *m, int n, bool unseen, bool flagged, bool replied)
Update the flags for one sequence.
Definition: sequence.c:107
#define NONULL(x)
Definition: string2.h:37
int msg_count
Total number of messages.
Definition: mailbox.h:91
int mutt_str_atoi(const char *str, int *dst)
Convert ASCII string to an integer.
Definition: string.c:252
The envelope/body of an email.
Definition: email.h:37
void mh_seq_free(struct MhSequences *mhs)
Free some sequences.
Definition: sequence.c:67
Structs that make up an email.
MH Mailbox Sequences.
static int mh_seq_read_token(char *t, int *first, int *last)
Parse a number, or number range.
Definition: sequence.c:356
int mh_seq_changed(struct Mailbox *m)
Has the mailbox changed.
Definition: sequence.c:445
int max
Number of flags stored.
Definition: sequence.h:42
char * mutt_file_read_line(char *line, size_t *size, FILE *fp, int *line_num, ReadLineFlags flags)
Read a line from a file.
Definition: file.c:667
int mh_seq_read(struct MhSequences *mhs, const char *path)
Read a set of MH sequences.
Definition: sequence.c:381
uint8_t MhSeqFlags
Flags, e.g. MH_SEQ_UNSEEN.
Definition: sequence.h:29
Set of MH sequence numbers.
Definition: sequence.h:40
Container for Accounts, Notifications.
Definition: neomutt.h:36
Convenience wrapper for the config headers.
struct timespec last_visited
Time of last exit from this mailbox.
Definition: mailbox.h:108
bool read
Email is read.
Definition: email.h:51
int mutt_file_fclose(FILE **fp)
Close a FILE handle (and NULL the pointer)
Definition: file.c:153
Convenience wrapper for the core headers.
#define MH_SEQ_REPLIED
Email has been replied to.
Definition: sequence.h:34
File/dir&#39;s mtime - last modified time.
Definition: file.h:63
MhSeqFlags mh_seq_set(struct MhSequences *mhs, int i, MhSeqFlags f)
Set a flag for a given sequence.
Definition: sequence.c:92
void mutt_mem_realloc(void *ptr, size_t size)
Resize a block of memory on the heap.
Definition: memory.c:114
Maildir/MH private types.
size_t mutt_str_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix.
Definition: string.c:160
A mailbox.
Definition: mailbox.h:81
#define PATH_MAX
Definition: mutt.h:40
#define MH_SEQ_FLAGGED
Email is flagged.
Definition: sequence.h:35
const char * cs_subset_string(const struct ConfigSubset *sub, const char *name)
Get a string config item by name.
Definition: helpers.c:317
static void mh_seq_write_one(FILE *fp, struct MhSequences *mhs, MhSeqFlags f, const char *tag)
Write a flag sequence to a file.
Definition: sequence.c:188
int mutt_file_stat_timespec_compare(struct stat *sba, enum MuttStatType type, struct timespec *b)
Compare stat info with a time value.
Definition: file.c:1580
bool mh_mkstemp(struct Mailbox *m, FILE **fp, char **tgt)
Create a temporary file.
Definition: mh.c:77
MhSeqFlags * flags
Flags for each email.
Definition: sequence.h:43
size_t mutt_str_len(const char *a)
Calculate the length of a string, safely.
Definition: string.c:664
void mh_seq_update(struct Mailbox *m)
Update sequence numbers.
Definition: sequence.c:234
bool flagged
Marked important?
Definition: email.h:43
bool mutt_strn_equal(const char *a, const char *b, size_t num)
Check for equality of two strings (to a maximum), safely.
Definition: string.c:593
bool deleted
Email is deleted.
Definition: email.h:45
bool replied
Email has been replied to.
Definition: email.h:54
#define MUTT_RL_NO_FLAGS
No flags are set.
Definition: file.h:38
char * path
Path of Email (for local Mailboxes)
Definition: email.h:92
#define FREE(x)
Definition: memory.h:40
static void mh_seq_alloc(struct MhSequences *mhs, int i)
Allocate more memory for sequences.
Definition: sequence.c:49
#define MH_SEQ_UNSEEN
Email hasn&#39;t been read.
Definition: sequence.h:33
struct ConfigSubset * sub
Inherited config items.
Definition: neomutt.h:39
Convenience wrapper for the library headers.
int mutt_file_safe_rename(const char *src, const char *target)
NFS-safe renaming of files.
Definition: file.c:354