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