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