From: buhr@umanitoba.ca (Kevin Andrew Buhr)
Subject: autoZ.el -- auto (un)compress [was: Creating a *.Z file from scratch]
Followup-To: gnu.emacs.help
Organization: University of Manitoba, Canada
Date: Sat, 5 Sep 1992 23:00:09 GMT

In <RALPH.92Sep2200122@orion.laas.fr>, ralph@laas.fr writes:

   Under GNUS or VM, or even elsewhere, crypt.el works *very* nicely when
   you save to compressed files, uncompressing the data in the buffer and
   then recompressing before writing the data out!

I was interested in a general purpose compressor/decompressor, too.  I
looked at "ange-crypt.el" and "zcat.el" and couldn't get them to work
exactly the way I liked, either.

I tried writing my own which recognized the two-byte magic cookie at
the file's beginning as an indication it was compressed and then
stored this fact in a buffer-local variable.  Unfortunately, many
modes cleared local variables, messing up the saving of these files.

In the end, I settled on using a regular expression to recognize file
*names* that indicated a file was compressed.  With the default
variable values, the code will treat files ending in ".Z" as
compressed.  When loaded, they will be uncompressed if the magic
cookie is found at file start, and, when saved, they will be
recompressed if the absence of the cookie indicates they haven't
already been compressed.  This scheme has the disadvantage that *all*
files whose names end in ".Z" will be compressed when saved, whether
they were originally compressed or not.

I've tested this using compressed Rmail files and compressed GNUS
".newsrc" files.  The only problem I have encountered is this:
attempts to append articles to the end of compressed Rmail files on
disk will fail; the uncompressed message will be tacked onto the end
of the compressed file.  Just don't try using compressed Rmail files
to automatically store author copies or save outgoing mail: if you
forget to load the appropriate Rmail buffer before a message is
automatically saved, you'll trash the file.  To save a message to a
compressed Rmail file, run Rmail on the buffer first.  Then, use a
function that saves the article or message to the end of the *buffer*
rather than the file.  Better yet, uncompress the disk file first,
save your articles, and then recompress it.

I have also tested the code using "ange-ftp.el" to a limited extent.
Loading compressed files works fine, though I haven't tried saving
them.

Inserting compressed files in an existing buffer does not work in the
current version, either.  You have to load the compressed file into
another buffer and then "insert-buffer" it.

This is alpha code, provided as-is, with no guarantees or warranties
of any kind including fitness for a particular purpose.  Please do not
redistribute it in modified form.  I will be releasing a cleaner
version in the near future.  That version will be governed by GNU's
copyleft and will be redistributable subject to the terms of that
document.

Kevin Buhr <buhr@ccu.UManitoba.CA>

;;; autoZ.el
;;;
;;; Copyright (c) 1992 by Kevin Buhr
;;;
;;; This program code is provided as-is, with no guarantees or
;;; warranties of any kind including, but not limited to, fitness for
;;; any particular purpose.  The author is not responsible for the
;;; loss of data or other damages caused by the use of this software.
;;; USE IT AT YOUR OWN RISK.
;;; 
;;; This code incorporates modified GNU emacs lisp code, which is
;;; distributed under the GNU Copyleft document.  The distribution of
;;; this software may therefore be subject to additional limitations
;;; or considerations outlined in that document.  You are advised to
;;; consult that document before redistributing this program.
;;;
;;; -----
;;;
;;; "autoZ.el" provides automatic decompression and compression of
;;; data files from within GNU emacs.  To use it, optionally byte
;;; compile it, and load it in your initialization file.
;;;
;;; If you find any bugs in this code or come up with a good patch to
;;; increase its usefulness, please e-mail the author at the following
;;; address:
;;;
;;;		Kevin Buhr <buhr@ccu.UManitoba.CA>

;; Define variables
(defvar autoZ-magic-cookie "\037\235"
    "*A regular expression to match the beginning of a buffer and identify
its contents as compressed.  See also autoZ-compressed-filename-regexp.")

(defvar autoZ-compressed-filename-regexp ".\\.Z$"
  "*A regular expression that matches the filenames of compressed
files.  If a file has a name that is matched by this expression, autoZ
considers it a compressed file.  It will be uncompressed when read, if
necessary, and compressed when written, if necessary.

The \"if necessary\" qualification depends on the value of
autoZ-magic-cookie.")


;; Function to uncompress the buffer if necessary
(defun autoZ-uncompress-buffer-if-necessary ()
  "Uncompresses the current buffer if the autoZ-magic-cookie is 
found at the start and the filename is matched by
autoZ-compressed-filename-regexp."
  (if (and (string-match autoZ-compressed-filename-regexp (buffer-file-name))
	   (save-excursion
	     (goto-char (point-min))
	     (looking-at autoZ-magic-cookie)))
      (progn (message "Auto-uncompressing buffer...")
	     (let ((ro buffer-read-only) (bufmod (buffer-modified-p)))
	       (if ro (toggle-read-only))
	       (call-process-region (point-min) (point-max) "zcat" t t nil)
	       (if ro (toggle-read-only))
	       (set-buffer-modified-p bufmod))
	     (message "Auto-uncompressing buffer... done"))))

;; Function to recompress buffer if necessary
(defun autoZ-compress-buffer-if-necessary ()
  "Recompress and save the current buffer if the filename matches
autoZ-compressed-filename-regexp and the absence of the
autoZ-magic-cookie at the beginning of the buffer indicates it has not
already been compressed."
  (save-excursion
    (if (and (string-match autoZ-compressed-filename-regexp (buffer-file-name))
	     (progn (goto-char (point-min))
		    (not (looking-at autoZ-magic-cookie))))
	;; so, remember uncompressed version and then compress and save it
	(let ((ro buffer-read-only) (saved-point-max (point-max)))
	  (message "Auto-compressing buffer...")
	  (if ro (toggle-read-only))
	  (goto-char saved-point-max)
	  (call-process-region 
	   (point-min) saved-point-max "compress" nil t nil "-c")
	  (message "Auto-compressing buffer... done")
	  ;; following code stolen from basic-save-file defun
	  (if file-precious-flag
	      ;; If file is precious, rename it away before
	      ;; overwriting it.
	      (let ((rename t) nodelete
		    (file (concat buffer-file-name "#")))
		(condition-case ()
		    (progn (rename-file buffer-file-name file t)
			   (setq setmodes (file-modes file)))
		  (file-error (setq rename nil nodelete t)))
		(unwind-protect
		    (progn (clear-visited-file-modtime)
			   (write-region saved-point-max (point-max)
					 buffer-file-name nil t)
			   (setq rename nil))
		  ;; If rename is still t, writing failed.
		  ;; So rename the old file back to original name,
		  (if rename
		      (progn
			(rename-file file buffer-file-name t)
			(clear-visited-file-modtime))
		    ;; Otherwise we don't need the original file,
		    ;; so flush it.  Unless we already lost it.
		    (or nodelete
			(condition-case ()
			    (delete-file file)
			  (error nil))))))
	    ;; If file not writable, see if we can make it writable
	    ;; temporarily while we write it.
	    ;; But no need to do so if we have just backed it up
	    ;; (setmodes is set) because that says we're superseding.
	    (cond ((and tempsetmodes (not setmodes))
		   ;; Change the mode back, after writing.
		   (setq setmodes (file-modes buffer-file-name))
		   (set-file-modes buffer-file-name 511)))
	    (write-region saved-point-max (point-max) 
			  buffer-file-name nil t))
	  (delete-region saved-point-max (point-max))
	  (set-buffer-modified-p nil)
	  (setq buffer-read-only ro)
	  t)
      ;; otherwise, we didn't save anything
      nil)))

;; Add functions to appropriate hooks
(or (memq 'autoZ-uncompress-buffer-if-necessary find-file-hooks)
    (setq find-file-hooks
	  (append find-file-hooks
		  (list 'autoZ-uncompress-buffer-if-necessary))))

(or (memq 'autoZ-compress-buffer-if-necessary write-file-hooks)
    (setq write-file-hooks
	  (append write-file-hooks
		  (list 'autoZ-compress-buffer-if-necessary))))
Newsgroups: gnu.emacs.help