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