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