Remove the time module alltogether.
[apps/madmutt.git] / lib-mx / mx.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 1996-2002 Michael R. Elkins <me@mutt.org>
4  * Copyright (C) 1999-2000 Thomas Roessler <roessler@does-not-exist.org>
5  *
6  * This file is part of mutt-ng, see http://www.muttng.org/.
7  * It's licensed under the GNU General Public License,
8  * please see the file GPL in the top level source directory.
9  */
10
11 #include <lib-lib/lib-lib.h>
12
13 #include <lockfile.h>
14
15 #include <lib-lua/lib-lua.h>
16 #include <lib-sys/unix.h>
17 #include <lib-mime/mime.h>
18 #include <lib-ui/sidebar.h>
19
20 #include "mutt.h"
21 #include "crypt.h"
22 #include "pattern.h"
23 #include "mx.h"
24 #include "mbox.h"
25 #include "mh.h"
26 #include "sort.h"
27 #include "thread.h"
28 #include "copy.h"
29 #include "keymap.h"
30 #include "compress.h"
31 #include "score.h"
32
33 #include <imap/imap.h>
34 #include "pop.h"
35 #ifdef USE_NNTP
36 #include "nntp.h"
37 #endif
38
39 static mx_t const *mxfmts[] = {
40     &mbox_mx,
41     &mh_mx,
42     &maildir_mx,
43     &imap_mx,
44     &pop_mx,
45 #ifdef USE_NNTP
46     &nntp_mx,
47 #endif
48     &compress_mx,
49 };
50
51 #define MX_IDX(idx)          (idx >= 0 && idx < countof(mxfmts))
52 #define mutt_is_spool(s)     (m_strcmp(Spoolfile, s) == 0)
53
54 static int dotlock_file(const char *path, int retry)
55 {
56     char lockfile[_POSIX_PATH_MAX];
57     snprintf(lockfile, sizeof(lockfile), "%s.lock", path);
58
59     if (lockfile_create(lockfile, retry ? 1 : 0, 0)) {
60         if (retry)
61             mutt_error (_("Can't dotlock %s.\n"), lockfile);
62     }
63     return 0;
64 }
65
66 static int undotlock_file (const char *path)
67 {
68     char lockfile[_POSIX_PATH_MAX];
69     snprintf(lockfile, sizeof(lockfile), "%s.lock", path);
70     return lockfile_remove(lockfile);
71 }
72
73 /* looks up index of type for path in mxfmts */
74 static int mx_get_idx (const char* path) {
75     int i = 0, t = 0;
76     struct stat st;
77
78     /* first, test all non-local folders to avoid stat() call */
79     for (i = 0; i < countof(mxfmts); i++) {
80         if (!mxfmts[i]->local)
81             t = mxfmts[i]->mx_is_magic(path, NULL);
82         if (t >= 1)
83             return (t-1);
84     }
85     if (stat (path, &st) == 0) {
86         /* if stat() succeeded, keep testing until success and
87          * pass stat() info so that we only need to do it once */
88         for (i = 0; i < countof(mxfmts); i++) {
89             if (mxfmts[i]->local)
90                 t = mxfmts[i]->mx_is_magic(path, &st);
91             if (t >= 1)
92                 return (t-1);
93         }
94     }
95     return (-1);
96 }
97
98 /* Args:
99  *      excl            if excl != 0, request an exclusive lock
100  *      dot             if dot != 0, try to dotlock the file
101  *      time_out        should retry locking?
102  */
103 int mx_lock_file (const char *path, int fd, int excl, int dot, int time_out)
104 {
105 #if defined (USE_FCNTL) || defined (USE_FLOCK)
106     int count;
107     int attempt;
108     struct stat prev_sb;
109 #endif
110     int r = 0;
111
112 #ifdef USE_FCNTL
113     struct flock lck;
114
115     p_clear(&lck, 1);
116     lck.l_type = excl ? F_WRLCK : F_RDLCK;
117     lck.l_whence = SEEK_SET;
118
119     count = 0;
120     attempt = 0;
121     prev_sb.st_size = 0;
122     while (fcntl (fd, F_SETLK, &lck) == -1) {
123         struct stat sb;
124
125         if (errno != EAGAIN && errno != EACCES) {
126             mutt_perror ("fcntl");
127             return (-1);
128         }
129
130         if (fstat (fd, &sb) != 0)
131             sb.st_size = 0;
132
133         if (count == 0)
134             prev_sb = sb;
135
136         /* only unlock file if it is unchanged */
137         if (prev_sb.st_size == sb.st_size
138             && ++count >= (time_out ? MAXLOCKATTEMPT : 0)) {
139             if (time_out)
140                 mutt_error _("Timeout exceeded while attempting fcntl lock!");
141
142             return (-1);
143         }
144
145         prev_sb = sb;
146
147         mutt_message (_("Waiting for fcntl lock... %d"), ++attempt);
148         sleep (1);
149     }
150 #endif /* USE_FCNTL */
151
152 #ifdef USE_FLOCK
153     count = 0;
154     attempt = 0;
155     while (flock (fd, (excl ? LOCK_EX : LOCK_SH) | LOCK_NB) == -1) {
156         struct stat sb;
157
158         if (errno != EWOULDBLOCK) {
159             mutt_perror ("flock");
160             r = -1;
161             break;
162         }
163
164         if (fstat (fd, &sb) != 0)
165             sb.st_size = 0;
166
167         if (count == 0)
168             prev_sb = sb;
169
170         /* only unlock file if it is unchanged */
171         if (prev_sb.st_size == sb.st_size
172             && ++count >= (time_out ? MAXLOCKATTEMPT : 0)) {
173             if (time_out)
174                 mutt_error _("Timeout exceeded while attempting flock lock!");
175
176             r = -1;
177             break;
178         }
179
180         prev_sb = sb;
181
182         mutt_message (_("Waiting for flock attempt... %d"), ++attempt);
183         sleep (1);
184     }
185 #endif /* USE_FLOCK */
186
187     if (r == 0 && dot)
188         r = dotlock_file(path, time_out);
189
190     if (r == -1) {
191         /* release any other locks obtained in this routine */
192
193 #ifdef USE_FCNTL
194         lck.l_type = F_UNLCK;
195         fcntl (fd, F_SETLK, &lck);
196 #endif /* USE_FCNTL */
197
198 #ifdef USE_FLOCK
199         flock (fd, LOCK_UN);
200 #endif /* USE_FLOCK */
201
202         return (-1);
203     }
204
205     return 0;
206 }
207
208 int mx_unlock_file (const char *path, int fd, int dot)
209 {
210 #ifdef USE_FCNTL
211     struct flock unlockit;
212
213     p_clear(&unlockit, 1);
214     unlockit.l_type = F_UNLCK;
215     unlockit.l_whence = SEEK_SET;
216     fcntl (fd, F_SETLK, &unlockit);
217 #endif
218
219 #ifdef USE_FLOCK
220     flock (fd, LOCK_UN);
221 #endif
222
223     if (dot)
224         undotlock_file (path);
225
226     return 0;
227 }
228
229 /* try to figure out what type of mailbox ``path'' is */
230 int mx_get_magic (const char *path) {
231   int i = 0;
232
233   if (m_strlen(path) == 0)
234     return (-1);
235   if ((i = mx_get_idx (path)) >= 0)
236     return (mxfmts[i]->type);
237   return (-1);
238 }
239
240 int mx_is_local (int m) {
241   if (!MX_IDX(m))
242     return (0);
243   return (mxfmts[m]->local);
244 }
245
246 /*
247  * set DefaultMagic to the given value
248  */
249 int mx_set_magic (const char *s)
250 {
251   if (ascii_strcasecmp (s, "mbox") == 0)
252     DefaultMagic = M_MBOX;
253   else if (ascii_strcasecmp (s, "mh") == 0)
254     DefaultMagic = M_MH;
255   else if (ascii_strcasecmp (s, "maildir") == 0)
256     DefaultMagic = M_MAILDIR;
257   else
258     return (-1);
259
260   return 0;
261 }
262
263 /* mx_access: Wrapper for access, checks permissions on a given mailbox.
264  *   We may be interested in using ACL-style flags at some point, currently
265  *   we use the normal access() flags. */
266 int mx_access (const char *path, int flags)
267 {
268   int i = 0;
269
270   if ((i = mx_get_idx (path)) >= 0 && mxfmts[i]->mx_access)
271     return (mxfmts[i]->mx_access(path,flags));
272   return (0);
273 }
274
275 static int mx_open_mailbox_append (CONTEXT * ctx, int flags)
276 {
277   struct stat sb;
278
279   /* special case for appending to compressed folders -
280    * even if we can not open them for reading */
281   if (mutt_can_append_compressed (ctx->path))
282     mutt_open_append_compressed (ctx);
283
284   ctx->append = 1;
285
286   if (mx_get_magic (ctx->path) == M_IMAP)
287     return imap_open_mailbox_append (ctx);
288
289   if (stat (ctx->path, &sb) == 0) {
290     ctx->magic = mx_get_magic (ctx->path);
291
292     switch (ctx->magic) {
293     case 0:
294       mutt_error (_("%s is not a mailbox."), ctx->path);
295       /* fall through */
296     case -1:
297       return (-1);
298     }
299   }
300   else if (errno == ENOENT) {
301     ctx->magic = DefaultMagic;
302
303     if (ctx->magic == M_MH || ctx->magic == M_MAILDIR) {
304       char tmp[_POSIX_PATH_MAX];
305
306       if (mkdir (ctx->path, S_IRWXU)) {
307         mutt_perror (ctx->path);
308         return (-1);
309       }
310
311       if (ctx->magic == M_MAILDIR) {
312         snprintf (tmp, sizeof (tmp), "%s/cur", ctx->path);
313         if (mkdir (tmp, S_IRWXU)) {
314           mutt_perror (tmp);
315           rmdir (ctx->path);
316           return (-1);
317         }
318
319         snprintf (tmp, sizeof (tmp), "%s/new", ctx->path);
320         if (mkdir (tmp, S_IRWXU)) {
321           mutt_perror (tmp);
322           snprintf (tmp, sizeof (tmp), "%s/cur", ctx->path);
323           rmdir (tmp);
324           rmdir (ctx->path);
325           return (-1);
326         }
327         snprintf (tmp, sizeof (tmp), "%s/tmp", ctx->path);
328         if (mkdir (tmp, S_IRWXU)) {
329           mutt_perror (tmp);
330           snprintf (tmp, sizeof (tmp), "%s/cur", ctx->path);
331           rmdir (tmp);
332           snprintf (tmp, sizeof (tmp), "%s/new", ctx->path);
333           rmdir (tmp);
334           rmdir (ctx->path);
335           return (-1);
336         }
337       }
338       else {
339         int i;
340
341         snprintf (tmp, sizeof (tmp), "%s/.mh_sequences", ctx->path);
342         if ((i = creat (tmp, S_IRWXU)) == -1) {
343           mutt_perror (tmp);
344           rmdir (ctx->path);
345           return (-1);
346         }
347         close (i);
348       }
349     }
350   }
351   else {
352     mutt_perror (ctx->path);
353     return (-1);
354   }
355
356   switch (ctx->magic) {
357   case M_MBOX:
358     if ((ctx->fp =
359          safe_fopen (ctx->path, flags & M_NEWFOLDER ? "w" : "a")) == NULL
360         || mbox_lock_mailbox (ctx, 1, 1) != 0) {
361       if (!ctx->fp)
362         mutt_perror (ctx->path);
363       else {
364         mutt_error (_("Couldn't lock %s\n"), ctx->path);
365         m_fclose(&ctx->fp);
366       }
367       return (-1);
368     }
369     fseeko (ctx->fp, 0, 2);
370     break;
371
372   case M_MH:
373   case M_MAILDIR:
374     /* nothing to do */
375     break;
376
377   default:
378     return (-1);
379   }
380
381   return 0;
382 }
383
384 /*
385  * open a mailbox and parse it
386  *
387  * Args:
388  *      flags   M_NOSORT        do not sort mailbox
389  *              M_APPEND        open mailbox for appending
390  *              M_READONLY      open mailbox in read-only mode
391  *              M_QUIET         only print error messages
392  *      ctx     if non-null, context struct to use
393  */
394 CONTEXT *mx_open_mailbox (const char *path, int flags, CONTEXT * pctx)
395 {
396   CONTEXT *ctx = pctx;
397   int rc;
398
399   if (!ctx)
400     ctx = p_new(CONTEXT, 1);
401   p_clear(ctx, 1);
402   ctx->path = m_strdup(path);
403
404   ctx->msgnotreadyet = -1;
405   ctx->collapsed = 0;
406
407   if (flags & M_QUIET)
408     ctx->quiet = 1;
409   if (flags & M_READONLY)
410     ctx->readonly = 1;
411   if (flags & M_COUNT)
412     ctx->counting = 1;
413
414   if (flags & (M_APPEND | M_NEWFOLDER)) {
415     if (mx_open_mailbox_append (ctx, flags) != 0) {
416       mx_fastclose_mailbox (ctx);
417       if (!pctx)
418         p_delete(&ctx);
419       return NULL;
420     }
421     return ctx;
422   }
423
424   if (!MX_IDX(ctx->magic-1))
425     ctx->magic = mx_get_magic (path);
426
427   if (ctx->magic == M_COMPRESSED)
428     mutt_open_read_compressed (ctx);
429
430   if (ctx->magic == 0)
431     mutt_error (_("%s is not a mailbox."), path);
432
433   if (ctx->magic == -1)
434     mutt_perror (path);
435
436   if (ctx->magic <= 0) {
437     mx_fastclose_mailbox (ctx);
438     if (!pctx)
439       p_delete(&ctx);
440     return (NULL);
441   }
442
443   /* if the user has a `push' command in their .muttrc, or in a folder-hook,
444    * it will cause the progress messages not to be displayed because
445    * mutt_refresh() will think we are in the middle of a macro.  so set a
446    * flag to indicate that we should really refresh the screen.
447    */
448   set_option (OPTFORCEREFRESH);
449
450   if (!ctx->quiet)
451     mutt_message (_("Reading %s..."), ctx->path);
452
453   rc = mxfmts[ctx->magic-1]->mx_open_mailbox(ctx);
454
455   if (rc == 0) {
456     if ((flags & M_NOSORT) == 0) {
457       /* avoid unnecessary work since the mailbox is completely unthreaded
458          to begin with */
459       unset_option (OPTSORTSUBTHREADS);
460       unset_option (OPTNEEDRESCORE);
461       mutt_sort_headers (ctx, 1);
462     }
463     if (!ctx->quiet)
464       mutt_clear_error ();
465   }
466   else {
467     mx_fastclose_mailbox (ctx);
468     if (!pctx)
469       p_delete(&ctx);
470   }
471
472   unset_option (OPTFORCEREFRESH);
473   return (ctx);
474 }
475
476 /* free up memory associated with the mailbox context */
477 void mx_fastclose_mailbox (CONTEXT * ctx)
478 {
479   int i;
480
481   if (!ctx)
482     return;
483
484   if (MX_IDX(ctx->magic-1) && mxfmts[ctx->magic-1]->mx_fastclose_mailbox)
485     mxfmts[ctx->magic-1]->mx_fastclose_mailbox(ctx);
486   if (ctx->subj_hash)
487     hash_delete (&ctx->subj_hash, NULL);
488   if (ctx->id_hash)
489     hash_delete (&ctx->id_hash, NULL);
490   mutt_clear_threads (ctx);
491   for (i = 0; i < ctx->msgcount; i++)
492     header_delete(&ctx->hdrs[i]);
493   p_delete(&ctx->hdrs);
494   p_delete(&ctx->v2r);
495
496   if (ctx->cinfo)
497     mutt_fast_close_compressed (ctx);
498
499   p_delete(&ctx->path);
500   p_delete(&ctx->pattern);
501   pattern_list_wipe(&ctx->limit_pattern);
502   m_fclose(&ctx->fp);
503   p_clear(ctx, 1);
504 }
505
506 /* save changes to disk */
507 static int sync_mailbox (CONTEXT * ctx, int *index_hint)
508 {
509   int rc = -1;
510
511   if (!ctx->quiet)
512     mutt_message (_("Writing %s..."), ctx->path);
513
514   if (MX_IDX(ctx->magic-1))
515     /* the 1 is only of interest for IMAP and means EXPUNGE */
516     rc = mxfmts[ctx->magic-1]->mx_sync_mailbox(ctx,1,index_hint);
517
518   if (rc == 0 && ctx->cinfo)
519     return mutt_sync_compressed (ctx);
520
521   return rc;
522 }
523
524 /* move deleted mails to the trash folder */
525 static int trash_append (CONTEXT * ctx)
526 {
527   CONTEXT *ctx_trash;
528   int i = 0;
529   struct stat st, stc;
530
531   if (!TrashPath || !ctx->deleted ||
532       (ctx->magic == M_MAILDIR && option (OPTMAILDIRTRASH)))
533     return 0;
534
535   for (; i < ctx->msgcount && (!ctx->hdrs[i]->deleted ||
536                                ctx->hdrs[i]->appended); i++);
537   if (i == ctx->msgcount)
538     return 0;                   /* nothing to be done */
539
540   if (mutt_save_confirm (TrashPath, &st) != 0) {
541     mutt_error _("message(s) not deleted");
542
543     return -1;
544   }
545
546   if (lstat (ctx->path, &stc) == 0 && stc.st_ino == st.st_ino
547       && stc.st_dev == st.st_dev && stc.st_rdev == st.st_rdev)
548     return 0;                   /* we are in the trash folder: simple sync */
549
550   if ((ctx_trash = mx_open_mailbox (TrashPath, M_APPEND, NULL)) != NULL) {
551     for (i = 0; i < ctx->msgcount; i++)
552       if (ctx->hdrs[i]->deleted && !ctx->hdrs[i]->appended
553           && !ctx->hdrs[i]->purged
554           && mutt_append_message (ctx_trash, ctx, ctx->hdrs[i], 0, 0) == -1) {
555         mx_close_mailbox (ctx_trash, NULL);
556         return -1;
557       }
558
559     mx_close_mailbox (ctx_trash, NULL);
560   }
561   else {
562     mutt_error _("Can't open trash folder");
563
564     return -1;
565   }
566
567   return 0;
568 }
569
570 /* save changes and close mailbox */
571 static int _mx_close_mailbox (CONTEXT * ctx, int *index_hint)
572 {
573   int i, move_messages = 0, purge = 1, read_msgs = 0;
574   int check;
575   int isSpool = 0;
576   CONTEXT f;
577   char mbox[_POSIX_PATH_MAX];
578   char buf[STRING];
579
580   if (!ctx)
581     return 0;
582
583   ctx->closing = 1;
584
585 #ifdef USE_NNTP
586   if (ctx->magic == M_NNTP) {
587     int ret;
588
589     ret = nntp_close_mailbox (ctx);
590     mx_fastclose_mailbox (ctx);
591     return ret;
592   }
593 #endif
594   if (ctx->readonly || ctx->dontwrite) {
595     /* mailbox is readonly or we don't want to write */
596     mx_fastclose_mailbox (ctx);
597     return 0;
598   }
599
600   if (ctx->append) {
601     /* mailbox was opened in write-mode */
602     if (ctx->magic == M_MBOX)
603       mbox_close_mailbox (ctx);
604     else
605       mx_fastclose_mailbox (ctx);
606     return 0;
607   }
608
609   for (i = 0; i < ctx->msgcount; i++) {
610     if (!ctx->hdrs[i]->deleted && ctx->hdrs[i]->read
611         && !(ctx->hdrs[i]->flagged && option (OPTKEEPFLAGGED)))
612       read_msgs++;
613   }
614
615   if (read_msgs && quadoption (OPT_MOVE) != M_NO) {
616     char *p;
617
618     if ((p = mutt_find_hook (M_MBOXHOOK, ctx->path))) {
619       isSpool = 1;
620       m_strcpy(mbox, sizeof(mbox), p);
621     }
622     else {
623       m_strcpy(mbox, sizeof(mbox), NONULL(Inbox));
624       isSpool = mutt_is_spool (ctx->path) && !mutt_is_spool (mbox);
625     }
626     mutt_expand_path (mbox, sizeof (mbox));
627
628     if (isSpool) {
629       snprintf (buf, sizeof (buf), _("Move read messages to %s?"), mbox);
630       if ((move_messages = query_quadoption (OPT_MOVE, buf)) == -1) {
631         ctx->closing = 0;
632         return (-1);
633       }
634     }
635   }
636
637   /* 
638    * There is no point in asking whether or not to purge if we are
639    * just marking messages as "trash".
640    */
641   if (ctx->deleted && !(ctx->magic == M_MAILDIR && option (OPTMAILDIRTRASH))) {
642     snprintf (buf, sizeof (buf), ctx->deleted == 1
643               ? _("Purge %d deleted message?") :
644               _("Purge %d deleted messages?"), ctx->deleted);
645     if ((purge = query_quadoption (OPT_DELETE, buf)) < 0) {
646       ctx->closing = 0;
647       return (-1);
648     }
649   }
650
651   /* IMAP servers manage the OLD flag themselves */
652   if (ctx->magic != M_IMAP)
653     if (option (OPTMARKOLD)) {
654       for (i = 0; i < ctx->msgcount; i++) {
655         if (!ctx->hdrs[i]->deleted && !ctx->hdrs[i]->old)
656           mutt_set_flag (ctx, ctx->hdrs[i], M_OLD, 1);
657       }
658     }
659
660   if (move_messages) {
661     mutt_message (_("Moving read messages to %s..."), mbox);
662
663     /* try to use server-side copy first */
664     i = 1;
665
666     if (ctx->magic == M_IMAP && imap_is_magic (mbox, NULL) == M_IMAP) {
667       /* tag messages for moving, and clear old tags, if any */
668       for (i = 0; i < ctx->msgcount; i++)
669         if (ctx->hdrs[i]->read && !ctx->hdrs[i]->deleted
670             && !(ctx->hdrs[i]->flagged && option (OPTKEEPFLAGGED)))
671           ctx->hdrs[i]->tagged = 1;
672         else
673           ctx->hdrs[i]->tagged = 0;
674
675       i = imap_copy_messages (ctx, NULL, mbox, 1);
676     }
677
678     if (i == 0)                 /* success */
679       mutt_clear_error ();
680     else if (i == -1) {         /* horrible error, bail */
681       ctx->closing = 0;
682       return -1;
683     }
684     else                        /* use regular append-copy mode */
685     {
686       if (mx_open_mailbox (mbox, M_APPEND, &f) == NULL) {
687         ctx->closing = 0;
688         return -1;
689       }
690
691       for (i = 0; i < ctx->msgcount; i++) {
692         if (ctx->hdrs[i]->read && !ctx->hdrs[i]->deleted
693             && !(ctx->hdrs[i]->flagged && option (OPTKEEPFLAGGED))) {
694           if (mutt_append_message (&f, ctx, ctx->hdrs[i], 0, CH_UPDATE_LEN) ==
695               0) {
696             mutt_set_flag (ctx, ctx->hdrs[i], M_DELETE, 1);
697             mutt_set_flag (ctx, ctx->hdrs[i], M_APPENDED, 1);
698           }
699           else {
700             mx_close_mailbox (&f, NULL);
701             ctx->closing = 0;
702             return -1;
703           }
704         }
705       }
706
707       mx_close_mailbox (&f, NULL);
708     }
709
710   }
711   else if (!ctx->changed && ctx->deleted == 0) {
712     mutt_message _("Mailbox is unchanged.");
713
714     mx_fastclose_mailbox (ctx);
715     return 0;
716   }
717
718   /* copy mails to the trash before expunging */
719   if (purge && ctx->deleted)
720     if (trash_append (ctx) != 0) {
721       ctx->closing = 0;
722       return -1;
723     }
724
725   /* allow IMAP to preserve the deleted flag across sessions */
726   if (ctx->magic == M_IMAP) {
727     if ((check = imap_sync_mailbox (ctx, purge, index_hint)) != 0) {
728       ctx->closing = 0;
729       return check;
730     }
731   }
732   else
733   {
734     if (!purge) {
735       for (i = 0; i < ctx->msgcount; i++)
736         ctx->hdrs[i]->deleted = 0;
737       ctx->deleted = 0;
738     }
739
740     if (ctx->changed || ctx->deleted) {
741       if ((check = sync_mailbox (ctx, index_hint)) != 0) {
742         ctx->closing = 0;
743         return check;
744       }
745     }
746   }
747
748   if (move_messages)
749     mutt_message (_("%d kept, %d moved, %d deleted."),
750                   ctx->msgcount - ctx->deleted, read_msgs, ctx->deleted);
751   else
752     mutt_message (_("%d kept, %d deleted."),
753                   ctx->msgcount - ctx->deleted, ctx->deleted);
754
755   if (ctx->cinfo && mutt_slow_close_compressed (ctx))
756     return (-1);
757
758   mx_fastclose_mailbox (ctx);
759
760   return 0;
761 }
762
763 int mx_close_mailbox (CONTEXT * ctx, int *index_hint) {
764   int ret = 0;
765   if (!ctx)
766     return (0);
767   ret = _mx_close_mailbox (ctx, index_hint);
768   sidebar_set_buffystats (ctx);
769   return (ret);
770 }
771
772 /* update a Context structure's internal tables. */
773
774 void mx_update_tables (CONTEXT * ctx, int committing)
775 {
776   int i, j;
777
778   /* update memory to reflect the new state of the mailbox */
779   ctx->vcount = 0;
780   ctx->vsize = 0;
781   ctx->tagged = 0;
782   ctx->deleted = 0;
783   ctx->new = 0;
784   ctx->unread = 0;
785   ctx->changed = 0;
786   ctx->flagged = 0;
787 #define this_body ctx->hdrs[j]->content
788   for (i = 0, j = 0; i < ctx->msgcount; i++) {
789     if ((committing && (!ctx->hdrs[i]->deleted ||
790                         (ctx->magic == M_MAILDIR
791                          && option (OPTMAILDIRTRASH)))) || (!committing
792                                                             && ctx->hdrs[i]->
793                                                             active)) {
794       if (i != j) {
795         ctx->hdrs[j] = ctx->hdrs[i];
796         ctx->hdrs[i] = NULL;
797       }
798       ctx->hdrs[j]->msgno = j;
799       if (ctx->hdrs[j]->virtual != -1) {
800         ctx->v2r[ctx->vcount] = j;
801         ctx->hdrs[j]->virtual = ctx->vcount++;
802         ctx->vsize += this_body->length + this_body->offset -
803           this_body->hdr_offset;
804       }
805
806       if (committing)
807         ctx->hdrs[j]->changed = 0;
808       else if (ctx->hdrs[j]->changed)
809         ctx->changed++;
810
811       if (!committing
812           || (ctx->magic == M_MAILDIR && option (OPTMAILDIRTRASH))) {
813         if (ctx->hdrs[j]->deleted)
814           ctx->deleted++;
815       }
816
817       if (ctx->hdrs[j]->tagged)
818         ctx->tagged++;
819       if (ctx->hdrs[j]->flagged)
820         ctx->flagged++;
821       if (!ctx->hdrs[j]->read) {
822         ctx->unread++;
823         if (!ctx->hdrs[j]->old)
824           ctx->new++;
825       }
826
827       j++;
828     }
829     else {
830       if (ctx->magic == M_MH || ctx->magic == M_MAILDIR)
831         ctx->size -= (ctx->hdrs[i]->content->length +
832                       ctx->hdrs[i]->content->offset -
833                       ctx->hdrs[i]->content->hdr_offset);
834       /* remove message from the hash tables */
835       if (ctx->subj_hash && ctx->hdrs[i]->env->real_subj)
836         hash_remove (ctx->subj_hash, ctx->hdrs[i]->env->real_subj,
837                      ctx->hdrs[i], NULL);
838       if (ctx->id_hash && ctx->hdrs[i]->env->message_id)
839         hash_remove (ctx->id_hash, ctx->hdrs[i]->env->message_id,
840                      ctx->hdrs[i], NULL);
841       header_delete(&ctx->hdrs[i]);
842     }
843   }
844 #undef this_body
845   ctx->msgcount = j;
846
847   /* update sidebar count */
848   sidebar_set_buffystats (ctx);
849 }
850
851
852 /* save changes to mailbox
853  *
854  * return values:
855  *      0               success
856  *      -1              error
857  */
858 static int _mx_sync_mailbox (CONTEXT * ctx, int *index_hint)
859 {
860   int rc, i;
861   int purge = 1;
862   int msgcount, deleted;
863
864   if (ctx->dontwrite) {
865     char buf[STRING], tmp[STRING];
866
867     if (km_expand_key (buf, sizeof (buf),
868                        km_find_func (MENU_MAIN, OP_TOGGLE_WRITE)))
869       snprintf (tmp, sizeof (tmp), _(" Press '%s' to toggle write"), buf);
870     else
871       m_strcpy(tmp, sizeof(tmp), _("Use 'toggle-write' to re-enable write!"));
872
873     mutt_error (_("Mailbox is marked unwritable. %s"), tmp);
874     return -1;
875   }
876   else if (ctx->readonly) {
877     mutt_error _("Mailbox is read-only.");
878
879     return -1;
880   }
881
882   if (!ctx->changed && !ctx->deleted) {
883     mutt_message _("Mailbox is unchanged.");
884
885     return (0);
886   }
887
888   if (ctx->deleted) {
889     char buf[STRING];
890
891     snprintf (buf, sizeof (buf), ctx->deleted == 1
892               ? _("Purge %d deleted message?") :
893               _("Purge %d deleted messages?"), ctx->deleted);
894     if ((purge = query_quadoption (OPT_DELETE, buf)) < 0)
895       return (-1);
896     else if (purge == M_NO) {
897       if (!ctx->changed)
898         return 0;               /* nothing to do! */
899       /* let IMAP servers hold on to D flags */
900       if (ctx->magic != M_IMAP)
901       {
902         for (i = 0; i < ctx->msgcount; i++)
903           ctx->hdrs[i]->deleted = 0;
904         ctx->deleted = 0;
905       }
906     }
907     else if (ctx->last_tag && ctx->last_tag->deleted)
908       ctx->last_tag = NULL;     /* reset last tagged msg now useless */
909   }
910
911   /* really only for IMAP - imap_sync_mailbox results in a call to
912    * mx_update_tables, so ctx->deleted is 0 when it comes back */
913   msgcount = ctx->msgcount;
914   deleted = ctx->deleted;
915
916   if (purge && ctx->deleted) {
917     if (trash_append (ctx) == -1)
918       return -1;
919   }
920
921   if (ctx->magic == M_IMAP)
922     rc = imap_sync_mailbox (ctx, purge, index_hint);
923   else
924     rc = sync_mailbox (ctx, index_hint);
925   if (rc == 0) {
926     if (ctx->magic == M_IMAP && !purge)
927       mutt_message (_("Mailbox checkpointed."));
928     else
929       mutt_message (_("%d kept, %d deleted."), msgcount - deleted, deleted);
930
931     mutt_sleep (0);
932
933     /* if we haven't deleted any messages, we don't need to resort */
934     /* ... except for certain folder formats which need "unsorted" 
935      * sort order in order to synchronize folders.
936      * 
937      * MH and maildir are safe.  mbox-style seems to need re-sorting,
938      * at least with the new threading code.
939      */
940     if (purge || (ctx->magic != M_MAILDIR && ctx->magic != M_MH)) {
941       /* IMAP does this automatically after handling EXPUNGE */
942       if (ctx->magic != M_IMAP)
943       {
944         mx_update_tables (ctx, 1);
945         mutt_sort_headers (ctx, 1);     /* rethread from scratch */
946       }
947     }
948   }
949
950   return (rc);
951 }
952
953 int mx_sync_mailbox (CONTEXT* ctx, int* index_hint) {
954   int ret = _mx_sync_mailbox (ctx, index_hint);
955   sidebar_set_buffystats (ctx);
956   return (ret);
957 }
958
959 /* args:
960  *      dest    destintation mailbox
961  *      hdr     message being copied (required for maildir support, because
962  *              the filename depends on the message flags)
963  */
964 MESSAGE *mx_open_new_message (CONTEXT * dest, HEADER * hdr, int flags)
965 {
966   MESSAGE *msg;
967   address_t *p = NULL;
968
969   if (!MX_IDX(dest->magic-1)) {
970     return (NULL);
971   }
972
973   msg = p_new(MESSAGE, 1);
974   msg->magic = dest->magic;
975   msg->write = 1;
976
977   if (hdr) {
978     msg->flags.flagged = hdr->flagged;
979     msg->flags.replied = hdr->replied;
980     msg->flags.read = hdr->read;
981     msg->received = hdr->received;
982   }
983
984   if (msg->received == 0)
985     time (&msg->received);
986
987   if (mxfmts[dest->magic-1]->mx_open_new_message(msg, dest, hdr) == 0) {
988     if (msg->magic == M_MBOX && flags & M_ADD_FROM) {
989       if (hdr) {
990         if (hdr->env->return_path)
991           p = hdr->env->return_path;
992         else if (hdr->env->sender)
993           p = hdr->env->sender;
994         else
995           p = hdr->env->from;
996       }
997
998       fprintf (msg->fp, "From %s %s", p ? p->mailbox : NONULL(mod_core.username),
999                ctime (&msg->received));
1000     }
1001   }
1002   else
1003     p_delete(&msg);
1004
1005   return msg;
1006 }
1007
1008 /* check for new mail */
1009 int mx_check_mailbox (CONTEXT * ctx, int *index_hint, int lock) {
1010   if (ctx->cinfo)
1011     return mutt_check_mailbox_compressed (ctx);
1012
1013   if (ctx) {
1014     if (ctx->locked)
1015       lock = 0;
1016     if (MX_IDX(ctx->magic-1) && mxfmts[ctx->magic-1]->mx_check_mailbox)
1017       return (mxfmts[ctx->magic-1]->mx_check_mailbox(ctx, index_hint, lock));
1018   }
1019
1020   return (-1);
1021 }
1022
1023 /* return a stream pointer for a message */
1024 MESSAGE *mx_open_message (CONTEXT * ctx, int msgno)
1025 {
1026   MESSAGE *msg;
1027
1028   msg = p_new(MESSAGE, 1);
1029   switch (msg->magic = ctx->magic) {
1030   case M_MBOX:
1031     msg->fp = ctx->fp;
1032     break;
1033
1034   case M_MH:
1035   case M_MAILDIR:
1036     {
1037       HEADER *cur = ctx->hdrs[msgno];
1038       char path[_POSIX_PATH_MAX];
1039
1040       snprintf (path, sizeof (path), "%s/%s", ctx->path, cur->path);
1041
1042       if ((msg->fp = fopen (path, "r")) == NULL && errno == ENOENT &&
1043           ctx->magic == M_MAILDIR)
1044         msg->fp = maildir_open_find_message (ctx->path, cur->path);
1045
1046       if (msg->fp == NULL) {
1047         mutt_perror (path);
1048         p_delete(&msg);
1049       }
1050     }
1051     break;
1052
1053   case M_IMAP:
1054     {
1055       if (imap_fetch_message (msg, ctx, msgno) != 0)
1056         p_delete(&msg);
1057       break;
1058     }
1059
1060   case M_POP:
1061     {
1062       if (pop_fetch_message (msg, ctx, msgno) != 0)
1063         p_delete(&msg);
1064       break;
1065     }
1066
1067 #ifdef USE_NNTP
1068   case M_NNTP:
1069     {
1070       if (nntp_fetch_message (msg, ctx, msgno) != 0)
1071         p_delete(&msg);
1072       break;
1073     }
1074 #endif /* USE_NNTP */
1075
1076   default:
1077     p_delete(&msg);
1078     break;
1079   }
1080   return (msg);
1081 }
1082
1083 /* commit a message to a folder */
1084
1085 int mx_commit_message (MESSAGE * msg, CONTEXT * ctx) {
1086   if (!(msg->write && ctx->append)) {
1087     return -1;
1088   }
1089   if (!ctx || !MX_IDX(ctx->magic-1) || !mxfmts[ctx->magic-1]->mx_commit_message)
1090     return (-1);
1091   return (mxfmts[ctx->magic-1]->mx_commit_message (msg, ctx));
1092 }
1093
1094 /* close a pointer to a message */
1095 int mx_close_message (MESSAGE ** msg)
1096 {
1097   int r = 0;
1098
1099   if ((*msg)->magic == M_MH || (*msg)->magic == M_MAILDIR
1100       || (*msg)->magic == M_IMAP
1101       || (*msg)->magic == M_POP
1102 #ifdef USE_NNTP
1103       || (*msg)->magic == M_NNTP
1104 #endif
1105     ) {
1106     r = m_fclose(&(*msg)->fp);
1107   }
1108   else
1109     (*msg)->fp = NULL;
1110
1111   if ((*msg)->path) {
1112     unlink ((*msg)->path);
1113     p_delete(&(*msg)->path);
1114   }
1115
1116   p_delete(msg);
1117   return (r);
1118 }
1119
1120 void mx_alloc_memory (CONTEXT * ctx)
1121 {
1122     ctx->hdrmax += 32;
1123
1124     p_realloc(&ctx->hdrs, ctx->hdrmax);
1125     p_realloc(&ctx->v2r, ctx->hdrmax);
1126     p_clear(ctx->hdrs + ctx->msgcount, ctx->hdrmax - ctx->msgcount);
1127     p_clear(ctx->v2r + ctx->msgcount, ctx->hdrmax - ctx->msgcount);
1128 }
1129
1130 /* this routine is called to update the counts in the context structure for
1131  * the last message header parsed.
1132  */
1133 void mx_update_context (CONTEXT * ctx, int new_messages)
1134 {
1135   HEADER *h;
1136   int msgno;
1137
1138   for (msgno = ctx->msgcount - new_messages; msgno < ctx->msgcount; msgno++) {
1139     h = ctx->hdrs[msgno];
1140
1141     /* NOTE: this _must_ be done before the check for mailcap! */
1142     h->security = crypt_query (h->content);
1143
1144     if (!ctx->pattern) {
1145       ctx->v2r[ctx->vcount] = msgno;
1146       h->virtual = ctx->vcount++;
1147     }
1148     else
1149       h->virtual = -1;
1150     h->msgno = msgno;
1151
1152     if (h->env->supersedes) {
1153       HEADER *h2;
1154
1155       if (!ctx->id_hash)
1156         ctx->id_hash = mutt_make_id_hash (ctx);
1157
1158       h2 = hash_find (ctx->id_hash, h->env->supersedes);
1159
1160       /* p_delete(&h->env->supersedes); should I ? */
1161       if (h2) {
1162         h2->superseded = 1;
1163         if (!ctx->counting && mod_score.enable)
1164           mutt_score_message (ctx, h2, 1);
1165       }
1166     }
1167
1168     /* add this message to the hash tables */
1169     if (ctx->id_hash && h->env->message_id)
1170       hash_insert (ctx->id_hash, h->env->message_id, h);
1171     if (!ctx->counting) {
1172       if (ctx->subj_hash && h->env->real_subj)
1173         hash_insert (ctx->subj_hash, h->env->real_subj, h);
1174
1175       if (mod_score.enable)
1176         mutt_score_message (ctx, h, 0);
1177     }
1178
1179     if (h->changed)
1180       ctx->changed = 1;
1181     if (h->flagged)
1182       ctx->flagged++;
1183     if (h->deleted)
1184       ctx->deleted++;
1185     if (!h->read) {
1186       ctx->unread++;
1187       if (!h->old)
1188         ctx->new++;
1189     }
1190   }
1191   /* update sidebar count */
1192   sidebar_set_buffystats (ctx);
1193 }
1194
1195 /*
1196  * Return:
1197  * 1 if the specified mailbox contains 0 messages.
1198  * 0 if the mailbox contains messages
1199  * -1 on error
1200  */
1201 int mx_check_empty (const char *path)
1202 {
1203   int i = 0;
1204   if ((i = mx_get_idx (path)) >= 0 && mxfmts[i]->mx_check_empty)
1205     return (mxfmts[i]->mx_check_empty(path));
1206   errno = EINVAL;
1207   return (-1);
1208 }
1209
1210 int mx_acl_check(CONTEXT *ctx, int flag)
1211 {
1212     if (!mxfmts[ctx->magic-1]->mx_acl_check)
1213         return 1;
1214     return mxfmts[ctx->magic-1]->mx_acl_check(ctx,flag);
1215 }
1216
1217 void mutt_parse_mime_message (CONTEXT * ctx, HEADER * cur)
1218 {
1219   MESSAGE *msg;
1220   int flags = 0;
1221
1222   do {
1223     if (cur->content->type != TYPEMESSAGE
1224         && cur->content->type != TYPEMULTIPART)
1225       break;                     /* nothing to do */
1226
1227     if (cur->content->parts)
1228       break;                     /* The message was parsed earlier. */
1229
1230     if ((msg = mx_open_message (ctx, cur->msgno))) {
1231       mutt_parse_part (msg->fp, cur->content);
1232
1233       cur->security = crypt_query (cur->content);
1234
1235       mx_close_message (&msg);
1236     }
1237   } while (0);
1238   mutt_count_body_parts (cur, flags | M_PARTS_RECOUNT);
1239 }