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