mirror of
https://git.savannah.gnu.org/git/emacs.git
synced 2024-11-24 07:20:37 +00:00
72fc04186a
* mail-source.el (mail-sources): Revert to nil. 2000-10-03 ShengHuo ZHU <zsh@cs.rochester.edu> * mail-source.el (mail-source-report-new-mail): Use nnheader-cancel-timer. 2000-10-03 Simon Josefsson <simon@josefsson.org> * mail-source.el (mail-source-imap-file-coding-system): New variable. (mail-source-fetch-imap): Use it.
879 lines
29 KiB
EmacsLisp
879 lines
29 KiB
EmacsLisp
;;; mail-source.el --- functions for fetching mail
|
|
;; Copyright (C) 1999, 2000 Free Software Foundation, Inc.
|
|
|
|
;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
|
|
;; Keywords: news, mail
|
|
|
|
;; This file is part of GNU Emacs.
|
|
|
|
;; GNU Emacs is free software; you can redistribute it and/or modify
|
|
;; it under the terms of the GNU General Public License as published by
|
|
;; the Free Software Foundation; either version 2, or (at your option)
|
|
;; any later version.
|
|
|
|
;; GNU Emacs is distributed in the hope that it will be useful,
|
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
;; GNU General Public License for more details.
|
|
|
|
;; You should have received a copy of the GNU General Public License
|
|
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
|
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
|
;; Boston, MA 02111-1307, USA.
|
|
|
|
;;; Commentary:
|
|
|
|
;;; Code:
|
|
|
|
(eval-when-compile (require 'cl))
|
|
(eval-and-compile
|
|
(autoload 'pop3-movemail "pop3")
|
|
(autoload 'pop3-get-message-count "pop3"))
|
|
(require 'format-spec)
|
|
|
|
(defgroup mail-source nil
|
|
"The mail-fetching library."
|
|
:version "21.1"
|
|
:group 'gnus)
|
|
|
|
(defcustom mail-sources nil
|
|
"*Where the mail backends will look for incoming mail.
|
|
This variable is a list of mail source specifiers.
|
|
See Info node `(gnus)Mail Source Specifiers'."
|
|
:group 'mail-source
|
|
;; This specification should be tidied up, particularly to avoid
|
|
;; constant items appearing. (Perhaps there's scope for improvment
|
|
;; in the widget code.)
|
|
:type `(repeat
|
|
(choice (const :tag "Default spool file" (file))
|
|
(list :tag "Specified spool file"
|
|
(const file)
|
|
(const :value :path)
|
|
file)
|
|
(cons :tag "Several files in a directory"
|
|
(const directory)
|
|
(choice
|
|
:tag "Options"
|
|
(const :tag "None" nil)
|
|
(repeat
|
|
(choice
|
|
(list :inline t :tag "path"
|
|
(const :value :path) directory)
|
|
(list :inline t :tag "suffix"
|
|
(const :value :suffix) string)
|
|
(list :inline t :tag "predicate"
|
|
(const :value :predicate) function)
|
|
(list :inline t :tag "prescript"
|
|
(const :value :prescript) string)
|
|
(list :inline t :tag "postscript"
|
|
(const :value :postscript) string)
|
|
(list :inline t :tag "plugged"
|
|
(const :value :plugged) boolean)))))
|
|
(cons :tag "POP3 server"
|
|
(const pop)
|
|
(choice
|
|
:tag "Options"
|
|
(const :tag "None" nil)
|
|
(repeat
|
|
(choice
|
|
(list :inline t :tag "server"
|
|
(const :value :server) string)
|
|
(list :inline t :tag "port"
|
|
(const :value :port) (choice number string))
|
|
(list :inline t :tag "user"
|
|
(const :value :user) string)
|
|
(list :inline t :tag "password"
|
|
(const :value :password) string)
|
|
(list :inline t :tag "program"
|
|
(const :value :program) string)
|
|
(list :inline t :tag "prescript"
|
|
(const :value :prescript) string)
|
|
(list :inline t :tag "postscript"
|
|
(const :value :postscript) string)
|
|
(list :inline t :tag "function"
|
|
(const :value :function) function)
|
|
(list :inline t :tag "authentication"
|
|
(const :value :authentication)
|
|
(choice (const password)
|
|
(const apop)))
|
|
(list :inline t :tag "plugged"
|
|
(const :value :plugged) boolean)))))
|
|
(cons :tag "Maildir (qmail, postfix...)"
|
|
(const maildir)
|
|
(choice
|
|
:tag "Options"
|
|
(const :tag "None" nil)
|
|
(repeat
|
|
(choice
|
|
(list :inline t :tag "path"
|
|
(const :value :path) directory)
|
|
(list :inline t :tag "plugged"
|
|
(const :value :plugged) boolean)))))
|
|
(cons :tag "IMAP server"
|
|
(const imap)
|
|
(choice
|
|
:tag "Options"
|
|
(const :tag "None" nil)
|
|
(repeat
|
|
(choice
|
|
(list :inline t :tag "server"
|
|
(const :value :server) string)
|
|
(list :inline t :tag "port"
|
|
(const :value :port)
|
|
(choice number string))
|
|
(list :inline t :tag "user"
|
|
(const :value :user) string)
|
|
(list :inline t :tag "password"
|
|
(const :value :password) string)
|
|
(list :inline t :tag "stream"
|
|
(const :value :stream)
|
|
(choice ,@(progn (require 'imap)
|
|
(mapcar
|
|
(lambda (a)
|
|
(list 'const (car a)))
|
|
imap-stream-alist))))
|
|
(list :inline t :tag "authenticator"
|
|
(const :value :authenticator)
|
|
(choice ,@(progn (require 'imap)
|
|
(mapcar
|
|
(lambda (a)
|
|
(list 'const (car a)))
|
|
imap-authenticator-alist))))
|
|
(list :inline t :tag "mailbox"
|
|
(const :value :mailbox) string)
|
|
(list :inline t :tag "predicate"
|
|
(const :value :predicate) function)
|
|
(list :inline t :tag "fetchflag"
|
|
(const :value :fetchflag) string)
|
|
(list :inline t :tag "dontexpunge"
|
|
(const :value :dontexpunge) boolean)
|
|
(list :inline t :tag "plugged"
|
|
(const :value :plugged) )))))
|
|
(cons :tag "Webmail server"
|
|
(const webmail)
|
|
(choice
|
|
:tag "Options"
|
|
(const :tag "None" nil)
|
|
(repeat
|
|
(choice
|
|
(list :inline t :tag "subtype"
|
|
(const :value :subtype)
|
|
;; Should be generated from
|
|
;; `webmail-type-definition', but we
|
|
;; can't require webmail without W3.
|
|
(choice (const hotmail) (const yahoo)
|
|
(const netaddress) (const netscape)
|
|
(const my-deja)))
|
|
(list :inline t :tag "user"
|
|
(const :value :user) string)
|
|
(list :inline t :tag "password"
|
|
(const :value :password) string)
|
|
(list :inline t :tag "dontexpunge"
|
|
(const :value :dontexpunge) boolean)
|
|
(list :inline t :tag "plugged"
|
|
(const :value :plugged) boolean))))))))
|
|
|
|
(defcustom mail-source-primary-source nil
|
|
"*Primary source for incoming mail.
|
|
If non-nil, this maildrop will be checked periodically for new mail."
|
|
:group 'mail-source
|
|
:type 'sexp)
|
|
|
|
(defcustom mail-source-crash-box "~/.emacs-mail-crash-box"
|
|
"File where mail will be stored while processing it."
|
|
:group 'mail-source
|
|
:type 'file)
|
|
|
|
(defcustom mail-source-directory "~/Mail/"
|
|
"Directory where files (if any) will be stored."
|
|
:group 'mail-source
|
|
:type 'directory)
|
|
|
|
(defcustom mail-source-default-file-modes 384
|
|
"Set the mode bits of all new mail files to this integer."
|
|
:group 'mail-source
|
|
:type 'integer)
|
|
|
|
(defcustom mail-source-delete-incoming t
|
|
"*If non-nil, delete incoming files after handling."
|
|
:group 'mail-source
|
|
:type 'boolean)
|
|
|
|
(defcustom mail-source-incoming-file-prefix "Incoming"
|
|
"Prefix for file name for storing incoming mail"
|
|
:group 'mail-source
|
|
:type 'string)
|
|
|
|
(defcustom mail-source-report-new-mail-interval 5
|
|
"Interval in minutes between checks for new mail."
|
|
:group 'mail-source
|
|
:type 'number)
|
|
|
|
(defcustom mail-source-idle-time-delay 5
|
|
"Number of idle seconds to wait before checking for new mail."
|
|
:group 'mail-source
|
|
:type 'number)
|
|
|
|
;;; Internal variables.
|
|
|
|
(defvar mail-source-string ""
|
|
"A dynamically bound string that says what the current mail source is.")
|
|
|
|
(defvar mail-source-new-mail-available nil
|
|
"Flag indicating when new mail is available.")
|
|
|
|
(eval-and-compile
|
|
(defvar mail-source-common-keyword-map
|
|
'((:plugged))
|
|
"Mapping from keywords to default values.
|
|
Common keywords should be listed here.")
|
|
|
|
(defvar mail-source-keyword-map
|
|
'((file
|
|
(:prescript)
|
|
(:prescript-delay)
|
|
(:postscript)
|
|
(:path (or (getenv "MAIL")
|
|
(concat "/usr/spool/mail/" (user-login-name)))))
|
|
(directory
|
|
(:path)
|
|
(:suffix ".spool")
|
|
(:predicate identity))
|
|
(pop
|
|
(:prescript)
|
|
(:prescript-delay)
|
|
(:postscript)
|
|
(:server (getenv "MAILHOST"))
|
|
(:port 110)
|
|
(:user (or (user-login-name) (getenv "LOGNAME") (getenv "USER")))
|
|
(:program)
|
|
(:function)
|
|
(:password)
|
|
(:authentication password))
|
|
(maildir
|
|
(:path (or (getenv "MAILDIR") "~/Maildir/"))
|
|
(:subdirs ("new" "cur"))
|
|
(:function))
|
|
(imap
|
|
(:server (getenv "MAILHOST"))
|
|
(:port)
|
|
(:stream)
|
|
(:authentication)
|
|
(:user (or (user-login-name) (getenv "LOGNAME") (getenv "USER")))
|
|
(:password)
|
|
(:mailbox "INBOX")
|
|
(:predicate "UNSEEN UNDELETED")
|
|
(:fetchflag "\\Deleted")
|
|
(:dontexpunge))
|
|
(webmail
|
|
(:subtype hotmail)
|
|
(:user (or (user-login-name) (getenv "LOGNAME") (getenv "USER")))
|
|
(:password)
|
|
(:dontexpunge)
|
|
(:authentication password)))
|
|
"Mapping from keywords to default values.
|
|
All keywords that can be used must be listed here."))
|
|
|
|
(defvar mail-source-fetcher-alist
|
|
'((file mail-source-fetch-file)
|
|
(directory mail-source-fetch-directory)
|
|
(pop mail-source-fetch-pop)
|
|
(maildir mail-source-fetch-maildir)
|
|
(imap mail-source-fetch-imap)
|
|
(webmail mail-source-fetch-webmail))
|
|
"A mapping from source type to fetcher function.")
|
|
|
|
(defvar mail-source-password-cache nil)
|
|
|
|
(defvar mail-source-plugged t)
|
|
|
|
;;; Functions
|
|
|
|
(eval-and-compile
|
|
(defun mail-source-strip-keyword (keyword)
|
|
"Strip the leading colon off the KEYWORD."
|
|
(intern (substring (symbol-name keyword) 1))))
|
|
|
|
(eval-and-compile
|
|
(defun mail-source-bind-1 (type)
|
|
(let* ((defaults (cdr (assq type mail-source-keyword-map)))
|
|
default bind)
|
|
(while (setq default (pop defaults))
|
|
(push (list (mail-source-strip-keyword (car default))
|
|
nil)
|
|
bind))
|
|
bind)))
|
|
|
|
(defmacro mail-source-bind (type-source &rest body)
|
|
"Return a `let' form that binds all variables in source TYPE.
|
|
TYPE-SOURCE is a list where the first element is the TYPE, and
|
|
the second variable is the SOURCE.
|
|
At run time, the mail source specifier SOURCE will be inspected,
|
|
and the variables will be set according to it. Variables not
|
|
specified will be given default values.
|
|
|
|
After this is done, BODY will be executed in the scope
|
|
of the `let' form.
|
|
|
|
The variables bound and their default values are described by
|
|
the `mail-source-keyword-map' variable."
|
|
`(let ,(mail-source-bind-1 (car type-source))
|
|
(mail-source-set-1 ,(cadr type-source))
|
|
,@body))
|
|
|
|
(put 'mail-source-bind 'lisp-indent-function 1)
|
|
(put 'mail-source-bind 'edebug-form-spec '(form body))
|
|
|
|
(defun mail-source-set-1 (source)
|
|
(let* ((type (pop source))
|
|
(defaults (cdr (assq type mail-source-keyword-map)))
|
|
default value keyword)
|
|
(while (setq default (pop defaults))
|
|
(set (mail-source-strip-keyword (setq keyword (car default)))
|
|
(if (setq value (plist-get source keyword))
|
|
(mail-source-value value)
|
|
(mail-source-value (cadr default)))))))
|
|
|
|
(eval-and-compile
|
|
(defun mail-source-bind-common-1 ()
|
|
(let* ((defaults mail-source-common-keyword-map)
|
|
default bind)
|
|
(while (setq default (pop defaults))
|
|
(push (list (mail-source-strip-keyword (car default))
|
|
nil)
|
|
bind))
|
|
bind)))
|
|
|
|
(defun mail-source-set-common-1 (source)
|
|
(let* ((type (pop source))
|
|
(defaults mail-source-common-keyword-map)
|
|
(defaults-1 (cdr (assq type mail-source-keyword-map)))
|
|
default value keyword)
|
|
(while (setq default (pop defaults))
|
|
(set (mail-source-strip-keyword (setq keyword (car default)))
|
|
(if (setq value (plist-get source keyword))
|
|
(mail-source-value value)
|
|
(if (setq value (assq keyword defaults-1))
|
|
(mail-source-value (cadr value))
|
|
(mail-source-value (cadr default))))))))
|
|
|
|
(defmacro mail-source-bind-common (source &rest body)
|
|
"Return a `let' form that binds all common variables.
|
|
See `mail-source-bind'."
|
|
`(let ,(mail-source-bind-common-1)
|
|
(mail-source-set-common-1 source)
|
|
,@body))
|
|
|
|
(put 'mail-source-bind-common 'lisp-indent-function 1)
|
|
(put 'mail-source-bind-common 'edebug-form-spec '(form body))
|
|
|
|
(defun mail-source-value (value)
|
|
"Return the value of VALUE."
|
|
(cond
|
|
;; String
|
|
((stringp value)
|
|
value)
|
|
;; Function
|
|
((and (listp value)
|
|
(functionp (car value)))
|
|
(eval value))
|
|
;; Just return the value.
|
|
(t
|
|
value)))
|
|
|
|
(defun mail-source-fetch (source callback)
|
|
"Fetch mail from SOURCE and call CALLBACK zero or more times.
|
|
CALLBACK will be called with the name of the file where (some of)
|
|
the mail from SOURCE is put.
|
|
Return the number of files that were found."
|
|
(mail-source-bind-common source
|
|
(if (or mail-source-plugged plugged)
|
|
(save-excursion
|
|
(let ((function (cadr (assq (car source) mail-source-fetcher-alist)))
|
|
(found 0))
|
|
(unless function
|
|
(error "%S is an invalid mail source specification" source))
|
|
;; If there's anything in the crash box, we do it first.
|
|
(when (file-exists-p mail-source-crash-box)
|
|
(message "Processing mail from %s..." mail-source-crash-box)
|
|
(setq found (mail-source-callback
|
|
callback mail-source-crash-box)))
|
|
(+ found
|
|
(condition-case err
|
|
(funcall function source callback)
|
|
(error
|
|
(unless (yes-or-no-p
|
|
(format "Mail source error (%s). Continue? " err))
|
|
(error "Cannot get new mail."))
|
|
0))))))))
|
|
|
|
(defun mail-source-make-complex-temp-name (prefix)
|
|
(let ((newname (make-temp-name prefix))
|
|
(newprefix prefix))
|
|
(while (file-exists-p newname)
|
|
(setq newprefix (concat newprefix "x"))
|
|
(setq newname (make-temp-name newprefix)))
|
|
newname))
|
|
|
|
(defun mail-source-callback (callback info)
|
|
"Call CALLBACK on the mail file, and then remove the mail file.
|
|
Pass INFO on to CALLBACK."
|
|
(if (or (not (file-exists-p mail-source-crash-box))
|
|
(zerop (nth 7 (file-attributes mail-source-crash-box))))
|
|
(progn
|
|
(when (file-exists-p mail-source-crash-box)
|
|
(delete-file mail-source-crash-box))
|
|
0)
|
|
(prog1
|
|
(funcall callback mail-source-crash-box info)
|
|
(when (file-exists-p mail-source-crash-box)
|
|
;; Delete or move the incoming mail out of the way.
|
|
(if mail-source-delete-incoming
|
|
(delete-file mail-source-crash-box)
|
|
(let ((incoming
|
|
(mail-source-make-complex-temp-name
|
|
(expand-file-name
|
|
mail-source-incoming-file-prefix
|
|
mail-source-directory))))
|
|
(unless (file-exists-p (file-name-directory incoming))
|
|
(make-directory (file-name-directory incoming) t))
|
|
(rename-file mail-source-crash-box incoming t)))))))
|
|
|
|
(defun mail-source-movemail (from to)
|
|
"Move FROM to TO using movemail."
|
|
(if (not (file-writable-p to))
|
|
(error "Can't write to crash box %s. Not moving mail" to)
|
|
(let ((to (file-truename (expand-file-name to)))
|
|
errors result)
|
|
(setq to (file-truename to)
|
|
from (file-truename from))
|
|
;; Set TO if have not already done so, and rename or copy
|
|
;; the file FROM to TO if and as appropriate.
|
|
(cond
|
|
((file-exists-p to)
|
|
;; The crash box exists already.
|
|
t)
|
|
((not (file-exists-p from))
|
|
;; There is no inbox.
|
|
(setq to nil))
|
|
((zerop (nth 7 (file-attributes from)))
|
|
;; Empty file.
|
|
(setq to nil))
|
|
(t
|
|
;; If getting from mail spool directory, use movemail to move
|
|
;; rather than just renaming, so as to interlock with the
|
|
;; mailer.
|
|
(unwind-protect
|
|
(save-excursion
|
|
(setq errors (generate-new-buffer " *mail source loss*"))
|
|
(let ((default-directory "/"))
|
|
(setq result
|
|
(apply
|
|
'call-process
|
|
(append
|
|
(list
|
|
(expand-file-name "movemail" exec-directory)
|
|
nil errors nil from to)))))
|
|
(when (file-exists-p to)
|
|
(set-file-modes to mail-source-default-file-modes))
|
|
(if (and (not (buffer-modified-p errors))
|
|
(zerop result))
|
|
;; No output => movemail won.
|
|
t
|
|
(set-buffer errors)
|
|
;; There may be a warning about older revisions. We
|
|
;; ignore that.
|
|
(goto-char (point-min))
|
|
(if (search-forward "older revision" nil t)
|
|
t
|
|
;; Probably a real error.
|
|
(subst-char-in-region (point-min) (point-max) ?\n ?\ )
|
|
(goto-char (point-max))
|
|
(skip-chars-backward " \t")
|
|
(delete-region (point) (point-max))
|
|
(goto-char (point-min))
|
|
(when (looking-at "movemail: ")
|
|
(delete-region (point-min) (match-end 0)))
|
|
(unless (yes-or-no-p
|
|
(format "movemail: %s (%d return). Continue? "
|
|
(buffer-string) result))
|
|
(error "%s" (buffer-string)))
|
|
(setq to nil)))))))
|
|
(when (and errors
|
|
(buffer-name errors))
|
|
(kill-buffer errors))
|
|
;; Return whether we moved successfully or not.
|
|
to)))
|
|
|
|
(defun mail-source-movemail-and-remove (from to)
|
|
"Move FROM to TO using movemail, then remove FROM if empty."
|
|
(or (not (mail-source-movemail from to))
|
|
(not (zerop (nth 7 (file-attributes from))))
|
|
(delete-file from)))
|
|
|
|
(defvar mail-source-read-passwd nil)
|
|
(defun mail-source-read-passwd (prompt &rest args)
|
|
"Read a password using PROMPT.
|
|
If ARGS, PROMPT is used as an argument to `format'."
|
|
(let ((prompt
|
|
(if args
|
|
(apply 'format prompt args)
|
|
prompt)))
|
|
(unless mail-source-read-passwd
|
|
(if (or (fboundp 'read-passwd) (load "passwd" t))
|
|
(setq mail-source-read-passwd 'read-passwd)
|
|
(unless (fboundp 'ange-ftp-read-passwd)
|
|
(autoload 'ange-ftp-read-passwd "ange-ftp"))
|
|
(setq mail-source-read-passwd 'ange-ftp-read-passwd)))
|
|
(funcall mail-source-read-passwd prompt)))
|
|
|
|
(defun mail-source-fetch-with-program (program)
|
|
(zerop (call-process shell-file-name nil nil nil
|
|
shell-command-switch program)))
|
|
|
|
(defun mail-source-run-script (script spec &optional delay)
|
|
(when script
|
|
(if (and (symbolp script) (fboundp script))
|
|
(funcall script)
|
|
(mail-source-call-script
|
|
(format-spec script spec))))
|
|
(when delay
|
|
(sleep-for delay)))
|
|
|
|
(defun mail-source-call-script (script)
|
|
(let ((background nil))
|
|
(when (string-match "& *$" script)
|
|
(setq script (substring script 0 (match-beginning 0))
|
|
background 0))
|
|
(call-process shell-file-name nil background nil
|
|
shell-command-switch script)))
|
|
|
|
;;;
|
|
;;; Different fetchers
|
|
;;;
|
|
|
|
(defun mail-source-fetch-file (source callback)
|
|
"Fetcher for single-file sources."
|
|
(mail-source-bind (file source)
|
|
(mail-source-run-script
|
|
prescript (format-spec-make ?t mail-source-crash-box)
|
|
prescript-delay)
|
|
(let ((mail-source-string (format "file:%s" path)))
|
|
(if (mail-source-movemail path mail-source-crash-box)
|
|
(prog1
|
|
(mail-source-callback callback path)
|
|
(mail-source-run-script
|
|
postscript (format-spec-make ?t mail-source-crash-box)))
|
|
0))))
|
|
|
|
(defun mail-source-fetch-directory (source callback)
|
|
"Fetcher for directory sources."
|
|
(mail-source-bind (directory source)
|
|
(let ((found 0)
|
|
(mail-source-string (format "directory:%s" path)))
|
|
(dolist (file (directory-files
|
|
path t (concat (regexp-quote suffix) "$")))
|
|
(when (and (file-regular-p file)
|
|
(funcall predicate file)
|
|
(mail-source-movemail file mail-source-crash-box))
|
|
(incf found (mail-source-callback callback file))))
|
|
found)))
|
|
|
|
(defun mail-source-fetch-pop (source callback)
|
|
"Fetcher for single-file sources."
|
|
(mail-source-bind (pop source)
|
|
(mail-source-run-script
|
|
prescript
|
|
(format-spec-make ?p password ?t mail-source-crash-box
|
|
?s server ?P port ?u user)
|
|
prescript-delay)
|
|
(let ((from (format "%s:%s:%s" server user port))
|
|
(mail-source-string (format "pop:%s@%s" user server))
|
|
result)
|
|
(when (eq authentication 'password)
|
|
(setq password
|
|
(or password
|
|
(cdr (assoc from mail-source-password-cache))
|
|
(mail-source-read-passwd
|
|
(format "Password for %s at %s: " user server)))))
|
|
(when server
|
|
(setenv "MAILHOST" server))
|
|
(setq result
|
|
(cond
|
|
(program
|
|
(mail-source-fetch-with-program
|
|
(format-spec
|
|
program
|
|
(format-spec-make ?p password ?t mail-source-crash-box
|
|
?s server ?P port ?u user))))
|
|
(function
|
|
(funcall function mail-source-crash-box))
|
|
;; The default is to use pop3.el.
|
|
(t
|
|
(let ((pop3-password password)
|
|
(pop3-maildrop user)
|
|
(pop3-mailhost server)
|
|
(pop3-port port)
|
|
(pop3-authentication-scheme
|
|
(if (eq authentication 'apop) 'apop 'pass)))
|
|
(save-excursion (pop3-movemail mail-source-crash-box))))))
|
|
(if result
|
|
(progn
|
|
(when (eq authentication 'password)
|
|
(unless (assoc from mail-source-password-cache)
|
|
(push (cons from password) mail-source-password-cache)))
|
|
(prog1
|
|
(mail-source-callback callback server)
|
|
;; Update display-time's mail flag, if relevant.
|
|
(if (equal source mail-source-primary-source)
|
|
(setq mail-source-new-mail-available nil))
|
|
(mail-source-run-script
|
|
postscript
|
|
(format-spec-make ?p password ?t mail-source-crash-box
|
|
?s server ?P port ?u user))))
|
|
;; We nix out the password in case the error
|
|
;; was because of a wrong password being given.
|
|
(setq mail-source-password-cache
|
|
(delq (assoc from mail-source-password-cache)
|
|
mail-source-password-cache))
|
|
0))))
|
|
|
|
(defun mail-source-check-pop (source)
|
|
"Check whether there is new mail."
|
|
(mail-source-bind (pop source)
|
|
(let ((from (format "%s:%s:%s" server user port))
|
|
(mail-source-string (format "pop:%s@%s" user server))
|
|
result)
|
|
(when (eq authentication 'password)
|
|
(setq password
|
|
(or password
|
|
(cdr (assoc from mail-source-password-cache))
|
|
(mail-source-read-passwd
|
|
(format "Password for %s at %s: " user server))))
|
|
(unless (assoc from mail-source-password-cache)
|
|
(push (cons from password) mail-source-password-cache)))
|
|
(when server
|
|
(setenv "MAILHOST" server))
|
|
(setq result
|
|
(cond
|
|
;; No easy way to check whether mail is waiting for these.
|
|
(program)
|
|
(function)
|
|
;; The default is to use pop3.el.
|
|
(t
|
|
(let ((pop3-password password)
|
|
(pop3-maildrop user)
|
|
(pop3-mailhost server)
|
|
(pop3-port port)
|
|
(pop3-authentication-scheme
|
|
(if (eq authentication 'apop) 'apop 'pass)))
|
|
(save-excursion (pop3-get-message-count))))))
|
|
(if result
|
|
;; Inform display-time that we have new mail.
|
|
(setq mail-source-new-mail-available (> result 0))
|
|
;; We nix out the password in case the error
|
|
;; was because of a wrong password being given.
|
|
(setq mail-source-password-cache
|
|
(delq (assoc from mail-source-password-cache)
|
|
mail-source-password-cache)))
|
|
result)))
|
|
|
|
(defun mail-source-new-mail-p ()
|
|
"Handler for `display-time' to indicate when new mail is available."
|
|
;; Only report flag setting; flag is updated on a different schedule.
|
|
mail-source-new-mail-available)
|
|
|
|
|
|
(defvar mail-source-report-new-mail nil)
|
|
(defvar mail-source-report-new-mail-timer nil)
|
|
(defvar mail-source-report-new-mail-idle-timer nil)
|
|
|
|
(eval-when-compile (require 'timer))
|
|
|
|
(defun mail-source-start-idle-timer ()
|
|
;; Start our idle timer if necessary, so we delay the check until the
|
|
;; user isn't typing.
|
|
(unless mail-source-report-new-mail-idle-timer
|
|
(setq mail-source-report-new-mail-idle-timer
|
|
(run-with-idle-timer
|
|
mail-source-idle-time-delay
|
|
nil
|
|
(lambda ()
|
|
(setq mail-source-report-new-mail-idle-timer nil)
|
|
(mail-source-check-pop mail-source-primary-source))))
|
|
;; Since idle timers created when Emacs is already in the idle
|
|
;; state don't get activated until Emacs _next_ becomes idle, we
|
|
;; need to force our timer to be considered active now. We do
|
|
;; this by being naughty and poking the timer internals directly
|
|
;; (element 0 of the vector is nil if the timer is active).
|
|
(aset mail-source-report-new-mail-idle-timer 0 nil)))
|
|
|
|
(defun mail-source-report-new-mail (arg)
|
|
"Toggle whether to report when new mail is available.
|
|
This only works when `display-time' is enabled."
|
|
(interactive "P")
|
|
(if (not mail-source-primary-source)
|
|
(error "Need to set `mail-source-primary-source' to check for new mail."))
|
|
(let ((on (if (null arg)
|
|
(not mail-source-report-new-mail)
|
|
(> (prefix-numeric-value arg) 0))))
|
|
(setq mail-source-report-new-mail on)
|
|
(and mail-source-report-new-mail-timer
|
|
(nnheader-cancel-timer mail-source-report-new-mail-timer))
|
|
(and mail-source-report-new-mail-idle-timer
|
|
(nnheader-cancel-timer mail-source-report-new-mail-idle-timer))
|
|
(setq mail-source-report-new-mail-timer nil)
|
|
(setq mail-source-report-new-mail-idle-timer nil)
|
|
(if on
|
|
(progn
|
|
(require 'time)
|
|
;; display-time-mail-function is an Emacs 21 feature.
|
|
(setq display-time-mail-function #'mail-source-new-mail-p)
|
|
;; Set up the main timer.
|
|
(setq mail-source-report-new-mail-timer
|
|
(run-at-time t (* 60 mail-source-report-new-mail-interval)
|
|
#'mail-source-start-idle-timer))
|
|
;; When you get new mail, clear "Mail" from the mode line.
|
|
(add-hook 'nnmail-post-get-new-mail-hook
|
|
'display-time-event-handler)
|
|
(message "Mail check enabled"))
|
|
(setq display-time-mail-function nil)
|
|
(remove-hook 'nnmail-post-get-new-mail-hook
|
|
'display-time-event-handler)
|
|
(message "Mail check disabled"))))
|
|
|
|
(defun mail-source-fetch-maildir (source callback)
|
|
"Fetcher for maildir sources."
|
|
(mail-source-bind (maildir source)
|
|
(let ((found 0)
|
|
mail-source-string)
|
|
(unless (string-match "/$" path)
|
|
(setq path (concat path "/")))
|
|
(dolist (subdir subdirs)
|
|
(when (file-directory-p (concat path subdir))
|
|
(setq mail-source-string (format "maildir:%s%s" path subdir))
|
|
(dolist (file (directory-files (concat path subdir) t))
|
|
(when (and (not (file-directory-p file))
|
|
(not (if function
|
|
(funcall function file mail-source-crash-box)
|
|
(let ((coding-system-for-write
|
|
mm-text-coding-system)
|
|
(coding-system-for-read
|
|
mm-text-coding-system))
|
|
(with-temp-file mail-source-crash-box
|
|
(insert-file-contents file)
|
|
(goto-char (point-min))
|
|
;;; ;; Unix mail format
|
|
;;; (unless (looking-at "\n*From ")
|
|
;;; (insert "From maildir "
|
|
;;; (current-time-string) "\n"))
|
|
;;; (while (re-search-forward "^From " nil t)
|
|
;;; (replace-match ">From "))
|
|
;; MMDF mail format
|
|
(insert "\001\001\001\001\n")
|
|
(goto-char (point-max))
|
|
(insert "\n\n"))
|
|
(delete-file file)))))
|
|
(incf found (mail-source-callback callback file))))))
|
|
found)))
|
|
|
|
(eval-and-compile
|
|
(autoload 'imap-open "imap")
|
|
(autoload 'imap-authenticate "imap")
|
|
(autoload 'imap-mailbox-select "imap")
|
|
(autoload 'imap-mailbox-unselect "imap")
|
|
(autoload 'imap-mailbox-close "imap")
|
|
(autoload 'imap-search "imap")
|
|
(autoload 'imap-fetch "imap")
|
|
(autoload 'imap-close "imap")
|
|
(autoload 'imap-error-text "imap")
|
|
(autoload 'imap-message-flags-add "imap")
|
|
(autoload 'imap-list-to-message-set "imap")
|
|
(autoload 'imap-range-to-message-set "imap")
|
|
(autoload 'nnheader-ms-strip-cr "nnheader"))
|
|
|
|
(defvar mail-source-imap-file-coding-system 'binary
|
|
"Coding system for the crashbox made by `mail-source-fetch-imap'.")
|
|
|
|
(defun mail-source-fetch-imap (source callback)
|
|
"Fetcher for imap sources."
|
|
(mail-source-bind (imap source)
|
|
(let ((from (format "%s:%s:%s" server user port))
|
|
(found 0)
|
|
(buf (get-buffer-create (generate-new-buffer-name " *imap source*")))
|
|
(mail-source-string (format "imap:%s:%s" server mailbox))
|
|
remove)
|
|
(if (and (imap-open server port stream authentication buf)
|
|
(imap-authenticate
|
|
user (or (cdr (assoc from mail-source-password-cache))
|
|
password) buf)
|
|
(imap-mailbox-select mailbox nil buf))
|
|
(let (str (coding-system-for-write mail-source-imap-file-coding-system))
|
|
(with-temp-file mail-source-crash-box
|
|
;; In some versions of FSF Emacs, inserting unibyte
|
|
;; string into multibyte buffer may convert 8-bit chars
|
|
;; into latin-iso8859-1 chars, which results \201's.
|
|
(mm-disable-multibyte)
|
|
;; remember password
|
|
(with-current-buffer buf
|
|
(when (or imap-password
|
|
(assoc from mail-source-password-cache))
|
|
(push (cons from imap-password) mail-source-password-cache)))
|
|
;; if predicate is nil, use all uids
|
|
(dolist (uid (imap-search (or predicate "1:*") buf))
|
|
(when (setq str (imap-fetch uid "RFC822.PEEK" 'RFC822 nil buf))
|
|
(push uid remove)
|
|
(insert "From imap " (current-time-string) "\n")
|
|
(save-excursion
|
|
(insert str "\n\n"))
|
|
(while (re-search-forward "^From " nil t)
|
|
(replace-match ">From "))
|
|
(goto-char (point-max))))
|
|
(nnheader-ms-strip-cr))
|
|
(incf found (mail-source-callback callback server))
|
|
(when (and remove fetchflag)
|
|
(imap-message-flags-add
|
|
(imap-range-to-message-set (gnus-compress-sequence remove))
|
|
fetchflag nil buf))
|
|
(if dontexpunge
|
|
(imap-mailbox-unselect buf)
|
|
(imap-mailbox-close buf))
|
|
(imap-close buf))
|
|
(imap-close buf)
|
|
;; We nix out the password in case the error
|
|
;; was because of a wrong password being given.
|
|
(setq mail-source-password-cache
|
|
(delq (assoc from mail-source-password-cache)
|
|
mail-source-password-cache))
|
|
(error (imap-error-text buf)))
|
|
(kill-buffer buf)
|
|
found)))
|
|
|
|
(eval-and-compile
|
|
(autoload 'webmail-fetch "webmail"))
|
|
|
|
(defun mail-source-fetch-webmail (source callback)
|
|
"Fetch for webmail source."
|
|
(mail-source-bind (webmail source)
|
|
(let ((mail-source-string (format "webmail:%s:%s" subtype user))
|
|
(webmail-newmail-only dontexpunge)
|
|
(webmail-move-to-trash-can (not dontexpunge)))
|
|
(when (eq authentication 'password)
|
|
(setq password
|
|
(or password
|
|
(cdr (assoc (format "webmail:%s:%s" subtype user)
|
|
mail-source-password-cache))
|
|
(mail-source-read-passwd
|
|
(format "Password for %s at %s: " user subtype))))
|
|
(when (and password
|
|
(not (assoc (format "webmail:%s:%s" subtype user)
|
|
mail-source-password-cache)))
|
|
(push (cons (format "webmail:%s:%s" subtype user) password)
|
|
mail-source-password-cache)))
|
|
(webmail-fetch mail-source-crash-box subtype user password)
|
|
(mail-source-callback callback (symbol-name subtype)))))
|
|
|
|
(provide 'mail-source)
|
|
|
|
;;; mail-source.el ends here
|