Mercurial > hg > xemacs-beta
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 |