mutt stable branch with some hacks
1/*
2 * Copyright (C) 1996-2002,2010,2013 Michael R. Elkins <me@mutt.org>
3 * Copyright (C) 1999-2003 Thomas Roessler <roessler@does-not-exist.org>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 */
19
20#if HAVE_CONFIG_H
21# include "config.h"
22#endif
23
24#include "mutt.h"
25#include "mx.h"
26#include "rfc2047.h"
27#include "sort.h"
28#include "mailbox.h"
29#include "copy.h"
30#include "keymap.h"
31#include "url.h"
32#ifdef USE_SIDEBAR
33#include "sidebar.h"
34#endif
35
36#ifdef USE_COMPRESSED
37#include "compress.h"
38#endif
39
40#ifdef USE_IMAP
41#include "imap.h"
42#endif
43
44#ifdef USE_POP
45#include "pop.h"
46#endif
47
48#include "buffy.h"
49
50#ifdef USE_DOTLOCK
51#include "dotlock.h"
52#endif
53
54#include "mutt_crypt.h"
55
56#include <dirent.h>
57#include <fcntl.h>
58#include <sys/file.h>
59#include <sys/stat.h>
60#include <errno.h>
61#include <unistd.h>
62#include <stdlib.h>
63#include <string.h>
64#include <ctype.h>
65#include <utime.h>
66
67struct mx_ops* mx_get_ops (int magic)
68{
69 switch (magic)
70 {
71#ifdef USE_IMAP
72 case MUTT_IMAP:
73 return &mx_imap_ops;
74#endif
75 case MUTT_MAILDIR:
76 return &mx_maildir_ops;
77 case MUTT_MBOX:
78 return &mx_mbox_ops;
79 case MUTT_MH:
80 return &mx_mh_ops;
81 case MUTT_MMDF:
82 return &mx_mmdf_ops;
83#ifdef USE_POP
84 case MUTT_POP:
85 return &mx_pop_ops;
86#endif
87#ifdef USE_COMPRESSED
88 case MUTT_COMPRESSED:
89 return &mx_comp_ops;
90#endif
91 default:
92 return NULL;
93 }
94}
95
96#define mutt_is_spool(s) (mutt_strcmp (Spoolfile, s) == 0)
97
98#ifdef USE_DOTLOCK
99/* parameters:
100 * path - file to lock
101 * retry - should retry if unable to lock?
102 */
103
104#ifdef DL_STANDALONE
105
106static int invoke_dotlock (const char *path, int dummy, int flags, int retry)
107{
108 BUFFER *cmd = NULL;
109 BUFFER *f = NULL;
110 char r[SHORT_STRING];
111 int rc;
112
113 cmd = mutt_buffer_pool_get ();
114 f = mutt_buffer_pool_get ();
115
116 if (flags & DL_FL_RETRY)
117 snprintf (r, sizeof (r), "-r %d ", retry ? MAXLOCKATTEMPT : 0);
118
119 mutt_buffer_quote_filename (f, path);
120
121 mutt_buffer_printf (cmd,
122 "%s %s%s%s%s%s%s%s",
123 NONULL (MuttDotlock),
124 flags & DL_FL_TRY ? "-t " : "",
125 flags & DL_FL_UNLOCK ? "-u " : "",
126 flags & DL_FL_USEPRIV ? "-p " : "",
127 flags & DL_FL_FORCE ? "-f " : "",
128 flags & DL_FL_UNLINK ? "-d " : "",
129 flags & DL_FL_RETRY ? r : "",
130 mutt_b2s (f));
131
132 rc = mutt_system (mutt_b2s (cmd));
133
134 mutt_buffer_pool_release (&cmd);
135 mutt_buffer_pool_release (&f);
136
137 return rc;
138}
139
140#else
141
142#define invoke_dotlock dotlock_invoke
143
144#endif
145
146static int dotlock_file (const char *path, int fd, int retry)
147{
148 int r;
149 int flags = DL_FL_USEPRIV | DL_FL_RETRY;
150
151 if (retry) retry = 1;
152
153retry_lock:
154 if ((r = invoke_dotlock(path, fd, flags, retry)) == DL_EX_EXIST)
155 {
156 if (!option (OPTNOCURSES))
157 {
158 char msg[LONG_STRING];
159
160 snprintf(msg, sizeof(msg), _("Lock count exceeded, remove lock for %s?"),
161 path);
162 if (retry && mutt_yesorno(msg, MUTT_YES) == MUTT_YES)
163 {
164 flags |= DL_FL_FORCE;
165 retry--;
166 mutt_clear_error ();
167 goto retry_lock;
168 }
169 }
170 else
171 {
172 mutt_error ( _("Can't dotlock %s.\n"), path);
173 }
174 }
175 return (r == DL_EX_OK ? 0 : -1);
176}
177
178static int undotlock_file (const char *path, int fd)
179{
180 return (invoke_dotlock(path, fd, DL_FL_USEPRIV | DL_FL_UNLOCK, 0) == DL_EX_OK ?
181 0 : -1);
182}
183
184#endif /* USE_DOTLOCK */
185
186/* Args:
187 * excl if excl != 0, request an exclusive lock
188 * dot if dot != 0, try to dotlock the file
189 * timeout should retry locking?
190 */
191int mx_lock_file (const char *path, int fd, int excl, int dot, int timeout)
192{
193#if defined (USE_FCNTL) || defined (USE_FLOCK)
194 int count;
195 int attempt;
196 struct stat sb = { 0 }, prev_sb = { 0 }; /* silence gcc warnings */
197#endif
198 int r = 0;
199
200#ifdef USE_FCNTL
201 struct flock lck;
202
203 memset (&lck, 0, sizeof (struct flock));
204 lck.l_type = excl ? F_WRLCK : F_RDLCK;
205 lck.l_whence = SEEK_SET;
206
207 count = 0;
208 attempt = 0;
209 while (fcntl (fd, F_SETLK, &lck) == -1)
210 {
211 dprint(1,(debugfile, "mx_lock_file(): fcntl errno %d.\n", errno));
212 if (errno != EAGAIN && errno != EACCES)
213 {
214 mutt_perror ("fcntl");
215 return -1;
216 }
217
218 if (fstat (fd, &sb) != 0)
219 sb.st_size = 0;
220
221 if (count == 0)
222 prev_sb = sb;
223
224 /* only unlock file if it is unchanged */
225 if (prev_sb.st_size == sb.st_size && ++count >= (timeout?MAXLOCKATTEMPT:0))
226 {
227 if (timeout)
228 mutt_error _("Timeout exceeded while attempting fcntl lock!");
229 return -1;
230 }
231
232 prev_sb = sb;
233
234 mutt_message (_("Waiting for fcntl lock... %d"), ++attempt);
235 sleep (1);
236 }
237#endif /* USE_FCNTL */
238
239#ifdef USE_FLOCK
240 count = 0;
241 attempt = 0;
242 while (flock (fd, (excl ? LOCK_EX : LOCK_SH) | LOCK_NB) == -1)
243 {
244 if (errno != EWOULDBLOCK)
245 {
246 mutt_perror ("flock");
247 r = -1;
248 break;
249 }
250
251 if (fstat(fd, &sb) != 0)
252 sb.st_size = 0;
253
254 if (count == 0)
255 prev_sb = sb;
256
257 /* only unlock file if it is unchanged */
258 if (prev_sb.st_size == sb.st_size && ++count >= (timeout?MAXLOCKATTEMPT:0))
259 {
260 if (timeout)
261 mutt_error _("Timeout exceeded while attempting flock lock!");
262 r = -1;
263 break;
264 }
265
266 prev_sb = sb;
267
268 mutt_message (_("Waiting for flock attempt... %d"), ++attempt);
269 sleep (1);
270 }
271#endif /* USE_FLOCK */
272
273#ifdef USE_DOTLOCK
274 if (r == 0 && dot)
275 r = dotlock_file (path, fd, timeout);
276#endif /* USE_DOTLOCK */
277
278 if (r != 0)
279 {
280 /* release any other locks obtained in this routine */
281
282#ifdef USE_FCNTL
283 lck.l_type = F_UNLCK;
284 fcntl (fd, F_SETLK, &lck);
285#endif /* USE_FCNTL */
286
287#ifdef USE_FLOCK
288 flock (fd, LOCK_UN);
289#endif /* USE_FLOCK */
290 }
291
292 return r;
293}
294
295int mx_unlock_file (const char *path, int fd, int dot)
296{
297#ifdef USE_FCNTL
298 struct flock unlockit = { F_UNLCK, 0, 0, 0, 0 };
299
300 memset (&unlockit, 0, sizeof (struct flock));
301 unlockit.l_type = F_UNLCK;
302 unlockit.l_whence = SEEK_SET;
303 fcntl (fd, F_SETLK, &unlockit);
304#endif
305
306#ifdef USE_FLOCK
307 flock (fd, LOCK_UN);
308#endif
309
310#ifdef USE_DOTLOCK
311 if (dot)
312 undotlock_file (path, fd);
313#endif
314
315 return 0;
316}
317
318static void mx_unlink_empty (const char *path)
319{
320 int fd;
321#ifndef USE_DOTLOCK
322 struct stat sb;
323#endif
324
325 if ((fd = open (path, O_RDWR)) == -1)
326 return;
327
328 if (mx_lock_file (path, fd, 1, 0, 1) == -1)
329 {
330 close (fd);
331 return;
332 }
333
334#ifdef USE_DOTLOCK
335 invoke_dotlock (path, fd, DL_FL_UNLINK, 1);
336#else
337 if (fstat (fd, &sb) == 0 && sb.st_size == 0)
338 unlink (path);
339#endif
340
341 mx_unlock_file (path, fd, 0);
342 close (fd);
343}
344
345/* try to figure out what type of mailbox ``path'' is
346 *
347 * return values:
348 * M_* mailbox type
349 * 0 not a mailbox
350 * -1 error
351 */
352
353#ifdef USE_IMAP
354
355int mx_is_imap(const char *p)
356{
357 url_scheme_t scheme;
358
359 if (!p)
360 return 0;
361
362 if (*p == '{')
363 return 1;
364
365 scheme = url_check_scheme (p);
366 if (scheme == U_IMAP || scheme == U_IMAPS)
367 return 1;
368
369 return 0;
370}
371
372#endif
373
374#ifdef USE_POP
375int mx_is_pop (const char *p)
376{
377 url_scheme_t scheme;
378
379 if (!p)
380 return 0;
381
382 scheme = url_check_scheme (p);
383 if (scheme == U_POP || scheme == U_POPS)
384 return 1;
385
386 return 0;
387}
388#endif
389
390int mx_get_magic (const char *path)
391{
392 struct stat st;
393 int magic = 0;
394 FILE *f;
395
396#ifdef USE_IMAP
397 if (mx_is_imap(path))
398 return MUTT_IMAP;
399#endif /* USE_IMAP */
400
401#ifdef USE_POP
402 if (mx_is_pop (path))
403 return MUTT_POP;
404#endif /* USE_POP */
405
406 if (stat (path, &st) == -1)
407 {
408 dprint (1, (debugfile, "mx_get_magic(): unable to stat %s: %s (errno %d).\n",
409 path, strerror (errno), errno));
410 return (-1);
411 }
412
413 if (S_ISDIR (st.st_mode))
414 {
415 /* check for maildir-style mailbox */
416 if (mx_is_maildir (path))
417 return MUTT_MAILDIR;
418
419 /* check for mh-style mailbox */
420 if (mx_is_mh (path))
421 return MUTT_MH;
422 }
423 else if (st.st_size == 0)
424 {
425 /* hard to tell what zero-length files are, so assume the default magic */
426 if (DefaultMagic == MUTT_MBOX || DefaultMagic == MUTT_MMDF)
427 return (DefaultMagic);
428 else
429 return (MUTT_MBOX);
430 }
431 else if ((f = fopen (path, "r")) != NULL)
432 {
433#ifdef HAVE_UTIMENSAT
434 struct timespec ts[2];
435#else
436 struct utimbuf times;
437#endif /* HAVE_UTIMENSAT */
438 int ch;
439 char tmp[10];
440
441 /* Some mailbox creation tools erroneously append a blank line to
442 * a file before appending a mail message. This allows mutt to
443 * detect magic for and thus open those files. */
444 while ((ch = fgetc (f)) != EOF)
445 {
446 if (ch != '\n' && ch != '\r')
447 {
448 ungetc (ch, f);
449 break;
450 }
451 }
452
453 if (fgets (tmp, sizeof (tmp), f))
454 {
455 if (mutt_strncmp ("From ", tmp, 5) == 0)
456 magic = MUTT_MBOX;
457 else if (mutt_strcmp (MMDF_SEP, tmp) == 0)
458 magic = MUTT_MMDF;
459 }
460 safe_fclose (&f);
461
462 if (!option(OPTCHECKMBOXSIZE))
463 {
464 /* need to restore the times here, the file was not really accessed,
465 * only the type was accessed. This is important, because detection
466 * of "new mail" depends on those times set correctly.
467 */
468#ifdef HAVE_UTIMENSAT
469 mutt_get_stat_timespec (&ts[0], &st, MUTT_STAT_ATIME);
470 mutt_get_stat_timespec (&ts[1], &st, MUTT_STAT_MTIME);
471 utimensat (0, path, ts, 0);
472#else
473 times.actime = st.st_atime;
474 times.modtime = st.st_mtime;
475 utime (path, ×);
476#endif
477 }
478 }
479 else
480 {
481 dprint (1, (debugfile, "mx_get_magic(): unable to open file %s for reading.\n",
482 path));
483 return (-1);
484 }
485
486#ifdef USE_COMPRESSED
487 /* If there are no other matches, see if there are any
488 * compress hooks that match */
489 if ((magic == 0) && mutt_comp_can_read (path))
490 return MUTT_COMPRESSED;
491#endif
492 return (magic);
493}
494
495/*
496 * set DefaultMagic to the given value
497 */
498int mx_set_magic (const char *s)
499{
500 if (ascii_strcasecmp (s, "mbox") == 0)
501 DefaultMagic = MUTT_MBOX;
502 else if (ascii_strcasecmp (s, "mmdf") == 0)
503 DefaultMagic = MUTT_MMDF;
504 else if (ascii_strcasecmp (s, "mh") == 0)
505 DefaultMagic = MUTT_MH;
506 else if (ascii_strcasecmp (s, "maildir") == 0)
507 DefaultMagic = MUTT_MAILDIR;
508 else
509 return (-1);
510
511 return 0;
512}
513
514/* mx_access: Wrapper for access, checks permissions on a given mailbox.
515 * We may be interested in using ACL-style flags at some point, currently
516 * we use the normal access() flags. */
517int mx_access (const char* path, int flags)
518{
519#ifdef USE_IMAP
520 if (mx_is_imap (path))
521 return imap_access (path);
522#endif
523
524 return access (path, flags);
525}
526
527static int mx_open_mailbox_append (CONTEXT *ctx, int flags)
528{
529 struct stat sb;
530
531 ctx->append = 1;
532 ctx->magic = mx_get_magic (ctx->path);
533 if (ctx->magic == 0)
534 {
535 mutt_error (_("%s is not a mailbox."), ctx->path);
536 return -1;
537 }
538
539 if (ctx->magic < 0)
540 {
541 if (stat (ctx->path, &sb) == -1)
542 {
543 if (errno == ENOENT)
544 {
545#ifdef USE_COMPRESSED
546 if (mutt_comp_can_append (ctx))
547 ctx->magic = MUTT_COMPRESSED;
548 else
549#endif
550 ctx->magic = DefaultMagic;
551 flags |= MUTT_APPENDNEW;
552 }
553 else
554 {
555 mutt_perror (ctx->path);
556 return -1;
557 }
558 }
559 else
560 return -1;
561 }
562
563 ctx->mx_ops = mx_get_ops (ctx->magic);
564 if (!ctx->mx_ops || !ctx->mx_ops->open_append)
565 return -1;
566
567 return ctx->mx_ops->open_append (ctx, flags);
568}
569
570/*
571 * open a mailbox and parse it
572 *
573 * Args:
574 * flags MUTT_NOSORT do not sort mailbox
575 * MUTT_APPEND open mailbox for appending
576 * MUTT_READONLY open mailbox in read-only mode
577 * MUTT_QUIET only print error messages
578 * MUTT_PEEK revert atime where applicable
579 * ctx if non-null, context struct to use
580 */
581CONTEXT *mx_open_mailbox (const char *path, int flags, CONTEXT *pctx)
582{
583 CONTEXT *ctx = pctx;
584 int rc;
585 char realpathbuf[PATH_MAX];
586
587 if (!ctx)
588 ctx = safe_malloc (sizeof (CONTEXT));
589 memset (ctx, 0, sizeof (CONTEXT));
590
591 ctx->path = safe_strdup (path);
592 if (!ctx->path)
593 {
594 if (!pctx)
595 FREE (&ctx);
596 return NULL;
597 }
598 if (! realpath (ctx->path, realpathbuf) )
599 ctx->realpath = safe_strdup (ctx->path);
600 else
601 ctx->realpath = safe_strdup (realpathbuf);
602
603 ctx->msgnotreadyet = -1;
604 ctx->collapsed = 0;
605
606 for (rc=0; rc < RIGHTSMAX; rc++)
607 mutt_bit_set(ctx->rights,rc);
608
609 if (flags & MUTT_QUIET)
610 ctx->quiet = 1;
611 if (flags & MUTT_READONLY)
612 ctx->readonly = 1;
613 if (flags & MUTT_PEEK)
614 ctx->peekonly = 1;
615
616 if (flags & (MUTT_APPEND|MUTT_NEWFOLDER))
617 {
618 if (mx_open_mailbox_append (ctx, flags) != 0)
619 {
620 mx_fastclose_mailbox (ctx);
621 if (!pctx)
622 FREE (&ctx);
623 return NULL;
624 }
625 return ctx;
626 }
627
628 ctx->magic = mx_get_magic (path);
629 ctx->mx_ops = mx_get_ops (ctx->magic);
630
631 if (ctx->magic <= 0 || !ctx->mx_ops)
632 {
633 if (ctx->magic == -1)
634 mutt_perror(path);
635 else if (ctx->magic == 0 || !ctx->mx_ops)
636 mutt_error (_("%s is not a mailbox."), path);
637
638 mx_fastclose_mailbox (ctx);
639 if (!pctx)
640 FREE (&ctx);
641 return (NULL);
642 }
643
644 mutt_make_label_hash (ctx);
645
646 /* if the user has a `push' command in their .muttrc, or in a folder-hook,
647 * it will cause the progress messages not to be displayed because
648 * mutt_refresh() will think we are in the middle of a macro. so set a
649 * flag to indicate that we should really refresh the screen.
650 */
651 set_option (OPTFORCEREFRESH);
652
653 if (!ctx->quiet)
654 mutt_message (_("Reading %s..."), ctx->path);
655
656 rc = ctx->mx_ops->open(ctx);
657
658 if (rc == 0)
659 {
660 if ((flags & MUTT_NOSORT) == 0)
661 {
662 /* avoid unnecessary work since the mailbox is completely unthreaded
663 to begin with */
664 unset_option (OPTSORTSUBTHREADS);
665 unset_option (OPTNEEDRESCORE);
666 mutt_sort_headers (ctx, 1);
667 }
668 if (!ctx->quiet)
669 mutt_clear_error ();
670 }
671 else
672 {
673 mx_fastclose_mailbox (ctx);
674 if (!pctx)
675 FREE (&ctx);
676 }
677
678 unset_option (OPTFORCEREFRESH);
679 return (ctx);
680}
681
682/* free up memory associated with the mailbox context */
683void mx_fastclose_mailbox (CONTEXT *ctx)
684{
685 int i;
686#ifdef HAVE_UTIMENSAT
687 struct timespec ts[2];
688#else
689 struct utimbuf ut;
690#endif /* HAVE_UTIMENSAT */
691
692 if (!ctx)
693 return;
694
695 /* fix up the times so buffy won't get confused */
696 if (ctx->peekonly && ctx->path &&
697 (mutt_timespec_compare (&ctx->mtime, &ctx->atime) > 0))
698 {
699#ifdef HAVE_UTIMENSAT
700 ts[0] = ctx->atime;
701 ts[1] = ctx->mtime;
702 utimensat (0, ctx->path, ts, 0);
703#else
704 ut.actime = ctx->atime.tv_sec;
705 ut.modtime = ctx->mtime.tv_sec;
706 utime (ctx->path, &ut);
707#endif /* HAVE_UTIMENSAT */
708 }
709
710 /* never announce that a mailbox we've just left has new mail. #3290
711 * XXX: really belongs in mx_close_mailbox, but this is a nice hook point */
712 if (!ctx->peekonly)
713 mutt_buffy_setnotified(ctx->path);
714
715 if (ctx->mx_ops)
716 ctx->mx_ops->close (ctx);
717
718#ifdef USE_COMPRESSED
719 mutt_free_compress_info (ctx);
720#endif /* USE_COMPRESSED */
721
722 if (ctx->subj_hash)
723 hash_destroy (&ctx->subj_hash, NULL);
724 if (ctx->id_hash)
725 hash_destroy (&ctx->id_hash, NULL);
726 hash_destroy (&ctx->label_hash, NULL);
727 mutt_clear_threads (ctx);
728 for (i = 0; i < ctx->msgcount; i++)
729 mutt_free_header (&ctx->hdrs[i]);
730 FREE (&ctx->hdrs);
731 FREE (&ctx->v2r);
732 FREE (&ctx->path);
733 FREE (&ctx->realpath);
734 FREE (&ctx->pattern);
735 if (ctx->limit_pattern)
736 mutt_pattern_free (&ctx->limit_pattern);
737 safe_fclose (&ctx->fp);
738 memset (ctx, 0, sizeof (CONTEXT));
739}
740
741/* save changes to disk */
742static int sync_mailbox (CONTEXT *ctx, int *index_hint)
743{
744 int rc;
745
746 if (!ctx->mx_ops || !ctx->mx_ops->sync)
747 return -1;
748
749 if (!ctx->quiet)
750 {
751 /* L10N: Displayed before/as a mailbox is being synced */
752 mutt_message (_("Writing %s..."), ctx->path);
753 }
754
755 rc = ctx->mx_ops->sync (ctx, index_hint);
756 if (rc != 0 && !ctx->quiet)
757 {
758 /* L10N: Displayed if a mailbox sync fails */
759 mutt_error (_("Unable to write %s!"), ctx->path);
760 }
761
762 return rc;
763}
764
765/* move deleted mails to the trash folder */
766static int trash_append (CONTEXT *ctx)
767{
768 CONTEXT ctx_trash;
769 int i;
770 struct stat st, stc;
771 int opt_confappend, rc;
772
773 if (!TrashPath || !ctx->deleted ||
774 (ctx->magic == MUTT_MAILDIR && option (OPTMAILDIRTRASH)))
775 return 0;
776
777 for (i = 0; i < ctx->msgcount; i++)
778 if (ctx->hdrs[i]->deleted && (!ctx->hdrs[i]->purge))
779 break;
780 if (i == ctx->msgcount)
781 return 0; /* nothing to be done */
782
783 /* avoid the "append messages" prompt */
784 opt_confappend = option (OPTCONFIRMAPPEND);
785 if (opt_confappend)
786 unset_option (OPTCONFIRMAPPEND);
787 rc = mutt_save_confirm (TrashPath, &st);
788 if (opt_confappend)
789 set_option (OPTCONFIRMAPPEND);
790 if (rc != 0)
791 {
792 mutt_error _("message(s) not deleted");
793 return -1;
794 }
795
796 if (lstat (ctx->path, &stc) == 0 && stc.st_ino == st.st_ino
797 && stc.st_dev == st.st_dev && stc.st_rdev == st.st_rdev)
798 return 0; /* we are in the trash folder: simple sync */
799
800#ifdef USE_IMAP
801 if (Context->magic == MUTT_IMAP && mx_is_imap (TrashPath))
802 {
803 if (!imap_fast_trash (Context, TrashPath))
804 return 0;
805 }
806#endif
807
808 if (mx_open_mailbox (TrashPath, MUTT_APPEND, &ctx_trash) != NULL)
809 {
810 /* continue from initial scan above */
811 for (; i < ctx->msgcount ; i++)
812 if (ctx->hdrs[i]->deleted && (!ctx->hdrs[i]->purge))
813 {
814 if (mutt_append_message (&ctx_trash, ctx, ctx->hdrs[i], 0, 0) == -1)
815 {
816 mx_close_mailbox (&ctx_trash, NULL);
817 return -1;
818 }
819 }
820
821 mx_close_mailbox (&ctx_trash, NULL);
822 }
823 else
824 {
825 mutt_error _("Can't open trash folder");
826 return -1;
827 }
828
829 return 0;
830}
831
832/* save changes and close mailbox.
833 *
834 * returns 0 on success.
835 * -1 on error
836 * one of the check_mailbox enums if aborted for one of those reasons.
837 *
838 * Note: it's very important to ensure the mailbox is properly closed
839 * before free'ing the context. For selected mailboxes, IMAP
840 * will cache the context inside connection->idata until
841 * imap_close_mailbox() removes it.
842 *
843 * Readonly, dontwrite, and append mailboxes are guaranteed to call
844 * mx_fastclose_mailbox(), so for most of Mutt's code you won't see
845 * return value checks for temporary contexts.
846 */
847int mx_close_mailbox (CONTEXT *ctx, int *index_hint)
848{
849 int i, move_messages = 0, purge = 1, read_msgs = 0;
850 int rc = -1;
851 int check;
852 int isSpool = 0;
853 CONTEXT f;
854 BUFFER *mbox = NULL;
855 char buf[SHORT_STRING];
856
857 if (!ctx) return 0;
858
859 ctx->closing = 1;
860
861 if (ctx->readonly || ctx->dontwrite || ctx->append)
862 {
863 mx_fastclose_mailbox (ctx);
864 return 0;
865 }
866
867 for (i = 0; i < ctx->msgcount; i++)
868 {
869 if (!ctx->hdrs[i]->deleted && ctx->hdrs[i]->read
870 && !(ctx->hdrs[i]->flagged && option (OPTKEEPFLAGGED)))
871 read_msgs++;
872 }
873
874 if (read_msgs && quadoption (OPT_MOVE) != MUTT_NO)
875 {
876 char *p;
877
878 mbox = mutt_buffer_pool_get ();
879
880 if ((p = mutt_find_hook (MUTT_MBOXHOOK, ctx->path)))
881 {
882 isSpool = 1;
883 mutt_buffer_strcpy (mbox, p);
884 }
885 else
886 {
887 mutt_buffer_strcpy (mbox, NONULL(Inbox));
888 isSpool = mutt_is_spool (ctx->path) && !mutt_is_spool (mutt_b2s (mbox));
889 }
890
891 if (isSpool && mutt_buffer_len (mbox))
892 {
893 mutt_buffer_expand_path (mbox);
894 snprintf (buf, sizeof (buf), _("Move %d read messages to %s?"),
895 read_msgs, mutt_b2s (mbox));
896 if ((move_messages = query_quadoption (OPT_MOVE, buf)) == -1)
897 {
898 ctx->closing = 0;
899 goto cleanup;
900 }
901 }
902 }
903
904 /*
905 * There is no point in asking whether or not to purge if we are
906 * just marking messages as "trash".
907 */
908 if (ctx->deleted && !(ctx->magic == MUTT_MAILDIR && option (OPTMAILDIRTRASH)))
909 {
910 snprintf (buf, sizeof (buf), ctx->deleted == 1
911 ? _("Purge %d deleted message?") : _("Purge %d deleted messages?"),
912 ctx->deleted);
913 if ((purge = query_quadoption (OPT_DELETE, buf)) < 0)
914 {
915 ctx->closing = 0;
916 goto cleanup;
917 }
918 }
919
920 if (option (OPTMARKOLD))
921 {
922 for (i = 0; i < ctx->msgcount; i++)
923 {
924 if (!ctx->hdrs[i]->deleted && !ctx->hdrs[i]->old && !ctx->hdrs[i]->read)
925 mutt_set_flag (ctx, ctx->hdrs[i], MUTT_OLD, 1);
926 }
927 }
928
929 if (move_messages)
930 {
931 if (!ctx->quiet)
932 mutt_message (_("Moving read messages to %s..."), mutt_b2s (mbox));
933
934#ifdef USE_IMAP
935 /* try to use server-side copy first */
936 i = 1;
937
938 if (ctx->magic == MUTT_IMAP && mx_is_imap (mutt_b2s (mbox)))
939 {
940 /* tag messages for moving, and clear old tags, if any */
941 for (i = 0; i < ctx->msgcount; i++)
942 if (ctx->hdrs[i]->read && !ctx->hdrs[i]->deleted
943 && !(ctx->hdrs[i]->flagged && option (OPTKEEPFLAGGED)))
944 ctx->hdrs[i]->tagged = 1;
945 else
946 ctx->hdrs[i]->tagged = 0;
947
948 i = imap_copy_messages (ctx, NULL, mutt_b2s (mbox), 1);
949 }
950
951 if (i == 0) /* success */
952 mutt_clear_error ();
953 else if (i == -1) /* horrible error, bail */
954 {
955 ctx->closing=0;
956 goto cleanup;
957 }
958 else /* use regular append-copy mode */
959#endif
960 {
961 if (mx_open_mailbox (mutt_b2s (mbox), MUTT_APPEND, &f) == NULL)
962 {
963 ctx->closing = 0;
964 goto cleanup;
965 }
966
967 for (i = 0; i < ctx->msgcount; i++)
968 {
969 if (ctx->hdrs[i]->read && !ctx->hdrs[i]->deleted
970 && !(ctx->hdrs[i]->flagged && option (OPTKEEPFLAGGED)))
971 {
972 if (mutt_append_message (&f, ctx, ctx->hdrs[i], 0, CH_UPDATE_LEN) == 0)
973 {
974 mutt_set_flag (ctx, ctx->hdrs[i], MUTT_DELETE, 1);
975 mutt_set_flag (ctx, ctx->hdrs[i], MUTT_PURGE, 1);
976 }
977 else
978 {
979 mx_close_mailbox (&f, NULL);
980 ctx->closing = 0;
981 goto cleanup;
982 }
983 }
984 }
985
986 mx_close_mailbox (&f, NULL);
987 }
988
989 }
990 else if (!ctx->changed && ctx->deleted == 0)
991 {
992 if (!ctx->quiet)
993 mutt_message _("Mailbox is unchanged.");
994 if (ctx->magic == MUTT_MBOX || ctx->magic == MUTT_MMDF)
995 mbox_reset_atime (ctx, NULL);
996 mx_fastclose_mailbox (ctx);
997 rc = 0;
998 goto cleanup;
999 }
1000
1001 /* copy mails to the trash before expunging */
1002 if (purge && ctx->deleted && mutt_strcmp (ctx->path, TrashPath))
1003 {
1004 if (trash_append (ctx) != 0)
1005 {
1006 ctx->closing = 0;
1007 goto cleanup;
1008 }
1009 }
1010
1011#ifdef USE_IMAP
1012 /* allow IMAP to preserve the deleted flag across sessions */
1013 if (ctx->magic == MUTT_IMAP)
1014 {
1015 if ((check = imap_sync_mailbox (ctx, purge, index_hint)) != 0)
1016 {
1017 ctx->closing = 0;
1018 rc = check;
1019 goto cleanup;
1020 }
1021 }
1022 else
1023#endif
1024 {
1025 if (!purge)
1026 {
1027 for (i = 0; i < ctx->msgcount; i++)
1028 {
1029 ctx->hdrs[i]->deleted = 0;
1030 ctx->hdrs[i]->purge = 0;
1031 }
1032 ctx->deleted = 0;
1033 }
1034
1035 if (ctx->changed || ctx->deleted)
1036 {
1037 if ((check = sync_mailbox (ctx, index_hint)) != 0)
1038 {
1039 ctx->closing = 0;
1040 rc = check;
1041 goto cleanup;
1042 }
1043 }
1044 }
1045
1046 if (!ctx->quiet)
1047 {
1048 if (move_messages)
1049 mutt_message (_("%d kept, %d moved, %d deleted."),
1050 ctx->msgcount - ctx->deleted, read_msgs, ctx->deleted);
1051 else
1052 mutt_message (_("%d kept, %d deleted."),
1053 ctx->msgcount - ctx->deleted, ctx->deleted);
1054 }
1055
1056 if (ctx->msgcount == ctx->deleted &&
1057 (ctx->magic == MUTT_MMDF || ctx->magic == MUTT_MBOX) &&
1058 !mutt_is_spool(ctx->path) && !option (OPTSAVEEMPTY))
1059 mx_unlink_empty (ctx->path);
1060
1061#ifdef USE_SIDEBAR
1062 if (purge && ctx->deleted)
1063 {
1064 int orig_msgcount = ctx->msgcount;
1065
1066 for (i = 0; i < ctx->msgcount; i++)
1067 {
1068 if (ctx->hdrs[i]->deleted && !ctx->hdrs[i]->read)
1069 ctx->unread--;
1070 if (ctx->hdrs[i]->deleted && ctx->hdrs[i]->flagged)
1071 ctx->flagged--;
1072 }
1073 ctx->msgcount -= ctx->deleted;
1074 mutt_sb_set_buffystats (ctx);
1075 ctx->msgcount = orig_msgcount;
1076 }
1077#endif
1078
1079 mx_fastclose_mailbox (ctx);
1080
1081 rc = 0;
1082
1083cleanup:
1084 mutt_buffer_pool_release (&mbox);
1085 return rc;
1086}
1087
1088
1089/* update a Context structure's internal tables. */
1090
1091void mx_update_tables(CONTEXT *ctx, int committing)
1092{
1093 int i, j, padding;
1094
1095 /* update memory to reflect the new state of the mailbox */
1096 ctx->vcount = 0;
1097 ctx->vsize = 0;
1098 ctx->tagged = 0;
1099 ctx->deleted = 0;
1100 ctx->new = 0;
1101 ctx->unread = 0;
1102 ctx->changed = 0;
1103 ctx->flagged = 0;
1104 padding = mx_msg_padding_size (ctx);
1105#define this_body ctx->hdrs[j]->content
1106 for (i = 0, j = 0; i < ctx->msgcount; i++)
1107 {
1108 if ((committing && (!ctx->hdrs[i]->deleted ||
1109 (ctx->magic == MUTT_MAILDIR && option (OPTMAILDIRTRASH)))) ||
1110 (!committing && ctx->hdrs[i]->active))
1111 {
1112 if (i != j)
1113 {
1114 ctx->hdrs[j] = ctx->hdrs[i];
1115 ctx->hdrs[i] = NULL;
1116 }
1117 ctx->hdrs[j]->msgno = j;
1118 if (ctx->hdrs[j]->virtual != -1)
1119 {
1120 ctx->v2r[ctx->vcount] = j;
1121 ctx->hdrs[j]->virtual = ctx->vcount++;
1122 ctx->vsize += this_body->length + this_body->offset -
1123 this_body->hdr_offset + padding;
1124 }
1125
1126 if (committing)
1127 {
1128 ctx->hdrs[j]->changed = 0;
1129 ctx->hdrs[j]->env->changed = 0;
1130 }
1131 else if (ctx->hdrs[j]->changed)
1132 ctx->changed = 1;
1133
1134 if (!committing || (ctx->magic == MUTT_MAILDIR && option (OPTMAILDIRTRASH)))
1135 {
1136 if (ctx->hdrs[j]->deleted)
1137 ctx->deleted++;
1138 }
1139
1140 if (ctx->hdrs[j]->tagged)
1141 ctx->tagged++;
1142 if (ctx->hdrs[j]->flagged)
1143 ctx->flagged++;
1144 if (!ctx->hdrs[j]->read)
1145 {
1146 ctx->unread++;
1147 if (!ctx->hdrs[j]->old)
1148 ctx->new++;
1149 }
1150
1151 j++;
1152 }
1153 else
1154 {
1155 if (ctx->magic == MUTT_MH || ctx->magic == MUTT_MAILDIR)
1156 ctx->size -= (ctx->hdrs[i]->content->length +
1157 ctx->hdrs[i]->content->offset -
1158 ctx->hdrs[i]->content->hdr_offset);
1159 /* remove message from the hash tables */
1160 if (ctx->subj_hash && ctx->hdrs[i]->env->real_subj)
1161 hash_delete (ctx->subj_hash, ctx->hdrs[i]->env->real_subj, ctx->hdrs[i], NULL);
1162 if (ctx->id_hash && ctx->hdrs[i]->env->message_id)
1163 hash_delete (ctx->id_hash, ctx->hdrs[i]->env->message_id, ctx->hdrs[i], NULL);
1164 mutt_label_hash_remove (ctx, ctx->hdrs[i]);
1165 /* The path mx_check_mailbox() -> imap_check_mailbox() ->
1166 * imap_expunge_mailbox() -> mx_update_tables()
1167 * can occur before a call to mx_sync_mailbox(), resulting in
1168 * last_tag being stale if it's not reset here.
1169 */
1170 if (ctx->last_tag == ctx->hdrs[i])
1171 ctx->last_tag = NULL;
1172 mutt_free_header (&ctx->hdrs[i]);
1173 }
1174 }
1175#undef this_body
1176 ctx->msgcount = j;
1177}
1178
1179
1180/* save changes to mailbox
1181 *
1182 * return values:
1183 * 0 success
1184 * -1 error
1185 */
1186int mx_sync_mailbox (CONTEXT *ctx, int *index_hint)
1187{
1188 int rc, i;
1189 int purge = 1;
1190 int msgcount, deleted;
1191
1192 if (ctx->dontwrite)
1193 {
1194 char buf[STRING], tmp[STRING];
1195 if (km_expand_key (buf, sizeof(buf),
1196 km_find_func (MENU_MAIN, OP_TOGGLE_WRITE)))
1197 snprintf (tmp, sizeof(tmp), _(" Press '%s' to toggle write"), buf);
1198 else
1199 strfcpy (tmp, _("Use 'toggle-write' to re-enable write!"), sizeof(tmp));
1200
1201 mutt_error (_("Mailbox is marked unwritable. %s"), tmp);
1202 return -1;
1203 }
1204 else if (ctx->readonly)
1205 {
1206 mutt_error _("Mailbox is read-only.");
1207 return -1;
1208 }
1209
1210 if (!ctx->changed && !ctx->deleted)
1211 {
1212 if (!ctx->quiet)
1213 mutt_message _("Mailbox is unchanged.");
1214 return (0);
1215 }
1216
1217 if (ctx->deleted)
1218 {
1219 char buf[SHORT_STRING];
1220
1221 snprintf (buf, sizeof (buf), ctx->deleted == 1
1222 ? _("Purge %d deleted message?") : _("Purge %d deleted messages?"),
1223 ctx->deleted);
1224 if ((purge = query_quadoption (OPT_DELETE, buf)) < 0)
1225 return (-1);
1226 else if (purge == MUTT_NO)
1227 {
1228 if (!ctx->changed)
1229 return 0; /* nothing to do! */
1230 /* let IMAP servers hold on to D flags */
1231 if (ctx->magic != MUTT_IMAP)
1232 {
1233 for (i = 0 ; i < ctx->msgcount ; i++)
1234 {
1235 ctx->hdrs[i]->deleted = 0;
1236 ctx->hdrs[i]->purge = 0;
1237 }
1238 ctx->deleted = 0;
1239 }
1240 }
1241 else if (ctx->last_tag && ctx->last_tag->deleted)
1242 ctx->last_tag = NULL; /* reset last tagged msg now useless */
1243 }
1244
1245 /* really only for IMAP - imap_sync_mailbox results in a call to
1246 * mx_update_tables, so ctx->deleted is 0 when it comes back */
1247 msgcount = ctx->msgcount;
1248 deleted = ctx->deleted;
1249
1250 if (purge && ctx->deleted && mutt_strcmp (ctx->path, TrashPath))
1251 {
1252 if (trash_append (ctx) != 0)
1253 return -1;
1254 }
1255
1256#ifdef USE_IMAP
1257 if (ctx->magic == MUTT_IMAP)
1258 rc = imap_sync_mailbox (ctx, purge, index_hint);
1259 else
1260#endif
1261 rc = sync_mailbox (ctx, index_hint);
1262 if (rc == 0)
1263 {
1264#ifdef USE_IMAP
1265 if (ctx->magic == MUTT_IMAP && !purge)
1266 {
1267 if (!ctx->quiet)
1268 mutt_message _("Mailbox checkpointed.");
1269 }
1270 else
1271#endif
1272 {
1273 if (!ctx->quiet)
1274 mutt_message (_("%d kept, %d deleted."), msgcount - deleted,
1275 deleted);
1276 }
1277
1278 mutt_sleep (0);
1279
1280 if (ctx->msgcount == ctx->deleted &&
1281 (ctx->magic == MUTT_MBOX || ctx->magic == MUTT_MMDF) &&
1282 !mutt_is_spool (ctx->path) && !option (OPTSAVEEMPTY))
1283 {
1284 unlink (ctx->path);
1285 mx_fastclose_mailbox (ctx);
1286 return 0;
1287 }
1288
1289 /* if we haven't deleted any messages, we don't need to resort */
1290 /* ... except for certain folder formats which need "unsorted"
1291 * sort order in order to synchronize folders.
1292 *
1293 * MH and maildir are safe. mbox-style seems to need re-sorting,
1294 * at least with the new threading code.
1295 */
1296 if (purge || (ctx->magic != MUTT_MAILDIR && ctx->magic != MUTT_MH))
1297 {
1298 /* IMAP does this automatically after handling EXPUNGE */
1299 if (ctx->magic != MUTT_IMAP)
1300 {
1301 mx_update_tables (ctx, 1);
1302 mutt_sort_headers (ctx, 1); /* rethread from scratch */
1303 }
1304 }
1305 }
1306
1307 return (rc);
1308}
1309
1310/* args:
1311 * dest destination mailbox
1312 * hdr message being copied (required for maildir support, because
1313 * the filename depends on the message flags)
1314 */
1315MESSAGE *mx_open_new_message (CONTEXT *dest, HEADER *hdr, int flags)
1316{
1317 ADDRESS *p = NULL;
1318 MESSAGE *msg;
1319
1320 if (!dest->mx_ops || !dest->mx_ops->open_new_msg)
1321 {
1322 dprint (1, (debugfile, "mx_open_new_message(): function unimplemented for mailbox type %d.\n",
1323 dest->magic));
1324 return NULL;
1325 }
1326
1327 msg = safe_calloc (1, sizeof (MESSAGE));
1328 msg->write = 1;
1329
1330 if (hdr)
1331 {
1332 msg->flags.flagged = hdr->flagged;
1333 msg->flags.replied = hdr->replied;
1334 msg->flags.read = hdr->read;
1335 msg->flags.draft = (flags & MUTT_SET_DRAFT) ? 1 : 0;
1336 msg->received = hdr->received;
1337 }
1338
1339 if (msg->received == 0)
1340 time(&msg->received);
1341
1342 if (dest->mx_ops->open_new_msg (msg, dest, hdr) == 0)
1343 {
1344 if (dest->magic == MUTT_MMDF)
1345 fputs (MMDF_SEP, msg->fp);
1346
1347 if ((dest->magic == MUTT_MBOX || dest->magic == MUTT_MMDF) &&
1348 flags & MUTT_ADD_FROM)
1349 {
1350 if (hdr)
1351 {
1352 if (hdr->env->return_path)
1353 p = hdr->env->return_path;
1354 else if (hdr->env->sender)
1355 p = hdr->env->sender;
1356 else
1357 p = hdr->env->from;
1358 }
1359
1360 fprintf (msg->fp, "From %s %s", p ? p->mailbox : NONULL(Username), ctime (&msg->received));
1361 }
1362 }
1363 else
1364 FREE (&msg);
1365
1366 return msg;
1367}
1368
1369/* check for new mail */
1370int mx_check_mailbox (CONTEXT *ctx, int *index_hint)
1371{
1372 if (!ctx || !ctx->mx_ops)
1373 {
1374 dprint (1, (debugfile, "mx_check_mailbox: null or invalid context.\n"));
1375 return -1;
1376 }
1377
1378 return ctx->mx_ops->check (ctx, index_hint);
1379}
1380
1381/* return a stream pointer for a message */
1382MESSAGE *mx_open_message (CONTEXT *ctx, int msgno)
1383{
1384 MESSAGE *msg;
1385
1386 if (!ctx->mx_ops || !ctx->mx_ops->open_msg)
1387 {
1388 dprint (1, (debugfile, "mx_open_message(): function not implemented for mailbox type %d.\n", ctx->magic));
1389 return NULL;
1390 }
1391
1392 msg = safe_calloc (1, sizeof (MESSAGE));
1393 if (ctx->mx_ops->open_msg (ctx, msg, msgno))
1394 FREE (&msg);
1395
1396 return msg;
1397}
1398
1399/* commit a message to a folder */
1400
1401int mx_commit_message (MESSAGE *msg, CONTEXT *ctx)
1402{
1403 if (!ctx->mx_ops || !ctx->mx_ops->commit_msg)
1404 return -1;
1405
1406 if (!(msg->write && ctx->append))
1407 {
1408 dprint (1, (debugfile, "mx_commit_message(): msg->write = %d, ctx->append = %d\n",
1409 msg->write, ctx->append));
1410 return -1;
1411 }
1412
1413 return ctx->mx_ops->commit_msg (ctx, msg);
1414}
1415
1416/* close a pointer to a message */
1417int mx_close_message (CONTEXT *ctx, MESSAGE **msg)
1418{
1419 int r = 0;
1420
1421 if (ctx->mx_ops && ctx->mx_ops->close_msg)
1422 r = ctx->mx_ops->close_msg (ctx, *msg);
1423
1424 if ((*msg)->path)
1425 {
1426 dprint (1, (debugfile, "mx_close_message (): unlinking %s\n",
1427 (*msg)->path));
1428 unlink ((*msg)->path);
1429 FREE (&(*msg)->path);
1430 }
1431
1432 FREE (msg); /* __FREE_CHECKED__ */
1433 return (r);
1434}
1435
1436void mx_alloc_memory (CONTEXT *ctx)
1437{
1438 int i;
1439 size_t s = MAX (sizeof (HEADER *), sizeof (int));
1440
1441 if ((ctx->hdrmax + 25) * s < ctx->hdrmax * s)
1442 {
1443 mutt_error _("Integer overflow -- can't allocate memory.");
1444 sleep (1);
1445 mutt_exit (1);
1446 }
1447
1448 if (ctx->hdrs)
1449 {
1450 safe_realloc (&ctx->hdrs, sizeof (HEADER *) * (ctx->hdrmax += 25));
1451 safe_realloc (&ctx->v2r, sizeof (int) * ctx->hdrmax);
1452 }
1453 else
1454 {
1455 ctx->hdrs = safe_calloc ((ctx->hdrmax += 25), sizeof (HEADER *));
1456 ctx->v2r = safe_calloc (ctx->hdrmax, sizeof (int));
1457 }
1458 for (i = ctx->msgcount ; i < ctx->hdrmax ; i++)
1459 {
1460 ctx->hdrs[i] = NULL;
1461 ctx->v2r[i] = -1;
1462 }
1463}
1464
1465/* this routine is called to update the counts in the context structure for
1466 * the last message header parsed.
1467 */
1468void mx_update_context (CONTEXT *ctx, int new_messages)
1469{
1470 HEADER *h;
1471 int msgno;
1472
1473 for (msgno = ctx->msgcount - new_messages; msgno < ctx->msgcount; msgno++)
1474 {
1475 h = ctx->hdrs[msgno];
1476
1477 if (WithCrypto)
1478 {
1479 /* NOTE: this _must_ be done before the check for mailcap! */
1480 h->security = crypt_query (h->content);
1481 }
1482
1483 if (!ctx->pattern)
1484 {
1485 ctx->v2r[ctx->vcount] = msgno;
1486 h->virtual = ctx->vcount++;
1487 }
1488 else
1489 h->virtual = -1;
1490 h->msgno = msgno;
1491
1492 if (h->env->supersedes)
1493 {
1494 HEADER *h2;
1495
1496 if (!ctx->id_hash)
1497 ctx->id_hash = mutt_make_id_hash (ctx);
1498
1499 h2 = hash_find (ctx->id_hash, h->env->supersedes);
1500
1501 /* FREE (&h->env->supersedes); should I ? */
1502 if (h2)
1503 {
1504 h2->superseded = 1;
1505 if (option (OPTSCORE))
1506 mutt_score_message (ctx, h2, 1);
1507 }
1508 }
1509
1510 /* add this message to the hash tables */
1511 if (ctx->id_hash && h->env->message_id)
1512 hash_insert (ctx->id_hash, h->env->message_id, h);
1513 if (ctx->subj_hash && h->env->real_subj)
1514 hash_insert (ctx->subj_hash, h->env->real_subj, h);
1515 mutt_label_hash_add (ctx, h);
1516
1517 if (option (OPTSCORE))
1518 mutt_score_message (ctx, h, 0);
1519
1520 if (h->changed)
1521 ctx->changed = 1;
1522 if (h->flagged)
1523 ctx->flagged++;
1524 if (h->deleted)
1525 ctx->deleted++;
1526 if (!h->read)
1527 {
1528 ctx->unread++;
1529 if (!h->old)
1530 ctx->new++;
1531 }
1532 }
1533}
1534
1535/*
1536 * Return:
1537 * 1 if the specified mailbox contains 0 messages.
1538 * 0 if the mailbox contains messages
1539 * -1 on error
1540 */
1541int mx_check_empty (const char *path)
1542{
1543 switch (mx_get_magic (path))
1544 {
1545 case MUTT_MBOX:
1546 case MUTT_MMDF:
1547 return mbox_check_empty (path);
1548 case MUTT_MH:
1549 return mh_check_empty (path);
1550 case MUTT_MAILDIR:
1551 return maildir_check_empty (path);
1552#ifdef USE_IMAP
1553 case MUTT_IMAP:
1554 {
1555 int passive, rv;
1556
1557 passive = option (OPTIMAPPASSIVE);
1558 if (passive)
1559 unset_option (OPTIMAPPASSIVE);
1560 rv = imap_status (path, 0);
1561 if (passive)
1562 set_option (OPTIMAPPASSIVE);
1563 return (rv <= 0);
1564 }
1565#endif
1566 default:
1567 errno = EINVAL;
1568 return -1;
1569 }
1570 /* not reached */
1571}
1572
1573/* mx_msg_padding_size: Returns the padding size between messages for the
1574 * mailbox type pointed to by ctx.
1575 *
1576 * mmdf and mbox add separators, which leads a small discrepancy when computing
1577 * vsize for a limited view.
1578 */
1579int mx_msg_padding_size (CONTEXT *ctx)
1580{
1581 if (!ctx->mx_ops || !ctx->mx_ops->msg_padding_size)
1582 return 0;
1583
1584 return ctx->mx_ops->msg_padding_size (ctx);
1585}
1586
1587/* Writes a single header out to the header cache. */
1588int mx_save_to_header_cache (CONTEXT *ctx, HEADER *h)
1589{
1590 if (!ctx->mx_ops || !ctx->mx_ops->save_to_header_cache)
1591 return 0;
1592
1593 return ctx->mx_ops->save_to_header_cache (ctx, h);
1594}
1595
1596/* vim: set sw=2: */