0db6026e5ef74d432c7e17c98bcb80c8510a753a
[apps/madmutt.git] / buffy.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
4  *
5  * Parts were written/modified by:
6  * Rocco Rutte <pdmef@cs.tu-berlin.de>
7  *
8  * This file is part of mutt-ng, see http://www.muttng.org/.
9  * It's licensed under the GNU General Public License,
10  * please see the file GPL in the top level source directory.
11  */
12
13 #include <lib-lib/lib-lib.h>
14 #include <dirent.h>
15 #include <utime.h>
16
17 #include <lib-ui/curses.h>
18 #include <lib-ui/sidebar.h>
19
20 #include <imap/imap.h>
21
22 #include "mutt.h"
23 #include "buffy.h"
24 #include "mx.h"
25 #include "mh.h"
26
27 static time_t BuffyTime = 0;    /* last time we started checking for mail */
28
29 static time_t ImapBuffyTime = 0;        /* last time we started checking for mail */
30 static short BuffyCount = 0;    /* how many boxes with new mail */
31 static short BuffyNotify = 0;   /* # of unnotified new boxes */
32
33 #ifdef BUFFY_SIZE
34
35 /* Find the last message in the file. 
36  * upon success return 0. If no message found - return -1 */
37
38 static int fseeko_last_message (FILE * f)
39 {
40   LOFF_T pos;
41   char buffer[BUFSIZ + 9];      /* 7 for "\n\nFrom " */
42   int bytes_read;
43   int i;                        /* Index into `buffer' for scanning.  */
44
45   p_clear(buffer, 1);
46   fseeko (f, 0, SEEK_END);
47   pos = ftello (f);
48
49   /* Set `bytes_read' to the size of the last, probably partial, buffer; 0 <
50    * `bytes_read' <= `BUFSIZ'.  */
51   bytes_read = pos % BUFSIZ;
52   if (bytes_read == 0)
53     bytes_read = BUFSIZ;
54   /* Make `pos' a multiple of `BUFSIZ' (0 if the file is short), so that all
55    * reads will be on block boundaries, which might increase efficiency.  */
56   while ((pos -= bytes_read) >= 0) {
57     /* we save in the buffer at the end the first 7 chars from the last read */
58     strncpy (buffer + BUFSIZ, buffer, 5 + 2);   /* 2 == 2 * m_strlen(CRLF) */
59     fseeko (f, pos, SEEK_SET);
60     bytes_read = fread (buffer, sizeof (char), bytes_read, f);
61     if (bytes_read == -1)
62       return -1;
63     for (i = bytes_read; --i >= 0;)
64       if (!m_strncmp(buffer + i, "\n\nFrom ", m_strlen("\n\nFrom "))) { /* found it - go to the beginning of the From */
65         fseeko (f, pos + i + 2, SEEK_SET);
66         return 0;
67       }
68     bytes_read = BUFSIZ;
69   }
70
71   /* here we are at the beginning of the file */
72   if (!m_strncmp("From ", buffer, 5)) {
73     fseeko (f, 0, 0);
74     return (0);
75   }
76
77   return (-1);
78 }
79
80 /* Return 1 if the last message is new */
81 static int test_last_status_new (FILE * f)
82 {
83   HEADER *hdr;
84   ENVELOPE *tmp_envelope;
85   int result = 0;
86
87   if (fseeko_last_message (f) == -1)
88     return (0);
89
90   hdr = header_new();
91   tmp_envelope = mutt_read_rfc822_header (f, hdr, 0, 0);
92   if (!(hdr->read || hdr->old))
93     result = 1;
94
95   envelope_delete(&tmp_envelope);
96   header_delete(&hdr);
97
98   return result;
99 }
100
101 static int test_new_folder (const char *path)
102 {
103   FILE *f;
104   int rc = 0;
105   int typ;
106
107   typ = mx_get_magic (path);
108
109   if (typ != M_MBOX && typ != M_MMDF)
110     return 0;
111
112   if ((f = fopen (path, "rb"))) {
113     rc = test_last_status_new (f);
114     fclose (f);
115   }
116
117   return rc;
118 }
119
120 BUFFY *buffy_find_mailbox (const char *path)
121 {
122   struct stat sb;
123   struct stat tmp_sb;
124   int i = 0;
125
126   if (stat (path, &sb) != 0)
127     return NULL;
128
129   if (!list_empty(Incoming)) {
130     for (i = 0; i < Incoming->length; i++) {
131       if (stat (Incoming->data[i], &tmp_sb) == 0 &&
132           sb.st_dev == tmp_sb.st_dev && sb.st_ino == tmp_sb.st_ino)
133         return ((BUFFY*) Incoming->data[i]);
134     }
135   }
136   return (NULL);
137 }
138
139 void buffy_update_mailbox (BUFFY * b)
140 {
141   struct stat sb;
142
143   if (!b)
144     return;
145
146   if (stat (b->path, &sb) == 0)
147     b->size = (long) sb.st_size;
148   else
149     b->size = 0;
150   return;
151 }
152 #endif
153
154 /* func to free buffy for list_del() */
155 static void buffy_free (BUFFY** p) {
156   p_delete(&(*p)->path);
157   p_delete(p);
158 }
159
160 int buffy_lookup (const char* path) {
161   int i = 0;
162   if (list_empty(Incoming) || !path || !*path)
163     return (-1);
164   for (i = 0; i < Incoming->length; i++) {
165     if (!m_strcmp(((BUFFY*)Incoming->data[i])->path, path) )
166       return (i);
167   }
168   return (-1);
169 }
170
171 int buffy_parse_mailboxes (BUFFER * path, BUFFER * s, unsigned long data,
172                           BUFFER * err __attribute__ ((unused)))
173 {
174   BUFFY* tmp;
175   char buf[_POSIX_PATH_MAX];
176   int i = 0;
177 #ifdef BUFFY_SIZE
178   struct stat sb;
179 #endif /* BUFFY_SIZE */
180
181   while (MoreArgs (s)) {
182     mutt_extract_token (path, s, 0);
183     m_strcpy(buf, sizeof(buf), path->data);
184
185     if (data == M_UNMAILBOXES && !strcmp(buf, "*")) {
186       list_del (&Incoming, (list_del_t*) buffy_free);
187       return 0;
188     }
189
190     /* Skip empty tokens. */
191     if (!*buf)
192       continue;
193
194     mutt_expand_path (buf, sizeof (buf));
195     i = buffy_lookup (buf);
196
197     if (data == M_UNMAILBOXES) {
198       if (i >= 0) {
199         tmp = (BUFFY*) list_pop_idx (Incoming, i);
200         buffy_free (&tmp);
201       }
202       continue;
203     }
204
205     if (i < 0) {
206       tmp = p_new(BUFFY, 1);
207       tmp->path = m_strdup(buf);
208       tmp->magic = 0;
209       list_push_back (&Incoming, tmp);
210       i = Incoming->length-1;
211     } else
212       tmp = (BUFFY*) Incoming->data[i];
213
214     tmp->new = 0;
215     tmp->notified = 1;
216     tmp->newly_created = 0;
217
218 #ifdef BUFFY_SIZE
219     /* for buffy_size, it is important that if the folder is new (tested by
220      * reading it), the size is set to 0 so that later when we check we see
221      * that it increased .  without buffy_size we probably don't care.
222      */
223     if (stat (tmp->path, &sb) == 0 && !test_new_folder (tmp->path)) {
224       /* some systems out there don't have an off_t type */
225       tmp->size = (long) sb.st_size;
226     }
227     else
228       tmp->size = 0;
229 #endif /* BUFFY_SIZE */
230   }
231   return 0;
232 }
233
234 #ifdef BUFFY_SIZE
235 /* people use buffy_size on systems where modified time attributes are BADLY
236  * broken. Ignore them.
237  */
238 #define STAT_CHECK (sb.st_size > tmp->size)
239 #else
240 #define STAT_CHECK (sb.st_mtime > sb.st_atime || (tmp->newly_created && sb.st_ctime == sb.st_mtime && sb.st_ctime == sb.st_atime))
241 #endif /* BUFFY_SIZE */
242
243 /* values for force:
244  * 0    don't force any checks + update sidebar
245  * 1    force all checks + update sidebar
246  * 2    don't force any checks + _don't_ update sidebar
247  */
248 int buffy_check (int force)
249 {
250   BUFFY *tmp;
251   struct stat sb;
252   struct dirent *de;
253   DIR *dirp;
254   char path[_POSIX_PATH_MAX];
255   struct stat contex_sb;
256   time_t now, last1;
257   CONTEXT *ctx;
258   int i = 0;
259   int local = 0, count = 0;
260   time_t last2;
261
262   /* update postponed count as well, on force */
263   if (force == 1)
264     mutt_update_num_postponed ();
265
266   /* fastest return if there are no mailboxes */
267   if (list_empty(Incoming))
268     return 0;
269   now = time (NULL);
270   if (force == 0 && (now - BuffyTime < BuffyTimeout)
271       && (now - ImapBuffyTime < ImapBuffyTimeout))
272     return BuffyCount;
273
274   last1 = BuffyTime;
275   if (force == 1 || now - BuffyTime >= BuffyTimeout)
276     BuffyTime = now;
277   last2 = ImapBuffyTime;
278   if (force == 1 || now - ImapBuffyTime >= ImapBuffyTimeout)
279     ImapBuffyTime = now;
280   BuffyCount = 0;
281   BuffyNotify = 0;
282
283   count = sidebar_need_count ();
284
285   if (!Context || !Context->path || 
286       (mx_is_local (Context->magic-1) && stat (Context->path, &contex_sb) != 0)) {
287     /* check device ID and serial number instead of comparing paths */
288     contex_sb.st_dev = 0;
289     contex_sb.st_ino = 0;
290   }
291
292   for (i = 0; i < Incoming->length; i++) {
293     tmp = (BUFFY*) Incoming->data[i];
294     tmp->magic = mx_get_magic (tmp->path);
295     local = mx_is_local (tmp->magic-1);
296     if ((tmp->magic <= 0 || local) && (stat (tmp->path, &sb) != 0 || sb.st_size == 0)) {
297       /* if the mailbox still doesn't exist, set the newly created flag to
298        * be ready for when it does. */
299       tmp->newly_created = 1;
300       tmp->magic = -1;
301 #ifdef BUFFY_SIZE
302       tmp->size = 0;
303 #endif
304       continue;
305     }
306
307     /* check to see if the folder is the currently selected folder
308      * before polling */
309     if (!Context || !Context->path || (local ? (sb.st_dev != contex_sb.st_dev ||
310                                                 sb.st_ino != contex_sb.st_ino) : 
311                                        m_strcmp(tmp->path, Context->path))) {
312       switch (tmp->magic) {
313       case M_MBOX:
314       case M_MMDF:
315         /* only check on force or $mail_check reached */
316         if (force == 1 || (now - last1 >= BuffyTimeout)) {
317           if (!count) {
318             if (STAT_CHECK) {
319               BuffyCount++;
320               tmp->new = 1;
321             }
322 #ifdef BUFFY_SIZE
323             else {
324               /* some other program has deleted mail from the folder */
325               tmp->size = (long) sb.st_size;
326             }
327 #endif
328           }
329           else if (STAT_CHECK || tmp->msgcount == 0) {
330             /* sidebar visible */
331             BuffyCount++;
332             if ((ctx =
333                  mx_open_mailbox (tmp->path, M_READONLY | M_QUIET | M_NOSORT | M_COUNT,
334                                   NULL)) != NULL) {
335               tmp->msgcount = ctx->msgcount;
336               tmp->new = ctx->new;
337               tmp->msg_unread = ctx->new;       /* for sidebar, wtf? */
338               tmp->msg_flagged = ctx->flagged;
339               mx_close_mailbox (ctx, 0);
340             }
341           }
342           if (tmp->newly_created &&
343               (sb.st_ctime != sb.st_mtime || sb.st_ctime != sb.st_atime))
344             tmp->newly_created = 0;
345         }
346         else if (tmp->new > 0)
347           BuffyCount++;
348         break;
349
350       case M_MAILDIR:
351         /* only check on force or $mail_check reached */
352         if (force == 1 || (now - last1 >= BuffyTimeout)) {
353           snprintf (path, sizeof (path), "%s/new", tmp->path);
354           if ((dirp = opendir (path)) == NULL) {
355             tmp->magic = 0;
356             break;
357           }
358           tmp->new = 0;
359           tmp->msg_unread = 0;
360           tmp->msgcount = 0;
361           while ((de = readdir (dirp)) != NULL) {
362             char *p;
363
364             if (*de->d_name != '.' &&
365                 (!(p = strstr (de->d_name, ":2,")) || !strchr (p + 3, 'T'))) {
366               /* one new and undeleted message is enough */
367               if (tmp->new == 0) {
368                 BuffyCount++;
369                 if (!count) {
370                   /* if sidebar invisible -> done */
371                   tmp->new = 1;
372                   break;
373                 }
374               }
375               tmp->msgcount++;
376               tmp->msg_unread++;
377               tmp->new++;
378             }
379           }
380           closedir (dirp);
381
382           if (count) {
383             /* only count total mail if sidebar visible */
384             snprintf (path, sizeof (path), "%s/cur", tmp->path);
385             if ((dirp = opendir (path)) == NULL) {
386               tmp->magic = 0;
387               break;
388             }
389             tmp->msg_flagged = 0;
390             while ((de = readdir (dirp)) != NULL) {
391               char *p;
392
393               if (*de->d_name != '.'
394                   && (p = strstr (de->d_name, ":2,")) != NULL) {
395                 if (!strchr (p + 3, 'T'))
396                   tmp->msgcount++;
397                 if (strchr (p + 3, 'F'))
398                   tmp->msg_flagged++;
399               }
400             }
401             closedir (dirp);
402           }
403         }
404         else if (tmp->new > 0)
405           /* keep current stats if !force and !$mail_check reached */
406           BuffyCount++;
407         break;
408
409       case M_MH:
410         /* only check on force or $mail_check reached */
411         if (force == 1 || (now - last1 >= BuffyTimeout)) {
412           if ((tmp->new = mh_buffy (tmp->path)) > 0)
413             BuffyCount++;
414           if (count) {
415             DIR *dp;
416
417             if ((dp = opendir (path)) == NULL)
418               break;
419             tmp->new = 0;
420             tmp->msgcount = 0;
421             tmp->msg_unread = 0;
422             while ((de = readdir (dp))) {
423               if (mh_valid_message (de->d_name)) {
424                 tmp->msgcount++;
425                 tmp->msg_unread++;
426                 tmp->new++;
427               }
428             }
429             closedir (dp);
430           }
431         }
432         else if (tmp->new > 0)
433           /* keep current stats if !force and !$mail_check reached */
434           BuffyCount++;
435         break;
436
437       case M_IMAP:
438         /* only check on force or $imap_mail_check reached */
439         if (force == 1 || (now - last2 >= ImapBuffyTimeout)) {
440           tmp->msgcount = imap_mailbox_check (tmp->path, 0);
441           tmp->new = imap_mailbox_check (tmp->path, 1);
442           tmp->msg_unread = imap_mailbox_check (tmp->path, 2);
443           if (tmp->new > 0)
444             BuffyCount++;
445           else
446             tmp->new = 0;
447           if (tmp->msg_unread < 0)
448             tmp->msg_unread = 0;
449         }
450         else if (tmp->new > 0)
451           /* keep current stats if !force and !$imap_mail_check reached */
452           BuffyCount++;
453         break;
454
455       }
456     }
457 #ifdef BUFFY_SIZE
458     else if (Context && Context->path)
459       tmp->size = (long) sb.st_size;    /* update the size */
460 #endif
461
462     if (tmp->new <= 0)
463       tmp->notified = 0;
464     else if (!tmp->notified)
465       BuffyNotify++;
466     tmp->has_new = tmp->new > 0;
467   }
468   if (BuffyCount > 0 && force != 2)
469     sidebar_draw (CurrentMenu);
470   return (BuffyCount);
471 }
472
473 int buffy_list (void)
474 {
475   BUFFY *tmp;
476   char path[_POSIX_PATH_MAX];
477   char buffylist[160];
478   int pos;
479   int first;
480   int have_unnotified = BuffyNotify;
481   int i = 0;
482
483   pos = 0;
484   first = 1;
485   buffylist[0] = 0;
486   pos += m_strlen(strncat (buffylist, _("New mail in "), sizeof (buffylist) - 1 - pos)); /* __STRNCAT_CHECKED__ */
487   if (Incoming) {
488     for (i = 0; i < Incoming->length; i++) {
489       tmp = (BUFFY*) Incoming->data[i];
490       /* Is there new mail in this mailbox? */
491       if (tmp->new <= 0 || (have_unnotified && tmp->notified))
492         continue;
493
494       m_strcpy(path, sizeof(path), tmp->path);
495       mutt_pretty_mailbox (path);
496
497       if (!first && pos + m_strlen(path) >= COLS - 7)
498         break;
499
500       if (!first)
501         pos += m_strlen(strncat (buffylist + pos, ", ", sizeof (buffylist) - 1 - pos));    /* __STRNCAT_CHECKED__ */
502
503       /* Prepend an asterisk to mailboxes not already notified */
504       if (!tmp->notified) {
505         /* pos += m_strlen(strncat(buffylist + pos, "*", sizeof(buffylist)-1-pos));  __STRNCAT_CHECKED__ */
506         tmp->notified = 1;
507         BuffyNotify--;
508       }
509       pos += m_strlen(strncat (buffylist + pos, path, sizeof (buffylist) - 1 - pos));      /* __STRNCAT_CHECKED__ */
510       first = 0;
511     }
512   }
513   if (!first && i < Incoming->length) {
514     strncat (buffylist + pos, ", ...", sizeof (buffylist) - 1 - pos);   /* __STRNCAT_CHECKED__ */
515   }
516   if (!first) {
517     /* on new mail: redraw sidebar */
518     sidebar_draw (CurrentMenu);
519     mutt_message ("%s", buffylist);
520     return (1);
521   }
522   /* there were no mailboxes needing to be notified, so clean up since 
523    * BuffyNotify has somehow gotten out of sync
524    */
525   BuffyNotify = 0;
526   return (0);
527 }
528
529 int buffy_notify (void)
530 {
531   if (buffy_check (0) && BuffyNotify) {
532     return (buffy_list ());
533   }
534   return (0);
535 }
536
537 /* 
538  * mutt_buffy() -- incoming folders completion routine
539  *
540  * given a folder name, this routine gives the next incoming folder with new
541  * new mail.
542  */
543 void buffy_next (char *s, size_t slen)
544 {
545   int l = 0;
546   int c = 0, i = 0;
547
548   if (list_empty(Incoming))
549     return;
550
551   mutt_expand_path (s, _POSIX_PATH_MAX);
552   if (buffy_check (0) == 0) {
553     *s = '\0';
554     return;
555   }
556
557   /*
558    * If buffy_lookup returns the index,
559    * or -1 if not found (-1..Incoming->length-1);
560    * plus one --> (0..Incoming->length).
561    * Modulo mapps it into the correct range.
562    */
563   i = 1 + buffy_lookup (s);
564   for (l=0; l < Incoming->length; l++) {
565     c = (l+i) % Incoming->length;
566     if ((!Context || !Context->path || m_strcmp(((BUFFY*) Incoming->data[c])->path, Context->path)) &&
567         ((BUFFY*) Incoming->data[c])->new > 0)
568       break;
569   }
570   if (l >= Incoming->length) {
571     *s = '\0';
572     /* something went wrong since we're here when buffy_check
573      * reported new mail */
574     buffy_check (0);
575   } else {
576     m_strcpy(s, slen, ((BUFFY*)Incoming->data[c])->path);
577     mutt_pretty_mailbox (s);
578   }
579 }