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