/src/neomutt/maildir/sequence.c
Line | Count | Source (jump to first uncovered line) |
1 | | /** |
2 | | * @file |
3 | | * MH Mailbox Sequences |
4 | | * |
5 | | * @authors |
6 | | * Copyright (C) 2020 Richard Russon <rich@flatcap.org> |
7 | | * |
8 | | * @copyright |
9 | | * This program is free software: you can redistribute it and/or modify it under |
10 | | * the terms of the GNU General Public License as published by the Free Software |
11 | | * Foundation, either version 2 of the License, or (at your option) any later |
12 | | * version. |
13 | | * |
14 | | * This program is distributed in the hope that it will be useful, but WITHOUT |
15 | | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
16 | | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
17 | | * details. |
18 | | * |
19 | | * You should have received a copy of the GNU General Public License along with |
20 | | * this program. If not, see <http://www.gnu.org/licenses/>. |
21 | | */ |
22 | | |
23 | | /** |
24 | | * @page maildir_sequence MH Mailbox Sequences |
25 | | * |
26 | | * MH Mailbox Sequences |
27 | | */ |
28 | | |
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 | | |
42 | | /** |
43 | | * mh_seq_alloc - Allocate more memory for sequences |
44 | | * @param mhs Existing sequences |
45 | | * @param i Number required |
46 | | * |
47 | | * @note Memory is allocated in blocks of 128. |
48 | | */ |
49 | | static void mh_seq_alloc(struct MhSequences *mhs, int i) |
50 | 0 | { |
51 | 0 | if ((i <= mhs->max) && mhs->flags) |
52 | 0 | return; |
53 | | |
54 | 0 | const int newmax = i + 128; |
55 | 0 | int j = mhs->flags ? mhs->max + 1 : 0; |
56 | 0 | mutt_mem_realloc(&mhs->flags, sizeof(mhs->flags[0]) * (newmax + 1)); |
57 | 0 | while (j <= newmax) |
58 | 0 | mhs->flags[j++] = 0; |
59 | |
|
60 | 0 | mhs->max = newmax; |
61 | 0 | } |
62 | | |
63 | | /** |
64 | | * mh_seq_free - Free some sequences |
65 | | * @param mhs Sequences to free |
66 | | */ |
67 | | void mh_seq_free(struct MhSequences *mhs) |
68 | 0 | { |
69 | 0 | FREE(&mhs->flags); |
70 | 0 | } |
71 | | |
72 | | /** |
73 | | * mh_seq_check - Get the flags for a given sequence |
74 | | * @param mhs Sequences |
75 | | * @param i Index number required |
76 | | * @retval num Flags, see #MhSeqFlags |
77 | | */ |
78 | | MhSeqFlags mh_seq_check(struct MhSequences *mhs, int i) |
79 | 0 | { |
80 | 0 | if (!mhs->flags || (i > mhs->max)) |
81 | 0 | return 0; |
82 | 0 | return mhs->flags[i]; |
83 | 0 | } |
84 | | |
85 | | /** |
86 | | * mh_seq_set - Set a flag for a given sequence |
87 | | * @param mhs Sequences |
88 | | * @param i Index number |
89 | | * @param f Flags, see #MhSeqFlags |
90 | | * @retval num Resulting flags, see #MhSeqFlags |
91 | | */ |
92 | | static MhSeqFlags mh_seq_set(struct MhSequences *mhs, int i, MhSeqFlags f) |
93 | 0 | { |
94 | 0 | mh_seq_alloc(mhs, i); |
95 | 0 | mhs->flags[i] |= f; |
96 | 0 | return mhs->flags[i]; |
97 | 0 | } |
98 | | |
99 | | /** |
100 | | * mh_seq_add_one - Update the flags for one sequence |
101 | | * @param m Mailbox |
102 | | * @param n Sequence number to update |
103 | | * @param unseen Update the unseen sequence |
104 | | * @param flagged Update the flagged sequence |
105 | | * @param replied Update the replied sequence |
106 | | */ |
107 | | void mh_seq_add_one(struct Mailbox *m, int n, bool unseen, bool flagged, bool replied) |
108 | 0 | { |
109 | 0 | bool unseen_done = false; |
110 | 0 | bool flagged_done = false; |
111 | 0 | bool replied_done = false; |
112 | |
|
113 | 0 | char *tmpfname = NULL; |
114 | 0 | char sequences[PATH_MAX] = { 0 }; |
115 | |
|
116 | 0 | char seq_unseen[256] = { 0 }; |
117 | 0 | char seq_replied[256] = { 0 }; |
118 | 0 | char seq_flagged[256] = { 0 }; |
119 | |
|
120 | 0 | char *buf = NULL; |
121 | 0 | size_t sz; |
122 | |
|
123 | 0 | FILE *fp_new = NULL; |
124 | 0 | if (!mh_mkstemp(m, &fp_new, &tmpfname)) |
125 | 0 | return; |
126 | | |
127 | 0 | const char *const c_mh_seq_unseen = cs_subset_string(NeoMutt->sub, "mh_seq_unseen"); |
128 | 0 | const char *const c_mh_seq_replied = cs_subset_string(NeoMutt->sub, "mh_seq_replied"); |
129 | 0 | const char *const c_mh_seq_flagged = cs_subset_string(NeoMutt->sub, "mh_seq_flagged"); |
130 | 0 | snprintf(seq_unseen, sizeof(seq_unseen), "%s:", NONULL(c_mh_seq_unseen)); |
131 | 0 | snprintf(seq_replied, sizeof(seq_replied), "%s:", NONULL(c_mh_seq_replied)); |
132 | 0 | snprintf(seq_flagged, sizeof(seq_flagged), "%s:", NONULL(c_mh_seq_flagged)); |
133 | |
|
134 | 0 | snprintf(sequences, sizeof(sequences), "%s/.mh_sequences", mailbox_path(m)); |
135 | 0 | FILE *fp_old = fopen(sequences, "r"); |
136 | 0 | if (fp_old) |
137 | 0 | { |
138 | 0 | while ((buf = mutt_file_read_line(buf, &sz, fp_old, NULL, MUTT_RL_NO_FLAGS))) |
139 | 0 | { |
140 | 0 | if (unseen && mutt_strn_equal(buf, seq_unseen, mutt_str_len(seq_unseen))) |
141 | 0 | { |
142 | 0 | fprintf(fp_new, "%s %d\n", buf, n); |
143 | 0 | unseen_done = true; |
144 | 0 | } |
145 | 0 | else if (flagged && mutt_strn_equal(buf, seq_flagged, mutt_str_len(seq_flagged))) |
146 | 0 | { |
147 | 0 | fprintf(fp_new, "%s %d\n", buf, n); |
148 | 0 | flagged_done = true; |
149 | 0 | } |
150 | 0 | else if (replied && mutt_strn_equal(buf, seq_replied, mutt_str_len(seq_replied))) |
151 | 0 | { |
152 | 0 | fprintf(fp_new, "%s %d\n", buf, n); |
153 | 0 | replied_done = true; |
154 | 0 | } |
155 | 0 | else |
156 | 0 | { |
157 | 0 | fprintf(fp_new, "%s\n", buf); |
158 | 0 | } |
159 | 0 | } |
160 | 0 | } |
161 | 0 | mutt_file_fclose(&fp_old); |
162 | 0 | FREE(&buf); |
163 | |
|
164 | 0 | if (!unseen_done && unseen) |
165 | 0 | fprintf(fp_new, "%s: %d\n", NONULL(c_mh_seq_unseen), n); |
166 | 0 | if (!flagged_done && flagged) |
167 | 0 | fprintf(fp_new, "%s: %d\n", NONULL(c_mh_seq_flagged), n); |
168 | 0 | if (!replied_done && replied) |
169 | 0 | fprintf(fp_new, "%s: %d\n", NONULL(c_mh_seq_replied), n); |
170 | |
|
171 | 0 | mutt_file_fclose(&fp_new); |
172 | |
|
173 | 0 | unlink(sequences); |
174 | 0 | if (mutt_file_safe_rename(tmpfname, sequences) != 0) |
175 | 0 | unlink(tmpfname); |
176 | |
|
177 | 0 | FREE(&tmpfname); |
178 | 0 | } |
179 | | |
180 | | /** |
181 | | * mh_seq_write_one - Write a flag sequence to a file |
182 | | * @param fp File to write to |
183 | | * @param mhs Sequence list |
184 | | * @param f Flag, see #MhSeqFlags |
185 | | * @param tag string tag, e.g. "unseen" |
186 | | */ |
187 | | static void mh_seq_write_one(FILE *fp, struct MhSequences *mhs, MhSeqFlags f, const char *tag) |
188 | 0 | { |
189 | 0 | fprintf(fp, "%s:", tag); |
190 | |
|
191 | 0 | int first = -1; |
192 | 0 | int last = -1; |
193 | |
|
194 | 0 | for (int i = 0; i <= mhs->max; i++) |
195 | 0 | { |
196 | 0 | if ((mh_seq_check(mhs, i) & f)) |
197 | 0 | { |
198 | 0 | if (first < 0) |
199 | 0 | first = i; |
200 | 0 | else |
201 | 0 | last = i; |
202 | 0 | } |
203 | 0 | else if (first >= 0) |
204 | 0 | { |
205 | 0 | if (last < 0) |
206 | 0 | fprintf(fp, " %d", first); |
207 | 0 | else |
208 | 0 | fprintf(fp, " %d-%d", first, last); |
209 | |
|
210 | 0 | first = -1; |
211 | 0 | last = -1; |
212 | 0 | } |
213 | 0 | } |
214 | |
|
215 | 0 | if (first >= 0) |
216 | 0 | { |
217 | 0 | if (last < 0) |
218 | 0 | fprintf(fp, " %d", first); |
219 | 0 | else |
220 | 0 | fprintf(fp, " %d-%d", first, last); |
221 | 0 | } |
222 | |
|
223 | 0 | fputc('\n', fp); |
224 | 0 | } |
225 | | |
226 | | /** |
227 | | * mh_seq_update - Update sequence numbers |
228 | | * @param m Mailbox |
229 | | * |
230 | | * XXX we don't currently remove deleted messages from sequences we don't know. |
231 | | * Should we? |
232 | | */ |
233 | | void mh_seq_update(struct Mailbox *m) |
234 | 0 | { |
235 | 0 | char sequences[PATH_MAX] = { 0 }; |
236 | 0 | char *tmpfname = NULL; |
237 | 0 | char *buf = NULL; |
238 | 0 | char *p = NULL; |
239 | 0 | size_t s; |
240 | 0 | int seq_num = 0; |
241 | |
|
242 | 0 | int unseen = 0; |
243 | 0 | int flagged = 0; |
244 | 0 | int replied = 0; |
245 | |
|
246 | 0 | char seq_unseen[256] = { 0 }; |
247 | 0 | char seq_replied[256] = { 0 }; |
248 | 0 | char seq_flagged[256] = { 0 }; |
249 | |
|
250 | 0 | struct MhSequences mhs = { 0 }; |
251 | |
|
252 | 0 | const char *const c_mh_seq_unseen = cs_subset_string(NeoMutt->sub, "mh_seq_unseen"); |
253 | 0 | const char *const c_mh_seq_replied = cs_subset_string(NeoMutt->sub, "mh_seq_replied"); |
254 | 0 | const char *const c_mh_seq_flagged = cs_subset_string(NeoMutt->sub, "mh_seq_flagged"); |
255 | 0 | snprintf(seq_unseen, sizeof(seq_unseen), "%s:", NONULL(c_mh_seq_unseen)); |
256 | 0 | snprintf(seq_replied, sizeof(seq_replied), "%s:", NONULL(c_mh_seq_replied)); |
257 | 0 | snprintf(seq_flagged, sizeof(seq_flagged), "%s:", NONULL(c_mh_seq_flagged)); |
258 | |
|
259 | 0 | FILE *fp_new = NULL; |
260 | 0 | if (!mh_mkstemp(m, &fp_new, &tmpfname)) |
261 | 0 | { |
262 | | /* error message? */ |
263 | 0 | return; |
264 | 0 | } |
265 | | |
266 | 0 | snprintf(sequences, sizeof(sequences), "%s/.mh_sequences", mailbox_path(m)); |
267 | | |
268 | | /* first, copy unknown sequences */ |
269 | 0 | FILE *fp_old = fopen(sequences, "r"); |
270 | 0 | if (fp_old) |
271 | 0 | { |
272 | 0 | while ((buf = mutt_file_read_line(buf, &s, fp_old, NULL, MUTT_RL_NO_FLAGS))) |
273 | 0 | { |
274 | 0 | if (mutt_str_startswith(buf, seq_unseen) || mutt_str_startswith(buf, seq_flagged) || |
275 | 0 | mutt_str_startswith(buf, seq_replied)) |
276 | 0 | { |
277 | 0 | continue; |
278 | 0 | } |
279 | | |
280 | 0 | fprintf(fp_new, "%s\n", buf); |
281 | 0 | } |
282 | 0 | } |
283 | 0 | mutt_file_fclose(&fp_old); |
284 | | |
285 | | /* now, update our unseen, flagged, and replied sequences */ |
286 | 0 | for (int i = 0; i < m->msg_count; i++) |
287 | 0 | { |
288 | 0 | struct Email *e = m->emails[i]; |
289 | 0 | if (!e) |
290 | 0 | break; |
291 | | |
292 | 0 | if (e->deleted) |
293 | 0 | continue; |
294 | | |
295 | 0 | p = strrchr(e->path, '/'); |
296 | 0 | if (p) |
297 | 0 | p++; |
298 | 0 | else |
299 | 0 | p = e->path; |
300 | |
|
301 | 0 | if (!mutt_str_atoi_full(p, &seq_num)) |
302 | 0 | continue; |
303 | | |
304 | 0 | if (!e->read) |
305 | 0 | { |
306 | 0 | mh_seq_set(&mhs, seq_num, MH_SEQ_UNSEEN); |
307 | 0 | unseen++; |
308 | 0 | } |
309 | 0 | if (e->flagged) |
310 | 0 | { |
311 | 0 | mh_seq_set(&mhs, seq_num, MH_SEQ_FLAGGED); |
312 | 0 | flagged++; |
313 | 0 | } |
314 | 0 | if (e->replied) |
315 | 0 | { |
316 | 0 | mh_seq_set(&mhs, seq_num, MH_SEQ_REPLIED); |
317 | 0 | replied++; |
318 | 0 | } |
319 | 0 | } |
320 | | |
321 | | /* write out the new sequences */ |
322 | 0 | if (unseen) |
323 | 0 | mh_seq_write_one(fp_new, &mhs, MH_SEQ_UNSEEN, NONULL(c_mh_seq_unseen)); |
324 | 0 | if (flagged) |
325 | 0 | mh_seq_write_one(fp_new, &mhs, MH_SEQ_FLAGGED, NONULL(c_mh_seq_flagged)); |
326 | 0 | if (replied) |
327 | 0 | mh_seq_write_one(fp_new, &mhs, MH_SEQ_REPLIED, NONULL(c_mh_seq_replied)); |
328 | |
|
329 | 0 | mh_seq_free(&mhs); |
330 | | |
331 | | /* try to commit the changes - no guarantee here */ |
332 | 0 | mutt_file_fclose(&fp_new); |
333 | |
|
334 | 0 | unlink(sequences); |
335 | 0 | if (mutt_file_safe_rename(tmpfname, sequences) != 0) |
336 | 0 | { |
337 | | /* report an error? */ |
338 | 0 | unlink(tmpfname); |
339 | 0 | } |
340 | |
|
341 | 0 | FREE(&tmpfname); |
342 | 0 | } |
343 | | |
344 | | /** |
345 | | * mh_seq_read_token - Parse a number, or number range |
346 | | * @param t String to parse |
347 | | * @param first First number |
348 | | * @param last Last number (if a range, first number if not) |
349 | | * @retval 0 Success |
350 | | * @retval -1 Error |
351 | | */ |
352 | | static int mh_seq_read_token(char *t, int *first, int *last) |
353 | 0 | { |
354 | 0 | char *p = strchr(t, '-'); |
355 | 0 | if (p) |
356 | 0 | { |
357 | 0 | *p++ = '\0'; |
358 | 0 | if (!mutt_str_atoi_full(t, first) || !mutt_str_atoi_full(p, last)) |
359 | 0 | return -1; |
360 | 0 | } |
361 | 0 | else |
362 | 0 | { |
363 | 0 | if (!mutt_str_atoi_full(t, first)) |
364 | 0 | return -1; |
365 | 0 | *last = *first; |
366 | 0 | } |
367 | 0 | return 0; |
368 | 0 | } |
369 | | |
370 | | /** |
371 | | * mh_seq_read - Read a set of MH sequences |
372 | | * @param mhs Existing sequences |
373 | | * @param path File to read from |
374 | | * @retval 0 Success |
375 | | * @retval -1 Error |
376 | | */ |
377 | | int mh_seq_read(struct MhSequences *mhs, const char *path) |
378 | 0 | { |
379 | 0 | char *buf = NULL; |
380 | 0 | size_t sz = 0; |
381 | |
|
382 | 0 | MhSeqFlags flags; |
383 | 0 | int first, last, rc = 0; |
384 | |
|
385 | 0 | char pathname[PATH_MAX] = { 0 }; |
386 | 0 | snprintf(pathname, sizeof(pathname), "%s/.mh_sequences", path); |
387 | |
|
388 | 0 | FILE *fp = fopen(pathname, "r"); |
389 | 0 | if (!fp) |
390 | 0 | return 0; /* yes, ask callers to silently ignore the error */ |
391 | | |
392 | 0 | const char *const c_mh_seq_unseen = cs_subset_string(NeoMutt->sub, "mh_seq_unseen"); |
393 | 0 | const char *const c_mh_seq_flagged = cs_subset_string(NeoMutt->sub, "mh_seq_flagged"); |
394 | 0 | const char *const c_mh_seq_replied = cs_subset_string(NeoMutt->sub, "mh_seq_replied"); |
395 | 0 | while ((buf = mutt_file_read_line(buf, &sz, fp, NULL, MUTT_RL_NO_FLAGS))) |
396 | 0 | { |
397 | 0 | char *t = strtok(buf, " \t:"); |
398 | 0 | if (!t) |
399 | 0 | continue; |
400 | | |
401 | 0 | if (mutt_str_equal(t, c_mh_seq_unseen)) |
402 | 0 | flags = MH_SEQ_UNSEEN; |
403 | 0 | else if (mutt_str_equal(t, c_mh_seq_flagged)) |
404 | 0 | flags = MH_SEQ_FLAGGED; |
405 | 0 | else if (mutt_str_equal(t, c_mh_seq_replied)) |
406 | 0 | flags = MH_SEQ_REPLIED; |
407 | 0 | else /* unknown sequence */ |
408 | 0 | continue; |
409 | | |
410 | 0 | while ((t = strtok(NULL, " \t:"))) |
411 | 0 | { |
412 | 0 | if (mh_seq_read_token(t, &first, &last) < 0) |
413 | 0 | { |
414 | 0 | mh_seq_free(mhs); |
415 | 0 | rc = -1; |
416 | 0 | goto out; |
417 | 0 | } |
418 | 0 | for (; first <= last; first++) |
419 | 0 | mh_seq_set(mhs, first, flags); |
420 | 0 | } |
421 | 0 | } |
422 | | |
423 | 0 | rc = 0; |
424 | |
|
425 | 0 | out: |
426 | 0 | FREE(&buf); |
427 | 0 | mutt_file_fclose(&fp); |
428 | 0 | return rc; |
429 | 0 | } |
430 | | |
431 | | /** |
432 | | * mh_seq_changed - Has the mailbox changed |
433 | | * @param m Mailbox |
434 | | * @retval 1 mh_sequences last modification time is more recent than the last visit to this mailbox |
435 | | * @retval 0 modification time is older |
436 | | * @retval -1 Error |
437 | | */ |
438 | | int mh_seq_changed(struct Mailbox *m) |
439 | 0 | { |
440 | 0 | char path[PATH_MAX] = { 0 }; |
441 | 0 | struct stat st = { 0 }; |
442 | |
|
443 | 0 | if ((snprintf(path, sizeof(path), "%s/.mh_sequences", mailbox_path(m)) < sizeof(path)) && |
444 | 0 | (stat(path, &st) == 0)) |
445 | 0 | { |
446 | 0 | return (mutt_file_stat_timespec_compare(&st, MUTT_STAT_MTIME, &m->last_visited) > 0); |
447 | 0 | } |
448 | 0 | return -1; |
449 | 0 | } |