]> git.llucax.com Git - software/mutt-debian.git/blob - buffy.c
Merge commit 'upstream/1.5.20'
[software/mutt-debian.git] / buffy.c
1 /* 
2  * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
3  * 
4  *     This program is free software; you can redistribute it and/or modify
5  *     it under the terms of the GNU General Public License as published by
6  *     the Free Software Foundation; either version 2 of the License, or
7  *     (at your option) any later version.
8  * 
9  *     This program is distributed in the hope that it will be useful,
10  *     but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *     GNU General Public License for more details.
13  * 
14  *     You should have received a copy of the GNU General Public License
15  *     along with this program; if not, write to the Free Software
16  *     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17  */
18
19 #if HAVE_CONFIG_H
20 # include "config.h"
21 #endif
22
23 #include "mutt.h"
24 #include "buffy.h"
25 #include "mailbox.h"
26 #include "mx.h"
27
28 #include "mutt_curses.h"
29
30 #ifdef USE_IMAP
31 #include "imap.h"
32 #endif
33
34 #include <string.h>
35 #include <sys/stat.h>
36 #include <dirent.h>
37 #include <utime.h>
38 #include <ctype.h>
39 #include <unistd.h>
40
41 #include <stdio.h>
42
43 static time_t BuffyTime = 0;    /* last time we started checking for mail */
44 time_t BuffyDoneTime = 0;       /* last time we knew for sure how much mail there was. */
45 static short BuffyCount = 0;    /* how many boxes with new mail */
46 static short BuffyNotify = 0;   /* # of unnotified new boxes */
47
48 /* Find the last message in the file. 
49  * upon success return 0. If no message found - return -1 */
50
51 static int fseek_last_message (FILE * f)
52 {
53   LOFF_T pos;
54   char buffer[BUFSIZ + 9];      /* 7 for "\n\nFrom " */
55   int bytes_read;
56   int i;                        /* Index into `buffer' for scanning.  */
57
58   memset (buffer, 0, sizeof(buffer));
59   fseek (f, 0, SEEK_END);
60   pos = ftello (f);
61
62   /* Set `bytes_read' to the size of the last, probably partial, buffer; 0 <
63    * `bytes_read' <= `BUFSIZ'.  */
64   bytes_read = pos % BUFSIZ;
65   if (bytes_read == 0)
66     bytes_read = BUFSIZ;
67   /* Make `pos' a multiple of `BUFSIZ' (0 if the file is short), so that all
68    * reads will be on block boundaries, which might increase efficiency.  */
69   while ((pos -= bytes_read) >= 0)
70   {
71     /* we save in the buffer at the end the first 7 chars from the last read */
72     strncpy (buffer + BUFSIZ, buffer, 5+2); /* 2 == 2 * mutt_strlen(CRLF) */
73     fseeko (f, pos, SEEK_SET);
74     bytes_read = fread (buffer, sizeof (char), bytes_read, f);
75     if (bytes_read == -1)
76       return -1;
77     for (i = bytes_read; --i >= 0;)
78       if (!mutt_strncmp (buffer + i, "\n\nFrom ", mutt_strlen ("\n\nFrom ")))
79       {                         /* found it - go to the beginning of the From */
80         fseeko (f, pos + i + 2, SEEK_SET);
81         return 0;
82       }
83     bytes_read = BUFSIZ;
84   }
85
86   /* here we are at the beginning of the file */
87   if (!mutt_strncmp ("From ", buffer, 5))
88   {
89     fseek (f, 0, 0);
90     return (0);
91   }
92
93   return (-1);
94 }
95
96 /* Return 1 if the last message is new */
97 static int test_last_status_new (FILE * f)
98 {
99   HEADER *hdr;
100   ENVELOPE* tmp_envelope;
101   int result = 0;
102
103   if (fseek_last_message (f) == -1)
104     return (0);
105
106   hdr = mutt_new_header ();
107   tmp_envelope = mutt_read_rfc822_header (f, hdr, 0, 0);
108   if (!(hdr->read || hdr->old))
109     result = 1;
110
111   mutt_free_envelope(&tmp_envelope);
112   mutt_free_header (&hdr);
113
114   return result;
115 }
116
117 static int test_new_folder (const char *path)
118 {
119   FILE *f;
120   int rc = 0;
121   int typ;
122
123   typ = mx_get_magic (path);
124
125   if (typ != M_MBOX && typ != M_MMDF)
126     return 0;
127
128   if ((f = fopen (path, "rb")))
129   {
130     rc = test_last_status_new (f);
131     safe_fclose (&f);
132   }
133
134   return rc;
135 }
136
137 void mutt_buffy_cleanup (const char *buf, struct stat *st)
138 {
139   struct utimbuf ut;
140   BUFFY *tmp;
141
142   if (option(OPTCHECKMBOXSIZE))
143   {
144     tmp = mutt_find_mailbox (buf);
145     if (tmp && !tmp->new)
146       mutt_update_mailbox (tmp);
147   }
148   else
149   {
150     /* fix up the times so buffy won't get confused */
151     if (st->st_mtime > st->st_atime)
152     {
153       ut.actime = st->st_atime;
154       ut.modtime = time (NULL);
155       utime (buf, &ut); 
156     }
157     else
158       utime (buf, NULL);
159   }
160 }
161
162 BUFFY *mutt_find_mailbox (const char *path)
163 {
164   BUFFY *tmp = NULL;
165   struct stat sb;
166   struct stat tmp_sb;
167   
168   if (stat (path,&sb) != 0)
169     return NULL;
170
171   for (tmp = Incoming; tmp; tmp = tmp->next)
172   {
173     if (stat (tmp->path,&tmp_sb) ==0 && 
174         sb.st_dev == tmp_sb.st_dev && sb.st_ino == tmp_sb.st_ino)
175       break;
176   }
177   return tmp;
178 }
179
180 void mutt_update_mailbox (BUFFY * b)
181 {
182   struct stat sb;
183
184   if (!b)
185     return;
186
187   if (stat (b->path, &sb) == 0)
188     b->size = (off_t) sb.st_size;
189   else
190     b->size = 0;
191   return;
192 }
193
194 int mutt_parse_mailboxes (BUFFER *path, BUFFER *s, unsigned long data, BUFFER *err)
195 {
196   BUFFY **tmp,*tmp1;
197   char buf[_POSIX_PATH_MAX];
198   struct stat sb;
199   char f1[PATH_MAX], f2[PATH_MAX];
200   char *p, *q;
201
202   while (MoreArgs (s))
203   {
204     mutt_extract_token (path, s, 0);
205     strfcpy (buf, path->data, sizeof (buf));
206
207     if(data == M_UNMAILBOXES && mutt_strcmp(buf,"*") == 0)
208     {
209       for (tmp = &Incoming; *tmp;)
210       {
211         tmp1=(*tmp)->next;
212         FREE (tmp);             /* __FREE_CHECKED__ */
213         *tmp=tmp1;
214       }
215       return 0;
216     }
217
218     mutt_expand_path (buf, sizeof (buf));
219
220     /* Skip empty tokens. */
221     if(!*buf) continue;
222
223     /* avoid duplicates */
224     p = realpath (buf, f1);
225     for (tmp = &Incoming; *tmp; tmp = &((*tmp)->next))
226     {
227       q = realpath ((*tmp)->path, f2);
228       if (mutt_strcmp (p ? p : buf, q ? q : (*tmp)->path) == 0)
229       {
230         dprint(3,(debugfile,"mailbox '%s' already registered as '%s'\n", buf, (*tmp)->path));
231         break;
232       }
233     }
234
235     if(data == M_UNMAILBOXES)
236     {
237       if(*tmp)
238       {
239         FREE (&((*tmp)->path));
240         tmp1=(*tmp)->next;
241         FREE (tmp);             /* __FREE_CHECKED__ */
242         *tmp=tmp1;
243       }
244       continue;
245     }
246
247     if (!*tmp)
248     {
249       *tmp = (BUFFY *) safe_calloc (1, sizeof (BUFFY));
250       strfcpy ((*tmp)->path, buf, sizeof ((*tmp)->path));
251       (*tmp)->next = NULL;
252       /* it is tempting to set magic right here */
253       (*tmp)->magic = 0;
254       
255     }
256
257     (*tmp)->new = 0;
258     (*tmp)->notified = 1;
259     (*tmp)->newly_created = 0;
260
261     /* for check_mbox_size, it is important that if the folder is new (tested by
262      * reading it), the size is set to 0 so that later when we check we see
263      * that it increased .  without check_mbox_size we probably don't care.
264      */
265     if (option(OPTCHECKMBOXSIZE) &&
266         stat ((*tmp)->path, &sb) == 0 && !test_new_folder ((*tmp)->path))
267     {
268       /* some systems out there don't have an off_t type */
269       (*tmp)->size = (off_t) sb.st_size;
270     }
271     else
272       (*tmp)->size = 0;
273   }
274   return 0;
275 }
276
277 /* people use check_mbox_size on systems where modified time attributes are 
278  * BADLY broken. Ignore them.
279  */
280 #define STAT_CHECK_SIZE (sb.st_size > tmp->size)
281 #define STAT_CHECK_TIME (sb.st_mtime > sb.st_atime || (tmp->newly_created && sb.st_ctime == sb.st_mtime && sb.st_ctime == sb.st_atime))
282 #define STAT_CHECK (option(OPTCHECKMBOXSIZE) ? STAT_CHECK_SIZE : STAT_CHECK_TIME)
283
284 int mutt_buffy_check (int force)
285 {
286   BUFFY *tmp;
287   struct stat sb;
288   struct dirent *de;
289   DIR *dirp;
290   char path[_POSIX_PATH_MAX];
291   struct stat contex_sb;
292   time_t t;
293
294   sb.st_size=0;
295   contex_sb.st_dev=0;
296   contex_sb.st_ino=0;
297
298 #ifdef USE_IMAP
299   /* update postponed count as well, on force */
300   if (force)
301     mutt_update_num_postponed ();
302 #endif
303
304   /* fastest return if there are no mailboxes */
305   if (!Incoming)
306     return 0;
307   t = time (NULL);
308   if (!force && (t - BuffyTime < BuffyTimeout))
309     return BuffyCount;
310  
311   BuffyTime = t;
312   BuffyCount = 0;
313   BuffyNotify = 0;
314
315 #ifdef USE_IMAP
316   BuffyCount += imap_buffy_check (force);
317
318   if (!Context || Context->magic != M_IMAP)
319 #endif
320 #ifdef USE_POP
321   if (!Context || Context->magic != M_POP)
322 #endif
323   /* check device ID and serial number instead of comparing paths */
324   if (!Context || !Context->path || stat (Context->path, &contex_sb) != 0)
325   {
326     contex_sb.st_dev=0;
327     contex_sb.st_ino=0;
328   }
329   
330   for (tmp = Incoming; tmp; tmp = tmp->next)
331   {
332 #ifdef USE_IMAP
333     if (tmp->magic != M_IMAP)
334 #endif
335     tmp->new = 0;
336
337 #ifdef USE_IMAP
338     if (tmp->magic != M_IMAP)
339     {
340 #endif
341 #ifdef USE_POP
342     if (mx_is_pop (tmp->path))
343       tmp->magic = M_POP;
344     else
345 #endif
346     if (stat (tmp->path, &sb) != 0 || (S_ISREG(sb.st_mode) && sb.st_size == 0) ||
347         (!tmp->magic && (tmp->magic = mx_get_magic (tmp->path)) <= 0))
348     {
349       /* if the mailbox still doesn't exist, set the newly created flag to
350        * be ready for when it does. */
351       tmp->newly_created = 1;
352       tmp->magic = 0;
353       tmp->size = 0;
354       continue;
355     }
356 #ifdef USE_IMAP
357     }
358 #endif
359
360     /* check to see if the folder is the currently selected folder
361      * before polling */
362     if (!Context || !Context->path ||
363 #if defined USE_IMAP || defined USE_POP
364         ((
365 #ifdef USE_IMAP
366         tmp->magic == M_IMAP
367 #endif
368 #ifdef USE_POP
369 #ifdef USE_IMAP
370         ||
371 #endif
372         tmp->magic == M_POP
373 #endif
374         ) ? mutt_strcmp (tmp->path, Context->path) :
375 #endif
376          (sb.st_dev != contex_sb.st_dev || sb.st_ino != contex_sb.st_ino)
377 #if defined USE_IMAP || defined USE_POP  
378             )
379 #endif
380         )
381         
382     {
383       switch (tmp->magic)
384       {
385       case M_MBOX:
386       case M_MMDF:
387
388         if (STAT_CHECK)
389         {
390           BuffyCount++;
391           tmp->new = 1;
392         }
393         else if (option(OPTCHECKMBOXSIZE))
394         {
395           /* some other program has deleted mail from the folder */
396           tmp->size = (off_t) sb.st_size;
397         }
398         if (tmp->newly_created &&
399             (sb.st_ctime != sb.st_mtime || sb.st_ctime != sb.st_atime))
400           tmp->newly_created = 0;
401
402         break;
403
404       case M_MAILDIR:
405
406         snprintf (path, sizeof (path), "%s/new", tmp->path);
407         if ((dirp = opendir (path)) == NULL)
408         {
409           tmp->magic = 0;
410           break;
411         }
412         while ((de = readdir (dirp)) != NULL)
413         {
414           char *p;
415           if (*de->d_name != '.' && 
416               (!(p = strstr (de->d_name, ":2,")) || !strchr (p + 3, 'T')))
417           {
418             /* one new and undeleted message is enough */
419             BuffyCount++;
420             tmp->new = 1;
421             break;
422           }
423         }
424         closedir (dirp);
425         break;
426
427       case M_MH:
428         if ((tmp->new = mh_buffy (tmp->path)) > 0)
429           BuffyCount++;
430         break;
431       }
432     }
433     else if (option(OPTCHECKMBOXSIZE) && Context && Context->path)
434       tmp->size = (off_t) sb.st_size;   /* update the size of current folder */
435
436     if (!tmp->new)
437       tmp->notified = 0;
438     else if (!tmp->notified)
439       BuffyNotify++;
440   }
441
442   BuffyDoneTime = BuffyTime;
443   return (BuffyCount);
444 }
445
446 int mutt_buffy_list (void)
447 {
448   BUFFY *tmp;
449   char path[_POSIX_PATH_MAX];
450   char buffylist[2*STRING];
451   int pos;
452   int first;
453
454   int have_unnotified = BuffyNotify;
455   
456   pos = 0;
457   first = 1;
458   buffylist[0] = 0;
459   pos += strlen (strncat (buffylist, _("New mail in "), sizeof (buffylist) - 1 - pos)); /* __STRNCAT_CHECKED__ */
460   for (tmp = Incoming; tmp; tmp = tmp->next)
461   {
462     /* Is there new mail in this mailbox? */
463     if (!tmp->new || (have_unnotified && tmp->notified))
464       continue;
465
466     strfcpy (path, tmp->path, sizeof (path));
467     mutt_pretty_mailbox (path, sizeof (path));
468     
469     if (!first && pos + strlen (path) >= COLS - 7)
470       break;
471     
472     if (!first)
473       pos += strlen (strncat(buffylist + pos, ", ", sizeof(buffylist)-1-pos)); /* __STRNCAT_CHECKED__ */
474
475     /* Prepend an asterisk to mailboxes not already notified */
476     if (!tmp->notified)
477     {
478       /* pos += strlen (strncat(buffylist + pos, "*", sizeof(buffylist)-1-pos));  __STRNCAT_CHECKED__ */
479       tmp->notified = 1;
480       BuffyNotify--;
481     }
482     pos += strlen (strncat(buffylist + pos, path, sizeof(buffylist)-1-pos)); /* __STRNCAT_CHECKED__ */
483     first = 0;
484   }
485   if (!first && tmp)
486   {
487     strncat (buffylist + pos, ", ...", sizeof (buffylist) - 1 - pos); /* __STRNCAT_CHECKED__ */
488   }
489   if (!first)
490   {
491     mutt_message ("%s", buffylist);
492     return (1);
493   }
494   /* there were no mailboxes needing to be notified, so clean up since 
495    * BuffyNotify has somehow gotten out of sync
496    */
497   BuffyNotify = 0;
498   return (0);
499 }
500
501 int mutt_buffy_notify (void)
502 {
503   if (mutt_buffy_check (0) && BuffyNotify)
504   {
505     return (mutt_buffy_list ());
506   }
507   return (0);
508 }
509
510 /* 
511  * mutt_buffy() -- incoming folders completion routine
512  *
513  * given a folder name, this routine gives the next incoming folder with new
514  * mail.
515  */
516 void mutt_buffy (char *s, size_t slen)
517 {
518   BUFFY *tmp = Incoming;
519   int pass, found = 0;
520
521   mutt_expand_path (s, slen);
522
523   if (mutt_buffy_check (0)) 
524   {
525     for (pass = 0; pass < 2; pass++)
526       for (tmp = Incoming; tmp; tmp = tmp->next) 
527       {
528         mutt_expand_path (tmp->path, sizeof (tmp->path));
529         if ((found || pass) && tmp->new) 
530         {
531           strfcpy (s, tmp->path, slen);
532           mutt_pretty_mailbox (s, slen);
533           return;
534         }
535         if (mutt_strcmp (s, tmp->path) == 0)
536           found = 1;
537       }
538
539     mutt_buffy_check (1); /* buffy was wrong - resync things */
540   }
541
542   /* no folders with new mail */
543   *s = '\0';
544 }