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