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