Drop sidebar.h
[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         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         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     if ((p = mutt_find_hook (M_MBOXHOOK, ctx->path))) {
604       isSpool = 1;
605       m_strcpy(mbox, sizeof(mbox), p);
606     }
607     else {
608       m_strcpy(mbox, sizeof(mbox), NONULL(Inbox));
609       isSpool = mutt_is_spool (ctx->path) && !mutt_is_spool (mbox);
610     }
611     mutt_expand_path (mbox, sizeof (mbox));
612
613     if (isSpool) {
614       snprintf (buf, sizeof (buf), _("Move read messages to %s?"), mbox);
615       if ((move_messages = query_quadoption (OPT_MOVE, buf)) == -1) {
616         ctx->closing = 0;
617         return (-1);
618       }
619     }
620   }
621
622   /* 
623    * There is no point in asking whether or not to purge if we are
624    * just marking messages as "trash".
625    */
626   if (ctx->deleted && !(ctx->magic == M_MAILDIR && option (OPTMAILDIRTRASH))) {
627     snprintf (buf, sizeof (buf), ctx->deleted == 1
628               ? _("Purge %d deleted message?") :
629               _("Purge %d deleted messages?"), ctx->deleted);
630     if ((purge = query_quadoption (OPT_DELETE, buf)) < 0) {
631       ctx->closing = 0;
632       return (-1);
633     }
634   }
635
636   /* IMAP servers manage the OLD flag themselves */
637   if (ctx->magic != M_IMAP)
638     if (option (OPTMARKOLD)) {
639       for (i = 0; i < ctx->msgcount; i++) {
640         if (!ctx->hdrs[i]->deleted && !ctx->hdrs[i]->old)
641           mutt_set_flag (ctx, ctx->hdrs[i], M_OLD, 1);
642       }
643     }
644
645   if (move_messages) {
646     mutt_message (_("Moving read messages to %s..."), mbox);
647
648     /* try to use server-side copy first */
649     i = 1;
650
651     if (ctx->magic == M_IMAP && imap_is_magic (mbox, NULL) == M_IMAP) {
652       /* tag messages for moving, and clear old tags, if any */
653       for (i = 0; i < ctx->msgcount; i++)
654         if (ctx->hdrs[i]->read && !ctx->hdrs[i]->deleted
655             && !(ctx->hdrs[i]->flagged && option (OPTKEEPFLAGGED)))
656           ctx->hdrs[i]->tagged = 1;
657         else
658           ctx->hdrs[i]->tagged = 0;
659
660       i = imap_copy_messages (ctx, NULL, mbox, 1);
661     }
662
663     if (i == 0)                 /* success */
664       mutt_clear_error ();
665     else if (i == -1) {         /* horrible error, bail */
666       ctx->closing = 0;
667       return -1;
668     }
669     else                        /* use regular append-copy mode */
670     {
671       if (mx_open_mailbox (mbox, M_APPEND, &f) == NULL) {
672         ctx->closing = 0;
673         return -1;
674       }
675
676       for (i = 0; i < ctx->msgcount; i++) {
677         if (ctx->hdrs[i]->read && !ctx->hdrs[i]->deleted
678             && !(ctx->hdrs[i]->flagged && option (OPTKEEPFLAGGED))) {
679           if (mutt_append_message (&f, ctx, ctx->hdrs[i], 0, CH_UPDATE_LEN) ==
680               0) {
681             mutt_set_flag (ctx, ctx->hdrs[i], M_DELETE, 1);
682             mutt_set_flag (ctx, ctx->hdrs[i], M_APPENDED, 1);
683           }
684           else {
685             mx_close_mailbox (&f, NULL);
686             ctx->closing = 0;
687             return -1;
688           }
689         }
690       }
691
692       mx_close_mailbox (&f, NULL);
693     }
694
695   }
696   else if (!ctx->changed && ctx->deleted == 0) {
697     mutt_message _("Mailbox is unchanged.");
698
699     mx_fastclose_mailbox (ctx);
700     return 0;
701   }
702
703   /* copy mails to the trash before expunging */
704   if (purge && ctx->deleted)
705     if (trash_append (ctx) != 0) {
706       ctx->closing = 0;
707       return -1;
708     }
709
710   /* allow IMAP to preserve the deleted flag across sessions */
711   if (ctx->magic == M_IMAP) {
712     if ((check = imap_sync_mailbox (ctx, purge, index_hint)) != 0) {
713       ctx->closing = 0;
714       return check;
715     }
716   }
717   else
718   {
719     if (!purge) {
720       for (i = 0; i < ctx->msgcount; i++)
721         ctx->hdrs[i]->deleted = 0;
722       ctx->deleted = 0;
723     }
724
725     if (ctx->changed || ctx->deleted) {
726       if ((check = sync_mailbox (ctx, index_hint)) != 0) {
727         ctx->closing = 0;
728         return check;
729       }
730     }
731   }
732
733   if (move_messages)
734     mutt_message (_("%d kept, %d moved, %d deleted."),
735                   ctx->msgcount - ctx->deleted, read_msgs, ctx->deleted);
736   else
737     mutt_message (_("%d kept, %d deleted."),
738                   ctx->msgcount - ctx->deleted, ctx->deleted);
739
740   if (ctx->cinfo && mutt_slow_close_compressed (ctx))
741     return (-1);
742
743   mx_fastclose_mailbox (ctx);
744
745   return 0;
746 }
747
748 int mx_close_mailbox (CONTEXT * ctx, int *index_hint) {
749   int ret = 0;
750   if (!ctx)
751     return (0);
752   ret = _mx_close_mailbox (ctx, index_hint);
753   sidebar_set_buffystats (ctx);
754   return (ret);
755 }
756
757 /* update a Context structure's internal tables. */
758
759 void mx_update_tables (CONTEXT * ctx, int committing)
760 {
761   int i, j;
762
763   /* update memory to reflect the new state of the mailbox */
764   ctx->vcount = 0;
765   ctx->vsize = 0;
766   ctx->tagged = 0;
767   ctx->deleted = 0;
768   ctx->new = 0;
769   ctx->unread = 0;
770   ctx->changed = 0;
771   ctx->flagged = 0;
772 #define this_body ctx->hdrs[j]->content
773   for (i = 0, j = 0; i < ctx->msgcount; i++) {
774     if ((committing && (!ctx->hdrs[i]->deleted ||
775                         (ctx->magic == M_MAILDIR
776                          && option (OPTMAILDIRTRASH)))) || (!committing
777                                                             && ctx->hdrs[i]->
778                                                             active)) {
779       if (i != j) {
780         ctx->hdrs[j] = ctx->hdrs[i];
781         ctx->hdrs[i] = NULL;
782       }
783       ctx->hdrs[j]->msgno = j;
784       if (ctx->hdrs[j]->virtual != -1) {
785         ctx->v2r[ctx->vcount] = j;
786         ctx->hdrs[j]->virtual = ctx->vcount++;
787         ctx->vsize += this_body->length + this_body->offset -
788           this_body->hdr_offset;
789       }
790
791       if (committing)
792         ctx->hdrs[j]->changed = 0;
793       else if (ctx->hdrs[j]->changed)
794         ctx->changed++;
795
796       if (!committing
797           || (ctx->magic == M_MAILDIR && option (OPTMAILDIRTRASH))) {
798         if (ctx->hdrs[j]->deleted)
799           ctx->deleted++;
800       }
801
802       if (ctx->hdrs[j]->tagged)
803         ctx->tagged++;
804       if (ctx->hdrs[j]->flagged)
805         ctx->flagged++;
806       if (!ctx->hdrs[j]->read) {
807         ctx->unread++;
808         if (!ctx->hdrs[j]->old)
809           ctx->new++;
810       }
811
812       j++;
813     }
814     else {
815       if (ctx->magic == M_MH || ctx->magic == M_MAILDIR)
816         ctx->size -= (ctx->hdrs[i]->content->length +
817                       ctx->hdrs[i]->content->offset -
818                       ctx->hdrs[i]->content->hdr_offset);
819       /* remove message from the hash tables */
820       if (ctx->subj_hash && ctx->hdrs[i]->env->real_subj)
821         hash_remove (ctx->subj_hash, ctx->hdrs[i]->env->real_subj,
822                      ctx->hdrs[i], NULL);
823       if (ctx->id_hash && ctx->hdrs[i]->env->message_id)
824         hash_remove (ctx->id_hash, ctx->hdrs[i]->env->message_id,
825                      ctx->hdrs[i], NULL);
826       header_delete(&ctx->hdrs[i]);
827     }
828   }
829 #undef this_body
830   ctx->msgcount = j;
831
832   /* update sidebar count */
833   sidebar_set_buffystats (ctx);
834 }
835
836
837 /* save changes to mailbox
838  *
839  * return values:
840  *      0               success
841  *      -1              error
842  */
843 static int _mx_sync_mailbox (CONTEXT * ctx, int *index_hint)
844 {
845   int rc, i;
846   int purge = 1;
847   int msgcount, deleted;
848
849   if (ctx->dontwrite) {
850     char buf[STRING], tmp[STRING];
851
852     if (km_expand_key (buf, sizeof (buf),
853                        km_find_func (MENU_MAIN, OP_TOGGLE_WRITE)))
854       snprintf (tmp, sizeof (tmp), _(" Press '%s' to toggle write"), buf);
855     else
856       m_strcpy(tmp, sizeof(tmp), _("Use 'toggle-write' to re-enable write!"));
857
858     mutt_error (_("Mailbox is marked unwritable. %s"), tmp);
859     return -1;
860   }
861   else if (ctx->readonly) {
862     mutt_error _("Mailbox is read-only.");
863
864     return -1;
865   }
866
867   if (!ctx->changed && !ctx->deleted) {
868     mutt_message _("Mailbox is unchanged.");
869
870     return (0);
871   }
872
873   if (ctx->deleted) {
874     char buf[STRING];
875
876     snprintf (buf, sizeof (buf), ctx->deleted == 1
877               ? _("Purge %d deleted message?") :
878               _("Purge %d deleted messages?"), ctx->deleted);
879     if ((purge = query_quadoption (OPT_DELETE, buf)) < 0)
880       return (-1);
881     else if (purge == M_NO) {
882       if (!ctx->changed)
883         return 0;               /* nothing to do! */
884       /* let IMAP servers hold on to D flags */
885       if (ctx->magic != M_IMAP)
886       {
887         for (i = 0; i < ctx->msgcount; i++)
888           ctx->hdrs[i]->deleted = 0;
889         ctx->deleted = 0;
890       }
891     }
892     else if (ctx->last_tag && ctx->last_tag->deleted)
893       ctx->last_tag = NULL;     /* reset last tagged msg now useless */
894   }
895
896   /* really only for IMAP - imap_sync_mailbox results in a call to
897    * mx_update_tables, so ctx->deleted is 0 when it comes back */
898   msgcount = ctx->msgcount;
899   deleted = ctx->deleted;
900
901   if (purge && ctx->deleted) {
902     if (trash_append (ctx) == -1)
903       return -1;
904   }
905
906   if (ctx->magic == M_IMAP)
907     rc = imap_sync_mailbox (ctx, purge, index_hint);
908   else
909     rc = sync_mailbox (ctx, index_hint);
910   if (rc == 0) {
911     if (ctx->magic == M_IMAP && !purge)
912       mutt_message (_("Mailbox checkpointed."));
913     else
914       mutt_message (_("%d kept, %d deleted."), msgcount - deleted, deleted);
915
916     mutt_sleep (0);
917
918     /* if we haven't deleted any messages, we don't need to resort */
919     /* ... except for certain folder formats which need "unsorted" 
920      * sort order in order to synchronize folders.
921      * 
922      * MH and maildir are safe.  mbox-style seems to need re-sorting,
923      * at least with the new threading code.
924      */
925     if (purge || (ctx->magic != M_MAILDIR && ctx->magic != M_MH)) {
926       /* IMAP does this automatically after handling EXPUNGE */
927       if (ctx->magic != M_IMAP)
928       {
929         mx_update_tables (ctx, 1);
930         mutt_sort_headers (ctx, 1);     /* rethread from scratch */
931       }
932     }
933   }
934
935   return (rc);
936 }
937
938 int mx_sync_mailbox (CONTEXT* ctx, int* index_hint) {
939   int ret = _mx_sync_mailbox (ctx, index_hint);
940   sidebar_set_buffystats (ctx);
941   return (ret);
942 }
943
944 /* args:
945  *      dest    destintation mailbox
946  *      hdr     message being copied (required for maildir support, because
947  *              the filename depends on the message flags)
948  */
949 MESSAGE *mx_open_new_message (CONTEXT * dest, HEADER * hdr, int flags)
950 {
951   MESSAGE *msg;
952   address_t *p = NULL;
953
954   if (!MX_IDX(dest->magic-1)) {
955     return (NULL);
956   }
957
958   msg = p_new(MESSAGE, 1);
959   msg->magic = dest->magic;
960   msg->write = 1;
961
962   if (hdr) {
963     msg->flags.flagged = hdr->flagged;
964     msg->flags.replied = hdr->replied;
965     msg->flags.read = hdr->read;
966     msg->received = hdr->received;
967   }
968
969   if (msg->received == 0)
970     time (&msg->received);
971
972   if (mxfmts[dest->magic-1]->mx_open_new_message(msg, dest, hdr) == 0) {
973     if (msg->magic == M_MBOX && flags & M_ADD_FROM) {
974       if (hdr) {
975         if (hdr->env->return_path)
976           p = hdr->env->return_path;
977         else if (hdr->env->sender)
978           p = hdr->env->sender;
979         else
980           p = hdr->env->from;
981       }
982
983       fprintf (msg->fp, "From %s %s", p ? p->mailbox : NONULL(mod_core.username),
984                ctime (&msg->received));
985     }
986   }
987   else
988     p_delete(&msg);
989
990   return msg;
991 }
992
993 /* check for new mail */
994 int mx_check_mailbox (CONTEXT * ctx, int *index_hint, int lock) {
995   if (ctx->cinfo)
996     return mutt_check_mailbox_compressed (ctx);
997
998   if (ctx) {
999     if (ctx->locked)
1000       lock = 0;
1001     if (MX_IDX(ctx->magic-1) && mxfmts[ctx->magic-1]->mx_check_mailbox)
1002       return (mxfmts[ctx->magic-1]->mx_check_mailbox(ctx, index_hint, lock));
1003   }
1004
1005   return (-1);
1006 }
1007
1008 /* return a stream pointer for a message */
1009 MESSAGE *mx_open_message (CONTEXT * ctx, int msgno)
1010 {
1011   MESSAGE *msg;
1012
1013   msg = p_new(MESSAGE, 1);
1014   switch (msg->magic = ctx->magic) {
1015   case M_MBOX:
1016     msg->fp = ctx->fp;
1017     break;
1018
1019   case M_MH:
1020   case M_MAILDIR:
1021     {
1022       HEADER *cur = ctx->hdrs[msgno];
1023       char path[_POSIX_PATH_MAX];
1024
1025       snprintf (path, sizeof (path), "%s/%s", ctx->path, cur->path);
1026
1027       if ((msg->fp = fopen (path, "r")) == NULL && errno == ENOENT &&
1028           ctx->magic == M_MAILDIR)
1029         msg->fp = maildir_open_find_message (ctx->path, cur->path);
1030
1031       if (msg->fp == NULL) {
1032         mutt_perror (path);
1033         p_delete(&msg);
1034       }
1035     }
1036     break;
1037
1038   case M_IMAP:
1039     {
1040       if (imap_fetch_message (msg, ctx, msgno) != 0)
1041         p_delete(&msg);
1042       break;
1043     }
1044
1045   case M_POP:
1046     {
1047       if (pop_fetch_message (msg, ctx, msgno) != 0)
1048         p_delete(&msg);
1049       break;
1050     }
1051
1052   default:
1053     p_delete(&msg);
1054     break;
1055   }
1056   return (msg);
1057 }
1058
1059 /* commit a message to a folder */
1060
1061 int mx_commit_message (MESSAGE * msg, CONTEXT * ctx) {
1062   if (!(msg->write && ctx->append)) {
1063     return -1;
1064   }
1065   if (!ctx || !MX_IDX(ctx->magic-1) || !mxfmts[ctx->magic-1]->mx_commit_message)
1066     return (-1);
1067   return (mxfmts[ctx->magic-1]->mx_commit_message (msg, ctx));
1068 }
1069
1070 /* close a pointer to a message */
1071 int mx_close_message (MESSAGE ** msg)
1072 {
1073   int r = 0;
1074
1075   if ((*msg)->magic == M_MH || (*msg)->magic == M_MAILDIR
1076   ||  (*msg)->magic == M_IMAP || (*msg)->magic == M_POP)
1077   {
1078     r = m_fclose(&(*msg)->fp);
1079   }
1080   else
1081     (*msg)->fp = NULL;
1082
1083   if ((*msg)->path) {
1084     unlink ((*msg)->path);
1085     p_delete(&(*msg)->path);
1086   }
1087
1088   p_delete(msg);
1089   return (r);
1090 }
1091
1092 void mx_alloc_memory (CONTEXT * ctx)
1093 {
1094     ctx->hdrmax += 32;
1095
1096     p_realloc(&ctx->hdrs, ctx->hdrmax);
1097     p_realloc(&ctx->v2r, ctx->hdrmax);
1098     p_clear(ctx->hdrs + ctx->msgcount, ctx->hdrmax - ctx->msgcount);
1099     p_clear(ctx->v2r + ctx->msgcount, ctx->hdrmax - ctx->msgcount);
1100 }
1101
1102 /* this routine is called to update the counts in the context structure for
1103  * the last message header parsed.
1104  */
1105 void mx_update_context (CONTEXT * ctx, int new_messages)
1106 {
1107   HEADER *h;
1108   int msgno;
1109
1110   for (msgno = ctx->msgcount - new_messages; msgno < ctx->msgcount; msgno++) {
1111     h = ctx->hdrs[msgno];
1112
1113     /* NOTE: this _must_ be done before the check for mailcap! */
1114     h->security = crypt_query (h->content);
1115
1116     if (!ctx->pattern) {
1117       ctx->v2r[ctx->vcount] = msgno;
1118       h->virtual = ctx->vcount++;
1119     }
1120     else
1121       h->virtual = -1;
1122     h->msgno = msgno;
1123
1124     if (h->env->supersedes) {
1125       HEADER *h2;
1126
1127       if (!ctx->id_hash)
1128         ctx->id_hash = mutt_make_id_hash (ctx);
1129
1130       h2 = hash_find (ctx->id_hash, h->env->supersedes);
1131
1132       /* p_delete(&h->env->supersedes); should I ? */
1133       if (h2) {
1134         h2->superseded = 1;
1135         if (!ctx->counting && mod_score.enable)
1136           mutt_score_message (ctx, h2, 1);
1137       }
1138     }
1139
1140     /* add this message to the hash tables */
1141     if (ctx->id_hash && h->env->message_id)
1142       hash_insert (ctx->id_hash, h->env->message_id, h);
1143     if (!ctx->counting) {
1144       if (ctx->subj_hash && h->env->real_subj)
1145         hash_insert (ctx->subj_hash, h->env->real_subj, h);
1146
1147       if (mod_score.enable)
1148         mutt_score_message (ctx, h, 0);
1149     }
1150
1151     if (h->changed)
1152       ctx->changed = 1;
1153     if (h->flagged)
1154       ctx->flagged++;
1155     if (h->deleted)
1156       ctx->deleted++;
1157     if (!h->read) {
1158       ctx->unread++;
1159       if (!h->old)
1160         ctx->new++;
1161     }
1162   }
1163   /* update sidebar count */
1164   sidebar_set_buffystats (ctx);
1165 }
1166
1167 /*
1168  * Return:
1169  * 1 if the specified mailbox contains 0 messages.
1170  * 0 if the mailbox contains messages
1171  * -1 on error
1172  */
1173 int mx_check_empty (const char *path)
1174 {
1175   int i = 0;
1176   if ((i = mx_get_idx (path)) >= 0 && mxfmts[i]->mx_check_empty)
1177     return (mxfmts[i]->mx_check_empty(path));
1178   errno = EINVAL;
1179   return (-1);
1180 }
1181
1182 int mx_acl_check(CONTEXT *ctx, int flag)
1183 {
1184     if (!mxfmts[ctx->magic-1]->mx_acl_check)
1185         return 1;
1186     return mxfmts[ctx->magic-1]->mx_acl_check(ctx,flag);
1187 }
1188
1189 void mutt_parse_mime_message (CONTEXT * ctx, HEADER * cur)
1190 {
1191   MESSAGE *msg;
1192   int flags = 0;
1193
1194   do {
1195     if (cur->content->type != TYPEMESSAGE
1196         && cur->content->type != TYPEMULTIPART)
1197       break;                     /* nothing to do */
1198
1199     if (cur->content->parts)
1200       break;                     /* The message was parsed earlier. */
1201
1202     if ((msg = mx_open_message (ctx, cur->msgno))) {
1203       mutt_parse_part (msg->fp, cur->content);
1204
1205       cur->security = crypt_query (cur->content);
1206
1207       mx_close_message (&msg);
1208     }
1209   } while (0);
1210   mutt_count_body_parts (cur, flags | M_PARTS_RECOUNT);
1211 }