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