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