]> granicus.if.org Git - neomutt/commitdiff
feature: compress
authorAlain Penders <Alain@Finale-Dev.com>
Tue, 26 Jan 2016 18:31:05 +0000 (18:31 +0000)
committerRichard Russon <rich@flatcap.org>
Thu, 18 Aug 2016 15:15:07 +0000 (16:15 +0100)
Read from/write to compressed mailboxes

13 files changed:
Makefile.am
commands.c
compress.c [new file with mode: 0644]
compress.h [new file with mode: 0644]
configure.ac
curs_main.c
hook.c
init.h
main.c
mutt.h
mx.c
mx.h
status.c

index a05249561c6456f08ac5a48c9a349cc2a696bcd4..5fb7811aa23b889f95a1d385518cd6bd9da15d3e 100644 (file)
@@ -50,7 +50,7 @@ DEFS=-DPKGDATADIR=\"$(pkgdatadir)\" -DSYSCONFDIR=\"$(sysconfdir)\" \
 
 AM_CPPFLAGS=-I. -I$(top_srcdir) $(IMAP_INCLUDES) $(GPGME_CFLAGS) -Iintl
 
-EXTRA_mutt_SOURCES = account.c bcache.c crypt-gpgme.c crypt-mod-pgp-classic.c \
+EXTRA_mutt_SOURCES = account.c bcache.c compress.c crypt-gpgme.c crypt-mod-pgp-classic.c \
        crypt-mod-pgp-gpgme.c crypt-mod-smime-classic.c \
        crypt-mod-smime-gpgme.c dotlock.c gnupgparse.c hcache.c md5.c \
        mutt_idna.c mutt_sasl.c mutt_socket.c mutt_ssl.c mutt_ssl_gnutls.c \
@@ -61,7 +61,7 @@ EXTRA_mutt_SOURCES = account.c bcache.c crypt-gpgme.c crypt-mod-pgp-classic.c \
 
 EXTRA_DIST = COPYRIGHT GPL OPS OPS.PGP OPS.CRYPT OPS.SMIME TODO UPDATING \
        configure account.h \
-       attach.h buffy.h charset.h copy.h crypthash.h dotlock.h functions.h gen_defs \
+       attach.h buffy.h charset.h compress.h copy.h crypthash.h dotlock.h functions.h gen_defs \
        globals.h hash.h history.h init.h keymap.h mutt_crypt.h \
        mailbox.h mapping.h md5.h mime.h mutt.h mutt_curses.h mutt_menu.h \
        mutt_regex.h mutt_sasl.h mutt_socket.h mutt_ssl.h mutt_tunnel.h \
index 2202a673328ec3ba027525f5f0e569da0133da8a..fee8adbed018e46fadad721fc866abb567273837 100644 (file)
@@ -837,6 +837,16 @@ int mutt_save_message (HEADER *h, int delete,
 
   if (mx_open_mailbox (buf, MUTT_APPEND, &ctx) != NULL)
   {
+#ifdef USE_COMPRESSED
+    /* If we're saving to a compressed mailbox, the stats won't be updated
+     * until the next open.  Until then, improvise. */
+    BUFFY *cm = NULL;
+    if (ctx.compress_info)
+      cm = mutt_find_mailbox (ctx.realpath);
+    /* We probably haven't been opened yet */
+    if (cm && (cm->msg_count == 0))
+      cm = NULL;
+#endif
     if (h)
     {
       if (_mutt_save_message(h, &ctx, delete, decode, decrypt) != 0)
@@ -844,6 +854,16 @@ int mutt_save_message (HEADER *h, int delete,
         mx_close_mailbox (&ctx, NULL);
         return -1;
       }
+#ifdef USE_COMPRESSED
+      if (cm)
+      {
+        cm->msg_count++;
+        if (!h->read)
+          cm->msg_unread++;
+        if (h->flagged)
+          cm->msg_flagged++;
+      }
+#endif
     }
     else
     {
@@ -858,6 +878,17 @@ int mutt_save_message (HEADER *h, int delete,
             mx_close_mailbox (&ctx, NULL);
             return -1;
           }
+#ifdef USE_COMPRESSED
+          if (cm)
+          {
+            HEADER *h = Context->hdrs[Context->v2r[i]];
+            cm->msg_count++;
+            if (!h->read)
+              cm->msg_unread++;
+            if (h->flagged)
+              cm->msg_flagged++;
+          }
+#endif
        }
       }
     }
diff --git a/compress.c b/compress.c
new file mode 100644 (file)
index 0000000..ce1c07f
--- /dev/null
@@ -0,0 +1,888 @@
+/* Copyright (C) 1997 Alain Penders <Alain@Finale-Dev.com>
+ * Copyright (C) 2016 Richard Russon <rich@flatcap.org>
+ *
+ *     This program is free software; you can redistribute it and/or modify
+ *     it under the terms of the GNU General Public License as published by
+ *     the Free Software Foundation; either version 2 of the License, or
+ *     (at your option) any later version.
+ *
+ *     This program is distributed in the hope that it will be useful,
+ *     but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *     GNU General Public License for more details.
+ *
+ *     You should have received a copy of the GNU General Public License
+ *     along with this program; if not, write to the Free Software
+ *     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <errno.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "mutt.h"
+#include "mailbox.h"
+#include "mutt_curses.h"
+#include "mx.h"
+
+/* Notes:
+ * Any references to compressed files also apply to encrypted files.
+ * ctx->path     == plaintext file
+ * ctx->realpath == compressed file
+ */
+
+/**
+ * struct COMPRESS_INFO - Private data for compress
+ *
+ * This object gets attached to the mailbox's CONTEXT.
+ */
+typedef struct
+{
+       const char *append;             /* append-hook command */
+       const char *close;              /* close-hook  command */
+       const char *open;               /* open-hook   command */
+       off_t size;                     /* size of the compressed file */
+       struct mx_ops *child_ops;       /* callbacks of de-compressed file */
+} COMPRESS_INFO;
+
+
+/**
+ * lock_mailbox - Try to lock a mailbox (exclusively)
+ * @ctx:  Mailbox to lock
+ * @fp:   File pointer to the mailbox file
+ * @excl: Lock exclusively?
+ *
+ * Try to (exclusively) lock the mailbox.  If we succeed, then we mark the
+ * mailbox as locked.  If we fail, but we didn't want exclusive rights, then
+ * the mailbox will be marked readonly.
+ *
+ * Returns:
+ *     1: Success (locked or readonly)
+ *     0: Error (can't lock the file)
+ */
+static int
+lock_mailbox (CONTEXT *ctx, FILE *fp, int excl)
+{
+       if (!ctx || !fp)
+               return 0;
+
+       int r = mx_lock_file (ctx->realpath, fileno (fp), excl, 1, 1);
+
+       if (r == 0) {
+               ctx->locked = 1;
+       } else if (excl == 0) {
+               ctx->readonly = 1;
+               return 1;
+       }
+
+       return (r == 0);
+}
+
+/**
+ * unlock_mailbox - Unlock a mailbox
+ * @ctx: Mailbox to unlock
+ * @fp:  File pointer to mailbox file
+ *
+ * Unlock a mailbox previously locked by lock_mailbox().
+ */
+static void
+unlock_mailbox (CONTEXT *ctx, FILE *fp)
+{
+       if (!ctx || !fp)
+               return;
+
+       if (!ctx->locked)
+               return;
+
+       fflush (fp);
+
+       mx_unlock_file (ctx->realpath, fileno (fp), 1);
+       ctx->locked = 0;
+}
+
+/**
+ * setup_paths - Set the mailbox paths
+ * @ctx: Mailbox to modify
+ *
+ * Save the compressed filename in ctx->realpath.
+ * Create a temporary filename and put its name in ctx->path.
+ *
+ * Note: The temporary file is NOT created.
+ * Note: ctx->path will be freed by restore_path()
+ */
+static void
+setup_paths (CONTEXT *ctx)
+{
+       if (!ctx)
+               return;
+
+       char tmppath[_POSIX_PATH_MAX];
+
+       /* Setup the right paths */
+       ctx->realpath = ctx->path;
+
+       /* We will uncompress to /tmp */
+       mutt_mktemp (tmppath, sizeof (tmppath));
+       ctx->path = safe_strdup (tmppath);
+}
+
+/**
+ * restore_path - Put back the original mailbox name
+ * @ctx: Mailbox to modify
+ *
+ * When we use a compressed mailbox, we change the CONTEXT to refer to the
+ * uncompressed file.  We store the original name in ctx->realpath.
+ *     ctx->path     = "/tmp/mailbox"
+ *     ctx->realpath = "mailbox.gz"
+ *
+ * When we have finished with a compressed mailbox, we put back the original
+ * name.
+ *     ctx->path     = "mailbox.gz"
+ *     ctx->realpath = NULL
+ */
+static void
+restore_path (CONTEXT *ctx)
+{
+       if (!ctx)
+               return;
+
+       FREE(&ctx->path);
+       ctx->path = ctx->realpath;
+       ctx->realpath = NULL;
+}
+
+/**
+ * get_size - Get the size of a file
+ * @path: File to measure
+ *
+ * Returns:
+ *     number: Size in bytes
+ *     0:      On error
+ */
+static int
+get_size (const char *path)
+{
+       if (!path)
+               return 0;
+
+       struct stat sb;
+       if (stat (path, &sb) != 0)
+               return 0;
+
+       return sb.st_size;
+}
+
+/**
+ * store_size - Save the size of the compressed file
+ * @ctx: Mailbox
+ *
+ * Save the compressed file size in the compress_info struct.
+ */
+static void
+store_size (const CONTEXT *ctx)
+{
+       if (!ctx)
+               return;
+
+       COMPRESS_INFO *ci = ctx->compress_info;
+       if (!ci)
+               return;
+
+       ci->size = get_size (ctx->realpath);
+}
+
+/**
+ * find_hook - Find a hook to match a path
+ * @type: Type of hook, e.g. MUTT_CLOSEHOOK
+ * @path: Filename to test
+ *
+ * Each hook has a type and a pattern.
+ * Find a command that matches the type and path supplied. e.g.
+ *
+ * User config:
+ *     open-hook '\.gz$' "gzip -cd '%f' > '%t'"
+ *
+ * Call:
+ *     find_hook (MUTT_OPENHOOK, "myfile.gz");
+ *
+ * Returns:
+ *     string: Matching hook command
+ *     NULL:   No matches
+ */
+static const char *
+find_hook (int type, const char *path)
+{
+       if (!path)
+               return NULL;
+
+       const char *c = mutt_find_hook (type, path);
+       if (!c || !*c)
+               return NULL;
+
+       return c;
+}
+
+/**
+ * set_compress_info - Find the compress hooks for a mailbox
+ * @ctx: Mailbox to examine
+ *
+ * When a mailbox is opened, we check if there are any matching hooks.
+ *
+ * Note: Caller must free the COMPRESS_INFO when done.
+ *
+ * Returns:
+ *     COMPRESS_INFO: Hook info for the mailbox's path
+ *     NULL:          On error
+ */
+static COMPRESS_INFO *
+set_compress_info (CONTEXT *ctx)
+{
+       if (!ctx || !ctx->path)
+               return NULL;
+
+       if (ctx->compress_info)
+               return ctx->compress_info;
+
+       /* Open is compulsory */
+       const char *o = find_hook (MUTT_OPENHOOK,   ctx->path);
+       if (!o)
+               return NULL;
+
+       const char *c = find_hook (MUTT_CLOSEHOOK,  ctx->path);
+       const char *a = find_hook (MUTT_APPENDHOOK, ctx->path);
+
+       COMPRESS_INFO *ci = safe_calloc (1, sizeof (COMPRESS_INFO));
+       ctx->compress_info = ci;
+
+       ci->open   = o;
+       ci->close  = c;
+       ci->append = a;
+
+       return ci;
+}
+
+/**
+ * cb_format_str - Expand the filenames in the command string
+ * @dest:        Buffer in which to save string
+ * @destlen:     Buffer length
+ * @col:         Starting column, UNUSED
+ * @cols:        Number of screen columns, UNUSED
+ * @op:          printf-like operator, e.g. 't'
+ * @src:         printf-like format string
+ * @fmt:         Field formatting string, UNUSED
+ * @ifstring:    If condition is met, display this string, UNUSED
+ * @elsestring:  Otherwise, display this string, UNUSED
+ * @data:        Pointer to the mailbox CONTEXT
+ * @flags:       Format flags, UNUSED
+ *
+ * cb_format_str is a callback function for mutt_FormatString.  It understands
+ * two operators. '%f' : 'from' filename, '%t' : 'to' filename.
+ *
+ * Returns: src (unchanged)
+ */
+static const char *
+cb_format_str (char *dest, size_t destlen, size_t col, int cols, char op, const char *src,
+       const char *fmt, const char *ifstring, const char *elsestring,
+       unsigned long data, format_flag flags)
+{
+       if (!dest || (data == 0))
+               return src;
+
+       CONTEXT *ctx = (CONTEXT *) data;
+
+       switch (op) {
+               case 'f':
+                       /* Compressed file */
+                       snprintf (dest, destlen, "%s", ctx->realpath);
+                       break;
+               case 't':
+                       /* Plaintext, temporary file */
+                       snprintf (dest, destlen, "%s", ctx->path);
+                       break;
+       }
+       return src;
+}
+
+/**
+ * expand_command_str - Expand placeholders in command string
+ * @ctx:    Mailbox for paths
+ * @buf:    Buffer to store the command
+ * @buflen: Size of the buffer
+ *
+ * This function takes a hook command and expands the filename placeholders
+ * within it.  The function calls mutt_FormatString() to do the replacement
+ * which calls our callback function cb_format_str(). e.g.
+ *
+ * Template command:
+ *     gzip -cd '%f' > '%t'
+ *
+ * Result:
+ *     gzip -dc '~/mail/abc.gz' > '/tmp/xyz'
+ */
+static void
+expand_command_str (const CONTEXT *ctx, const char *cmd, char *buf, int buflen)
+{
+       if (!ctx || !cmd || !buf)
+               return;
+
+       mutt_FormatString (buf, buflen, 0, buflen, cmd, cb_format_str, (unsigned long) ctx, 0);
+}
+
+/**
+ * execute_command - Run a system command
+ * @ctx:         Mailbox to work with
+ * @command:     Command string to execute
+ * @create_file: Should the tmp file be created?
+ * @progress:    Message to show the user
+ *
+ * Run the supplied command, taking care of all the Mutt requirements,
+ * such as locking files and blocking signals.
+ *
+ * Returns:
+ *     1: Success
+ *     0: Failure
+ */
+static int
+execute_command (CONTEXT *ctx, const char *command, int create_file, const char *progress)
+{
+       if (!ctx || !command || !progress)
+               return 0;
+
+       if (!ctx->quiet)
+               mutt_message (progress, ctx->realpath);
+
+       FILE *fp;
+       if (create_file)
+               fp = fopen (ctx->realpath, "a");
+       else
+               fp = fopen (ctx->realpath, "r");
+
+       if (!fp) {
+               mutt_perror (ctx->realpath);
+               return 0;
+       }
+
+       mutt_block_signals();
+        /* If we're creating the file, lock it exclusively */
+       if (!lock_mailbox (ctx, fp, create_file)) {
+               safe_fclose (&fp);
+               mutt_unblock_signals();
+               mutt_error (_("Unable to lock mailbox!"));
+               return 0;
+       }
+
+       endwin();
+       fflush (stdout);
+
+       char sys_cmd[HUGE_STRING];
+
+       expand_command_str (ctx, command, sys_cmd, sizeof (sys_cmd));
+
+       int rc = mutt_system (sys_cmd);
+       if (rc != 0) {
+               mutt_any_key_to_continue (NULL);
+               mutt_error (_("Error executing: %s\n"), sys_cmd);
+       }
+
+       unlock_mailbox (ctx, fp);
+       mutt_unblock_signals();
+       safe_fclose (&fp);
+
+       return 1;
+}
+
+/**
+ * open_read - Open a compressed mailbox for reading
+ * @ctx: Mailbox to open
+ *
+ * Decompress the mailbox and set up the paths and hooks needed.
+ *
+ * Note: The message handling will be delegated to the mbox code.
+ *
+ * Returns:
+ *     1: Success
+ *     0: Failure
+ */
+static int
+open_read (CONTEXT *ctx)
+{
+       if (!ctx)
+               return 0;
+
+       COMPRESS_INFO *ci = set_compress_info (ctx);
+       if (!ci) {
+               ctx->magic = 0;
+               return 0;
+       }
+
+       /* If there's no close-hook, or the file isn't writable */
+       if (!ci->close || (access (ctx->path, W_OK) != 0))
+               ctx->readonly = 1;
+
+       setup_paths (ctx);
+       store_size (ctx);
+
+       int rc = execute_command (ctx, ci->open, 0, _("Decompressing %s"));
+       if (rc == 0) {
+               goto or_fail;
+       }
+
+       ctx->magic = mx_get_magic (ctx->path);
+       if (ctx->magic == 0) {
+               mutt_error (_("Can't identify the contents of the compressed file"));
+               goto or_fail;
+       }
+
+       ci->child_ops = mx_get_ops (ctx->magic);
+       if (!ci->child_ops) {
+               mutt_error (_("Can't find mailbox ops for mailbox type %d"), ctx->magic);
+               goto or_fail;
+       }
+
+       return 1;
+
+or_fail:
+       /* remove the partial uncompressed file */
+       remove (ctx->path);
+       restore_path (ctx);
+       return 0;
+}
+
+
+struct mx_ops mx_comp_ops;
+
+/**
+ * open_mailbox - Open a compressed mailbox
+ * @ctx: Mailbox to open
+ *
+ * Set up a compressed mailbox to be read.
+ * First call open_read() to decompress the file.
+ * Then determine the type of the mailbox so we can delegate the handling of
+ * messages.
+ */
+static int
+open_mailbox (CONTEXT *ctx)
+{
+       if (!ctx || (ctx->magic != MUTT_COMPRESSED))
+               return 1;
+
+       if (!open_read (ctx))
+               return 1;
+
+       COMPRESS_INFO *ci = ctx->compress_info;
+       if (!ci)
+               return 1;
+
+       struct mx_ops *ops = ci->child_ops;
+       if (!ops)
+               return 1;
+
+       /* Delegate */
+       return ops->open (ctx);
+}
+
+/**
+ * open_append_mailbox - Open a compressed mailbox for appending
+ * @ctx:   Mailbox to open
+ * @flags: e.g. Does the file already exist?
+ *
+ * To append to a compressed mailbox we need an append-hook (or both open- and
+ * close-hooks).
+ *
+ * Returns:
+ *      0: Success
+ *     -1: Failure
+ */
+static int
+open_append_mailbox (CONTEXT *ctx, int flags)
+{
+       if (!ctx)
+               return -1;
+
+       /* If this succeeds, we know there's an open-hook */
+       COMPRESS_INFO *ci = set_compress_info (ctx);
+       if (!ci)
+               return -1;
+
+       /* To append we need an append-hook or a close-hook */
+       if (!ci->append && !ci->close) {
+               FREE(&ctx->compress_info);
+                mutt_error (_("Cannot append without an append-hook or close-hook : %s"), ctx->path);
+               return -1;
+       }
+
+       ctx->magic = DefaultMagic;
+        /* We can only deal with mbox and mmdf mailboxes */
+       if ((ctx->magic != MUTT_MBOX) && (ctx->magic != MUTT_MMDF))
+               return -1;
+
+       setup_paths (ctx);
+
+       ctx->mx_ops = &mx_comp_ops;
+       ci->child_ops = mx_get_ops (ctx->magic);
+       if (!ci->child_ops) {
+               mutt_error (_("Can't find mailbox ops for mailbox type %d"), ctx->magic);
+               return -1;
+       }
+
+        if (ci->append) {
+               /* Create an empty temporary file */
+               ctx->fp = safe_fopen (ctx->path, "w");
+               if (!ctx->fp) {
+                        mutt_perror (ctx->path);
+                       goto oa_fail;
+                }
+       } else {
+               /* Open the existing mailbox */
+               int rc = execute_command (ctx, ci->open, 0, _("Decompressing %s"));
+               if (rc == 0) {
+                        mutt_error (_("Compress command failed: %s"), ci->open);
+                       goto oa_fail;
+                }
+               ctx->fp = safe_fopen (ctx->path, "a");
+               if (!ctx->fp) {
+                        mutt_perror (ctx->path);
+                       goto oa_fail;
+                }
+       }
+
+       return 0;
+
+oa_fail:
+       /* remove the partial uncompressed file */
+       remove (ctx->path);
+       restore_path (ctx);
+       return -1;
+}
+
+/**
+ * close_mailbox - Close a compressed mailbox
+ * @ctx: Mailbox to close
+ *
+ * If the mailbox has been changed then re-compress the tmp file.
+ * Then delete the tmp file.
+ *
+ * Returns:
+ *      0: Success
+ *     -1: Failure
+ */
+static int
+close_mailbox (CONTEXT *ctx)
+{
+       if (!ctx)
+               return -1;
+
+       COMPRESS_INFO *ci = ctx->compress_info;
+       if (!ci)
+               return -1;
+
+       safe_fclose (&ctx->fp);
+
+       /* sync has already been called, so we only need to delete some files */
+       if (!ctx->append) {
+               /* If the file was removed, remove the compressed folder too */
+               if ((access (ctx->path, F_OK) != 0) && !option (OPTSAVEEMPTY)) {
+                       remove (ctx->realpath);
+               } else {
+                       remove (ctx->path);
+               }
+
+               restore_path (ctx);
+               FREE(&ctx->compress_info);
+               return 0;
+       }
+
+       const char *append;
+       const char *msg;
+
+       /* The file exists and we can append */
+       if ((access (ctx->realpath, F_OK) == 0) && ci->append) {
+               append = ci->append;
+               msg = _("Compressed-appending to %s...");
+        } else {
+               append = ci->close;
+               msg = _("Compressing %s...");
+        }
+
+       int rc = execute_command (ctx, append, 1, msg);
+       if (rc == 0) {
+               mutt_any_key_to_continue (NULL);
+               mutt_error (_(" %s: Error compressing mailbox!  Uncompressed one kept!\n"), ctx->path);
+       }
+
+       remove (ctx->path);
+       restore_path (ctx);
+       FREE(&ctx->compress_info);
+
+       return 0;
+}
+
+/**
+ * check_mailbox - Check for changes in the compressed file
+ * @ctx: Mailbox
+ *
+ * If the compressed file changes in size but the mailbox hasn't been changed
+ * in Mutt, then we can close and reopen the mailbox.
+ *
+ * If the mailbox has been changed in Mutt, warn the user.
+ *
+ * The return codes are picked to match mx_check_mailbox().
+ *
+ * Returns:
+ *     0:              Mailbox OK
+ *     MUTT_REOPENED:  The mailbox was closed and reopened
+ *     -1:             Mailbox bad
+ */
+static int
+check_mailbox (CONTEXT *ctx, int *index_hint)
+{
+       if (!ctx)
+               return -1;
+
+       COMPRESS_INFO *ci = ctx->compress_info;
+       if (!ci)
+               return -1;
+
+        int size = get_size (ctx->realpath);
+        if (size == ci->size)
+               return 0;
+
+       if (ctx->changed) {
+               FREE(&ctx->compress_info);
+               restore_path (ctx);
+               mutt_error (_("Mailbox was corrupted!"));
+               return -1;
+       }
+
+       close_mailbox (ctx);
+
+       const char *path = ctx->path;
+       ctx->path = NULL;
+
+       mx_open_mailbox (path, 0, ctx);
+       FREE(&path);
+
+       return MUTT_REOPENED;
+}
+
+
+/**
+ * open_message - Delegated to mbox handler
+ */
+static int
+open_message (CONTEXT *ctx,  MESSAGE *msg, int msgno)
+{
+       if (!ctx)
+               return -1;
+
+       COMPRESS_INFO *ci = ctx->compress_info;
+       if (!ci)
+               return -1;
+
+       struct mx_ops *ops = ci->child_ops;
+       if (!ops)
+               return -1;
+
+       /* Delegate */
+       return ops->open_msg (ctx, msg, msgno);
+}
+
+/**
+ * close_message - Delegated to mbox handler
+ */
+static int
+close_message (CONTEXT *ctx, MESSAGE *msg)
+{
+       if (!ctx)
+               return -1;
+
+       COMPRESS_INFO *ci = ctx->compress_info;
+       if (!ci)
+               return -1;
+
+       struct mx_ops *ops = ci->child_ops;
+       if (!ops)
+               return -1;
+
+       /* Delegate */
+       return ops->close_msg (ctx, msg);
+}
+
+/**
+ * commit_message - Delegated to mbox handler
+ */
+static int
+commit_message (CONTEXT *ctx, MESSAGE *msg)
+{
+       if (!ctx)
+               return -1;
+
+       COMPRESS_INFO *ci = ctx->compress_info;
+       if (!ci)
+               return -1;
+
+       struct mx_ops *ops = ci->child_ops;
+       if (!ops)
+               return -1;
+
+       /* Delegate */
+       return ops->commit_msg (ctx, msg);
+}
+
+/**
+ * open_new_message - Delegated to mbox handler
+ */
+static int
+open_new_message (MESSAGE *msg, CONTEXT *ctx, HEADER *hdr)
+{
+       if (!ctx)
+               return -1;
+
+       COMPRESS_INFO *ci = ctx->compress_info;
+       if (!ci)
+               return -1;
+
+       struct mx_ops *ops = ci->child_ops;
+       if (!ops)
+               return -1;
+
+       /* Delegate */
+       return ops->open_new_msg (msg, ctx, hdr);
+}
+
+
+/**
+ * comp_can_append - Can we append to this path?
+ * @path: pathname of file to be tested
+ *
+ * To append to a file we can either use an 'append-hook' or a combination of
+ * 'open-hook' and 'close-hook'.
+ *
+ * A match means it's our responsibility to append to the file.
+ *
+ * Returns:
+ *     1: Yes, we can append to the file
+ *     0: No, appending isn't possible
+ */
+int
+comp_can_append (CONTEXT *ctx)
+{
+       if (!ctx)
+               return 0;
+
+       /* If this succeeds, we know there's an open-hook */
+       COMPRESS_INFO *ci = set_compress_info (ctx);
+       if (!ci)
+               return 0;
+
+       /* We have an open-hook, so to append we need an append-hook,
+        * or a close-hook. */
+       if (ci->append || ci->close)
+               return 1;
+
+        mutt_error (_("Cannot append without an append-hook or close-hook : %s"), ctx->path);
+       return 0;
+}
+
+/**
+ * comp_can_read - Can we read from this file?
+ * @path: Pathname of file to be tested
+ *
+ * Search for an 'open-hook' with a regex that matches the path.
+ *
+ * A match means it's our responsibility to open the file.
+ *
+ * Returns:
+ *     1: Yes, we can read the file
+ *     0: No, we cannot read the file
+ */
+int
+comp_can_read (const char *path)
+{
+       if (!path)
+               return 0;
+
+       if (find_hook (MUTT_OPENHOOK, path))
+               return 1;
+       else
+               return 0;
+}
+
+/**
+ * comp_sync - Save changes to the compressed mailbox file
+ * @ctx: Mailbox to sync
+ *
+ * Changes in Mutt only affect the tmp file.  Calling comp_sync() will commit
+ * them to the compressed file.
+ *
+ * Returns:
+ *      0: Success
+ *     -1: Failure
+ */
+int
+comp_sync (CONTEXT *ctx)
+{
+       if (!ctx)
+               return -1;
+
+       COMPRESS_INFO *ci = ctx->compress_info;
+       if (!ci)
+               return -1;
+
+       if (!ci->close) {
+               mutt_error (_("Can't sync a compressed file without a close-hook"));
+               return -1;
+       }
+
+       int rc = execute_command (ctx, ci->close, 1, _("Compressing %s"));
+       if (rc == 0)
+               return -1;
+
+       store_size (ctx);
+
+       return 0;
+}
+
+/**
+ * comp_valid_command - Is this command string allowed?
+ * @cmd:  Command string
+ *
+ * A valid command string must have both "%f" (from file) and "%t" (to file).
+ * We don't check if we can actually run the command.
+ *
+ * Returns:
+ *     1: Valid command
+ *     0: "%f" and/or "%t" is missing
+ */
+int
+comp_valid_command (const char *cmd)
+{
+       if (!cmd)
+               return 0;
+
+       return (strstr (cmd, "%f") && strstr (cmd, "%t"));
+}
+
+
+/**
+ * mx_comp_ops - Mailbox callback functions
+ *
+ * Compress only uses open, close and check.
+ * The message functions are delegated to mbox.
+ */
+struct mx_ops mx_comp_ops = {
+       .open         = open_mailbox,
+        .open_append  = open_append_mailbox,
+       .close        = close_mailbox,
+       .check        = check_mailbox,
+       .open_msg     = open_message,
+       .close_msg    = close_message,
+       .commit_msg   = commit_message,
+       .open_new_msg = open_new_message
+};
+
diff --git a/compress.h b/compress.h
new file mode 100644 (file)
index 0000000..26beae6
--- /dev/null
@@ -0,0 +1,29 @@
+/* Copyright (C) 1997 Alain Penders <Alain@Finale-Dev.com>
+ * Copyright (C) 2016 Richard Russon <rich@flatcap.org>
+ *
+ *     This program is free software; you can redistribute it and/or modify
+ *     it under the terms of the GNU General Public License as published by
+ *     the Free Software Foundation; either version 2 of the License, or
+ *     (at your option) any later version.
+ *
+ *     This program is distributed in the hope that it will be useful,
+ *     but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *     GNU General Public License for more details.
+ *
+ *     You should have received a copy of the GNU General Public License
+ *     along with this program; if not, write to the Free Software
+ *     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#ifndef _COMPRESS_H_
+#define _COMPRESS_H_
+
+int comp_can_append    (CONTEXT *ctx);
+int comp_can_read      (const char *path);
+int comp_sync          (CONTEXT *ctx);
+int comp_valid_command (const char *cmd);
+
+extern struct mx_ops mx_comp_ops;
+
+#endif /* _COMPRESS_H_ */
index 75517f6f23a216a2149eb0b385b3ad384a25d8f4..c171c15781e5aa708dbda26fb398feb3e2ab5f96 100644 (file)
@@ -183,6 +183,15 @@ AC_ARG_ENABLE(sidebar, AC_HELP_STRING([--enable-sidebar], [Enable Sidebar suppor
         fi
 ])
 
+AC_ARG_ENABLE(compressed, AC_HELP_STRING([--enable-compressed], [Enable compressed folders support]),
+              enable_compressed=$enableval, enable_compressed=no
+)
+AS_IF([test x$enable_compressed = "xyes"], [
+          AC_DEFINE(USE_COMPRESSED, 1, [Define to enable compressed folders support.])
+          MUTT_LIB_OBJECTS="$MUTT_LIB_OBJECTS compress.o"
+])
+AM_CONDITIONAL(BUILD_COMPRESS, test x$enable_compressed = xyes)
+
 AC_ARG_WITH(mixmaster, AS_HELP_STRING([--with-mixmaster@<:@=PATH@:>@],[Include Mixmaster support]),
   [if test "$withval" != no
    then
index 8e0f52ad837eadbee37601851c2626956f4e9e01..3b41837b7e7fe168835a88088ce46684b85076ca 100644 (file)
@@ -1241,6 +1241,11 @@ int mutt_index_menu (void)
         {
          int check;
 
+#ifdef USE_COMPRESSED
+         if (Context->compress_info && Context->realpath)
+           mutt_str_replace (&LastFolder, Context->realpath);
+         else
+#endif
          mutt_str_replace (&LastFolder, Context->path);
          oldcount = Context ? Context->msgcount : 0;
 
diff --git a/hook.c b/hook.c
index 1b906c33ece2820c8c352e521733652637f523fd..d0a111b27f3431229adce65fce8bb978a0eea654 100644 (file)
--- a/hook.c
+++ b/hook.c
 #include "mailbox.h"
 #include "mutt_crypt.h"
 
+#ifdef USE_COMPRESSED
+#include "compress.h"
+#endif
+
 #include <limits.h>
 #include <string.h>
 #include <stdlib.h>
@@ -109,6 +113,14 @@ int mutt_parse_hook (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err)
     memset (&pattern, 0, sizeof (pattern));
     pattern.data = safe_strdup (path);
   }
+#ifdef USE_COMPRESSED
+  else if (data & (MUTT_APPENDHOOK | MUTT_OPENHOOK | MUTT_CLOSEHOOK)) {
+    if (comp_valid_command (command.data) == 0) {
+      strfcpy (err->data, _("badly formatted command string"), err->dsize);
+      return -1;
+    }
+  }
+#endif
   else if (DefaultHook && !(data & (MUTT_CHARSETHOOK | MUTT_ICONVHOOK | MUTT_ACCOUNTHOOK))
            && (!WithCrypto || !(data & MUTT_CRYPTHOOK))
       )
diff --git a/init.h b/init.h
index 3f3f96d9dd57e3d12e609bd9e3c01f8cc3fd7d93..2f798fd6fc117a35aa923c7c0a40e9b9aa0f95fd 100644 (file)
--- a/init.h
+++ b/init.h
@@ -3897,6 +3897,11 @@ const struct command_t Commands[] = {
   { "fcc-hook",                mutt_parse_hook,        MUTT_FCCHOOK },
   { "fcc-save-hook",   mutt_parse_hook,        MUTT_FCCHOOK | MUTT_SAVEHOOK },
   { "folder-hook",     mutt_parse_hook,        MUTT_FOLDERHOOK },
+#ifdef USE_COMPRESSED
+  { "open-hook",       mutt_parse_hook,        MUTT_OPENHOOK },
+  { "close-hook",      mutt_parse_hook,        MUTT_CLOSEHOOK },
+  { "append-hook",     mutt_parse_hook,        MUTT_APPENDHOOK },
+#endif
   { "group",           parse_group,            MUTT_GROUP },
   { "ungroup",         parse_group,            MUTT_UNGROUP },
   { "hdr_order",       parse_list,             UL &HeaderOrderList },
diff --git a/main.c b/main.c
index b6541697f4c9a7a3c14d6862393197798bd82318..cb2e200e730ad741887ebd963c6fdb6a795a284a 100644 (file)
--- a/main.c
+++ b/main.c
@@ -436,7 +436,13 @@ static void show_version (void)
 #else
        "-LOCALES_HACK  "
 #endif
-             
+
+#ifdef USE_COMPRESSED
+       "+COMPRESSED  "
+#else
+       "-COMPRESSED  "
+#endif
+
 #ifdef HAVE_WC_FUNCS
        "+HAVE_WC_FUNCS  "
 #else
diff --git a/mutt.h b/mutt.h
index 2cf55b102640049fc25711e381550be4677208ad..152718c59b275dcc5d0cdb627bf0c54d745087b9 100644 (file)
--- a/mutt.h
+++ b/mutt.h
@@ -141,6 +141,11 @@ typedef enum
 #define MUTT_ACCOUNTHOOK (1<<9)
 #define MUTT_REPLYHOOK   (1<<10)
 #define MUTT_SEND2HOOK   (1<<11)
+#ifdef USE_COMPRESSED
+#define MUTT_OPENHOOK    (1<<12)
+#define MUTT_APPENDHOOK  (1<<13)
+#define MUTT_CLOSEHOOK   (1<<14)
+#endif
 
 /* tree characters for linearize_tree and print_enriched_string */
 #define MUTT_TREE_LLCORNER      1
@@ -946,6 +951,10 @@ typedef struct _context
   unsigned int closing : 1;    /* mailbox is being closed */
   unsigned int peekonly : 1;   /* just taking a glance, revert atime */
 
+#ifdef USE_COMPRESSED
+  void *compress_info;         /* compressed mbox module private data */
+#endif /* USE_COMPRESSED */
+
   /* driver hooks */
   void *data;                  /* driver specific data */
   struct mx_ops *mx_ops;
diff --git a/mx.c b/mx.c
index fbe82e4ca38d0cae230569603110a2b323fbd36a..71b80a4f43f4ab895ac80b567f128d270052c53b 100644 (file)
--- a/mx.c
+++ b/mx.c
 #include "sidebar.h"
 #endif
 
+#ifdef USE_COMPRESSED
+#include "compress.h"
+#endif
+
 #ifdef USE_IMAP
 #include "imap.h"
 #endif
@@ -60,7 +64,7 @@
 #include <ctype.h>
 #include <utime.h>
 
-static struct mx_ops* mx_get_ops (int magic)
+struct mx_ops* mx_get_ops (int magic)
 {
   switch (magic)
   {
@@ -79,6 +83,10 @@ static struct mx_ops* mx_get_ops (int magic)
 #ifdef USE_POP
     case MUTT_POP:
       return &mx_pop_ops;
+#endif
+#ifdef USE_COMPRESSED
+    case MUTT_COMPRESSED:
+      return &mx_comp_ops;
 #endif
     default:
       return NULL;
@@ -441,6 +449,12 @@ int mx_get_magic (const char *path)
     return (-1);
   }
 
+#ifdef USE_COMPRESSED
+  /* If there are no other matches, see if there are any
+   * compress hooks that match */
+  if ((magic == 0) && comp_can_read (path))
+    return MUTT_COMPRESSED;
+#endif
   return (magic);
 }
 
@@ -507,6 +521,11 @@ static int mx_open_mailbox_append (CONTEXT *ctx, int flags)
       return -1;
   }
 
+#ifdef USE_COMPRESSED
+  if (comp_can_append (ctx))
+    ctx->mx_ops = &mx_comp_ops;
+  else
+#endif
   ctx->mx_ops = mx_get_ops (ctx->magic);
   if (!ctx->mx_ops || !ctx->mx_ops->open_append)
     return -1;
@@ -709,6 +728,14 @@ static int sync_mailbox (CONTEXT *ctx, int *index_hint)
   
   if (tmp && tmp->new == 0)
     mutt_update_mailbox (tmp);
+
+#ifdef USE_COMPRESSED
+  /* If everything went well, the mbox handler saved the changes to our
+   * temporary file.  Next, comp_sync() will compress the temporary file. */
+  if ((rc == 0) && ctx->compress_info)
+    return comp_sync (ctx);
+#endif
+
   return rc;
 }
 
diff --git a/mx.h b/mx.h
index ba9a78f9ae3d3486cfa6c8da051d59f1c3d48e3a..6b3c535ea186e26bd02df2e7303c95669397a708 100644 (file)
--- a/mx.h
+++ b/mx.h
@@ -37,6 +37,9 @@ enum
   MUTT_MAILDIR,
   MUTT_IMAP,
   MUTT_POP
+#ifdef USE_COMPRESSED
+  , MUTT_COMPRESSED
+#endif
 };
 
 WHERE short DefaultMagic INITVAL (MUTT_MBOX);
@@ -70,6 +73,7 @@ void mx_update_tables (CONTEXT *, int);
 int mx_lock_file (const char *, int, int, int, int);
 int mx_unlock_file (const char *path, int fd, int dot);
 
+struct mx_ops* mx_get_ops (int magic);
 extern struct mx_ops mx_maildir_ops;
 extern struct mx_ops mx_mbox_ops;
 extern struct mx_ops mx_mh_ops;
index a8921a93d9190bbe63285d8f8d0c9694f8984c8f..52baa8c1d79afeccf48f361bc4959b95ea351d8e 100644 (file)
--- a/status.c
+++ b/status.c
@@ -96,6 +96,12 @@ status_format_str (char *buf, size_t buflen, size_t col, int cols, char op, cons
 
     case 'f':
       snprintf (fmt, sizeof(fmt), "%%%ss", prefix);
+#ifdef USE_COMPRESSED
+      if (Context && Context->compress_info && Context->realpath) {
+        strfcpy (tmp, Context->realpath, sizeof (tmp));
+        mutt_pretty_mailbox (tmp, sizeof (tmp));
+      } else
+#endif
       if (Context && Context->path)
       {
        strfcpy (tmp, Context->path, sizeof (tmp));