Mercurial > hg > xemacs-beta
diff lisp/vm/vm-save.el @ 0:376386a54a3c r19-14
Import from CVS: tag r19-14
author | cvs |
---|---|
date | Mon, 13 Aug 2007 08:45:50 +0200 |
parents | |
children | 859a2309aef8 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/vm/vm-save.el Mon Aug 13 08:45:50 2007 +0200 @@ -0,0 +1,506 @@ +;;; Saving and piping messages under VM +;;; Copyright (C) 1989, 1990, 1993, 1994 Kyle E. Jones +;;; +;;; 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 1, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +(provide 'vm-save) + +;; (match-data) returns the match data as MARKERS, often corrupting +;; it in the process due to buffer narrowing, and the fact that buffers are +;; indexed from 1 while strings are indexed from 0. :-( +(defun vm-match-data () + (let ((index '(9 8 7 6 5 4 3 2 1 0)) + (list)) + (while index + (setq list (cons (match-beginning (car index)) + (cons (match-end (car index)) list)) + index (cdr index))) + list )) + +(defun vm-auto-select-folder (mp auto-folder-alist) + (condition-case error-data + (catch 'match + (let (header alist tuple-list) + (setq alist auto-folder-alist) + (while alist + (setq header (vm-get-header-contents (car mp) (car (car alist)))) + (if (null header) + () + (setq tuple-list (cdr (car alist))) + (while tuple-list + (if (let ((case-fold-search vm-auto-folder-case-fold-search)) + (string-match (car (car tuple-list)) header)) + ;; Don't waste time eval'ing an atom. + (if (atom (cdr (car tuple-list))) + (throw 'match (cdr (car tuple-list))) + (let* ((match-data (vm-match-data)) + ;; allow this buffer to live forever + (buf (get-buffer-create " *vm-auto-folder*")) + (result)) + ;; Set up a buffer that matches our cached + ;; match data. + (save-excursion + (set-buffer buf) + (widen) + (erase-buffer) + (insert header) + ;; It appears that get-buffer-create clobbers the + ;; match-data. + ;; + ;; The match data is off by one because we matched + ;; a string and Emacs indexes strings from 0 and + ;; buffers from 1. + ;; + ;; Also store-match-data only accepts MARKERS!! + ;; AUGHGHGH!! + (store-match-data + (mapcar + (function (lambda (n) (and n (vm-marker n)))) + (mapcar + (function (lambda (n) (and n (1+ n)))) + match-data))) + (setq result (eval (cdr (car tuple-list)))) + (while (consp result) + (setq result (vm-auto-select-folder mp result))) + (if result + (throw 'match result)))))) + (setq tuple-list (cdr tuple-list)))) + (setq alist (cdr alist))) + nil )) + (error (error "error processing vm-auto-folder-alist: %s" + (prin1-to-string error-data))))) + +(defun vm-auto-archive-messages (&optional arg) + "Save all unfiled messages that auto-match a folder via +vm-auto-folder-alist to their appropriate folders. Messages that +are flagged for deletion are not saved. + +Prefix arg means to ask user for confirmation before saving each message. + +When invoked on marked messages (via vm-next-command-uses-marks), +only marked messages are checked against vm-auto-folder-alist. + +The saved messages are flagged as `filed'." + (interactive "P") + (vm-select-folder-buffer) + (vm-check-for-killed-summary) + (vm-error-if-folder-empty) + (vm-unsaved-message "Archiving...") + (let ((auto-folder) + (archived 0)) + (unwind-protect + ;; Need separate (let ...) so vm-message-pointer can + ;; revert back in time for + ;; (vm-update-summary-and-mode-line). + ;; vm-last-save-folder is tucked away here since archives + ;; shouldn't affect its value. + (let ((vm-message-pointer + (if (eq last-command 'vm-next-command-uses-marks) + (vm-select-marked-or-prefixed-messages 0) + vm-message-list)) + (done nil) + stop-point + (vm-last-save-folder vm-last-save-folder) + (vm-move-after-deleting nil)) + ;; mark the place where we should stop. otherwise if any + ;; messages in this folder are archived to this folder + ;; we would file messages into this folder forever. + (setq stop-point (vm-last vm-message-pointer)) + (while (not done) + (and (not (vm-filed-flag (car vm-message-pointer))) + ;; don't archive deleted messages + (not (vm-deleted-flag (car vm-message-pointer))) + (setq auto-folder (vm-auto-select-folder + vm-message-pointer + vm-auto-folder-alist)) + (or (null arg) + (y-or-n-p + (format "Save message %s in folder %s? " + (vm-number-of (car vm-message-pointer)) + auto-folder))) + (let ((vm-delete-after-saving vm-delete-after-archiving)) + (if (not (string-equal auto-folder "/dev/null")) + (vm-save-message auto-folder)) + (vm-increment archived) + (vm-unsaved-message "%d archived, still working..." + archived))) + (setq done (eq vm-message-pointer stop-point) + vm-message-pointer (cdr vm-message-pointer)))) + ;; fix mode line + (intern (buffer-name) vm-buffers-needing-display-update) + (vm-update-summary-and-mode-line)) + (if (zerop archived) + (message "No messages archived") + (message "%d message%s archived" + archived (if (= 1 archived) "" "s"))))) + +(defun vm-save-message (folder &optional count) + "Save the current message to a mail folder. +If the folder already exists, the message will be appended to it. + +Prefix arg COUNT means save this message and the next COUNT-1 +messages. A negative COUNT means save this message and the +previous COUNT-1 messages. + +When invoked on marked messages (via vm-next-command-uses-marks), +all marked messages in the current folder are saved; other messages are +ignored. + +The saved messages are flagged as `filed'." + (interactive + (list + ;; protect value of last-command + (let ((last-command last-command) + (this-command this-command)) + (vm-follow-summary-cursor) + (let ((default (save-excursion + (vm-select-folder-buffer) + (vm-check-for-killed-summary) + (vm-error-if-folder-empty) + (or (vm-auto-select-folder vm-message-pointer + vm-auto-folder-alist) + vm-last-save-folder))) + (dir (or vm-folder-directory default-directory))) + (cond ((and default + (let ((default-directory dir)) + (file-directory-p default))) + (vm-read-file-name "Save in folder: " dir nil nil default)) + (default + (vm-read-file-name + (format "Save in folder: (default %s) " default) + dir default)) + (t + (vm-read-file-name "Save in folder: " dir nil))))) + (prefix-numeric-value current-prefix-arg))) + (let (unexpanded-folder) + (setq unexpanded-folder folder) + (vm-select-folder-buffer) + (vm-check-for-killed-summary) + (vm-error-if-folder-empty) + (vm-display nil nil '(vm-save-message) '(vm-save-message)) + (or count (setq count 1)) + ;; Expand the filename, forcing relative paths to resolve + ;; into the folder directory. + (let ((default-directory + (expand-file-name (or vm-folder-directory default-directory)))) + (setq folder (expand-file-name folder))) + ;; Confirm new folders, if the user requested this. + (if (and vm-confirm-new-folders (interactive-p) + (not (file-exists-p folder)) + (or (not vm-visit-when-saving) (not (vm-get-file-buffer folder))) + (not (y-or-n-p (format "%s does not exist, save there anyway? " + folder)))) + (error "Save aborted")) + ;; Check and see if we are currently visiting the folder + ;; that the user wants to save to. + (if (and (not vm-visit-when-saving) (vm-get-file-buffer folder)) + (error "Folder %s is being visited, cannot save." folder)) + (let ((mlist (vm-select-marked-or-prefixed-messages count)) + (m nil) (count 0) folder-buffer target-type) + (cond ((and mlist (eq vm-visit-when-saving t)) + (setq folder-buffer (or (vm-get-file-buffer folder) + ;; avoid letter bombs + (let ((inhibit-local-variables t) + (enable-local-variables nil)) + (find-file-noselect folder))))) + ((and mlist vm-visit-when-saving) + (setq folder-buffer (vm-get-file-buffer folder)))) + (if (and mlist vm-check-folder-types) + (progn + (setq target-type (or (vm-get-folder-type folder) + vm-default-folder-type + (and mlist + (vm-message-type-of (car mlist))))) + (if (eq target-type 'unknown) + (error "Folder %s's type is unrecognized" folder)))) + ;; if target folder is empty or nonexistent we need to + ;; write out the folder header first. + (if mlist + (let ((attrs (file-attributes folder))) + (if (or (null attrs) (= 0 (nth 7 attrs))) + (if (null folder-buffer) + (vm-write-string folder (vm-folder-header target-type)) + (vm-write-string folder-buffer + (vm-folder-header target-type)))))) + (save-excursion + (while mlist + (setq m (vm-real-message-of (car mlist))) + (set-buffer (vm-buffer-of m)) + (vm-save-restriction + (widen) + ;; have to stuff the attributes in all cases because + ;; the deleted attribute may have been stuffed + ;; previously and we don't want to save that attribute. + ;; also we don't want to save out the cached summary entry. + (vm-stuff-attributes m t) + (if (null folder-buffer) + (if (or (null vm-check-folder-types) + (eq target-type (vm-message-type-of m))) + (write-region (vm-start-of m) + (vm-end-of m) + folder t 'quiet) + (if (null vm-convert-folder-types) + (if (not (vm-virtual-message-p (car mlist))) + (error "Folder type mismatch: %s, %s" + (vm-message-type-of m) target-type) + (error "Message %s type mismatches folder %s" + (vm-number-of (car mlist)) + folder + (vm-message-type-of m) + target-type)) + (vm-write-string + folder + (vm-leading-message-separator target-type m t)) + (if (eq target-type 'From_-with-Content-Length) + (vm-write-string + folder + (concat vm-content-length-header " " + (vm-su-byte-count m) "\n"))) + (write-region (vm-headers-of m) + (vm-text-end-of m) + folder t 'quiet) + (vm-write-string + folder + (vm-trailing-message-separator target-type)))) + (save-excursion + (set-buffer folder-buffer) + ;; if the buffer is a live VM folder + ;; honor vm-folder-read-only. + (if vm-folder-read-only + (signal 'folder-read-only (list (current-buffer)))) + (let ((buffer-read-only nil)) + (vm-save-restriction + (widen) + (save-excursion + (goto-char (point-max)) + (if (or (null vm-check-folder-types) + (eq target-type (vm-message-type-of m))) + (insert-buffer-substring + (vm-buffer-of m) + (vm-start-of m) (vm-end-of m)) + (if (null vm-convert-folder-types) + (if (not (vm-virtual-message-p (car mlist))) + (error "Folder type mismatch: %s, %s" + (vm-message-type-of m) target-type) + (error "Message %s type mismatches folder %s" + (vm-number-of (car mlist)) + folder + (vm-message-type-of m) + target-type)) + (vm-write-string + (current-buffer) + (vm-leading-message-separator target-type m t)) + (if (eq target-type 'From_-with-Content-Length) + (vm-write-string + (current-buffer) + (concat vm-content-length-header " " + (vm-su-byte-count m) "\n"))) + (insert-buffer-substring (vm-buffer-of m) + (vm-headers-of m) + (vm-text-end-of m)) + (vm-write-string + (current-buffer) + (vm-trailing-message-separator target-type))))) + ;; vars should exist and be local + ;; but they may have strange values, + ;; so check the major-mode. + (cond ((eq major-mode 'vm-mode) + (vm-increment vm-messages-not-on-disk) + (vm-clear-modification-flag-undos))))))) + (if (null (vm-filed-flag m)) + (vm-set-filed-flag m t)) + (vm-increment count) + (vm-update-summary-and-mode-line) + (setq mlist (cdr mlist))))) + (if m + (if folder-buffer + (progn + (save-excursion + (set-buffer folder-buffer) + (if (eq major-mode 'vm-mode) + (progn + (vm-check-for-killed-summary) + (vm-assimilate-new-messages) + (if (null vm-message-pointer) + (progn (setq vm-message-pointer vm-message-list + vm-need-summary-pointer-update t) + (intern (buffer-name) + vm-buffers-needing-display-update) + (vm-preview-current-message)) + (vm-update-summary-and-mode-line))))) + (if (interactive-p) + (message "%d message%s saved to buffer %s" + count + (if (/= 1 count) "s" "") + (buffer-name folder-buffer)))) + (if (interactive-p) + (message "%d message%s saved to %s" + count (if (/= 1 count) "s" "") folder))))) + (setq vm-last-save-folder unexpanded-folder) + (if vm-delete-after-saving + (vm-delete-message count)))) + +(defun vm-save-message-sans-headers (file &optional count) + "Save the current message to a file, without its header section. +If the file already exists, the message will be appended to it. +Prefix arg COUNT means save the next COUNT messages. A negative COUNT means +save the previous COUNT. + +When invoked on marked messages (via vm-next-command-uses-marks), +all marked messages in the current folder are saved; other messages are +ignored. + +The saved messages are flagged as `written'. + +This command should NOT be used to save message to mail folders; use +vm-save-message instead (normally bound to `s')." + (interactive + ;; protect value of last-command + (let ((last-command last-command) + (this-command this-command)) + (vm-follow-summary-cursor) + (vm-select-folder-buffer) + (list + (vm-read-file-name + (if vm-last-written-file + (format "Write text to file: (default %s) " + vm-last-written-file) + "Write text to file: ") + nil vm-last-written-file nil) + (prefix-numeric-value current-prefix-arg)))) + (vm-select-folder-buffer) + (vm-check-for-killed-summary) + (vm-error-if-folder-empty) + (vm-display nil nil '(vm-save-message-sans-headers) + '(vm-save-message-sans-headers)) + (or count (setq count 1)) + (setq file (expand-file-name file)) + ;; Check and see if we are currently visiting the file + ;; that the user wants to save to. + (if (and (not vm-visit-when-saving) (vm-get-file-buffer file)) + (error "File %s is being visited, cannot save." file)) + (let ((mlist (vm-select-marked-or-prefixed-messages count)) + (m nil) file-buffer) + (cond ((and mlist (eq vm-visit-when-saving t)) + (setq file-buffer (or (vm-get-file-buffer file) + (find-file-noselect file)))) + ((and mlist vm-visit-when-saving) + (setq file-buffer (vm-get-file-buffer file)))) + (save-excursion + (while mlist + (setq m (vm-real-message-of (car mlist))) + (set-buffer (vm-buffer-of m)) + (vm-save-restriction + (widen) + (if (null file-buffer) + (write-region (vm-text-of m) + (vm-text-end-of m) + file t 'quiet) + (let ((start (vm-text-of m)) + (end (vm-text-end-of m))) + (save-excursion + (set-buffer file-buffer) + (save-excursion + (let (buffer-read-only) + (vm-save-restriction + (widen) + (save-excursion + (goto-char (point-max)) + (insert-buffer-substring + (vm-buffer-of m) + start end)))))))) + (if (null (vm-written-flag m)) + (vm-set-written-flag m t)) + (vm-update-summary-and-mode-line) + (setq mlist (cdr mlist))))) + (if m + (if file-buffer + (message "Message%s written to buffer %s" (if (/= 1 count) "s" "") + (buffer-name file-buffer)) + (message "Message%s written to %s" (if (/= 1 count) "s" "") file))) + (setq vm-last-written-file file))) + +(defun vm-pipe-message-to-command (command prefix-arg) + "Run shell command with the some or all of the current message as input. +By default the entire message is used. +With one \\[universal-argument] the text portion of the message is used. +With two \\[universal-argument]'s the header portion of the message is used. +With three \\[universal-argument]'s the visible header portion of the message + plus the text portion is used. + +When invoked on marked messages (via vm-next-command-uses-marks), +each marked message is successively piped to the shell command, +one message per command invocation. + +Output, if any, is displayed. The message is not altered." + (interactive + ;; protect value of last-command + (let ((last-command last-command) + (this-command this-command)) + (vm-follow-summary-cursor) + (vm-select-folder-buffer) + (list (read-string "Pipe to command: " vm-last-pipe-command) + current-prefix-arg))) + (vm-select-folder-buffer) + (vm-check-for-killed-summary) + (vm-error-if-folder-empty) + (setq vm-last-pipe-command command) + (let ((buffer (get-buffer-create "*Shell Command Output*")) + m + (pop-up-windows (and pop-up-windows (eq vm-mutable-windows t))) + ;; prefix arg doesn't have "normal" meaning here, so only call + ;; vm-select-marked-or-prefixed-messages if we're using marks. + (mlist (if (eq last-command 'vm-next-command-uses-marks) + (vm-select-marked-or-prefixed-messages 0) + (list (car vm-message-pointer))))) + (set-buffer buffer) + (erase-buffer) + (while mlist + (setq m (vm-real-message-of (car mlist))) + (set-buffer (vm-buffer-of m)) + (save-restriction + (widen) + (goto-char (vm-headers-of m)) + (cond ((equal prefix-arg nil) + (narrow-to-region (point) (vm-text-end-of m))) + ((equal prefix-arg '(4)) + (narrow-to-region (vm-text-of m) + (vm-text-end-of m))) + ((equal prefix-arg '(16)) + (narrow-to-region (point) (vm-text-of m))) + ((equal prefix-arg '(64)) + (narrow-to-region (vm-vheaders-of m) (vm-text-end-of m))) + (t (narrow-to-region (point) (vm-text-end-of m)))) + (let ((pop-up-windows (and pop-up-windows (eq vm-mutable-windows t)))) + (call-process-region (point-min) (point-max) + (or shell-file-name "sh") + nil buffer nil "-c" command))) + (setq mlist (cdr mlist))) + (set-buffer buffer) + (if (not (zerop (buffer-size))) + (vm-display buffer t '(vm-pipe-message-to-command) + '(vm-pipe-message-to-command)) + (vm-display nil nil '(vm-pipe-message-to-command) + '(vm-pipe-message-to-command))))) + +(defun vm-print-message () + "Print the current message." + (interactive) + (vm-pipe-message-to-command + (mapconcat (function identity) + (nconc (list vm-print-command) vm-print-command-switches) + " ") + '(64))) +