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