;; .newsrc.eld)
;; o What about Gnus's article editing, can we support it? NO!
;; o Use \Draft to support the draft group??
+;; o Duplicate suppression
;;; Code:
(eval-and-compile
+ (require 'cl)
(require 'imap))
(require 'nnoo)
(nnoo-declare nnimap)
-(defconst nnimap-version "nnimap 0.131")
+(defconst nnimap-version "nnimap 1.0")
(defvoo nnimap-address nil
"Address of physical IMAP server. If nil, use the virtual server's name.")
"*Name of mailbox to split mail from.
Mail is read from this mailbox and split according to rules in
-`nnimap-split-rules'.
+`nnimap-split-rule'.
This can be a string or a list of strings.")
(setq nnimap-split-rule
'((\"my1server\" (\".*\" ((\"ding\" \"ding@gnus.org\")
- (\"junk\" \"From:.*Simon\")))
- (\"my2server\" (\"INBOX\" nnimap-split-fancy))
- (\"my[34]server\" (\".*\" ((\"private\" \"To:.*Simon\")
- (\"junk\" my-junk-func)))))
+ (\"junk\" \"From:.*Simon\")))
+ (\"my2server\" (\"INBOX\" nnimap-split-fancy))
+ (\"my[34]server\" (\".*\" ((\"private\" \"To:.*Simon\")
+ (\"junk\" my-junk-func)))))
The virtual server name is in fact a regexp, so that the same rules
may apply to several servers. In the example, the servers
everything in the current hierarchy.")
(defvoo nnimap-news-groups nil
- "IMAP support a news-like mode, also known as bulletin board mode, where replies is sent via IMAP instead of SMTP.
+ "IMAP support a news-like mode, also known as bulletin board mode,
+where replies is sent via IMAP instead of SMTP.
This variable should contain a regexp matching groups where you wish
replies to be stored to the mailbox directly.
similar which you wouldn't want to set up a mailing list for, you can
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.
+The first %s is replaced by a UID set of articles to search on,
+and the second %s is replaced by a date criterium.
+
+One useful (and perhaps the only useful) value to change this to would
+be `UID %s NOT SENTSINCE %s' to make nnimap use the Date: header
+instead of the internal date of messages. See section 6.4.4 of RFC
+2060 for more information on valid strings.")
+
+(defvoo nnimap-importantize-dormant t
+ "*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.")
+
(defvoo nnimap-server-address nil
"Obsolete. Use `nnimap-address'.")
;; Internal variables:
-(defvar nnimap-debug nil);; "*nnimap-debug*")
+(defvar nnimap-debug nil
+ "Name of buffer to record debugging info.
+For example: (setq nnimap-debug \"*nnimap-debug*\")")
(defvar nnimap-current-move-server nil)
(defvar nnimap-current-move-group nil)
(defvar nnimap-current-move-article nil)
(defvar nnimap-callback-buffer nil
"Which buffer the asynchronous article prefetch callback should work in.")
(defvar nnimap-server-buffer-alist nil) ;; Map server name to buffers.
-(defvar nnimap-current-server nil) ;; Current server
-(defvar nnimap-server-buffer nil) ;; Current servers' buffer
+(defvar nnimap-current-server nil) ;; Current server
+(defvar nnimap-server-buffer nil) ;; Current servers' buffer
\f
group (gnus-server-to-method
(format "nnimap:%s" server))))
(new-uidvalidity (imap-mailbox-get 'uidvalidity))
- (old-uidvalidity (gnus-group-get-parameter gnusgroup 'uidvalidity)))
+ (old-uidvalidity (gnus-group-get-parameter gnusgroup 'uidvalidity))
+ (dir (file-name-as-directory (expand-file-name nnimap-directory)))
+ (nameuid (nnheader-translate-file-chars
+ (concat nnimap-nov-file-name
+ (if (equal server "")
+ "unnamed"
+ server) "." group "." old-uidvalidity
+ nnimap-nov-file-name-suffix) t))
+ (file (if (or nnmail-use-long-file-names
+ (file-exists-p (expand-file-name nameuid dir)))
+ (expand-file-name nameuid dir)
+ (expand-file-name
+ (mm-encode-coding-string
+ (nnheader-replace-chars-in-string nameuid ?. ?/)
+ nnmail-pathname-coding-system)
+ dir))))
(if old-uidvalidity
(if (not (equal old-uidvalidity new-uidvalidity))
- nil ;; uidvalidity clash
+ ;; uidvalidity clash
+ (gnus-delete-file file)
(gnus-group-set-parameter gnusgroup 'uidvalidity new-uidvalidity)
t)
(gnus-group-add-parameter gnusgroup (cons 'uidvalidity new-uidvalidity))
t)))
+(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)))
+
(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."
maxuid (if maxuid (max maxuid uid) uid)))
'UID))
(list (imap-mailbox-get 'exists) minuid maxuid)))))
-
+
(defun nnimap-possibly-change-group (group &optional server)
"Make GROUP the current group, and SERVER the current server."
(when (nnimap-possibly-change-server server)
(if (or (nnimap-verify-uidvalidity
group (or server nnimap-current-server))
(zerop (imap-mailbox-get 'exists group))
+ t ;; for OGnus to see if ignoring uidvalidity
+ ;; changes has any bad effects.
(yes-or-no-p
(format
"nnimap: Group %s is not uidvalid. Continue? " group)))
imap-current-mailbox
(imap-mailbox-unselect)
- (error "nnimap: Group %s is not uid-valid." group))
+ (error "nnimap: Group %s is not uid-valid" group))
(nnheader-report 'nnimap (imap-error-text)))))))
(defun nnimap-replace-whitespace (string)
(with-current-buffer nnimap-server-buffer
(setq uid imap-current-message
mbx imap-current-mailbox
- headers (if (imap-capability 'IMAP4rev1)
- ;; xxx don't just use car? alist doesn't contain
- ;; anything else now, but it might...
- (nth 2 (car (imap-message-get uid 'BODYDETAIL)))
- (imap-message-get uid 'RFC822.HEADER))
+ headers (nnimap-demule
+ (if (imap-capability 'IMAP4rev1)
+ ;; xxx don't just use car? alist doesn't contain
+ ;; anything else now, but it might...
+ (nth 2 (car (imap-message-get uid 'BODYDETAIL)))
+ (imap-message-get uid 'RFC822.HEADER)))
lines (imap-body-lines (imap-message-body imap-current-message))
chars (imap-message-get imap-current-message 'RFC822.SIZE)))
(nnheader-insert-nov
(if (numberp (car-safe articles))
(imap-search
(concat "UID "
- (nnimap-range-to-string
+ (imap-range-to-message-set
(gnus-compress-sequence
(append (gnus-uncompress-sequence
(and fetch-old
articles)))))
(mapcar (lambda (msgid)
(imap-search
- (format "HEADER Message-Id %s" msgid)))
+ (format "HEADER Message-Id \"%s\"" msgid)))
articles))))
(defun nnimap-group-overview-filename (group server)
"Make pathname for GROUP on SERVER."
- (let ((dir (file-name-as-directory (expand-file-name nnimap-directory)))
- (file (nnheader-translate-file-chars
- (concat nnimap-nov-file-name
- (if (equal server "")
- "unnamed"
- server) "." group nnimap-nov-file-name-suffix) t)))
- (if (or nnmail-use-long-file-names
- (file-exists-p (concat dir file)))
- (concat dir file)
- (concat dir (mm-encode-coding-string
- (nnheader-replace-chars-in-string file ?. ?/)
- nnmail-pathname-coding-system)))))
+ (let* ((dir (file-name-as-directory (expand-file-name nnimap-directory)))
+ (uidvalidity (gnus-group-get-parameter
+ (gnus-group-prefixed-name
+ group (gnus-server-to-method
+ (format "nnimap:%s" server)))
+ 'uidvalidity))
+ (name (nnheader-translate-file-chars
+ (concat nnimap-nov-file-name
+ (if (equal server "")
+ "unnamed"
+ server) "." group nnimap-nov-file-name-suffix) t))
+ (nameuid (nnheader-translate-file-chars
+ (concat nnimap-nov-file-name
+ (if (equal server "")
+ "unnamed"
+ server) "." group "." uidvalidity
+ nnimap-nov-file-name-suffix) t))
+ (oldfile (if (or nnmail-use-long-file-names
+ (file-exists-p (expand-file-name name dir)))
+ (expand-file-name name dir)
+ (expand-file-name
+ (mm-encode-coding-string
+ (nnheader-replace-chars-in-string name ?. ?/)
+ nnmail-pathname-coding-system)
+ dir)))
+ (newfile (if (or nnmail-use-long-file-names
+ (file-exists-p (expand-file-name nameuid dir)))
+ (expand-file-name nameuid dir)
+ (expand-file-name
+ (mm-encode-coding-string
+ (nnheader-replace-chars-in-string nameuid ?. ?/)
+ nnmail-pathname-coding-system)
+ dir))))
+ (when (and (file-exists-p oldfile) (not (file-exists-p newfile)))
+ (message "nnimap: Upgrading novcache filename...")
+ (sit-for 1)
+ (gnus-make-directory (file-name-directory newfile))
+ (unless (ignore-errors (rename-file oldfile newfile) t)
+ (if (ignore-errors (copy-file oldfile newfile) t)
+ (delete-file oldfile)
+ (error "Can't rename `%s' to `%s'" oldfile newfile))))
+ newfile))
(defun nnimap-retrieve-headers-from-file (group server)
(with-current-buffer nntp-server-buffer
(when (file-exists-p nov)
(mm-insert-file-contents nov)
(set-buffer-modified-p nil)
- (let ((min (progn (goto-char (point-min))
- (when (not (eobp))
- (read (current-buffer)))))
- (max (progn (goto-char (point-max))
- (forward-line -1)
- (when (not (bobp))
- (read (current-buffer))))))
+ (let ((min (ignore-errors (goto-char (point-min))
+ (read (current-buffer))))
+ (max (ignore-errors (goto-char (point-max))
+ (forward-line -1)
+ (read (current-buffer)))))
(if (and (numberp min) (numberp max))
(cons min max)
;; junk, remove it, it's saved later
(let ((imap-fetch-data-hook '(nnimap-retrieve-headers-progress))
(nnimap-length (gnus-range-length articles))
(nnimap-counter 0))
- (imap-fetch (nnimap-range-to-string articles)
+ (imap-fetch (imap-range-to-message-set articles)
(concat "(UID RFC822.SIZE BODY "
(let ((headers
(append '(Subject From Date Message-Id
(> nnimap-length nnmail-large-newsgroup)
(nnheader-message 6 "nnimap: Retrieving headers...done")))))
-(defun nnimap-use-nov-p (group server)
+(defun nnimap-dont-use-nov-p (group server)
(or gnus-nov-is-evil nnimap-nov-is-evil
(unless (and (gnus-make-directory
(file-name-directory
(when (nnimap-possibly-change-group group server)
(with-current-buffer nntp-server-buffer
(erase-buffer)
- (if (nnimap-use-nov-p group server)
+ (if (nnimap-dont-use-nov-p group server)
(nnimap-retrieve-headers-from-server
(gnus-compress-sequence articles) group server)
(let (uids cached low high)
(nnimap-retrieve-headers-from-server
(cons (1+ (cdr cached)) high) group server))
(when nnimap-prune-cache
- ;; remove nov's for articles which has expired on server
+ ;; remove nov's for articles which has expired on server
(goto-char (point-min))
(dolist (uid (gnus-set-difference articles uids))
- (when (re-search-forward (format "^%d\t" uid) nil t)
- (gnus-delete-line)))))
+ (when (re-search-forward (format "^%d\t" uid) nil t)
+ (gnus-delete-line)))))
;; nothing cached, fetch whole range from server
(nnimap-retrieve-headers-from-server
(cons low high) group server))
(port (if nnimap-server-port
(int-to-string nnimap-server-port)
"imap"))
- (alist (gnus-netrc-machine list (or nnimap-server-address
- nnimap-address server)
- port "imap"))
+ (alist (gnus-netrc-machine list (or nnimap-server-address
+ nnimap-address server)
+ port "imap"))
(user (gnus-netrc-get alist "login"))
(passwd (gnus-netrc-get alist "password")))
(if (imap-authenticate user passwd nnimap-server-buffer)
(cadr (assq 'nnimap-server-address defs))) defs)
(push (list 'nnimap-address server) defs)))
(nnoo-change-server 'nnimap server defs)
+ (or nnimap-server-buffer
+ (setq nnimap-server-buffer (cadr (assq 'nnimap-server-buffer defs))))
+ (with-current-buffer (get-buffer-create nnimap-server-buffer)
+ (nnoo-change-server 'nnimap server defs))
(or (and nnimap-server-buffer
(imap-opened nnimap-server-buffer))
(nnimap-open-connection server))))
(insert
(with-current-buffer nnimap-server-buffer
(nnimap-demule
- (if (imap-capability 'IMAP4rev1)
- ;; xxx don't just use car? alist doesn't contain
- ;; anything else now, but it might...
- (nth 2 (car (imap-message-get (imap-current-message) 'BODYDETAIL)))
- (imap-message-get (imap-current-message) 'RFC822)))))
+ (if (imap-capability 'IMAP4rev1)
+ ;; xxx don't just use car? alist doesn't contain
+ ;; anything else now, but it might...
+ (nth 2 (car (imap-message-get (imap-current-message) 'BODYDETAIL)))
+ (imap-message-get (imap-current-message) 'RFC822)))))
(nnheader-ms-strip-cr)
(funcall nnimap-callback-callback-function t)))
(defun nnimap-request-article-part (article part prop &optional
- group server to-buffer detail)
+ group server to-buffer detail)
(when (nnimap-possibly-change-group group server)
(let ((article (if (stringp article)
(car-safe (imap-search
- (format "HEADER Message-Id %s" article)
+ (format "HEADER Message-Id \"%s\"" article)
nnimap-server-buffer))
article)))
(when article
- (gnus-message 9 "nnimap: Fetching (part of) article %d..." article)
+ (gnus-message 10 "nnimap: Fetching (part of) article %d..." article)
(if (not nnheader-callback-function)
(with-current-buffer (or to-buffer nntp-server-buffer)
(erase-buffer)
- (let ((data (imap-fetch article part prop nil
- nnimap-server-buffer)))
- (insert (nnimap-demule (if detail
- (nth 2 (car data))
- data))))
- (nnheader-ms-strip-cr)
- (gnus-message 9 "nnimap: Fetching (part of) article %d...done"
+ (let ((data (imap-fetch article part prop nil
+ nnimap-server-buffer)))
+ (insert (nnimap-demule (if detail
+ (nth 2 (car data))
+ data))))
+ (nnheader-ms-strip-cr)
+ (gnus-message 10 "nnimap: Fetching (part of) article %d...done"
article)
(if (bobp)
(nnheader-report 'nnimap "No such article: %s"
group (gnus-server-to-method (format "nnimap:%s" server))))
server)
(when (nnimap-possibly-change-group group server)
+ (nnimap-before-find-minmax-bugworkaround)
(let (info)
(cond (fast group)
((null (setq info (nnimap-find-minmax-uid group t)))
(erase-buffer))
(gnus-message 5 "nnimap: Generating active list%s..."
(if (> (length server) 0) (concat " for " server) ""))
+ (nnimap-before-find-minmax-bugworkaround)
(with-current-buffer nnimap-server-buffer
(dolist (pattern (nnimap-pattern-to-list-arguments nnimap-list-pattern))
(dolist (mbx (funcall nnimap-request-list-method
(let ((info (nnimap-find-minmax-uid mbx 'examine)))
(when info
(with-current-buffer nntp-server-buffer
- (insert (format "\"%s\" %d %d y\n"
- mbx (or (nth 2 info) 0)
- (max 1 (or (nth 1 info) 1)))))))))))
+ (insert (format "\"%s\" %d %d y\n"
+ mbx (or (nth 2 info) 0)
+ (max 1 (or (nth 1 info) 1)))))))))))
(gnus-message 5 "nnimap: Generating active list%s...done"
(if (> (length server) 0) (concat " for " server) ""))
t))
(deffoo nnimap-request-post (&optional server)
(let ((success t))
(dolist (mbx (message-unquote-tokens
- (message-tokenize-header
- (message-fetch-field "Newsgroups") ", ")) success)
+ (message-tokenize-header
+ (message-fetch-field "Newsgroups") ", ")) success)
(let ((to-newsgroup (gnus-group-prefixed-name mbx gnus-command-method)))
(or (gnus-active to-newsgroup)
(gnus-activate-group to-newsgroup)
(gnus-message 5 "nnimap: Checking mailboxes...")
(with-current-buffer nntp-server-buffer
(erase-buffer)
+ (nnimap-before-find-minmax-bugworkaround)
(dolist (group groups)
(