(eval-when-compile (require 'cl))
(eval-when-compile (require 'gnus-clfns))
-(eval-and-compile (require 'imap))
+(require 'imap)
(require 'nnoo)
(require 'nnmail)
(defconst nnimap-version "nnimap 1.0")
+(defgroup nnimap nil
+ "Reading IMAP mail with Gnus."
+ :group 'gnus)
+
(defvoo nnimap-address nil
"Address of physical IMAP server. If nil, use the virtual server's name.")
;; Splitting variables
-(defvar nnimap-split-crosspost t
+(defcustom nnimap-split-crosspost t
"If non-nil, do crossposting if several split methods match the mail.
-If nil, the first match found will be used.")
+If nil, the first match found will be used."
+ :group 'nnimap
+ :type 'boolean)
-(defvar nnimap-split-inbox nil
- "*Name of mailbox to split mail from.
+(defcustom nnimap-split-inbox nil
+ "Name of mailbox to split mail from.
Mail is read from this mailbox and split according to rules in
`nnimap-split-rule'.
-This can be a string or a list of strings.")
+This can be a string or a list of strings."
+ :group 'nnimap
+ :type '(choice (string)
+ (repeat string)))
+
+(define-widget 'nnimap-strict-function 'function
+ "This widget only matches values that are functionp.
+
+Warning: This means that a value that is the symbol of a not yet
+loaded function will not match. Use with care."
+ :match 'nnimap-strict-function-match)
-(defvar nnimap-split-rule nil
- "*Mail will be split according to theese rules.
+(defun nnimap-strict-function-match (widget value)
+ "Ignoring WIDGET, match if VALUE is a function."
+ (functionp value))
+
+(defcustom nnimap-split-rule nil
+ "Mail will be split according to theese rules.
Mail is read from mailbox(es) specified in `nnimap-split-inbox'.
everything else in the incoming mailbox, you could do something like
this:
-(setq nnimap-split-rule '((\"INBOX.gnus-imap\" \"From:.*gnus-imap\")
+\(setq nnimap-split-rule '((\"INBOX.gnus-imap\" \"From:.*gnus-imap\")
(\"INBOX.junk\" \"Subject:.*buy\")))
As you can see, `nnimap-split-rule' is a list of lists, where the first
even different split rules in different inboxes on the same server,
the syntax of this variable have been extended along the lines of:
-(setq nnimap-split-rule
+\(setq nnimap-split-rule
'((\"my1server\" (\".*\" ((\"ding\" \"ding@gnus.org\")
(\"junk\" \"From:.*Simon\")))
(\"my2server\" (\"INBOX\" nnimap-split-fancy))
\"my3server\" and \"my4server\" both use the same rules. Similarly,
the inbox string is also a regexp. The actual splitting rules are as
before, either a function, or a list with group/regexp or
-group/function elements.")
-
-(defvar nnimap-split-predicate "UNSEEN UNDELETED"
+group/function elements."
+ :group 'nnimap
+ :type '(choice :tag "Rule type"
+ (repeat :menu-tag "Single-server"
+ :tag "Single-server list"
+ (list (string :tag "Mailbox")
+ (choice :tag "Predicate"
+ (regexp :tag "A regexp")
+ (nnimap-strict-function :tag "A function"))))
+ (choice :menu-tag "A function"
+ :tag "A function"
+ (function-item nnimap-split-fancy)
+ (function-item nnmail-split-fancy)
+ (nnimap-strict-function :tag "User-defined function"))
+ (repeat :menu-tag "Multi-server (extended)"
+ :tag "Multi-server list"
+ (list (regexp :tag "Server regexp")
+ (list (regexp :tag "Incoming Mailbox regexp")
+ (repeat :tag "Rules for matching server(s) and mailbox(es)"
+ (list (string :tag "Destination mailbox")
+ (choice :tag "Predicate"
+ (regexp :tag "A Regexp")
+ (nnimap-strict-function :tag "A Function")))))))))
+
+(defcustom nnimap-split-predicate "UNSEEN UNDELETED"
"The predicate used to find articles to split.
If you use another IMAP client to peek on articles but always would
like nnimap to split them once it's started, you could change this to
\"UNDELETED\". Other available predicates are available in
-RFC2060 section 6.4.4.")
-
-(defvar nnimap-split-fancy nil
- "Like `nnmail-split-fancy', which see.")
+RFC2060 section 6.4.4."
+ :group 'nnimap
+ :type 'string)
+
+(defcustom nnimap-split-fancy nil
+ "Like `nnmail-split-fancy', which see."
+ :group 'nnimap
+ :type 'sexp)
+
+;; Performance / bug workaround variables
+
+(defcustom nnimap-close-asynchronous t
+ "Close mailboxes asynchronously in `nnimap-close-group'.
+This means that errors cought by nnimap when closing the mailbox will
+not prevent Gnus from updating the group status, which may be harmful.
+However, it increases speed."
+ :type 'boolean
+ :group 'nnimap)
+
+(defcustom nnimap-dont-close t
+ "Never close mailboxes.
+This increases the speed of closing mailboxes (quiting group) but may
+decrease the speed of selecting another mailbox later. Re-selecting
+the same mailbox will be faster though."
+ :type 'boolean
+ :group 'nnimap)
+
+(defvoo nnimap-need-unselect-to-notice-new-mail nil
+ "Unselect mailboxes before looking for new mail in them.
+Some servers seem to need this under some circumstances.")
;; Authorization / Privacy variables
use this to make replies go directly to the group.")
(defvoo nnimap-expunge-search-string "UID %s NOT SINCE %s"
- "*IMAP search command to use for articles that are to be expired.
+ "IMAP search command to use for articles that are to be expired.
The first %s is replaced by a UID set of articles to search on,
and the second %s is replaced by a date criterium.
2060 for more information on valid strings.")
(defvoo nnimap-importantize-dormant t
- "*If non-nil, mark \"dormant\" articles as \"ticked\" for other IMAP clients.
+ "If non-nil, mark \"dormant\" articles as \"ticked\" for other IMAP clients.
Note that within Gnus, dormant articles will still (only) be
marked as ticked. This is to make \"dormant\" articles stand out,
just like \"ticked\" articles, in other IMAP clients.")
(defun nnimap-before-find-minmax-bugworkaround ()
"Function called before iterating through mailboxes with
`nnimap-find-minmax-uid'."
- ;; XXX this is for UoW imapd problem, it doesn't notice new mail in
- ;; currently selected mailbox without a re-select/examine.
- (or (null (imap-current-mailbox nnimap-server-buffer))
- (imap-mailbox-unselect nnimap-server-buffer)))
+ (when nnimap-need-unselect-to-notice-new-mail
+ ;; XXX this is for UoW imapd problem, it doesn't notice new mail in
+ ;; currently selected mailbox without a re-select/examine.
+ (or (null (imap-current-mailbox nnimap-server-buffer))
+ (imap-mailbox-unselect nnimap-server-buffer))))
(defun nnimap-find-minmax-uid (group &optional examine)
"Find lowest and highest active article nummber in GROUP.
If EXAMINE is non-nil the group is selected read-only."
(with-current-buffer nnimap-server-buffer
- (when (imap-mailbox-select group examine)
+ (when (or (string= group (imap-current-mailbox))
+ (imap-mailbox-select group examine))
(let (minuid maxuid)
(when (> (imap-mailbox-get 'exists) 0)
(imap-fetch "1,*" "UID" nil 'nouidfetch)
(when (and (imap-opened)
(nnimap-possibly-change-group group server))
(case nnimap-expunge-on-close
- ('always (imap-mailbox-expunge)
- (imap-mailbox-close))
- ('ask (if (and (imap-search "DELETED")
- (gnus-y-or-n-p (format
- "Expunge articles in group `%s'? "
- imap-current-mailbox)))
- (progn (imap-mailbox-expunge)
- (imap-mailbox-close))
- (imap-mailbox-unselect)))
+ (always (progn
+ (imap-mailbox-expunge nnimap-close-asynchronous)
+ (unless nnimap-dont-close
+ (imap-mailbox-close nnimap-close-asynchronous))))
+ (ask (if (and (imap-search "DELETED")
+ (gnus-y-or-n-p (format "Expunge articles in group `%s'? "
+ imap-current-mailbox)))
+ (progn
+ (imap-mailbox-expunge nnimap-close-asynchronous)
+ (unless nnimap-dont-close
+ (imap-mailbox-close nnimap-close-asynchronous)))
+ (imap-mailbox-unselect)))
(t (imap-mailbox-unselect)))
(not imap-current-mailbox))))
(or (member "\\NoSelect"
(imap-mailbox-get 'list-flags group nnimap-server-buffer))
(let ((info (nnimap-find-minmax-uid group 'examine)))
- (when (> (or (imap-mailbox-get 'recent group
+ (when (> (or (imap-mailbox-get 'recent group
nnimap-server-buffer) 0)
0)
(push (list (cons group 0)) nnmail-split-history))
(what (nth 1 action))
(cmdmarks (nth 2 action))
marks)
+ ;; bookmark can't be stored (not list/range
+ (setq cmdmarks (delq 'bookmark cmdmarks))
+ ;; killed can't be stored (not list/range
+ (setq cmdmarks (delq 'killed cmdmarks))
+ ;; unsent are for nndraft groups only
+ (setq cmdmarks (delq 'unsent cmdmarks))
;; cache flags are pointless on the server
(setq cmdmarks (delq 'cache cmdmarks))
+ ;; seen flags are local to each gnus
+ (setq cmdmarks (delq 'seen cmdmarks))
;; recent marks can't be set
(setq cmdmarks (delq 'recent cmdmarks))
(when nnimap-importantize-dormant
(defun nnimap-expiry-target (arts group server)
(unless (eq nnmail-expiry-target 'delete)
- (with-current-buffer nntp-server-buffer
+ (with-temp-buffer
(dolist (art (gnus-uncompress-sequence arts))
- (nnimap-request-article art group server)
+ (nnimap-request-article art group server (current-buffer))
;; hints for optimization in `nnimap-request-accept-article'
(let ((nnimap-current-move-article art)
(nnimap-current-move-group group)
(nnimap-current-move-server server))
- (nnmail-expiry-target-group nnmail-expiry-target group))))))
+ (nnmail-expiry-target-group nnmail-expiry-target group))))
+ ;; It is not clear if `nnmail-expiry-target' somehow cause the
+ ;; current group to be changed or not, so we make sure here.
+ (nnimap-possibly-change-group group server)))
;; Notice that we don't actually delete anything, we just mark them deleted.
(deffoo nnimap-request-expire-articles (articles group &optional server force)
(setq result (eval accept-form))
(kill-buffer buf)
result)
- (nnimap-request-expire-articles (list article) group server t))
+ (imap-message-flags-add
+ (imap-range-to-message-set (list article))
+ "\\Deleted" 'silent nnimap-server-buffer))
result))))
(deffoo nnimap-request-accept-article (group &optional server last)
(defun nnimap-expunge (mailbox server)
(when (nnimap-possibly-change-group mailbox server)
- (imap-mailbox-expunge nnimap-server-buffer)))
+ (imap-mailbox-expunge nil nnimap-server-buffer)))
(defun nnimap-acl-get (mailbox server)
(when (nnimap-possibly-change-server server)