(ido-file-extensions-order): New defcustom.
[bpt/emacs.git] / lisp / gnus / mail-source.el
CommitLineData
c113de23 1;;; mail-source.el --- functions for fetching mail
23f87bed
MB
2;; Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004
3;; Free Software Foundation, Inc.
c113de23
GM
4
5;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
6;; Keywords: news, mail
7
8;; This file is part of GNU Emacs.
9
10;; GNU Emacs is free software; you can redistribute it and/or modify
11;; it under the terms of the GNU General Public License as published by
12;; the Free Software Foundation; either version 2, or (at your option)
13;; any later version.
14
15;; GNU Emacs is distributed in the hope that it will be useful,
16;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18;; GNU General Public License for more details.
19
20;; You should have received a copy of the GNU General Public License
21;; along with GNU Emacs; see the file COPYING. If not, write to the
22;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
23;; Boston, MA 02111-1307, USA.
24
25;;; Commentary:
26
27;;; Code:
28
4f926b3e
DL
29(eval-when-compile
30 (require 'cl)
31 (require 'imap)
32 (eval-when-compile (defvar display-time-mail-function)))
c113de23
GM
33(eval-and-compile
34 (autoload 'pop3-movemail "pop3")
4f926b3e 35 (autoload 'pop3-get-message-count "pop3")
23f87bed
MB
36 (autoload 'nnheader-cancel-timer "nnheader")
37 (autoload 'nnheader-run-at-time "nnheader"))
c113de23 38(require 'format-spec)
4f926b3e 39(require 'mm-util)
23f87bed 40(require 'message) ;; for `message-directory'
c113de23
GM
41
42(defgroup mail-source nil
43 "The mail-fetching library."
ce9401f3 44 :version "21.1"
c113de23
GM
45 :group 'gnus)
46
4f926b3e
DL
47;; Define these at compile time to avoid dragging in imap always.
48(defconst mail-source-imap-authenticators
49 (eval-when-compile
50 (mapcar (lambda (a)
51 (list 'const (car a)))
52 imap-authenticator-alist)))
53(defconst mail-source-imap-streams
54 (eval-when-compile
55 (mapcar (lambda (a)
56 (list 'const (car a)))
57 imap-stream-alist)))
58
72fc0418 59(defcustom mail-sources nil
c113de23 60 "*Where the mail backends will look for incoming mail.
ce9401f3
DL
61This variable is a list of mail source specifiers.
62See Info node `(gnus)Mail Source Specifiers'."
c113de23 63 :group 'mail-source
23f87bed 64 :link '(custom-manual "(gnus)Mail Source Specifiers")
ce9401f3 65 :type `(repeat
4f926b3e
DL
66 (choice :format "%[Value Menu%] %v"
67 :value (file)
68 (cons :tag "Spool file"
69 (const :format "" file)
70 (checklist :tag "Options" :greedy t
71 (group :inline t
72 (const :format "" :value :path)
73 file)))
ce9401f3 74 (cons :tag "Several files in a directory"
4f926b3e
DL
75 (const :format "" directory)
76 (checklist :tag "Options" :greedy t
77 (group :inline t
78 (const :format "" :value :path)
79 (directory :tag "Path"))
80 (group :inline t
81 (const :format "" :value :suffix)
82 (string :tag "Suffix"))
83 (group :inline t
84 (const :format "" :value :predicate)
85 (function :tag "Predicate"))
86 (group :inline t
87 (const :format "" :value :prescript)
23f87bed
MB
88 (choice :tag "Prescript"
89 :value nil
90 (string :format "%v")
91 (function :format "%v")))
4f926b3e
DL
92 (group :inline t
93 (const :format "" :value :postscript)
23f87bed
MB
94 (choice :tag "Postscript"
95 :value nil
96 (string :format "%v")
97 (function :format "%v")))
4f926b3e
DL
98 (group :inline t
99 (const :format "" :value :plugged)
100 (boolean :tag "Plugged"))))
ce9401f3 101 (cons :tag "POP3 server"
4f926b3e
DL
102 (const :format "" pop)
103 (checklist :tag "Options" :greedy t
104 (group :inline t
a1506d29 105 (const :format "" :value :server)
4f926b3e
DL
106 (string :tag "Server"))
107 (group :inline t
a1506d29 108 (const :format "" :value :port)
4f926b3e 109 (choice :tag "Port"
a1506d29 110 :value "pop3"
4f926b3e
DL
111 (number :format "%v")
112 (string :format "%v")))
113 (group :inline t
114 (const :format "" :value :user)
115 (string :tag "User"))
116 (group :inline t
117 (const :format "" :value :password)
118 (string :tag "Password"))
119 (group :inline t
120 (const :format "" :value :program)
121 (string :tag "Program"))
122 (group :inline t
123 (const :format "" :value :prescript)
23f87bed
MB
124 (choice :tag "Prescript"
125 :value nil
126 (string :format "%v")
127 (function :format "%v")))
4f926b3e
DL
128 (group :inline t
129 (const :format "" :value :postscript)
23f87bed
MB
130 (choice :tag "Postscript"
131 :value nil
132 (string :format "%v")
133 (function :format "%v")))
4f926b3e
DL
134 (group :inline t
135 (const :format "" :value :function)
136 (function :tag "Function"))
137 (group :inline t
a1506d29 138 (const :format ""
4f926b3e
DL
139 :value :authentication)
140 (choice :tag "Authentication"
141 :value apop
142 (const password)
143 (const apop)))
144 (group :inline t
145 (const :format "" :value :plugged)
146 (boolean :tag "Plugged"))))
ce9401f3 147 (cons :tag "Maildir (qmail, postfix...)"
4f926b3e
DL
148 (const :format "" maildir)
149 (checklist :tag "Options" :greedy t
150 (group :inline t
151 (const :format "" :value :path)
152 (directory :tag "Path"))
153 (group :inline t
154 (const :format "" :value :plugged)
155 (boolean :tag "Plugged"))))
ce9401f3 156 (cons :tag "IMAP server"
4f926b3e
DL
157 (const :format "" imap)
158 (checklist :tag "Options" :greedy t
159 (group :inline t
160 (const :format "" :value :server)
161 (string :tag "Server"))
162 (group :inline t
163 (const :format "" :value :port)
a1506d29
JB
164 (choice :tag "Port"
165 :value 143
4f926b3e
DL
166 number string))
167 (group :inline t
168 (const :format "" :value :user)
169 (string :tag "User"))
170 (group :inline t
171 (const :format "" :value :password)
172 (string :tag "Password"))
173 (group :inline t
174 (const :format "" :value :stream)
175 (choice :tag "Stream"
176 :value network
177 ,@mail-source-imap-streams))
23f87bed
MB
178 (group :inline t
179 (const :format "" :value :program)
180 (string :tag "Program"))
4f926b3e
DL
181 (group :inline t
182 (const :format ""
183 :value :authenticator)
184 (choice :tag "Authenticator"
185 :value login
186 ,@mail-source-imap-authenticators))
187 (group :inline t
188 (const :format "" :value :mailbox)
5dd7fd91
DL
189 (string :tag "Mailbox"
190 :value "INBOX"))
4f926b3e
DL
191 (group :inline t
192 (const :format "" :value :predicate)
a1506d29 193 (string :tag "Predicate"
5dd7fd91 194 :value "UNSEEN UNDELETED"))
4f926b3e
DL
195 (group :inline t
196 (const :format "" :value :fetchflag)
5dd7fd91
DL
197 (string :tag "Fetchflag"
198 :value "\\Deleted"))
4f926b3e
DL
199 (group :inline t
200 (const :format ""
201 :value :dontexpunge)
202 (boolean :tag "Dontexpunge"))
203 (group :inline t
204 (const :format "" :value :plugged)
205 (boolean :tag "Plugged"))))
ce9401f3 206 (cons :tag "Webmail server"
4f926b3e
DL
207 (const :format "" webmail)
208 (checklist :tag "Options" :greedy t
a1506d29 209 (group :inline t
4f926b3e
DL
210 (const :format "" :value :subtype)
211 ;; Should be generated from
212 ;; `webmail-type-definition', but we
213 ;; can't require webmail without W3.
214 (choice :tag "Subtype"
215 :value hotmail
216 (const hotmail)
217 (const yahoo)
218 (const netaddress)
219 (const netscape)
220 (const my-deja)))
221 (group :inline t
222 (const :format "" :value :user)
223 (string :tag "User"))
224 (group :inline t
225 (const :format "" :value :password)
226 (string :tag "Password"))
227 (group :inline t
228 (const :format ""
229 :value :dontexpunge)
230 (boolean :tag "Dontexpunge"))
231 (group :inline t
232 (const :format "" :value :plugged)
233 (boolean :tag "Plugged")))))))
c113de23 234
23f87bed
MB
235(defcustom mail-source-ignore-errors nil
236 "*Ignore errors when querying mail sources.
237If nil, the user will be prompted when an error occurs. If non-nil,
a08b59c9
MB
238the error will be ignored."
239 :version "21.4"
240 :group 'mail-source
241 :type 'boolean)
23f87bed 242
c113de23
GM
243(defcustom mail-source-primary-source nil
244 "*Primary source for incoming mail.
245If non-nil, this maildrop will be checked periodically for new mail."
246 :group 'mail-source
247 :type 'sexp)
248
23f87bed
MB
249(defcustom mail-source-flash t
250 "*If non-nil, flash periodically when mail is available."
251 :group 'mail-source
252 :type 'boolean)
253
c113de23
GM
254(defcustom mail-source-crash-box "~/.emacs-mail-crash-box"
255 "File where mail will be stored while processing it."
256 :group 'mail-source
257 :type 'file)
258
23f87bed 259(defcustom mail-source-directory message-directory
531e5812 260 "Directory where incoming mail source files (if any) will be stored."
c113de23
GM
261 :group 'mail-source
262 :type 'directory)
263
264(defcustom mail-source-default-file-modes 384
265 "Set the mode bits of all new mail files to this integer."
266 :group 'mail-source
267 :type 'integer)
268
805b7fc0 269(defcustom mail-source-delete-incoming t
23f87bed
MB
270 "*If non-nil, delete incoming files after handling.
271If t, delete immediately, if nil, never delete. If a positive number, delete
272files older than number of days."
273 ;; Note: The removing happens in `mail-source-callback', i.e. no old
274 ;; incoming files will be deleted, unless you receive new mail.
275 ;;
276 ;; You may also set this to `nil' and call `mail-source-delete-old-incoming'
277 ;; from a hook or interactively.
278 :group 'mail-source
279 :type '(choice (const :tag "immediately" t)
280 (const :tag "never" nil)
281 (integer :tag "days")))
282
283(defcustom mail-source-delete-old-incoming-confirm t
284 "*If non-nil, ask for for confirmation before deleting old incoming files.
285This variable only applies when `mail-source-delete-incoming' is a positive
286number."
a08b59c9 287 :version "21.4"
c113de23
GM
288 :group 'mail-source
289 :type 'boolean)
290
291(defcustom mail-source-incoming-file-prefix "Incoming"
292 "Prefix for file name for storing incoming mail"
293 :group 'mail-source
294 :type 'string)
295
296(defcustom mail-source-report-new-mail-interval 5
297 "Interval in minutes between checks for new mail."
298 :group 'mail-source
299 :type 'number)
300
301(defcustom mail-source-idle-time-delay 5
302 "Number of idle seconds to wait before checking for new mail."
303 :group 'mail-source
304 :type 'number)
305
23f87bed
MB
306(defcustom mail-source-movemail-program nil
307 "If non-nil, name of program for fetching new mail."
a08b59c9 308 :version "21.4"
23f87bed
MB
309 :group 'mail-source
310 :type '(choice (const nil) string))
311
c113de23
GM
312;;; Internal variables.
313
314(defvar mail-source-string ""
315 "A dynamically bound string that says what the current mail source is.")
316
317(defvar mail-source-new-mail-available nil
318 "Flag indicating when new mail is available.")
319
320(eval-and-compile
321 (defvar mail-source-common-keyword-map
322 '((:plugged))
323 "Mapping from keywords to default values.
324Common keywords should be listed here.")
325
326 (defvar mail-source-keyword-map
327 '((file
328 (:prescript)
329 (:prescript-delay)
330 (:postscript)
331 (:path (or (getenv "MAIL")
4f926b3e 332 (expand-file-name (user-login-name) rmail-spool-directory))))
c113de23 333 (directory
cf92160d
SZ
334 (:prescript)
335 (:prescript-delay)
336 (:postscript)
c113de23
GM
337 (:path)
338 (:suffix ".spool")
339 (:predicate identity))
340 (pop
341 (:prescript)
342 (:prescript-delay)
343 (:postscript)
344 (:server (getenv "MAILHOST"))
345 (:port 110)
346 (:user (or (user-login-name) (getenv "LOGNAME") (getenv "USER")))
347 (:program)
348 (:function)
349 (:password)
350 (:authentication password))
351 (maildir
352 (:path (or (getenv "MAILDIR") "~/Maildir/"))
23f87bed 353 (:subdirs ("cur" "new"))
c113de23
GM
354 (:function))
355 (imap
356 (:server (getenv "MAILHOST"))
357 (:port)
358 (:stream)
23f87bed 359 (:program)
c113de23
GM
360 (:authentication)
361 (:user (or (user-login-name) (getenv "LOGNAME") (getenv "USER")))
362 (:password)
363 (:mailbox "INBOX")
364 (:predicate "UNSEEN UNDELETED")
365 (:fetchflag "\\Deleted")
23f87bed
MB
366 (:prescript)
367 (:prescript-delay)
368 (:postscript)
c113de23
GM
369 (:dontexpunge))
370 (webmail
371 (:subtype hotmail)
372 (:user (or (user-login-name) (getenv "LOGNAME") (getenv "USER")))
373 (:password)
374 (:dontexpunge)
375 (:authentication password)))
376 "Mapping from keywords to default values.
377All keywords that can be used must be listed here."))
378
379(defvar mail-source-fetcher-alist
380 '((file mail-source-fetch-file)
381 (directory mail-source-fetch-directory)
382 (pop mail-source-fetch-pop)
383 (maildir mail-source-fetch-maildir)
384 (imap mail-source-fetch-imap)
385 (webmail mail-source-fetch-webmail))
386 "A mapping from source type to fetcher function.")
387
388(defvar mail-source-password-cache nil)
389
390(defvar mail-source-plugged t)
391
392;;; Functions
393
394(eval-and-compile
395 (defun mail-source-strip-keyword (keyword)
396 "Strip the leading colon off the KEYWORD."
397 (intern (substring (symbol-name keyword) 1))))
398
399(eval-and-compile
400 (defun mail-source-bind-1 (type)
401 (let* ((defaults (cdr (assq type mail-source-keyword-map)))
402 default bind)
403 (while (setq default (pop defaults))
404 (push (list (mail-source-strip-keyword (car default))
405 nil)
406 bind))
407 bind)))
408
409(defmacro mail-source-bind (type-source &rest body)
410 "Return a `let' form that binds all variables in source TYPE.
411TYPE-SOURCE is a list where the first element is the TYPE, and
412the second variable is the SOURCE.
413At run time, the mail source specifier SOURCE will be inspected,
414and the variables will be set according to it. Variables not
415specified will be given default values.
416
417After this is done, BODY will be executed in the scope
418of the `let' form.
419
420The variables bound and their default values are described by
421the `mail-source-keyword-map' variable."
422 `(let ,(mail-source-bind-1 (car type-source))
423 (mail-source-set-1 ,(cadr type-source))
424 ,@body))
425
426(put 'mail-source-bind 'lisp-indent-function 1)
23f87bed 427(put 'mail-source-bind 'edebug-form-spec '(sexp body))
c113de23
GM
428
429(defun mail-source-set-1 (source)
430 (let* ((type (pop source))
431 (defaults (cdr (assq type mail-source-keyword-map)))
432 default value keyword)
433 (while (setq default (pop defaults))
434 (set (mail-source-strip-keyword (setq keyword (car default)))
435 (if (setq value (plist-get source keyword))
436 (mail-source-value value)
437 (mail-source-value (cadr default)))))))
438
439(eval-and-compile
440 (defun mail-source-bind-common-1 ()
441 (let* ((defaults mail-source-common-keyword-map)
442 default bind)
443 (while (setq default (pop defaults))
444 (push (list (mail-source-strip-keyword (car default))
445 nil)
446 bind))
447 bind)))
448
449(defun mail-source-set-common-1 (source)
450 (let* ((type (pop source))
451 (defaults mail-source-common-keyword-map)
452 (defaults-1 (cdr (assq type mail-source-keyword-map)))
453 default value keyword)
454 (while (setq default (pop defaults))
455 (set (mail-source-strip-keyword (setq keyword (car default)))
456 (if (setq value (plist-get source keyword))
457 (mail-source-value value)
458 (if (setq value (assq keyword defaults-1))
459 (mail-source-value (cadr value))
460 (mail-source-value (cadr default))))))))
461
462(defmacro mail-source-bind-common (source &rest body)
463 "Return a `let' form that binds all common variables.
464See `mail-source-bind'."
465 `(let ,(mail-source-bind-common-1)
466 (mail-source-set-common-1 source)
467 ,@body))
468
469(put 'mail-source-bind-common 'lisp-indent-function 1)
23f87bed 470(put 'mail-source-bind-common 'edebug-form-spec '(sexp body))
c113de23
GM
471
472(defun mail-source-value (value)
473 "Return the value of VALUE."
474 (cond
475 ;; String
476 ((stringp value)
477 value)
478 ;; Function
479 ((and (listp value)
480 (functionp (car value)))
481 (eval value))
482 ;; Just return the value.
483 (t
484 value)))
485
486(defun mail-source-fetch (source callback)
487 "Fetch mail from SOURCE and call CALLBACK zero or more times.
488CALLBACK will be called with the name of the file where (some of)
489the mail from SOURCE is put.
490Return the number of files that were found."
491 (mail-source-bind-common source
492 (if (or mail-source-plugged plugged)
493 (save-excursion
494 (let ((function (cadr (assq (car source) mail-source-fetcher-alist)))
495 (found 0))
496 (unless function
497 (error "%S is an invalid mail source specification" source))
498 ;; If there's anything in the crash box, we do it first.
499 (when (file-exists-p mail-source-crash-box)
500 (message "Processing mail from %s..." mail-source-crash-box)
501 (setq found (mail-source-callback
502 callback mail-source-crash-box)))
503 (+ found
23f87bed 504 (if (or debug-on-quit debug-on-error)
c113de23 505 (funcall function source callback)
23f87bed
MB
506 (condition-case err
507 (funcall function source callback)
508 (error
509 (if (and (not mail-source-ignore-errors)
510 (not
511 (yes-or-no-p
512 (format "Mail source %s error (%s). Continue? "
513 (if (memq ':password source)
514 (let ((s (copy-sequence source)))
515 (setcar (cdr (memq ':password s))
516 "********")
517 s)
518 source)
519 (cadr err)))))
520 (error "Cannot get new mail"))
521 0)))))))))
522
523(defun mail-source-delete-old-incoming (&optional age confirm)
524 "Remove incoming files older than AGE days.
525If CONFIRM is non-nil, ask for confirmation before removing a file."
526 (interactive "P")
527 (let* ((high2days (/ 65536.0 60 60 24));; convert high bits to days
528 (low2days (/ 1.0 65536.0)) ;; convert low bits to days
529 (diff (if (natnump age) age 30));; fallback, if no valid AGE given
530 currday files)
531 (setq files (directory-files
532 mail-source-directory t
533 (concat mail-source-incoming-file-prefix "*"))
534 currday (* (car (current-time)) high2days)
535 currday (+ currday (* low2days (nth 1 (current-time)))))
536 (while files
537 (let* ((ffile (car files))
538 (bfile (gnus-replace-in-string
539 ffile "\\`.*/\\([^/]+\\)\\'" "\\1"))
540 (filetime (nth 5 (file-attributes ffile)))
541 (fileday (* (car filetime) high2days))
542 (fileday (+ fileday (* low2days (nth 1 filetime)))))
543 (setq files (cdr files))
544 (when (and (> (- currday fileday) diff)
545 (gnus-message 8 "File `%s' is older than %s day(s)"
546 bfile diff)
547 (or (not confirm)
548 (y-or-n-p (concat "Remove file `" bfile "'? "))))
549 (delete-file ffile))))))
c113de23
GM
550
551(defun mail-source-callback (callback info)
552 "Call CALLBACK on the mail file, and then remove the mail file.
553Pass INFO on to CALLBACK."
554 (if (or (not (file-exists-p mail-source-crash-box))
555 (zerop (nth 7 (file-attributes mail-source-crash-box))))
556 (progn
557 (when (file-exists-p mail-source-crash-box)
558 (delete-file mail-source-crash-box))
559 0)
560 (prog1
561 (funcall callback mail-source-crash-box info)
562 (when (file-exists-p mail-source-crash-box)
563 ;; Delete or move the incoming mail out of the way.
23f87bed 564 (if (eq mail-source-delete-incoming t)
c113de23
GM
565 (delete-file mail-source-crash-box)
566 (let ((incoming
23f87bed 567 (mm-make-temp-file
c113de23
GM
568 (expand-file-name
569 mail-source-incoming-file-prefix
570 mail-source-directory))))
571 (unless (file-exists-p (file-name-directory incoming))
572 (make-directory (file-name-directory incoming) t))
23f87bed
MB
573 (rename-file mail-source-crash-box incoming t)
574 ;; remove old incoming files?
575 (when (natnump mail-source-delete-incoming)
576 (mail-source-delete-old-incoming
577 mail-source-delete-incoming
578 mail-source-delete-old-incoming-confirm))))))))
c113de23
GM
579
580(defun mail-source-movemail (from to)
581 "Move FROM to TO using movemail."
582 (if (not (file-writable-p to))
583 (error "Can't write to crash box %s. Not moving mail" to)
584 (let ((to (file-truename (expand-file-name to)))
585 errors result)
586 (setq to (file-truename to)
587 from (file-truename from))
588 ;; Set TO if have not already done so, and rename or copy
589 ;; the file FROM to TO if and as appropriate.
590 (cond
591 ((file-exists-p to)
592 ;; The crash box exists already.
593 t)
594 ((not (file-exists-p from))
595 ;; There is no inbox.
596 (setq to nil))
597 ((zerop (nth 7 (file-attributes from)))
598 ;; Empty file.
599 (setq to nil))
600 (t
601 ;; If getting from mail spool directory, use movemail to move
602 ;; rather than just renaming, so as to interlock with the
603 ;; mailer.
604 (unwind-protect
605 (save-excursion
606 (setq errors (generate-new-buffer " *mail source loss*"))
607 (let ((default-directory "/"))
608 (setq result
609 (apply
610 'call-process
611 (append
612 (list
23f87bed
MB
613 (or mail-source-movemail-program
614 (expand-file-name "movemail" exec-directory))
c113de23
GM
615 nil errors nil from to)))))
616 (when (file-exists-p to)
617 (set-file-modes to mail-source-default-file-modes))
23f87bed
MB
618 (if (and (or (not (buffer-modified-p errors))
619 (zerop (buffer-size errors)))
620 (and (numberp result)
621 (zerop result)))
c113de23
GM
622 ;; No output => movemail won.
623 t
624 (set-buffer errors)
625 ;; There may be a warning about older revisions. We
626 ;; ignore that.
627 (goto-char (point-min))
628 (if (search-forward "older revision" nil t)
629 t
630 ;; Probably a real error.
631 (subst-char-in-region (point-min) (point-max) ?\n ?\ )
632 (goto-char (point-max))
633 (skip-chars-backward " \t")
634 (delete-region (point) (point-max))
635 (goto-char (point-min))
636 (when (looking-at "movemail: ")
637 (delete-region (point-min) (match-end 0)))
23f87bed 638 ;; Result may be a signal description string.
c113de23 639 (unless (yes-or-no-p
23f87bed 640 (format "movemail: %s (%s return). Continue? "
c113de23
GM
641 (buffer-string) result))
642 (error "%s" (buffer-string)))
643 (setq to nil)))))))
644 (when (and errors
645 (buffer-name errors))
646 (kill-buffer errors))
647 ;; Return whether we moved successfully or not.
648 to)))
649
650(defun mail-source-movemail-and-remove (from to)
651 "Move FROM to TO using movemail, then remove FROM if empty."
652 (or (not (mail-source-movemail from to))
653 (not (zerop (nth 7 (file-attributes from))))
654 (delete-file from)))
655
c113de23 656(defun mail-source-fetch-with-program (program)
23f87bed
MB
657 (eq 0 (call-process shell-file-name nil nil nil
658 shell-command-switch program)))
c113de23
GM
659
660(defun mail-source-run-script (script spec &optional delay)
661 (when script
23f87bed 662 (if (functionp script)
c113de23
GM
663 (funcall script)
664 (mail-source-call-script
665 (format-spec script spec))))
666 (when delay
667 (sleep-for delay)))
668
669(defun mail-source-call-script (script)
670 (let ((background nil))
671 (when (string-match "& *$" script)
672 (setq script (substring script 0 (match-beginning 0))
673 background 0))
674 (call-process shell-file-name nil background nil
675 shell-command-switch script)))
676
677;;;
678;;; Different fetchers
679;;;
680
681(defun mail-source-fetch-file (source callback)
682 "Fetcher for single-file sources."
683 (mail-source-bind (file source)
684 (mail-source-run-script
685 prescript (format-spec-make ?t mail-source-crash-box)
686 prescript-delay)
687 (let ((mail-source-string (format "file:%s" path)))
688 (if (mail-source-movemail path mail-source-crash-box)
689 (prog1
690 (mail-source-callback callback path)
691 (mail-source-run-script
692 postscript (format-spec-make ?t mail-source-crash-box)))
693 0))))
694
695(defun mail-source-fetch-directory (source callback)
696 "Fetcher for directory sources."
697 (mail-source-bind (directory source)
35037882 698 (mail-source-run-script
23f87bed 699 prescript (format-spec-make ?t path) prescript-delay)
c113de23
GM
700 (let ((found 0)
701 (mail-source-string (format "directory:%s" path)))
702 (dolist (file (directory-files
703 path t (concat (regexp-quote suffix) "$")))
704 (when (and (file-regular-p file)
705 (funcall predicate file)
706 (mail-source-movemail file mail-source-crash-box))
707 (incf found (mail-source-callback callback file))))
23f87bed 708 (mail-source-run-script postscript (format-spec-make ?t path))
c113de23
GM
709 found)))
710
711(defun mail-source-fetch-pop (source callback)
712 "Fetcher for single-file sources."
713 (mail-source-bind (pop source)
714 (mail-source-run-script
715 prescript
716 (format-spec-make ?p password ?t mail-source-crash-box
717 ?s server ?P port ?u user)
718 prescript-delay)
719 (let ((from (format "%s:%s:%s" server user port))
720 (mail-source-string (format "pop:%s@%s" user server))
721 result)
722 (when (eq authentication 'password)
723 (setq password
724 (or password
725 (cdr (assoc from mail-source-password-cache))
23f87bed 726 (read-passwd
c113de23
GM
727 (format "Password for %s at %s: " user server)))))
728 (when server
729 (setenv "MAILHOST" server))
730 (setq result
731 (cond
732 (program
733 (mail-source-fetch-with-program
734 (format-spec
735 program
736 (format-spec-make ?p password ?t mail-source-crash-box
737 ?s server ?P port ?u user))))
738 (function
739 (funcall function mail-source-crash-box))
740 ;; The default is to use pop3.el.
741 (t
742 (let ((pop3-password password)
743 (pop3-maildrop user)
744 (pop3-mailhost server)
745 (pop3-port port)
746 (pop3-authentication-scheme
747 (if (eq authentication 'apop) 'apop 'pass)))
23f87bed
MB
748 (if (or debug-on-quit debug-on-error)
749 (save-excursion (pop3-movemail mail-source-crash-box))
750 (condition-case err
751 (save-excursion (pop3-movemail mail-source-crash-box))
752 (error
753 ;; We nix out the password in case the error
754 ;; was because of a wrong password being given.
755 (setq mail-source-password-cache
756 (delq (assoc from mail-source-password-cache)
757 mail-source-password-cache))
758 (signal (car err) (cdr err)))))))))
c113de23
GM
759 (if result
760 (progn
761 (when (eq authentication 'password)
762 (unless (assoc from mail-source-password-cache)
763 (push (cons from password) mail-source-password-cache)))
764 (prog1
765 (mail-source-callback callback server)
766 ;; Update display-time's mail flag, if relevant.
767 (if (equal source mail-source-primary-source)
768 (setq mail-source-new-mail-available nil))
769 (mail-source-run-script
770 postscript
771 (format-spec-make ?p password ?t mail-source-crash-box
772 ?s server ?P port ?u user))))
773 ;; We nix out the password in case the error
774 ;; was because of a wrong password being given.
775 (setq mail-source-password-cache
776 (delq (assoc from mail-source-password-cache)
777 mail-source-password-cache))
778 0))))
779
780(defun mail-source-check-pop (source)
781 "Check whether there is new mail."
782 (mail-source-bind (pop source)
783 (let ((from (format "%s:%s:%s" server user port))
784 (mail-source-string (format "pop:%s@%s" user server))
785 result)
786 (when (eq authentication 'password)
787 (setq password
788 (or password
789 (cdr (assoc from mail-source-password-cache))
23f87bed 790 (read-passwd
c113de23
GM
791 (format "Password for %s at %s: " user server))))
792 (unless (assoc from mail-source-password-cache)
793 (push (cons from password) mail-source-password-cache)))
794 (when server
795 (setenv "MAILHOST" server))
796 (setq result
797 (cond
798 ;; No easy way to check whether mail is waiting for these.
799 (program)
800 (function)
801 ;; The default is to use pop3.el.
802 (t
803 (let ((pop3-password password)
804 (pop3-maildrop user)
805 (pop3-mailhost server)
806 (pop3-port port)
807 (pop3-authentication-scheme
808 (if (eq authentication 'apop) 'apop 'pass)))
23f87bed
MB
809 (if (or debug-on-quit debug-on-error)
810 (save-excursion (pop3-get-message-count))
811 (condition-case err
812 (save-excursion (pop3-get-message-count))
813 (error
814 ;; We nix out the password in case the error
815 ;; was because of a wrong password being given.
816 (setq mail-source-password-cache
817 (delq (assoc from mail-source-password-cache)
818 mail-source-password-cache))
819 (signal (car err) (cdr err)))))))))
c113de23
GM
820 (if result
821 ;; Inform display-time that we have new mail.
822 (setq mail-source-new-mail-available (> result 0))
823 ;; We nix out the password in case the error
824 ;; was because of a wrong password being given.
825 (setq mail-source-password-cache
826 (delq (assoc from mail-source-password-cache)
827 mail-source-password-cache)))
828 result)))
829
23f87bed
MB
830(defun mail-source-touch-pop ()
831 "Open and close a POP connection shortly.
832POP server should be defined in `mail-source-primary-source' (which is
833preferred) or `mail-sources'. You may use it for the POP-before-SMTP
834authentication. To do that, you need to set the
835`message-send-mail-function' variable as `message-smtpmail-send-it'
836and put the following line in your ~/.gnus.el file:
837
838\(add-hook 'message-send-mail-hook 'mail-source-touch-pop)
839
840See the Gnus manual for details."
841 (let ((sources (if mail-source-primary-source
842 (list mail-source-primary-source)
843 mail-sources)))
844 (while sources
845 (if (eq 'pop (car (car sources)))
846 (mail-source-check-pop (car sources)))
847 (setq sources (cdr sources)))))
848
c113de23
GM
849(defun mail-source-new-mail-p ()
850 "Handler for `display-time' to indicate when new mail is available."
23f87bed
MB
851 ;; Flash (ie. ring the visible bell) if mail is available.
852 (if (and mail-source-flash mail-source-new-mail-available)
853 (let ((visible-bell t))
854 (ding)))
c113de23
GM
855 ;; Only report flag setting; flag is updated on a different schedule.
856 mail-source-new-mail-available)
857
858
859(defvar mail-source-report-new-mail nil)
860(defvar mail-source-report-new-mail-timer nil)
861(defvar mail-source-report-new-mail-idle-timer nil)
862
a1506d29 863(eval-when-compile
126cbb42
DL
864 (if (featurep 'xemacs)
865 (require 'itimer)
866 (require 'timer)))
c113de23
GM
867
868(defun mail-source-start-idle-timer ()
869 ;; Start our idle timer if necessary, so we delay the check until the
870 ;; user isn't typing.
871 (unless mail-source-report-new-mail-idle-timer
872 (setq mail-source-report-new-mail-idle-timer
873 (run-with-idle-timer
874 mail-source-idle-time-delay
875 nil
876 (lambda ()
23f87bed
MB
877 (unwind-protect
878 (mail-source-check-pop mail-source-primary-source)
879 (setq mail-source-report-new-mail-idle-timer nil)))))
c113de23
GM
880 ;; Since idle timers created when Emacs is already in the idle
881 ;; state don't get activated until Emacs _next_ becomes idle, we
882 ;; need to force our timer to be considered active now. We do
883 ;; this by being naughty and poking the timer internals directly
884 ;; (element 0 of the vector is nil if the timer is active).
885 (aset mail-source-report-new-mail-idle-timer 0 nil)))
886
887(defun mail-source-report-new-mail (arg)
888 "Toggle whether to report when new mail is available.
889This only works when `display-time' is enabled."
890 (interactive "P")
891 (if (not mail-source-primary-source)
715a2ca2 892 (error "Need to set `mail-source-primary-source' to check for new mail"))
c113de23
GM
893 (let ((on (if (null arg)
894 (not mail-source-report-new-mail)
895 (> (prefix-numeric-value arg) 0))))
896 (setq mail-source-report-new-mail on)
897 (and mail-source-report-new-mail-timer
72fc0418 898 (nnheader-cancel-timer mail-source-report-new-mail-timer))
c113de23 899 (and mail-source-report-new-mail-idle-timer
72fc0418 900 (nnheader-cancel-timer mail-source-report-new-mail-idle-timer))
c113de23
GM
901 (setq mail-source-report-new-mail-timer nil)
902 (setq mail-source-report-new-mail-idle-timer nil)
903 (if on
904 (progn
905 (require 'time)
ce9401f3 906 ;; display-time-mail-function is an Emacs 21 feature.
c113de23
GM
907 (setq display-time-mail-function #'mail-source-new-mail-p)
908 ;; Set up the main timer.
909 (setq mail-source-report-new-mail-timer
23f87bed
MB
910 (nnheader-run-at-time
911 (* 60 mail-source-report-new-mail-interval)
912 (* 60 mail-source-report-new-mail-interval)
913 #'mail-source-start-idle-timer))
c113de23
GM
914 ;; When you get new mail, clear "Mail" from the mode line.
915 (add-hook 'nnmail-post-get-new-mail-hook
916 'display-time-event-handler)
917 (message "Mail check enabled"))
918 (setq display-time-mail-function nil)
919 (remove-hook 'nnmail-post-get-new-mail-hook
920 'display-time-event-handler)
921 (message "Mail check disabled"))))
922
923(defun mail-source-fetch-maildir (source callback)
924 "Fetcher for maildir sources."
925 (mail-source-bind (maildir source)
926 (let ((found 0)
927 mail-source-string)
928 (unless (string-match "/$" path)
929 (setq path (concat path "/")))
930 (dolist (subdir subdirs)
931 (when (file-directory-p (concat path subdir))
932 (setq mail-source-string (format "maildir:%s%s" path subdir))
933 (dolist (file (directory-files (concat path subdir) t))
934 (when (and (not (file-directory-p file))
935 (not (if function
936 (funcall function file mail-source-crash-box)
a1506d29 937 (let ((coding-system-for-write
c113de23 938 mm-text-coding-system)
a1506d29 939 (coding-system-for-read
c113de23
GM
940 mm-text-coding-system))
941 (with-temp-file mail-source-crash-box
942 (insert-file-contents file)
943 (goto-char (point-min))
23f87bed
MB
944;;; ;; Unix mail format
945;;; (unless (looking-at "\n*From ")
946;;; (insert "From maildir "
947;;; (current-time-string) "\n"))
948;;; (while (re-search-forward "^From " nil t)
949;;; (replace-match ">From "))
950;;; (goto-char (point-max))
126cbb42 951;;; (insert "\n\n")
c113de23 952 ;; MMDF mail format
126cbb42 953 (insert "\001\001\001\001\n"))
c113de23
GM
954 (delete-file file)))))
955 (incf found (mail-source-callback callback file))))))
956 found)))
957
958(eval-and-compile
959 (autoload 'imap-open "imap")
960 (autoload 'imap-authenticate "imap")
961 (autoload 'imap-mailbox-select "imap")
962 (autoload 'imap-mailbox-unselect "imap")
963 (autoload 'imap-mailbox-close "imap")
964 (autoload 'imap-search "imap")
965 (autoload 'imap-fetch "imap")
966 (autoload 'imap-close "imap")
967 (autoload 'imap-error-text "imap")
968 (autoload 'imap-message-flags-add "imap")
969 (autoload 'imap-list-to-message-set "imap")
72fc0418 970 (autoload 'imap-range-to-message-set "imap")
c113de23
GM
971 (autoload 'nnheader-ms-strip-cr "nnheader"))
972
72fc0418
DL
973(defvar mail-source-imap-file-coding-system 'binary
974 "Coding system for the crashbox made by `mail-source-fetch-imap'.")
975
c113de23
GM
976(defun mail-source-fetch-imap (source callback)
977 "Fetcher for imap sources."
978 (mail-source-bind (imap source)
23f87bed
MB
979 (mail-source-run-script
980 prescript (format-spec-make ?p password ?t mail-source-crash-box
981 ?s server ?P port ?u user)
982 prescript-delay)
c113de23
GM
983 (let ((from (format "%s:%s:%s" server user port))
984 (found 0)
23f87bed 985 (buf (generate-new-buffer " *imap source*"))
c113de23 986 (mail-source-string (format "imap:%s:%s" server mailbox))
23f87bed 987 (imap-shell-program (or (list program) imap-shell-program))
c113de23
GM
988 remove)
989 (if (and (imap-open server port stream authentication buf)
990 (imap-authenticate
991 user (or (cdr (assoc from mail-source-password-cache))
992 password) buf)
993 (imap-mailbox-select mailbox nil buf))
4f926b3e 994 (let ((coding-system-for-write mail-source-imap-file-coding-system)
126cbb42 995 str)
c113de23 996 (with-temp-file mail-source-crash-box
4f926b3e
DL
997 ;; Avoid converting 8-bit chars from inserted strings to
998 ;; multibyte.
999 (mm-disable-multibyte)
c113de23
GM
1000 ;; remember password
1001 (with-current-buffer buf
23f87bed
MB
1002 (when (and imap-password
1003 (not (assoc from mail-source-password-cache)))
c113de23
GM
1004 (push (cons from imap-password) mail-source-password-cache)))
1005 ;; if predicate is nil, use all uids
1006 (dolist (uid (imap-search (or predicate "1:*") buf))
23f87bed
MB
1007 (when (setq str
1008 (if (imap-capability 'IMAP4rev1 buf)
1009 (caddar (imap-fetch uid "BODY.PEEK[]"
1010 'BODYDETAIL nil buf))
1011 (imap-fetch uid "RFC822.PEEK" 'RFC822 nil buf)))
c113de23
GM
1012 (push uid remove)
1013 (insert "From imap " (current-time-string) "\n")
1014 (save-excursion
1015 (insert str "\n\n"))
1016 (while (re-search-forward "^From " nil t)
1017 (replace-match ">From "))
1018 (goto-char (point-max))))
1019 (nnheader-ms-strip-cr))
1020 (incf found (mail-source-callback callback server))
1021 (when (and remove fetchflag)
23f87bed 1022 (setq remove (nreverse remove))
c113de23 1023 (imap-message-flags-add
72fc0418
DL
1024 (imap-range-to-message-set (gnus-compress-sequence remove))
1025 fetchflag nil buf))
c113de23
GM
1026 (if dontexpunge
1027 (imap-mailbox-unselect buf)
23f87bed 1028 (imap-mailbox-close nil buf))
c113de23
GM
1029 (imap-close buf))
1030 (imap-close buf)
1031 ;; We nix out the password in case the error
1032 ;; was because of a wrong password being given.
1033 (setq mail-source-password-cache
1034 (delq (assoc from mail-source-password-cache)
1035 mail-source-password-cache))
23f87bed 1036 (error "IMAP error: %s" (imap-error-text buf)))
c113de23 1037 (kill-buffer buf)
23f87bed
MB
1038 (mail-source-run-script
1039 postscript
1040 (format-spec-make ?p password ?t mail-source-crash-box
1041 ?s server ?P port ?u user))
c113de23
GM
1042 found)))
1043
1044(eval-and-compile
1045 (autoload 'webmail-fetch "webmail"))
1046
1047(defun mail-source-fetch-webmail (source callback)
1048 "Fetch for webmail source."
1049 (mail-source-bind (webmail source)
1050 (let ((mail-source-string (format "webmail:%s:%s" subtype user))
1051 (webmail-newmail-only dontexpunge)
1052 (webmail-move-to-trash-can (not dontexpunge)))
1053 (when (eq authentication 'password)
1054 (setq password
1055 (or password
a1506d29 1056 (cdr (assoc (format "webmail:%s:%s" subtype user)
c113de23 1057 mail-source-password-cache))
23f87bed 1058 (read-passwd
c113de23
GM
1059 (format "Password for %s at %s: " user subtype))))
1060 (when (and password
a1506d29 1061 (not (assoc (format "webmail:%s:%s" subtype user)
c113de23 1062 mail-source-password-cache)))
a1506d29 1063 (push (cons (format "webmail:%s:%s" subtype user) password)
c113de23
GM
1064 mail-source-password-cache)))
1065 (webmail-fetch mail-source-crash-box subtype user password)
1066 (mail-source-callback callback (symbol-name subtype)))))
1067
1068(provide 'mail-source)
1069
ab5796a9 1070;;; arch-tag: 72948025-1d17-4d6c-bb12-ef1aa2c490fd
c113de23 1071;;; mail-source.el ends here