1 /**
2 * @file
3 * Mailbox helper functions
4 *
5 * Copyright (C) 2019 Richard Russon <rich@flatcap.org>
6 *
7 * @copyright
8 * This program is free software: you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License as published by the Free Software
10 * Foundation, either version 2 of the License, or (at your option) any later
11 * version.
12 *
13 * This program is distributed in the hope that it will be useful, but WITHOUT
14 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
16 * details.
17 *
18 * You should have received a copy of the GNU General Public License along with
19 * this program. If not, see <http://www.gnu.org/licenses/>.
20 */
21
22 /**
23 * @page neo_mutt_mailbox Mailbox helper functions
24 *
25 * Mailbox helper functions
26 */
27
28 #include "config.h"
29 #include <string.h>
30 #include <sys/stat.h>
31 #include <time.h>
32 #include <utime.h>
33 #include "mutt/lib.h"
34 #include "config/lib.h"
35 #include "core/lib.h"
36 #include "gui/lib.h"
37 #include "mutt_mailbox.h"
38 #include "muttlib.h"
39 #include "mx.h"
40 #include "protos.h"
41
42 static time_t MailboxTime = 0; ///< last time we started checking for mail
43 static time_t MailboxStatsTime = 0; ///< last time we check performed mail_check_stats
44 static short MailboxCount = 0; ///< how many boxes with new mail
45 static short MailboxNotify = 0; ///< # of unnotified new boxes
46
47 /**
48 * is_same_mailbox - Compare two Mailboxes to see if they're equal
49 * @param m1 First mailbox
50 * @param m2 Second mailbox
51 * @param st1 stat() info for first mailbox
52 * @param st2 stat() info for second mailbox
53 * @retval true Mailboxes are the same
54 * @retval false Mailboxes are different
55 */
is_same_mailbox(struct Mailbox * m1,struct Mailbox * m2,struct stat * st1,struct stat * st2)56 static bool is_same_mailbox(struct Mailbox *m1, struct Mailbox *m2,
57 struct stat *st1, struct stat *st2)
58 {
59 if (!m1 || mutt_buffer_is_empty(&m1->pathbuf) || !m2 ||
60 mutt_buffer_is_empty(&m2->pathbuf) || (m1->type != m2->type))
61 {
62 return false;
63 }
64
65 const bool uses_protocol = (m2->type == MUTT_IMAP) || (m2->type == MUTT_NNTP) ||
66 (m2->type == MUTT_NOTMUCH) || (m2->type == MUTT_POP);
67
68 if (uses_protocol)
69 return mutt_str_equal(mailbox_path(m1), mailbox_path(m2));
70 else
71 return ((st1->st_dev == st2->st_dev) && (st1->st_ino == st2->st_ino));
72 }
73
74 /**
75 * mailbox_check - Check a mailbox for new mail
76 * @param m_cur Current Mailbox
77 * @param m_check Mailbox to check
78 * @param st_ctx stat() info for the current Mailbox
79 * @param check_stats If true, also count the total, new and flagged messages
80 */
mailbox_check(struct Mailbox * m_cur,struct Mailbox * m_check,struct stat * st_ctx,bool check_stats)81 static void mailbox_check(struct Mailbox *m_cur, struct Mailbox *m_check,
82 struct stat *st_ctx, bool check_stats)
83 {
84 struct stat st = { 0 };
85
86 enum MailboxType mb_type = mx_path_probe(mailbox_path(m_check));
87
88 const bool c_mail_check_recent =
89 cs_subset_bool(NeoMutt->sub, "mail_check_recent");
90 if ((m_cur == m_check) && c_mail_check_recent)
91 m_check->has_new = false;
92
93 switch (mb_type)
94 {
95 case MUTT_POP:
96 case MUTT_NNTP:
97 case MUTT_NOTMUCH:
98 case MUTT_IMAP:
99 m_check->type = mb_type;
100 break;
101 default:
102 if ((stat(mailbox_path(m_check), &st) != 0) ||
103 ((m_check->type == MUTT_UNKNOWN) && S_ISREG(st.st_mode) && (st.st_size == 0)) ||
104 ((m_check->type == MUTT_UNKNOWN) &&
105 ((m_check->type = mx_path_probe(mailbox_path(m_check))) <= 0)))
106 {
107 /* if the mailbox still doesn't exist, set the newly created flag to be
108 * ready for when it does. */
109 m_check->newly_created = true;
110 m_check->type = MUTT_UNKNOWN;
111 m_check->size = 0;
112 return;
113 }
114 break; // kept for consistency.
115 }
116
117 const bool c_check_mbox_size =
118 cs_subset_bool(NeoMutt->sub, "check_mbox_size");
119
120 /* check to see if the folder is the currently selected folder before polling */
121 if (!is_same_mailbox(m_cur, m_check, st_ctx, &st))
122 {
123 switch (m_check->type)
124 {
125 case MUTT_NOTMUCH:
126 // Remove this when non-notmuch backends only check unread, flagged,
127 // and total counts per 'mbox_check_stats' docs.
128 if (!check_stats)
129 break;
130 /* fall through */
131 case MUTT_IMAP:
132 case MUTT_MBOX:
133 case MUTT_MMDF:
134 case MUTT_MAILDIR:
135 case MUTT_MH:
136 mx_mbox_check_stats(m_check, check_stats);
137 break;
138 default:; /* do nothing */
139 }
140 }
141 else if (c_check_mbox_size && m_cur && mutt_buffer_is_empty(&m_cur->pathbuf))
142 m_check->size = (off_t) st.st_size; /* update the size of current folder */
143
144 if (!m_check->has_new)
145 m_check->notified = false;
146 else if (!m_check->notified)
147 MailboxNotify++;
148 }
149
150 /**
151 * mutt_mailbox_check - Check all all Mailboxes for new mail
152 * @param m_cur Current Mailbox
153 * @param force Force flags, see below
154 * @retval num Number of mailboxes with new mail
155 *
156 * The force argument may be any combination of the following values:
157 * - MUTT_MAILBOX_CHECK_FORCE ignore MailboxTime and check for new mail
158 * - MUTT_MAILBOX_CHECK_FORCE_STATS ignore MailboxTime and calculate statistics
159 *
160 * Check all all Mailboxes for new mail and total/new/flagged messages
161 */
mutt_mailbox_check(struct Mailbox * m_cur,int force)162 int mutt_mailbox_check(struct Mailbox *m_cur, int force)
163 {
164 struct stat st_ctx = { 0 };
165 time_t t;
166 bool check_stats = false;
167 st_ctx.st_dev = 0;
168 st_ctx.st_ino = 0;
169
170 #ifdef USE_IMAP
171 /* update postponed count as well, on force */
172 if (force & MUTT_MAILBOX_CHECK_FORCE)
173 mutt_update_num_postponed();
174 #endif
175
176 /* fastest return if there are no mailboxes */
177 if (TAILQ_EMPTY(&NeoMutt->accounts))
178 return 0;
179
180 const short c_mail_check = cs_subset_number(NeoMutt->sub, "mail_check");
181 const bool c_mail_check_stats =
182 cs_subset_bool(NeoMutt->sub, "mail_check_stats");
183 const short c_mail_check_stats_interval =
184 cs_subset_number(NeoMutt->sub, "mail_check_stats_interval");
185
186 t = mutt_date_epoch();
187 if (!force && (t - MailboxTime < c_mail_check))
188 return MailboxCount;
189
190 if ((force & MUTT_MAILBOX_CHECK_FORCE_STATS) ||
191 (c_mail_check_stats && ((t - MailboxStatsTime) >= c_mail_check_stats_interval)))
192 {
193 check_stats = true;
194 MailboxStatsTime = t;
195 }
196
197 MailboxTime = t;
198 MailboxCount = 0;
199 MailboxNotify = 0;
200
201 /* check device ID and serial number instead of comparing paths */
202 if (!m_cur || (m_cur->type == MUTT_IMAP) || (m_cur->type == MUTT_POP)
203 #ifdef USE_NNTP
204 || (m_cur->type == MUTT_NNTP)
205 #endif
206 || stat(mailbox_path(m_cur), &st_ctx) != 0)
207 {
208 st_ctx.st_dev = 0;
209 st_ctx.st_ino = 0;
210 }
211
212 struct MailboxList ml = STAILQ_HEAD_INITIALIZER(ml);
213 neomutt_mailboxlist_get_all(&ml, NeoMutt, MUTT_MAILBOX_ANY);
214 struct MailboxNode *np = NULL;
215 STAILQ_FOREACH(np, &ml, entries)
216 {
217 if (np->mailbox->flags & MB_HIDDEN)
218 continue;
219
220 mailbox_check(m_cur, np->mailbox, &st_ctx,
221 check_stats || (!np->mailbox->first_check_stats_done && c_mail_check_stats));
222 if (np->mailbox->has_new)
223 MailboxCount++;
224 np->mailbox->first_check_stats_done = true;
225 }
226 neomutt_mailboxlist_clear(&ml);
227
228 return MailboxCount;
229 }
230
231 /**
232 * mutt_mailbox_notify - Notify the user if there's new mail
233 * @param m_cur Current Mailbox
234 * @retval true There is new mail
235 */
mutt_mailbox_notify(struct Mailbox * m_cur)236 bool mutt_mailbox_notify(struct Mailbox *m_cur)
237 {
238 if ((mutt_mailbox_check(m_cur, 0) > 0) && MailboxNotify)
239 {
240 return mutt_mailbox_list();
241 }
242 return false;
243 }
244
245 /**
246 * mutt_mailbox_list - List the mailboxes with new mail
247 * @retval true There is new mail
248 */
mutt_mailbox_list(void)249 bool mutt_mailbox_list(void)
250 {
251 char mailboxlist[512];
252 size_t pos = 0;
253 int first = 1;
254
255 int have_unnotified = MailboxNotify;
256
257 struct Buffer *path = mutt_buffer_pool_get();
258
259 mailboxlist[0] = '\0';
260 pos += strlen(strncat(mailboxlist, _("New mail in "), sizeof(mailboxlist) - 1 - pos));
261 struct MailboxList ml = STAILQ_HEAD_INITIALIZER(ml);
262 neomutt_mailboxlist_get_all(&ml, NeoMutt, MUTT_MAILBOX_ANY);
263 struct MailboxNode *np = NULL;
264 STAILQ_FOREACH(np, &ml, entries)
265 {
266 /* Is there new mail in this mailbox? */
267 if (!np->mailbox->has_new || (have_unnotified && np->mailbox->notified))
268 continue;
269
270 mutt_buffer_strcpy(path, mailbox_path(np->mailbox));
271 mutt_buffer_pretty_mailbox(path);
272
273 const size_t width = msgwin_get_width();
274 if (!first && (width >= 7) && ((pos + mutt_buffer_len(path)) >= (width - 7)))
275 {
276 break;
277 }
278
279 if (!first)
280 pos += strlen(strncat(mailboxlist + pos, ", ", sizeof(mailboxlist) - 1 - pos));
281
282 /* Prepend an asterisk to mailboxes not already notified */
283 if (!np->mailbox->notified)
284 {
285 /* pos += strlen (strncat(mailboxlist + pos, "*", sizeof(mailboxlist)-1-pos)); */
286 np->mailbox->notified = true;
287 MailboxNotify--;
288 }
289 pos += strlen(strncat(mailboxlist + pos, mutt_buffer_string(path),
290 sizeof(mailboxlist) - 1 - pos));
291 first = 0;
292 }
293 neomutt_mailboxlist_clear(&ml);
294
295 if (!first && np)
296 {
297 strncat(mailboxlist + pos, ", ...", sizeof(mailboxlist) - 1 - pos);
298 }
299
300 mutt_buffer_pool_release(&path);
301
302 if (!first)
303 {
304 mutt_message("%s", mailboxlist);
305 return true;
306 }
307
308 /* there were no mailboxes needing to be notified, so clean up since
309 * MailboxNotify has somehow gotten out of sync */
310 MailboxNotify = 0;
311 return false;
312 }
313
314 /**
315 * mutt_mailbox_set_notified - Note when the user was last notified of new mail
316 * @param m Mailbox
317 */
mutt_mailbox_set_notified(struct Mailbox * m)318 void mutt_mailbox_set_notified(struct Mailbox *m)
319 {
320 if (!m)
321 return;
322
323 m->notified = true;
324 #ifdef HAVE_CLOCK_GETTIME
325 clock_gettime(CLOCK_REALTIME, &m->last_visited);
326 #else
327 m->last_visited.tv_sec = mutt_date_epoch();
328 m->last_visited.tv_nsec = 0;
329 #endif
330 }
331
332 /**
333 * find_next_mailbox - Find the next mailbox with new or unread mail.
334 * @param s Buffer containing name of current mailbox
335 * @param find_new Boolean controlling new or unread check.
336 * @retval ptr Mailbox
337 *
338 * Given a folder name, find the next incoming folder with new or unread mail.
339 * The Mailbox will be returned and a pretty version of the path put into s.
340 */
find_next_mailbox(struct Buffer * s,bool find_new)341 static struct Mailbox *find_next_mailbox(struct Buffer *s, bool find_new)
342 {
343 bool found = false;
344 for (int pass = 0; pass < 2; pass++)
345 {
346 struct MailboxList ml = STAILQ_HEAD_INITIALIZER(ml);
347 neomutt_mailboxlist_get_all(&ml, NeoMutt, MUTT_MAILBOX_ANY);
348 struct MailboxNode *np = NULL;
349 STAILQ_FOREACH(np, &ml, entries)
350 {
351 // Match only real mailboxes if looking for new mail.
352 if (find_new && np->mailbox->type == MUTT_NOTMUCH)
353 continue;
354
355 mutt_buffer_expand_path(&np->mailbox->pathbuf);
356 struct Mailbox *m_cur = np->mailbox;
357
358 if ((found || (pass > 0)) && (find_new ? m_cur->has_new : m_cur->msg_unread > 0))
359 {
360 mutt_buffer_strcpy(s, mailbox_path(np->mailbox));
361 mutt_buffer_pretty_mailbox(s);
362 struct Mailbox *m_result = np->mailbox;
363 neomutt_mailboxlist_clear(&ml);
364 return m_result;
365 }
366 if (mutt_str_equal(mutt_buffer_string(s), mailbox_path(np->mailbox)))
367 found = true;
368 }
369 neomutt_mailboxlist_clear(&ml);
370 }
371
372 return NULL;
373 }
374
375 /**
376 * mutt_mailbox_next - Incoming folders completion routine
377 * @param m_cur Current Mailbox
378 * @param s Buffer containing name of current mailbox
379 * @retval ptr Mailbox
380 *
381 * Given a folder name, find the next incoming folder with new mail.
382 * The Mailbox will be returned and a pretty version of the path put into s.
383 */
mutt_mailbox_next(struct Mailbox * m_cur,struct Buffer * s)384 struct Mailbox *mutt_mailbox_next(struct Mailbox *m_cur, struct Buffer *s)
385 {
386 mutt_buffer_expand_path(s);
387
388 if (mutt_mailbox_check(m_cur, 0) > 0)
389 {
390 struct Mailbox *m_res = find_next_mailbox(s, true);
391 if (m_res)
392 return m_res;
393
394 mutt_mailbox_check(m_cur, MUTT_MAILBOX_CHECK_FORCE); /* mailbox was wrong - resync things */
395 }
396
397 mutt_buffer_reset(s); // no folders with new mail
398 return NULL;
399 }
400
401 /**
402 * mutt_mailbox_next_unread - Find next mailbox with unread mail
403 * @param m_cur Current Mailbox
404 * @param s Buffer containing name of current mailbox
405 * @retval ptr Mailbox
406 *
407 * Given a folder name, find the next mailbox with unread mail.
408 * The Mailbox will be returned and a pretty version of the path put into s.
409 */
mutt_mailbox_next_unread(struct Mailbox * m_cur,struct Buffer * s)410 struct Mailbox *mutt_mailbox_next_unread(struct Mailbox *m_cur, struct Buffer *s)
411 {
412 mutt_buffer_expand_path(s);
413
414 struct Mailbox *m_res = find_next_mailbox(s, false);
415 if (m_res)
416 return m_res;
417
418 mutt_buffer_reset(s); // no folders with new mail
419 return NULL;
420 }
421
422 /**
423 * mutt_mailbox_cleanup - Restore the timestamp of a mailbox
424 * @param path Path to the mailbox
425 * @param st Timestamp info from stat()
426 *
427 * Fix up the atime and mtime after mbox/mmdf mailbox was modified according to
428 * stat() info taken before a modification.
429 */
mutt_mailbox_cleanup(const char * path,struct stat * st)430 void mutt_mailbox_cleanup(const char *path, struct stat *st)
431 {
432 #ifdef HAVE_UTIMENSAT
433 struct timespec ts[2];
434 #else
435 struct utimbuf ut;
436 #endif
437
438 const bool c_check_mbox_size =
439 cs_subset_bool(NeoMutt->sub, "check_mbox_size");
440 if (c_check_mbox_size)
441 {
442 struct Mailbox *m = mailbox_find(path);
443 if (m && !m->has_new)
444 mailbox_update(m);
445 }
446 else
447 {
448 /* fix up the times so mailbox won't get confused */
449 if (st->st_mtime > st->st_atime)
450 {
451 #ifdef HAVE_UTIMENSAT
452 ts[0].tv_sec = 0;
453 ts[0].tv_nsec = UTIME_OMIT;
454 ts[1].tv_sec = 0;
455 ts[1].tv_nsec = UTIME_NOW;
456 utimensat(AT_FDCWD, buf, ts, 0);
457 #else
458 ut.actime = st->st_atime;
459 ut.modtime = mutt_date_epoch();
460 utime(path, &ut);
461 #endif
462 }
463 else
464 {
465 #ifdef HAVE_UTIMENSAT
466 ts[0].tv_sec = 0;
467 ts[0].tv_nsec = UTIME_NOW;
468 ts[1].tv_sec = 0;
469 ts[1].tv_nsec = UTIME_NOW;
470 utimensat(AT_FDCWD, buf, ts, 0);
471 #else
472 utime(path, NULL);
473 #endif
474 }
475 }
476 }
477