]> code.delx.au - gnu-emacs/blob - lisp/mail/mspools.el
dc81a7f3cc3b25d145787455120cb7d86426f818
[gnu-emacs] / lisp / mail / mspools.el
1 ;;; mspools.el --- show mail spools waiting to be read
2
3 ;; Copyright (C) 1997, 2001, 2002, 2003, 2004, 2005,
4 ;; 2006, 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
5
6 ;; Author: Stephen Eglen <stephen@gnu.org>
7 ;; Maintainer: Stephen Eglen <stephen@gnu.org>
8 ;; Created: 22 Jan 1997
9 ;; Keywords: mail
10 ;; location: http://www.anc.ed.ac.uk/~stephen/emacs/
11
12 ;; This file is part of GNU Emacs.
13
14 ;; GNU Emacs is free software: you can redistribute it and/or modify
15 ;; it under the terms of the GNU General Public License as published by
16 ;; the Free Software Foundation, either version 3 of the License, or
17 ;; (at your option) any later version.
18
19 ;; GNU Emacs is distributed in the hope that it will be useful,
20 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
21 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 ;; GNU General Public License for more details.
23
24 ;; You should have received a copy of the GNU General Public License
25 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
26
27 ;;; Commentary:
28
29 ;; If you use a mail filter (e.g. procmail, filter) to put mail messages in
30 ;; folders, this file will let you see which folders have mail waiting
31 ;; to be read in them. It assumes that new mail for the file `folder'
32 ;; is written by the filter to a file called `folder.spool'. (If the
33 ;; file writes directly to `folder' you may lose mail if new mail
34 ;; arrives whilst you are reading the folder in emacs, hence the use
35 ;; of a spool file.) For example, the following procmail recipe puts
36 ;; any mail with `emacs' in the subject line into the spool file
37 ;; `emacs.spool', ready to go into the folder `emacs'.
38 ;:0:
39 ;* ^Subject.*emacs
40 ;emacs.spool
41
42 ;; It also assumes that all of your spool files and mail folders live
43 ;; in the directory pointed to by `mspools-folder-directory', so you must
44 ;; set this (see Installation).
45
46 ;; When you run `mspools-show', it creates a *spools* buffer containing
47 ;; all of the spools in the folder directory that are waiting to be
48 ;; read. On each line is the spool name and its size in bytes. Move
49 ;; to the line of the folder that you would like to read, and then
50 ;; press return or space. The mailer (VM or RMAIL) should then read
51 ;; that folder and get the new mail for you. When you return to the
52 ;; *spools* buffer, you will either see "*" to indicate that the spool
53 ;; has been read, or the remaining unread spools, depending on the
54 ;; value of `mspools-update'.
55
56 ;; This file should work with both VM and RMAIL. See the variable
57 ;; `mspools-using-vm' for details.
58
59 ;;; Basic installation.
60 ;; (autoload 'mspools-show "mspools" "Show outstanding mail spools." t)
61 ;; (setq mspools-folder-directory "~/MAIL/")
62 ;;
63 ;; If you use VM, mspools-folder-directory will default to vm-folder-directory
64 ;; unless you have already given it a value.
65
66 ;; Extras.
67 ;;
68 ;; (global-set-key '[S-f1] 'mspools-show) ;Bind mspools-show to Shift F1.
69 ;; (setq mspools-update t) ;Automatically update buffer.
70
71 ;; Interface with the mail filter.
72 ;; We assume that the mail filter drops new mail into the spool
73 ;; `folder.spool'. If your spool files are something like folder.xyz
74 ;; for inbox `folder', then do:
75 ;; (setq mspools-suffix "xyz")
76 ;; If you use other conventions for your spool files, this code will
77 ;; need rewriting.
78
79 ;; Warning for VM users
80 ;; Don't use if you are not sure what you are doing. The value of
81 ;; vm-spool-files is altered, so you may not be able to read incoming
82 ;; mail with VM if this is incorrectly set.
83
84 ;; Useful settings for VM
85 ;; vm-auto-get-new-mail should be t (the default).
86
87 ;; Acknowledgements
88 ;; Thanks to jond@mitre.org (Jonathan Doughty) for help with code for
89 ;; setting up vm-spool-files.
90
91 ;;; TODO
92
93 ;; What if users have mail spools in more than one directory? Extend
94 ;; mspools-folder-directory to be a list of directories? Currently,
95 ;; if mail spools are in other directories, the way to read them is to
96 ;; put a symbolic link to the spool into the mspools-folder-directory.
97
98 ;; I was going to add mouse support so that you could click on a line
99 ;; to visit the buffer. Tell me if you want it, and I can put the
100 ;; code in (I don't use the mouse much, so I haven't bothered with it
101 ;; so far).
102
103 ;; Rather than showing size in bytes, could we see the number of msgs
104 ;; waiting? (Could be more time demanding / system dependent).
105 ;; Maybe just call a perl script to do all the hard work, and
106 ;; visualize the results in the buffer.
107
108 ;; Shrink wrap the buffer to remove excess white-space?
109
110 ;;; Code:
111
112 (defvar rmail-inbox-list)
113 (defvar vm-crash-box)
114 (defvar vm-folder-directory)
115 (defvar vm-init-file)
116 (defvar vm-init-file-loaded)
117 (defvar vm-primary-inbox)
118 (defvar vm-spool-files)
119
120 ;;; User Variables
121
122 (defgroup mspools nil
123 "Show mail spools waiting to be read."
124 :group 'mail
125 :link '(emacs-commentary-link :tag "Commentary" "mspools.el")
126 )
127
128 (defcustom mspools-update nil
129 "*Non-nil means update *spools* buffer after visiting any folder."
130 :type 'boolean
131 :group 'mspools)
132
133 (defcustom mspools-suffix "spool"
134 "*Extension used for spool files (not including full stop)."
135 :type 'string
136 :group 'mspools)
137
138 (defcustom mspools-using-vm (fboundp 'vm)
139 "*Non-nil if VM is used as mail reader, otherwise RMAIL is used."
140 :type 'boolean
141 :group 'mspools)
142
143 (defcustom mspools-folder-directory
144 (if (boundp 'vm-folder-directory)
145 vm-folder-directory
146 "~/MAIL/")
147 "*Directory where mail folders are kept. Ensure it has a trailing /.
148 Defaults to `vm-folder-directory' if bound else to ~/MAIL/."
149 :type 'directory
150 :group 'mspools)
151
152 (defcustom mspools-vm-system-mail (or (getenv "MAIL")
153 (concat rmail-spool-directory
154 (user-login-name)))
155 "*Spool file for main mailbox. Only used by VM.
156 This needs to be set to your primary mail spool - mspools will not run
157 without it. By default this will be set to the environment variable
158 $MAIL. Otherwise it will use `rmail-spool-directory' to guess where
159 your primary spool is. If this fails, set it to something like
160 /usr/spool/mail/login-name."
161 :type 'file
162 :group 'mspools)
163
164 ;;; Internal Variables
165
166 (defvar mspools-files nil
167 "List of entries (SPOOL . SIZE) giving spool name and file size.")
168
169 (defvar mspools-files-len nil
170 "Length of `mspools-files' list.")
171
172 (defvar mspools-buffer "*spools*"
173 "Name of buffer for displaying spool info.")
174
175 (defvar mspools-mode-map nil
176 "Keymap for the *spools* buffer.")
177
178 ;;; Code
179
180 ;;; VM Specific code
181 (if mspools-using-vm
182 ;; set up vm if not already loaded.
183 (progn
184 (require 'vm-vars)
185 (if (and (not vm-init-file-loaded) (file-readable-p vm-init-file))
186 (load-file vm-init-file))
187 (if (not mspools-folder-directory)
188 (setq mspools-folder-directory vm-folder-directory))
189 ))
190
191 (defun mspools-set-vm-spool-files ()
192 "Set value of `vm-spool-files'. Only needed for VM."
193 (if (not (file-readable-p mspools-vm-system-mail))
194 (error "Need to set mspools-vm-system-mail to the spool for primary inbox"))
195 (if (null mspools-folder-directory)
196 (error "Set `mspools-folder-directory' to where the spool files are"))
197 (setq
198 vm-spool-files
199 (append
200 (list
201 ;; Main mailbox
202 (list vm-primary-inbox
203 mspools-vm-system-mail ; your mailbox
204 vm-crash-box ;crash for mailbox
205 ))
206
207 ;; Mailing list inboxes
208 ;; must have VM already loaded to get vm-folder-directory.
209 (mapcar '(lambda (s)
210 "make the appropriate entry for vm-spool-files"
211 (list
212 (concat mspools-folder-directory s)
213 (concat mspools-folder-directory s "." mspools-suffix)
214 (concat mspools-folder-directory s ".crash")))
215 ;; So I create a vm-spool-files entry for each of those mail drops
216 (mapcar 'file-name-sans-extension
217 (directory-files mspools-folder-directory nil
218 (format "^[^.]+\\.%s" mspools-suffix)))
219 ))
220 ))
221
222 ;;; MSPOOLS-SHOW -- the main function
223 (defun mspools-show ( &optional noshow)
224 "Show the list of non-empty spool files in the *spools* buffer.
225 Buffer is not displayed if SHOW is non-nil."
226 (interactive)
227 (if (get-buffer mspools-buffer)
228 ;; buffer exists
229 (progn
230 (set-buffer mspools-buffer)
231 (setq buffer-read-only nil)
232 (delete-region (point-min) (point-max)))
233 ;; else buffer doesn't exist so create it
234 (get-buffer-create mspools-buffer))
235
236 ;; generate the list of spool files
237 (if mspools-using-vm
238 (mspools-set-vm-spool-files))
239
240 (mspools-get-spool-files)
241 (if (not noshow) (pop-to-buffer mspools-buffer))
242
243 (setq buffer-read-only t)
244 (mspools-mode)
245 )
246
247 (declare-function rmail-get-new-mail "rmail" (&optional file-name))
248
249 ;; External.
250 (declare-function vm-visit-folder "ext:vm-startup" (folder &optional read-only))
251
252 (defun mspools-visit-spool ()
253 "Visit the folder on the current line of the *spools* buffer."
254 (interactive)
255 (let ( spool-name folder-name)
256 (setq spool-name (mspools-get-spool-name))
257 (if (null spool-name)
258 (message "No spool on current line")
259
260 (setq folder-name (mspools-get-folder-from-spool spool-name))
261
262 ;; put in a little "*" to indicate spool file has been read.
263 (if (not mspools-update)
264 (save-excursion
265 (setq buffer-read-only nil)
266 (beginning-of-line)
267 (insert "*")
268 (delete-char 1)
269 (setq buffer-read-only t)
270 ))
271
272 (message "folder %s spool %s" folder-name spool-name)
273 (if (eq (count-lines (point-min)
274 (save-excursion
275 (end-of-line)
276 (point)))
277 mspools-files-len)
278 (forward-line (- 1 mspools-files-len)) ;back to top of list
279 ;; else just on to next line
280 (forward-line 1))
281
282 ;; Choose whether to use VM or RMAIL for reading folder.
283 (if mspools-using-vm
284 (vm-visit-folder (concat mspools-folder-directory folder-name))
285 ;; else using RMAIL
286 (rmail (concat mspools-folder-directory folder-name))
287 (setq rmail-inbox-list
288 (list (concat mspools-folder-directory spool-name)))
289 (rmail-get-new-mail))
290
291
292 (if mspools-update
293 ;; generate new list of spools.
294 (save-excursion
295 (mspools-show-again 'noshow))))))
296
297 (defun mspools-get-folder-from-spool (name)
298 "Return folder name corresponding to the spool file NAME."
299 ;; Simply strip of the extension.
300 (file-name-sans-extension name))
301
302 ;; Alternative version if you have more complicated mapping of spool name
303 ;; to file name.
304 ;(defun get-folder-from-spool-safe (name)
305 ; "Return the folder name corresponding to the spool file NAME."
306 ; (if (string-match "^\\(.*\\)\.spool$" name)
307 ; (substring name (match-beginning 1) (match-end 1))
308 ; (error "Could not extract folder name from spool name %s" name)))
309
310 ; test
311 ;(mspools-get-folder-from-spool "happy.spool")
312 ;(mspools-get-folder-from-spool "happy.sp")
313
314 (defun mspools-get-spool-name ()
315 "Return the name of the spool on the current line."
316 (let ((line-num (1- (count-lines (point-min)
317 (save-excursion
318 (end-of-line)
319 (point))
320 ))))
321 (car (nth line-num mspools-files))))
322
323 ;;; Keymap
324
325 (if mspools-mode-map
326 ()
327 (setq mspools-mode-map (make-sparse-keymap))
328
329 (define-key mspools-mode-map "\C-c\C-c" 'mspools-visit-spool)
330 (define-key mspools-mode-map "\C-m" 'mspools-visit-spool)
331 (define-key mspools-mode-map " " 'mspools-visit-spool)
332 (define-key mspools-mode-map "?" 'mspools-help)
333 (define-key mspools-mode-map "q" 'mspools-quit)
334 (define-key mspools-mode-map "n" 'next-line)
335 (define-key mspools-mode-map "p" 'previous-line)
336 (define-key mspools-mode-map "g" 'revert-buffer))
337
338 ;;; Spools mode functions
339
340 (defun mspools-revert-buffer (ignore noconfirm)
341 "Re-run mspools-show to revert the *spools* buffer."
342 (mspools-show 'noshow))
343
344 (defun mspools-show-again (&optional noshow)
345 "Update the *spools* buffer. This is useful if mspools-update is
346 nil."
347 (interactive)
348 (mspools-show noshow))
349
350 (defun mspools-help ()
351 "Show help for `mspools-mode'."
352 (interactive)
353 (describe-function 'mspools-mode))
354
355 (defun mspools-quit ()
356 "Quit the *spools* buffer."
357 (interactive)
358 (kill-buffer mspools-buffer))
359
360 (defun mspools-mode ()
361 "Major mode for output from mspools-show.
362 \\<mspools-mode-map>Move point to one of the items in this buffer, then use
363 \\[mspools-visit-spool] to go to the spool that the current line refers to.
364 \\[revert-buffer] to regenerate the list of spools.
365 \\{mspools-mode-map}"
366 (kill-all-local-variables)
367 (make-local-variable 'revert-buffer-function)
368 (setq revert-buffer-function 'mspools-revert-buffer)
369 (use-local-map mspools-mode-map)
370 (setq major-mode 'mspools-mode)
371 (setq mode-name "MSpools")
372 (run-mode-hooks 'mspools-mode-hook))
373
374 (defun mspools-get-spool-files ()
375 "Find the list of spool files and display them in *spools* buffer."
376 (let (folders head spool len beg end any)
377 (if (null mspools-folder-directory)
378 (error "Set `mspools-folder-directory' to where the spool files are"))
379 (setq folders (directory-files mspools-folder-directory nil
380 (format "^[^.]+\\.%s$" mspools-suffix)))
381 (setq folders (mapcar 'mspools-size-folder folders))
382 (setq folders (delq nil folders))
383 (setq mspools-files folders)
384 (setq mspools-files-len (length mspools-files))
385 (set-buffer mspools-buffer)
386 (while folders
387 (setq any t)
388 (setq head (car folders))
389 (setq spool (car head))
390 (setq len (cdr head))
391 (setq folders (cdr folders))
392 (setq beg (point))
393 (insert (format " %10d %s" len spool))
394 (setq end (point))
395 (insert "\n")
396 ;;(put-text-property beg end 'mouse-face 'highlight)
397 )
398 (if any
399 (delete-char -1)) ;delete last RET
400 (goto-char (point-min))
401 ))
402
403 (defun mspools-size-folder (spool)
404 "Return (SPOOL . SIZE ), if SIZE of spool file is non-zero."
405 ;; 7th file attribute is the size of the file in bytes.
406 (let ((file (concat mspools-folder-directory spool))
407 size)
408 (setq file (or (file-symlink-p file) file))
409 (setq size (nth 7 (file-attributes file)))
410 ;; size could be nil if the sym-link points to a non-existent file
411 ;; so check this first.
412 (if (and size (> size 0))
413 (cons spool size)
414 ;; else SPOOL is empty
415 nil)))
416
417 (provide 'mspools)
418
419 ;; arch-tag: 8990b3ee-68c8-4892-98f1-51a735c8bac6
420 ;;; mspools.el ends here