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