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