comparison 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
comparison
equal deleted inserted replaced
-1:000000000000 0:376386a54a3c
1 ;;; Saving and piping messages under VM
2 ;;; Copyright (C) 1989, 1990, 1993, 1994 Kyle E. Jones
3 ;;;
4 ;;; This program is free software; you can redistribute it and/or modify
5 ;;; it under the terms of the GNU General Public License as published by
6 ;;; the Free Software Foundation; either version 1, or (at your option)
7 ;;; any later version.
8 ;;;
9 ;;; This program is distributed in the hope that it will be useful,
10 ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
11 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 ;;; GNU General Public License for more details.
13 ;;;
14 ;;; You should have received a copy of the GNU General Public License
15 ;;; along with this program; if not, write to the Free Software
16 ;;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 (provide 'vm-save)
19
20 ;; (match-data) returns the match data as MARKERS, often corrupting
21 ;; it in the process due to buffer narrowing, and the fact that buffers are
22 ;; indexed from 1 while strings are indexed from 0. :-(
23 (defun vm-match-data ()
24 (let ((index '(9 8 7 6 5 4 3 2 1 0))
25 (list))
26 (while index
27 (setq list (cons (match-beginning (car index))
28 (cons (match-end (car index)) list))
29 index (cdr index)))
30 list ))
31
32 (defun vm-auto-select-folder (mp auto-folder-alist)
33 (condition-case error-data
34 (catch 'match
35 (let (header alist tuple-list)
36 (setq alist auto-folder-alist)
37 (while alist
38 (setq header (vm-get-header-contents (car mp) (car (car alist))))
39 (if (null header)
40 ()
41 (setq tuple-list (cdr (car alist)))
42 (while tuple-list
43 (if (let ((case-fold-search vm-auto-folder-case-fold-search))
44 (string-match (car (car tuple-list)) header))
45 ;; Don't waste time eval'ing an atom.
46 (if (atom (cdr (car tuple-list)))
47 (throw 'match (cdr (car tuple-list)))
48 (let* ((match-data (vm-match-data))
49 ;; allow this buffer to live forever
50 (buf (get-buffer-create " *vm-auto-folder*"))
51 (result))
52 ;; Set up a buffer that matches our cached
53 ;; match data.
54 (save-excursion
55 (set-buffer buf)
56 (widen)
57 (erase-buffer)
58 (insert header)
59 ;; It appears that get-buffer-create clobbers the
60 ;; match-data.
61 ;;
62 ;; The match data is off by one because we matched
63 ;; a string and Emacs indexes strings from 0 and
64 ;; buffers from 1.
65 ;;
66 ;; Also store-match-data only accepts MARKERS!!
67 ;; AUGHGHGH!!
68 (store-match-data
69 (mapcar
70 (function (lambda (n) (and n (vm-marker n))))
71 (mapcar
72 (function (lambda (n) (and n (1+ n))))
73 match-data)))
74 (setq result (eval (cdr (car tuple-list))))
75 (while (consp result)
76 (setq result (vm-auto-select-folder mp result)))
77 (if result
78 (throw 'match result))))))
79 (setq tuple-list (cdr tuple-list))))
80 (setq alist (cdr alist)))
81 nil ))
82 (error (error "error processing vm-auto-folder-alist: %s"
83 (prin1-to-string error-data)))))
84
85 (defun vm-auto-archive-messages (&optional arg)
86 "Save all unfiled messages that auto-match a folder via
87 vm-auto-folder-alist to their appropriate folders. Messages that
88 are flagged for deletion are not saved.
89
90 Prefix arg means to ask user for confirmation before saving each message.
91
92 When invoked on marked messages (via vm-next-command-uses-marks),
93 only marked messages are checked against vm-auto-folder-alist.
94
95 The saved messages are flagged as `filed'."
96 (interactive "P")
97 (vm-select-folder-buffer)
98 (vm-check-for-killed-summary)
99 (vm-error-if-folder-empty)
100 (vm-unsaved-message "Archiving...")
101 (let ((auto-folder)
102 (archived 0))
103 (unwind-protect
104 ;; Need separate (let ...) so vm-message-pointer can
105 ;; revert back in time for
106 ;; (vm-update-summary-and-mode-line).
107 ;; vm-last-save-folder is tucked away here since archives
108 ;; shouldn't affect its value.
109 (let ((vm-message-pointer
110 (if (eq last-command 'vm-next-command-uses-marks)
111 (vm-select-marked-or-prefixed-messages 0)
112 vm-message-list))
113 (done nil)
114 stop-point
115 (vm-last-save-folder vm-last-save-folder)
116 (vm-move-after-deleting nil))
117 ;; mark the place where we should stop. otherwise if any
118 ;; messages in this folder are archived to this folder
119 ;; we would file messages into this folder forever.
120 (setq stop-point (vm-last vm-message-pointer))
121 (while (not done)
122 (and (not (vm-filed-flag (car vm-message-pointer)))
123 ;; don't archive deleted messages
124 (not (vm-deleted-flag (car vm-message-pointer)))
125 (setq auto-folder (vm-auto-select-folder
126 vm-message-pointer
127 vm-auto-folder-alist))
128 (or (null arg)
129 (y-or-n-p
130 (format "Save message %s in folder %s? "
131 (vm-number-of (car vm-message-pointer))
132 auto-folder)))
133 (let ((vm-delete-after-saving vm-delete-after-archiving))
134 (if (not (string-equal auto-folder "/dev/null"))
135 (vm-save-message auto-folder))
136 (vm-increment archived)
137 (vm-unsaved-message "%d archived, still working..."
138 archived)))
139 (setq done (eq vm-message-pointer stop-point)
140 vm-message-pointer (cdr vm-message-pointer))))
141 ;; fix mode line
142 (intern (buffer-name) vm-buffers-needing-display-update)
143 (vm-update-summary-and-mode-line))
144 (if (zerop archived)
145 (message "No messages archived")
146 (message "%d message%s archived"
147 archived (if (= 1 archived) "" "s")))))
148
149 (defun vm-save-message (folder &optional count)
150 "Save the current message to a mail folder.
151 If the folder already exists, the message will be appended to it.
152
153 Prefix arg COUNT means save this message and the next COUNT-1
154 messages. A negative COUNT means save this message and the
155 previous COUNT-1 messages.
156
157 When invoked on marked messages (via vm-next-command-uses-marks),
158 all marked messages in the current folder are saved; other messages are
159 ignored.
160
161 The saved messages are flagged as `filed'."
162 (interactive
163 (list
164 ;; protect value of last-command
165 (let ((last-command last-command)
166 (this-command this-command))
167 (vm-follow-summary-cursor)
168 (let ((default (save-excursion
169 (vm-select-folder-buffer)
170 (vm-check-for-killed-summary)
171 (vm-error-if-folder-empty)
172 (or (vm-auto-select-folder vm-message-pointer
173 vm-auto-folder-alist)
174 vm-last-save-folder)))
175 (dir (or vm-folder-directory default-directory)))
176 (cond ((and default
177 (let ((default-directory dir))
178 (file-directory-p default)))
179 (vm-read-file-name "Save in folder: " dir nil nil default))
180 (default
181 (vm-read-file-name
182 (format "Save in folder: (default %s) " default)
183 dir default))
184 (t
185 (vm-read-file-name "Save in folder: " dir nil)))))
186 (prefix-numeric-value current-prefix-arg)))
187 (let (unexpanded-folder)
188 (setq unexpanded-folder folder)
189 (vm-select-folder-buffer)
190 (vm-check-for-killed-summary)
191 (vm-error-if-folder-empty)
192 (vm-display nil nil '(vm-save-message) '(vm-save-message))
193 (or count (setq count 1))
194 ;; Expand the filename, forcing relative paths to resolve
195 ;; into the folder directory.
196 (let ((default-directory
197 (expand-file-name (or vm-folder-directory default-directory))))
198 (setq folder (expand-file-name folder)))
199 ;; Confirm new folders, if the user requested this.
200 (if (and vm-confirm-new-folders (interactive-p)
201 (not (file-exists-p folder))
202 (or (not vm-visit-when-saving) (not (vm-get-file-buffer folder)))
203 (not (y-or-n-p (format "%s does not exist, save there anyway? "
204 folder))))
205 (error "Save aborted"))
206 ;; Check and see if we are currently visiting the folder
207 ;; that the user wants to save to.
208 (if (and (not vm-visit-when-saving) (vm-get-file-buffer folder))
209 (error "Folder %s is being visited, cannot save." folder))
210 (let ((mlist (vm-select-marked-or-prefixed-messages count))
211 (m nil) (count 0) folder-buffer target-type)
212 (cond ((and mlist (eq vm-visit-when-saving t))
213 (setq folder-buffer (or (vm-get-file-buffer folder)
214 ;; avoid letter bombs
215 (let ((inhibit-local-variables t)
216 (enable-local-variables nil))
217 (find-file-noselect folder)))))
218 ((and mlist vm-visit-when-saving)
219 (setq folder-buffer (vm-get-file-buffer folder))))
220 (if (and mlist vm-check-folder-types)
221 (progn
222 (setq target-type (or (vm-get-folder-type folder)
223 vm-default-folder-type
224 (and mlist
225 (vm-message-type-of (car mlist)))))
226 (if (eq target-type 'unknown)
227 (error "Folder %s's type is unrecognized" folder))))
228 ;; if target folder is empty or nonexistent we need to
229 ;; write out the folder header first.
230 (if mlist
231 (let ((attrs (file-attributes folder)))
232 (if (or (null attrs) (= 0 (nth 7 attrs)))
233 (if (null folder-buffer)
234 (vm-write-string folder (vm-folder-header target-type))
235 (vm-write-string folder-buffer
236 (vm-folder-header target-type))))))
237 (save-excursion
238 (while mlist
239 (setq m (vm-real-message-of (car mlist)))
240 (set-buffer (vm-buffer-of m))
241 (vm-save-restriction
242 (widen)
243 ;; have to stuff the attributes in all cases because
244 ;; the deleted attribute may have been stuffed
245 ;; previously and we don't want to save that attribute.
246 ;; also we don't want to save out the cached summary entry.
247 (vm-stuff-attributes m t)
248 (if (null folder-buffer)
249 (if (or (null vm-check-folder-types)
250 (eq target-type (vm-message-type-of m)))
251 (write-region (vm-start-of m)
252 (vm-end-of m)
253 folder t 'quiet)
254 (if (null vm-convert-folder-types)
255 (if (not (vm-virtual-message-p (car mlist)))
256 (error "Folder type mismatch: %s, %s"
257 (vm-message-type-of m) target-type)
258 (error "Message %s type mismatches folder %s"
259 (vm-number-of (car mlist))
260 folder
261 (vm-message-type-of m)
262 target-type))
263 (vm-write-string
264 folder
265 (vm-leading-message-separator target-type m t))
266 (if (eq target-type 'From_-with-Content-Length)
267 (vm-write-string
268 folder
269 (concat vm-content-length-header " "
270 (vm-su-byte-count m) "\n")))
271 (write-region (vm-headers-of m)
272 (vm-text-end-of m)
273 folder t 'quiet)
274 (vm-write-string
275 folder
276 (vm-trailing-message-separator target-type))))
277 (save-excursion
278 (set-buffer folder-buffer)
279 ;; if the buffer is a live VM folder
280 ;; honor vm-folder-read-only.
281 (if vm-folder-read-only
282 (signal 'folder-read-only (list (current-buffer))))
283 (let ((buffer-read-only nil))
284 (vm-save-restriction
285 (widen)
286 (save-excursion
287 (goto-char (point-max))
288 (if (or (null vm-check-folder-types)
289 (eq target-type (vm-message-type-of m)))
290 (insert-buffer-substring
291 (vm-buffer-of m)
292 (vm-start-of m) (vm-end-of m))
293 (if (null vm-convert-folder-types)
294 (if (not (vm-virtual-message-p (car mlist)))
295 (error "Folder type mismatch: %s, %s"
296 (vm-message-type-of m) target-type)
297 (error "Message %s type mismatches folder %s"
298 (vm-number-of (car mlist))
299 folder
300 (vm-message-type-of m)
301 target-type))
302 (vm-write-string
303 (current-buffer)
304 (vm-leading-message-separator target-type m t))
305 (if (eq target-type 'From_-with-Content-Length)
306 (vm-write-string
307 (current-buffer)
308 (concat vm-content-length-header " "
309 (vm-su-byte-count m) "\n")))
310 (insert-buffer-substring (vm-buffer-of m)
311 (vm-headers-of m)
312 (vm-text-end-of m))
313 (vm-write-string
314 (current-buffer)
315 (vm-trailing-message-separator target-type)))))
316 ;; vars should exist and be local
317 ;; but they may have strange values,
318 ;; so check the major-mode.
319 (cond ((eq major-mode 'vm-mode)
320 (vm-increment vm-messages-not-on-disk)
321 (vm-clear-modification-flag-undos)))))))
322 (if (null (vm-filed-flag m))
323 (vm-set-filed-flag m t))
324 (vm-increment count)
325 (vm-update-summary-and-mode-line)
326 (setq mlist (cdr mlist)))))
327 (if m
328 (if folder-buffer
329 (progn
330 (save-excursion
331 (set-buffer folder-buffer)
332 (if (eq major-mode 'vm-mode)
333 (progn
334 (vm-check-for-killed-summary)
335 (vm-assimilate-new-messages)
336 (if (null vm-message-pointer)
337 (progn (setq vm-message-pointer vm-message-list
338 vm-need-summary-pointer-update t)
339 (intern (buffer-name)
340 vm-buffers-needing-display-update)
341 (vm-preview-current-message))
342 (vm-update-summary-and-mode-line)))))
343 (if (interactive-p)
344 (message "%d message%s saved to buffer %s"
345 count
346 (if (/= 1 count) "s" "")
347 (buffer-name folder-buffer))))
348 (if (interactive-p)
349 (message "%d message%s saved to %s"
350 count (if (/= 1 count) "s" "") folder)))))
351 (setq vm-last-save-folder unexpanded-folder)
352 (if vm-delete-after-saving
353 (vm-delete-message count))))
354
355 (defun vm-save-message-sans-headers (file &optional count)
356 "Save the current message to a file, without its header section.
357 If the file already exists, the message will be appended to it.
358 Prefix arg COUNT means save the next COUNT messages. A negative COUNT means
359 save the previous COUNT.
360
361 When invoked on marked messages (via vm-next-command-uses-marks),
362 all marked messages in the current folder are saved; other messages are
363 ignored.
364
365 The saved messages are flagged as `written'.
366
367 This command should NOT be used to save message to mail folders; use
368 vm-save-message instead (normally bound to `s')."
369 (interactive
370 ;; protect value of last-command
371 (let ((last-command last-command)
372 (this-command this-command))
373 (vm-follow-summary-cursor)
374 (vm-select-folder-buffer)
375 (list
376 (vm-read-file-name
377 (if vm-last-written-file
378 (format "Write text to file: (default %s) "
379 vm-last-written-file)
380 "Write text to file: ")
381 nil vm-last-written-file nil)
382 (prefix-numeric-value current-prefix-arg))))
383 (vm-select-folder-buffer)
384 (vm-check-for-killed-summary)
385 (vm-error-if-folder-empty)
386 (vm-display nil nil '(vm-save-message-sans-headers)
387 '(vm-save-message-sans-headers))
388 (or count (setq count 1))
389 (setq file (expand-file-name file))
390 ;; Check and see if we are currently visiting the file
391 ;; that the user wants to save to.
392 (if (and (not vm-visit-when-saving) (vm-get-file-buffer file))
393 (error "File %s is being visited, cannot save." file))
394 (let ((mlist (vm-select-marked-or-prefixed-messages count))
395 (m nil) file-buffer)
396 (cond ((and mlist (eq vm-visit-when-saving t))
397 (setq file-buffer (or (vm-get-file-buffer file)
398 (find-file-noselect file))))
399 ((and mlist vm-visit-when-saving)
400 (setq file-buffer (vm-get-file-buffer file))))
401 (save-excursion
402 (while mlist
403 (setq m (vm-real-message-of (car mlist)))
404 (set-buffer (vm-buffer-of m))
405 (vm-save-restriction
406 (widen)
407 (if (null file-buffer)
408 (write-region (vm-text-of m)
409 (vm-text-end-of m)
410 file t 'quiet)
411 (let ((start (vm-text-of m))
412 (end (vm-text-end-of m)))
413 (save-excursion
414 (set-buffer file-buffer)
415 (save-excursion
416 (let (buffer-read-only)
417 (vm-save-restriction
418 (widen)
419 (save-excursion
420 (goto-char (point-max))
421 (insert-buffer-substring
422 (vm-buffer-of m)
423 start end))))))))
424 (if (null (vm-written-flag m))
425 (vm-set-written-flag m t))
426 (vm-update-summary-and-mode-line)
427 (setq mlist (cdr mlist)))))
428 (if m
429 (if file-buffer
430 (message "Message%s written to buffer %s" (if (/= 1 count) "s" "")
431 (buffer-name file-buffer))
432 (message "Message%s written to %s" (if (/= 1 count) "s" "") file)))
433 (setq vm-last-written-file file)))
434
435 (defun vm-pipe-message-to-command (command prefix-arg)
436 "Run shell command with the some or all of the current message as input.
437 By default the entire message is used.
438 With one \\[universal-argument] the text portion of the message is used.
439 With two \\[universal-argument]'s the header portion of the message is used.
440 With three \\[universal-argument]'s the visible header portion of the message
441 plus the text portion is used.
442
443 When invoked on marked messages (via vm-next-command-uses-marks),
444 each marked message is successively piped to the shell command,
445 one message per command invocation.
446
447 Output, if any, is displayed. The message is not altered."
448 (interactive
449 ;; protect value of last-command
450 (let ((last-command last-command)
451 (this-command this-command))
452 (vm-follow-summary-cursor)
453 (vm-select-folder-buffer)
454 (list (read-string "Pipe to command: " vm-last-pipe-command)
455 current-prefix-arg)))
456 (vm-select-folder-buffer)
457 (vm-check-for-killed-summary)
458 (vm-error-if-folder-empty)
459 (setq vm-last-pipe-command command)
460 (let ((buffer (get-buffer-create "*Shell Command Output*"))
461 m
462 (pop-up-windows (and pop-up-windows (eq vm-mutable-windows t)))
463 ;; prefix arg doesn't have "normal" meaning here, so only call
464 ;; vm-select-marked-or-prefixed-messages if we're using marks.
465 (mlist (if (eq last-command 'vm-next-command-uses-marks)
466 (vm-select-marked-or-prefixed-messages 0)
467 (list (car vm-message-pointer)))))
468 (set-buffer buffer)
469 (erase-buffer)
470 (while mlist
471 (setq m (vm-real-message-of (car mlist)))
472 (set-buffer (vm-buffer-of m))
473 (save-restriction
474 (widen)
475 (goto-char (vm-headers-of m))
476 (cond ((equal prefix-arg nil)
477 (narrow-to-region (point) (vm-text-end-of m)))
478 ((equal prefix-arg '(4))
479 (narrow-to-region (vm-text-of m)
480 (vm-text-end-of m)))
481 ((equal prefix-arg '(16))
482 (narrow-to-region (point) (vm-text-of m)))
483 ((equal prefix-arg '(64))
484 (narrow-to-region (vm-vheaders-of m) (vm-text-end-of m)))
485 (t (narrow-to-region (point) (vm-text-end-of m))))
486 (let ((pop-up-windows (and pop-up-windows (eq vm-mutable-windows t))))
487 (call-process-region (point-min) (point-max)
488 (or shell-file-name "sh")
489 nil buffer nil "-c" command)))
490 (setq mlist (cdr mlist)))
491 (set-buffer buffer)
492 (if (not (zerop (buffer-size)))
493 (vm-display buffer t '(vm-pipe-message-to-command)
494 '(vm-pipe-message-to-command))
495 (vm-display nil nil '(vm-pipe-message-to-command)
496 '(vm-pipe-message-to-command)))))
497
498 (defun vm-print-message ()
499 "Print the current message."
500 (interactive)
501 (vm-pipe-message-to-command
502 (mapconcat (function identity)
503 (nconc (list vm-print-command) vm-print-command-switches)
504 " ")
505 '(64)))
506