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