buffy.c 13.9 KB
Newer Older
Thomas Roessler's avatar
Thomas Roessler committed
1
/* 
2
 * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
Thomas Roessler's avatar
Thomas Roessler committed
3 4 5 6 7 8 9 10 11 12 13 14 15
 * 
 *     This program is free software; you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation; either version 2 of the License, or
 *     (at your option) any later version.
 * 
 *     This program is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 * 
 *     You should have received a copy of the GNU General Public License
 *     along with this program; if not, write to the Free Software
16
 *     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
Thomas Roessler's avatar
Thomas Roessler committed
17 18
 */

19 20 21 22
#if HAVE_CONFIG_H
# include "config.h"
#endif

Thomas Roessler's avatar
Thomas Roessler committed
23 24 25
#include "mutt.h"
#include "buffy.h"
#include "mailbox.h"
26 27
#include "mx.h"

28 29
#include "mutt_curses.h"

Thomas Roessler's avatar
Thomas Roessler committed
30 31 32
#ifdef USE_IMAP
#include "imap.h"
#endif
Thomas Roessler's avatar
Thomas Roessler committed
33 34 35 36 37 38

#include <string.h>
#include <sys/stat.h>
#include <dirent.h>
#include <utime.h>
#include <ctype.h>
Thomas Roessler's avatar
Thomas Roessler committed
39
#include <unistd.h>
Thomas Roessler's avatar
Thomas Roessler committed
40 41 42 43 44 45 46 47

#include <stdio.h>

static time_t BuffyTime = 0;	/* last time we started checking for mail */
time_t BuffyDoneTime = 0;	/* last time we knew for sure how much mail there was. */
static short BuffyCount = 0;	/* how many boxes with new mail */
static short BuffyNotify = 0;	/* # of unnotified new boxes */

48 49
static BUFFY* buffy_get (const char *path);

50 51 52
/* Find the last message in the file. 
 * upon success return 0. If no message found - return -1 */

53
static int fseek_last_message (FILE * f)
Thomas Roessler's avatar
Thomas Roessler committed
54
{
55
  LOFF_T pos;
56
  char buffer[BUFSIZ + 9];	/* 7 for "\n\nFrom " */
Thomas Roessler's avatar
Thomas Roessler committed
57 58
  int bytes_read;
  int i;			/* Index into `buffer' for scanning.  */
59 60

  memset (buffer, 0, sizeof(buffer));
Thomas Roessler's avatar
Thomas Roessler committed
61
  fseek (f, 0, SEEK_END);
62
  pos = ftello (f);
Thomas Roessler's avatar
Thomas Roessler committed
63 64 65 66 67 68 69 70 71 72 73

  /* Set `bytes_read' to the size of the last, probably partial, buffer; 0 <
   * `bytes_read' <= `BUFSIZ'.  */
  bytes_read = pos % BUFSIZ;
  if (bytes_read == 0)
    bytes_read = BUFSIZ;
  /* Make `pos' a multiple of `BUFSIZ' (0 if the file is short), so that all
   * reads will be on block boundaries, which might increase efficiency.  */
  while ((pos -= bytes_read) >= 0)
  {
    /* we save in the buffer at the end the first 7 chars from the last read */
74
    strncpy (buffer + BUFSIZ, buffer, 5+2); /* 2 == 2 * mutt_strlen(CRLF) */
75
    fseeko (f, pos, SEEK_SET);
Thomas Roessler's avatar
Thomas Roessler committed
76 77 78 79
    bytes_read = fread (buffer, sizeof (char), bytes_read, f);
    if (bytes_read == -1)
      return -1;
    for (i = bytes_read; --i >= 0;)
80
      if (!mutt_strncmp (buffer + i, "\n\nFrom ", mutt_strlen ("\n\nFrom ")))
Thomas Roessler's avatar
Thomas Roessler committed
81
      {				/* found it - go to the beginning of the From */
82
	fseeko (f, pos + i + 2, SEEK_SET);
Thomas Roessler's avatar
Thomas Roessler committed
83 84 85 86 87 88
	return 0;
      }
    bytes_read = BUFSIZ;
  }

  /* here we are at the beginning of the file */
89
  if (!mutt_strncmp ("From ", buffer, 5))
Thomas Roessler's avatar
Thomas Roessler committed
90 91 92 93 94 95 96 97 98
  {
    fseek (f, 0, 0);
    return (0);
  }

  return (-1);
}

/* Return 1 if the last message is new */
99
static int test_last_status_new (FILE * f)
Thomas Roessler's avatar
Thomas Roessler committed
100 101
{
  HEADER *hdr;
102
  ENVELOPE* tmp_envelope;
Thomas Roessler's avatar
Thomas Roessler committed
103 104 105 106 107 108
  int result = 0;

  if (fseek_last_message (f) == -1)
    return (0);

  hdr = mutt_new_header ();
109
  tmp_envelope = mutt_read_rfc822_header (f, hdr, 0, 0);
Thomas Roessler's avatar
Thomas Roessler committed
110 111
  if (!(hdr->read || hdr->old))
    result = 1;
112 113

  mutt_free_envelope(&tmp_envelope);
Thomas Roessler's avatar
Thomas Roessler committed
114 115 116 117 118
  mutt_free_header (&hdr);

  return result;
}

119
static int test_new_folder (const char *path)
Thomas Roessler's avatar
Thomas Roessler committed
120 121 122 123 124 125 126
{
  FILE *f;
  int rc = 0;
  int typ;

  typ = mx_get_magic (path);

127
  if (typ != M_MBOX && typ != M_MMDF)
Thomas Roessler's avatar
Thomas Roessler committed
128 129
    return 0;

130 131 132
  if ((f = fopen (path, "rb")))
  {
    rc = test_last_status_new (f);
133
    safe_fclose (&f);
134
  }
Thomas Roessler's avatar
Thomas Roessler committed
135 136 137 138

  return rc;
}

139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
void mutt_buffy_cleanup (const char *buf, struct stat *st)
{
  struct utimbuf ut;
  BUFFY *tmp;

  if (option(OPTCHECKMBOXSIZE))
  {
    tmp = mutt_find_mailbox (buf);
    if (tmp && !tmp->new)
      mutt_update_mailbox (tmp);
  }
  else
  {
    /* fix up the times so buffy won't get confused */
    if (st->st_mtime > st->st_atime)
    {
      ut.actime = st->st_atime;
      ut.modtime = time (NULL);
      utime (buf, &ut); 
    }
    else
      utime (buf, NULL);
  }
}

Thomas Roessler's avatar
Thomas Roessler committed
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
BUFFY *mutt_find_mailbox (const char *path)
{
  BUFFY *tmp = NULL;
  struct stat sb;
  struct stat tmp_sb;
  
  if (stat (path,&sb) != 0)
    return NULL;

  for (tmp = Incoming; tmp; tmp = tmp->next)
  {
    if (stat (tmp->path,&tmp_sb) ==0 && 
	sb.st_dev == tmp_sb.st_dev && sb.st_ino == tmp_sb.st_ino)
      break;
  }
  return tmp;
}

void mutt_update_mailbox (BUFFY * b)
{
  struct stat sb;

  if (!b)
    return;

  if (stat (b->path, &sb) == 0)
190
    b->size = (off_t) sb.st_size;
Thomas Roessler's avatar
Thomas Roessler committed
191 192 193 194 195
  else
    b->size = 0;
  return;
}

196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212
static BUFFY *buffy_new (const char *path)
{
  BUFFY* buffy;

  buffy = (BUFFY *) safe_calloc (1, sizeof (BUFFY));
  strfcpy (buffy->path, path, sizeof (buffy->path));
  buffy->next = NULL;
  buffy->magic = 0;

  return buffy;
}

static void buffy_free (BUFFY **mailbox)
{
  FREE (mailbox); /* __FREE_CHECKED__ */
}

Thomas Roessler's avatar
Thomas Roessler committed
213 214
int mutt_parse_mailboxes (BUFFER *path, BUFFER *s, unsigned long data, BUFFER *err)
{
215
  BUFFY **tmp,*tmp1;
Thomas Roessler's avatar
Thomas Roessler committed
216 217
  char buf[_POSIX_PATH_MAX];
  struct stat sb;
218
  char f1[PATH_MAX], f2[PATH_MAX];
219
  char *p, *q;
Thomas Roessler's avatar
Thomas Roessler committed
220 221 222 223 224

  while (MoreArgs (s))
  {
    mutt_extract_token (path, s, 0);
    strfcpy (buf, path->data, sizeof (buf));
225 226 227 228 229 230

    if(data == M_UNMAILBOXES && mutt_strcmp(buf,"*") == 0)
    {
      for (tmp = &Incoming; *tmp;)
      {
        tmp1=(*tmp)->next;
231
        buffy_free (tmp);
232 233 234 235 236
        *tmp=tmp1;
      }
      return 0;
    }

Thomas Roessler's avatar
Thomas Roessler committed
237
    mutt_expand_path (buf, sizeof (buf));
238 239 240 241

    /* Skip empty tokens. */
    if(!*buf) continue;

242
    /* avoid duplicates */
243
    p = realpath (buf, f1);
Thomas Roessler's avatar
Thomas Roessler committed
244 245
    for (tmp = &Incoming; *tmp; tmp = &((*tmp)->next))
    {
246 247
      q = realpath ((*tmp)->path, f2);
      if (mutt_strcmp (p ? p : buf, q ? q : (*tmp)->path) == 0)
248 249
      {
	dprint(3,(debugfile,"mailbox '%s' already registered as '%s'\n", buf, (*tmp)->path));
Thomas Roessler's avatar
Thomas Roessler committed
250
	break;
251
      }
Thomas Roessler's avatar
Thomas Roessler committed
252 253
    }

254 255 256 257 258
    if(data == M_UNMAILBOXES)
    {
      if(*tmp)
      {
        tmp1=(*tmp)->next;
259
        buffy_free (tmp);
260 261 262 263 264
        *tmp=tmp1;
      }
      continue;
    }

Thomas Roessler's avatar
Thomas Roessler committed
265
    if (!*tmp)
266
      *tmp = buffy_new (buf);
Thomas Roessler's avatar
Thomas Roessler committed
267 268 269 270 271

    (*tmp)->new = 0;
    (*tmp)->notified = 1;
    (*tmp)->newly_created = 0;

272
    /* for check_mbox_size, it is important that if the folder is new (tested by
Thomas Roessler's avatar
Thomas Roessler committed
273
     * reading it), the size is set to 0 so that later when we check we see
274
     * that it increased .  without check_mbox_size we probably don't care.
Thomas Roessler's avatar
Thomas Roessler committed
275
     */
276 277
    if (option(OPTCHECKMBOXSIZE) &&
	stat ((*tmp)->path, &sb) == 0 && !test_new_folder ((*tmp)->path))
Thomas Roessler's avatar
Thomas Roessler committed
278 279
    {
      /* some systems out there don't have an off_t type */
280
      (*tmp)->size = (off_t) sb.st_size;
Thomas Roessler's avatar
Thomas Roessler committed
281 282 283 284 285 286 287
    }
    else
      (*tmp)->size = 0;
  }
  return 0;
}

288 289 290 291 292 293 294 295
/* returns 1 if maildir has new mail */
static int buffy_maildir_hasnew (BUFFY* mailbox)
{
  char path[_POSIX_PATH_MAX];
  DIR *dirp;
  struct dirent *de;
  char *p;
  int rc = 0;
296
  struct stat sb;
297 298 299

  snprintf (path, sizeof (path), "%s/new", mailbox->path);

300 301 302 303 304 305 306 307 308
  /* when $mail_check_recent is set, if the new/ directory hasn't been modified since
   * the user last exited the mailbox, then we know there is no recent mail.
   */
  if (option(OPTMAILCHECKRECENT))
  {
    if (stat(path, &sb) == 0 && sb.st_mtime < mailbox->last_visited)
      return 0;
  }

309 310 311 312 313 314 315 316 317 318 319
  if ((dirp = opendir (path)) == NULL)
  {
    mailbox->magic = 0;
    return 0;
  }

  while ((de = readdir (dirp)) != NULL)
  {
    if (*de->d_name == '.')
      continue;

320 321 322 323 324 325 326 327 328 329 330
    if (!(p = strstr (de->d_name, ":2,")) || !strchr (p + 3, 'T'))
    {
      if (option(OPTMAILCHECKRECENT))
      {
	char msgpath[_POSIX_PATH_MAX];

	snprintf(msgpath, sizeof(msgpath), "%s/%s", path, de->d_name);
	/* ensure this message was received since leaving this mailbox */
	if (stat(msgpath, &sb) == 0 && (sb.st_ctime <= mailbox->last_visited))
	  continue;
      }
331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355
      /* one new and undeleted message is enough */
      mailbox->new = 1;
      rc = 1;
      break;
    }
  }

  closedir (dirp);

  return rc;
}

/* returns 1 if mailbox has new mail */ 
static int buffy_mbox_hasnew (BUFFY* mailbox, struct stat *sb)
{
  int rc = 0;
  int statcheck;

  if (option (OPTCHECKMBOXSIZE))
    statcheck = sb->st_size > mailbox->size;
  else
    statcheck = sb->st_mtime > sb->st_atime
      || (mailbox->newly_created && sb->st_ctime == sb->st_mtime && sb->st_ctime == sb->st_atime);
  if (statcheck)
  {
356 357 358 359 360
    if (!option(OPTMAILCHECKRECENT) || sb->st_mtime > mailbox->last_visited)
    {
      rc = 1;
      mailbox->new = 1;
    }
361 362 363 364 365 366 367 368 369 370 371 372
  }
  else if (option(OPTCHECKMBOXSIZE))
  {
    /* some other program has deleted mail from the folder */
    mailbox->size = (off_t) sb->st_size;
  }
  if (mailbox->newly_created &&
      (sb->st_ctime != sb->st_mtime || sb->st_ctime != sb->st_atime))
    mailbox->newly_created = 0;

  return rc;
}
Thomas Roessler's avatar
Thomas Roessler committed
373 374 375 376 377 378 379 380

int mutt_buffy_check (int force)
{
  BUFFY *tmp;
  struct stat sb;
  struct stat contex_sb;
  time_t t;

381 382 383 384
  sb.st_size=0;
  contex_sb.st_dev=0;
  contex_sb.st_ino=0;

Thomas Roessler's avatar
Thomas Roessler committed
385
#ifdef USE_IMAP
386 387 388 389
  /* update postponed count as well, on force */
  if (force)
    mutt_update_num_postponed ();
#endif
390

Thomas Roessler's avatar
Thomas Roessler committed
391 392 393 394
  /* fastest return if there are no mailboxes */
  if (!Incoming)
    return 0;
  t = time (NULL);
Thomas Roessler's avatar
Thomas Roessler committed
395
  if (!force && (t - BuffyTime < BuffyTimeout))
Thomas Roessler's avatar
Thomas Roessler committed
396 397 398 399 400 401
    return BuffyCount;
 
  BuffyTime = t;
  BuffyCount = 0;
  BuffyNotify = 0;

Thomas Roessler's avatar
Thomas Roessler committed
402
#ifdef USE_IMAP
403
  BuffyCount += imap_buffy_check (force);
Thomas Roessler's avatar
Thomas Roessler committed
404
#endif
405

Thomas Roessler's avatar
Thomas Roessler committed
406
  /* check device ID and serial number instead of comparing paths */
407
  if (!Context || Context->magic == M_IMAP || Context->magic == M_POP
408
      || stat (Context->path, &contex_sb) != 0)
Thomas Roessler's avatar
Thomas Roessler committed
409 410 411 412 413 414 415
  {
    contex_sb.st_dev=0;
    contex_sb.st_ino=0;
  }
  
  for (tmp = Incoming; tmp; tmp = tmp->next)
  {
416
    if (tmp->magic != M_IMAP)
417
    {
418
      tmp->new = 0;
419
#ifdef USE_POP
420 421 422
      if (mx_is_pop (tmp->path))
	tmp->magic = M_POP;
      else
Thomas Roessler's avatar
Thomas Roessler committed
423
#endif
424 425 426 427 428 429 430 431 432 433
      if (stat (tmp->path, &sb) != 0 || (S_ISREG(sb.st_mode) && sb.st_size == 0) ||
	  (!tmp->magic && (tmp->magic = mx_get_magic (tmp->path)) <= 0))
      {
	/* if the mailbox still doesn't exist, set the newly created flag to
	 * be ready for when it does. */
	tmp->newly_created = 1;
	tmp->magic = 0;
	tmp->size = 0;
	continue;
      }
434
    }
Thomas Roessler's avatar
Thomas Roessler committed
435

Thomas Roessler's avatar
Thomas Roessler committed
436 437
    /* check to see if the folder is the currently selected folder
     * before polling */
438
    if (!Context || !Context->path ||
439 440 441
	(( tmp->magic == M_IMAP || tmp->magic == M_POP )
	    ? mutt_strcmp (tmp->path, Context->path) :
	      (sb.st_dev != contex_sb.st_dev || sb.st_ino != contex_sb.st_ino)))
Thomas Roessler's avatar
Thomas Roessler committed
442 443 444 445 446
    {
      switch (tmp->magic)
      {
      case M_MBOX:
      case M_MMDF:
447
	if (buffy_mbox_hasnew (tmp, &sb) > 0)
Thomas Roessler's avatar
Thomas Roessler committed
448 449 450 451
	  BuffyCount++;
	break;

      case M_MAILDIR:
452 453
	if (buffy_maildir_hasnew (tmp) > 0)
	  BuffyCount++;
Thomas Roessler's avatar
Thomas Roessler committed
454 455
	break;

Thomas Roessler's avatar
Thomas Roessler committed
456
      case M_MH:
457 458
	if ((tmp->new = mh_buffy (tmp->path)) > 0)
	  BuffyCount++;
Thomas Roessler's avatar
Thomas Roessler committed
459
	break;
Thomas Roessler's avatar
Thomas Roessler committed
460 461
      }
    }
462
    else if (option(OPTCHECKMBOXSIZE) && Context && Context->path)
463
      tmp->size = (off_t) sb.st_size;	/* update the size of current folder */
Thomas Roessler's avatar
Thomas Roessler committed
464 465 466 467 468 469

    if (!tmp->new)
      tmp->notified = 0;
    else if (!tmp->notified)
      BuffyNotify++;
  }
Thomas Roessler's avatar
Thomas Roessler committed
470

Thomas Roessler's avatar
Thomas Roessler committed
471 472 473 474
  BuffyDoneTime = BuffyTime;
  return (BuffyCount);
}

475
int mutt_buffy_list (void)
Thomas Roessler's avatar
Thomas Roessler committed
476 477 478
{
  BUFFY *tmp;
  char path[_POSIX_PATH_MAX];
479
  char buffylist[2*STRING];
480 481
  size_t pos = 0;
  int first = 1;
482

483 484
  int have_unnotified = BuffyNotify;
  
485
  buffylist[0] = 0;
486
  pos += strlen (strncat (buffylist, _("New mail in "), sizeof (buffylist) - 1 - pos)); /* __STRNCAT_CHECKED__ */
487
  for (tmp = Incoming; tmp; tmp = tmp->next)
Thomas Roessler's avatar
Thomas Roessler committed
488
  {
489
    /* Is there new mail in this mailbox? */
490
    if (!tmp->new || (have_unnotified && tmp->notified))
491 492 493
      continue;

    strfcpy (path, tmp->path, sizeof (path));
494
    mutt_pretty_mailbox (path, sizeof (path));
495
    
496
    if (!first && (COLS - 7 >= 0) && (pos + strlen (path) >= (size_t)COLS - 7))
497 498 499
      break;
    
    if (!first)
500
      pos += strlen (strncat(buffylist + pos, ", ", sizeof(buffylist)-1-pos)); /* __STRNCAT_CHECKED__ */
501

502 503
    /* Prepend an asterisk to mailboxes not already notified */
    if (!tmp->notified)
Thomas Roessler's avatar
Thomas Roessler committed
504
    {
505
      /* pos += strlen (strncat(buffylist + pos, "*", sizeof(buffylist)-1-pos));  __STRNCAT_CHECKED__ */
506 507
      tmp->notified = 1;
      BuffyNotify--;
Thomas Roessler's avatar
Thomas Roessler committed
508
    }
509
    pos += strlen (strncat(buffylist + pos, path, sizeof(buffylist)-1-pos)); /* __STRNCAT_CHECKED__ */
510 511 512 513
    first = 0;
  }
  if (!first && tmp)
  {
514
    strncat (buffylist + pos, ", ...", sizeof (buffylist) - 1 - pos); /* __STRNCAT_CHECKED__ */
515 516 517 518 519 520 521 522 523 524 525 526 527
  }
  if (!first)
  {
    mutt_message ("%s", buffylist);
    return (1);
  }
  /* there were no mailboxes needing to be notified, so clean up since 
   * BuffyNotify has somehow gotten out of sync
   */
  BuffyNotify = 0;
  return (0);
}

528 529 530 531 532 533 534 535 536
void mutt_buffy_setnotified (const char *path)
{
  BUFFY *buffy;

  buffy = buffy_get(path);
  if (!buffy)
    return;

  buffy->notified = 1;
537
  time(&buffy->last_visited);
538 539
}

540 541 542 543 544
int mutt_buffy_notify (void)
{
  if (mutt_buffy_check (0) && BuffyNotify)
  {
    return (mutt_buffy_list ());
Thomas Roessler's avatar
Thomas Roessler committed
545 546 547 548 549 550 551 552
  }
  return (0);
}

/* 
 * mutt_buffy() -- incoming folders completion routine
 *
 * given a folder name, this routine gives the next incoming folder with new
553
 * mail.
Thomas Roessler's avatar
Thomas Roessler committed
554
 */
555
void mutt_buffy (char *s, size_t slen)
Thomas Roessler's avatar
Thomas Roessler committed
556 557
{
  BUFFY *tmp = Incoming;
558
  int pass, found = 0;
Thomas Roessler's avatar
Thomas Roessler committed
559

560
  mutt_expand_path (s, slen);
Thomas Roessler's avatar
Thomas Roessler committed
561

562 563 564 565
  if (mutt_buffy_check (0)) 
  {
    for (pass = 0; pass < 2; pass++)
      for (tmp = Incoming; tmp; tmp = tmp->next) 
Thomas Roessler's avatar
Thomas Roessler committed
566
      {
567 568 569 570 571 572 573 574 575
	mutt_expand_path (tmp->path, sizeof (tmp->path));
	if ((found || pass) && tmp->new) 
	{
	  strfcpy (s, tmp->path, slen);
	  mutt_pretty_mailbox (s, slen);
	  return;
	}
	if (mutt_strcmp (s, tmp->path) == 0)
	  found = 1;
Thomas Roessler's avatar
Thomas Roessler committed
576
      }
577 578

    mutt_buffy_check (1); /* buffy was wrong - resync things */
Thomas Roessler's avatar
Thomas Roessler committed
579
  }
580 581 582

  /* no folders with new mail */
  *s = '\0';
Thomas Roessler's avatar
Thomas Roessler committed
583
}
584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610

/* fetch buffy object for given path, if present */
static BUFFY* buffy_get (const char *path)
{
  BUFFY *cur;
  char *epath;

  if (!path)
    return NULL;

  epath = safe_strdup(path);
  mutt_expand_path(epath, mutt_strlen(epath));

  for (cur = Incoming; cur; cur = cur->next)
  {
    /* must be done late because e.g. IMAP delimiter may change */
    mutt_expand_path (cur->path, sizeof (cur->path));
    if (!mutt_strcmp(cur->path, path))
    {
      FREE (&epath);
      return cur;
    }
  }

  FREE (&epath);
  return NULL;
}