Sync.
[elisp/gnus.git-] / lisp / nnimap.el
1 ;;; nnimap.el --- imap backend for Gnus
2 ;; Copyright (C) 1998, 1999, 2000 Free Software Foundation, Inc.
3
4 ;; Author: Simon Josefsson <jas@pdc.kth.se>
5 ;;         Jim Radford <radford@robby.caltech.edu>
6 ;; Keywords: mail
7
8 ;; This file is part of GNU Emacs.
9
10 ;; GNU Emacs is free software; you can redistribute it and/or modify
11 ;; it under the terms of the GNU General Public License as published by
12 ;; the Free Software Foundation; either version 2, or (at your option)
13 ;; any later version.
14
15 ;; GNU Emacs is distributed in the hope that it will be useful,
16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 ;; GNU General Public License for more details.
19
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
22 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
23 ;; Boston, MA 02111-1307, USA.
24
25 ;;; Commentary:
26
27 ;; Todo, major things:
28 ;;
29 ;;   o Fix Gnus to view correct number of unread/total articles in group buffer
30 ;;   o Fix Gnus to handle leading '.' in group names (fixed?)
31 ;;   o Finish disconnected mode (moving articles between mailboxes unplugged)
32 ;;   o Sieve
33 ;;   o MIME (partial article fetches)
34 ;;   o Split to other backends, different split rules for different
35 ;;     servers/inboxes
36 ;;
37 ;; Todo, minor things:
38 ;;
39 ;;   o Don't require half of Gnus -- backends should be standalone
40 ;;   o Verify that we don't use IMAP4rev1 specific things (RFC2060 App B)
41 ;;   o Dont uid fetch 1,* in nnimap-retrive-groups (slow)
42 ;;   o Split up big fetches (1,* header especially) in smaller chunks
43 ;;   o What do I do with gnus-newsgroup-*?
44 ;;   o Tell Gnus about new groups (how can we tell?)
45 ;;   o Respooling (fix Gnus?) (unnecessery?)
46 ;;   o Add support for the following: (if applicable)
47 ;;       request-list-newsgroups, request-regenerate
48 ;;       list-active-group,
49 ;;       request-associate-buffer, request-restore-buffer,
50 ;;   o Do The Right Thing when UIDVALIDITY changes (what's the right thing?)
51 ;;   o Support RFC2221 (Login referrals)
52 ;;   o IMAP2BIS compatibility? (RFC2061)
53 ;;   o ACAP stuff (perhaps a different project, would be nice to ACAPify
54 ;;     .newsrc.eld)
55 ;;   o What about Gnus's article editing, can we support it?  NO!
56 ;;   o Use \Draft to support the draft group??
57
58 ;;; Code:
59
60 (eval-when-compile (require 'cl))
61 (eval-when-compile (require 'gnus-clfns))
62 (eval-and-compile (require 'imap))
63
64 (require 'nnoo)
65 (require 'nnmail)
66 (require 'nnheader)
67 (require 'gnus)
68 (require 'gnus-range)
69 (require 'gnus-start)
70 (require 'gnus-int)
71
72 (nnoo-declare nnimap)
73
74 (defconst nnimap-version "nnimap 0.131")
75
76 (defvoo nnimap-address nil
77   "Address of physical IMAP server.  If nil, use the virtual server's name.")
78
79 (defvoo nnimap-server-port nil
80   "Port number on physical IMAP server.
81 If nil, defaults to 993 for SSL connections and 143 otherwise.")
82
83 ;; Splitting variables
84
85 (defvar nnimap-split-crosspost t
86   "If non-nil, do crossposting if several split methods match the mail.
87 If nil, the first match found will be used.")
88
89 (defvar nnimap-split-inbox nil
90   "*Name of mailbox to split mail from.
91
92 Mail is read from this mailbox and split according to rules in
93 `nnimap-split-rules'.
94
95 This can be a string or a list of strings.")
96
97 (defvar nnimap-split-rule nil
98   "*Mail will be split according to theese rules.
99
100 Mail is read from mailbox(es) specified in `nnimap-split-inbox'.
101
102 If you'd like, for instance, one mail group for mail from the
103 \"gnus-imap\" mailing list, one group for junk mail and leave
104 everything else in the incoming mailbox, you could do something like
105 this:
106
107 (setq nnimap-split-rule '((\"INBOX.gnus-imap\"   \"From:.*gnus-imap\")
108                           (\"INBOX.junk\"        \"Subject:.*buy\")))
109
110 As you can see, `nnimap-split-rule' is a list of lists, where the first
111 element in each \"rule\" is the name of the IMAP mailbox, and the
112 second is a regexp that nnimap will try to match on the header to find
113 a fit.
114
115 The first element can also be a list.  In that case, the first element
116 is the server the second element is the group on that server in which
117 the matching article will be stored.
118
119 The second element can also be a function.  In that case, it will be
120 called narrowed to the headers with the first element of the rule as
121 the argument.  It should return a non-nil value if it thinks that the
122 mail belongs in that group.
123
124 This variable can also have a function as its value, the function will
125 be called with the headers narrowed and should return a group where it
126 thinks the article should be splitted to.")
127
128 (defvar nnimap-split-predicate "UNSEEN UNDELETED"
129   "The predicate used to find articles to split.
130 If you use another IMAP client to peek on articles but always would
131 like nnimap to split them once it's started, you could change this to
132 \"UNDELETED\". Other available predicates are available in
133 RFC2060 section 6.4.4.")
134
135 (defvar nnimap-split-fancy nil
136   "Like `nnmail-split-fancy', which see.")
137
138 ;; Authorization / Privacy variables
139
140 (defvoo nnimap-auth-method nil
141   "Obsolete.")
142
143 (defvoo nnimap-stream nil
144   "How nnimap will connect to the server.
145
146 The default, nil, will try to use the \"best\" method the server can
147 handle.
148
149 Change this if
150
151 1) you want to connect with SSL.  The SSL integration with IMAP is
152    brain-dead so you'll have to tell it specifically.
153
154 2) your server is more capable than your environment -- i.e. your
155    server accept Kerberos login's but you haven't installed the
156    `imtest' program or your machine isn't configured for Kerberos.
157
158 Possible choices: kerberos4, ssl, network")
159
160 (defvoo nnimap-authenticator nil
161   "How nnimap authenticate itself to the server.
162
163 The default, nil, will try to use the \"best\" method the server can
164 handle.
165
166 There is only one reason for fiddling with this variable, and that is
167 if your server is more capable than your environment -- i.e. you
168 connect to a server that accept Kerberos login's but you haven't
169 installed the `imtest' program or your machine isn't configured for
170 Kerberos.
171
172 Possible choices: kerberos4, cram-md5, login, anonymous.")
173
174 (defvoo nnimap-directory (nnheader-concat gnus-directory "overview/")
175   "Directory to keep NOV cache files for nnimap groups.
176 See also `nnimap-nov-file-name'.")
177
178 (defvoo nnimap-nov-file-name "nnimap."
179   "NOV cache base filename.
180 The group name and `nnimap-nov-file-name-suffix' will be appended.  A
181 typical complete file name would be
182 ~/News/overview/nnimap.pdc.INBOX.ding.nov, or
183 ~/News/overview/nnimap/pdc/INBOX/ding/nov if
184 `nnmail-use-long-file-names' is nil")
185
186 (defvoo nnimap-nov-file-name-suffix ".novcache"
187   "Suffix for NOV cache base filename.")
188
189 (defvoo nnimap-nov-is-evil nil
190   "If non-nil, nnimap will never generate or use a local nov database for this backend.
191 Using nov databases will speed up header fetching considerably.
192 Unlike other backends, you do not need to take special care if you
193 flip this variable.")
194
195 (defvoo nnimap-expunge-on-close 'always ; 'ask, 'never
196   "Whether to expunge a group when it is closed.
197 When a IMAP group with articles marked for deletion is closed, this
198 variable determine if nnimap should actually remove the articles or
199 not.
200
201 If always, nnimap always perform a expunge when closing the group.
202 If never, nnimap never expunges articles marked for deletion.
203 If ask, nnimap will ask you if you wish to expunge marked articles.
204
205 When setting this variable to `never', you can only expunge articles
206 by using `G x' (gnus-group-nnimap-expunge) from the Group buffer.")
207
208 (defvoo nnimap-list-pattern "*"
209   "A string LIMIT or list of strings with mailbox wildcards used to limit available groups.
210 See below for available wildcards.
211
212 The LIMIT string can be a cons cell (REFERENCE . LIMIT), where
213 REFERENCE will be passed as the first parameter to LIST/LSUB.  The
214 semantics of this are server specific, on the University of Washington
215 server you can specify a directory.
216
217 Example:
218  '(\"INBOX\" \"mail/*\" (\"~friend/mail/\" . \"list/*\"))
219
220 There are two wildcards * and %. * matches everything, % matches
221 everything in the current hierarchy.")
222
223 (defvoo nnimap-news-groups nil
224   "IMAP support a news-like mode, also known as bulletin board mode, where replies is sent via IMAP instead of SMTP.
225
226 This variable should contain a regexp matching groups where you wish
227 replies to be stored to the mailbox directly.
228
229 Example:
230   '(\"^[^I][^N][^B][^O][^X].*$\")
231
232 This will match all groups not beginning with \"INBOX\".
233
234 Note that there is nothing technically different between mail-like and
235 news-like mailboxes.  If you wish to have a group with todo items or
236 similar which you wouldn't want to set up a mailing list for, you can
237 use this to make replies go directly to the group.")
238
239 (defvoo nnimap-server-address nil
240   "Obsolete.  Use `nnimap-address'.")
241
242 (defcustom nnimap-authinfo-file "~/.authinfo"
243   "Authorization information for IMAP servers.  In .netrc format."
244   :type
245   '(choice file
246            (repeat :tag "Entries"
247                    :menu-tag "Inline"
248                    (list :format "%v"
249                          :value ("" ("login" . "") ("password" . ""))
250                          (string :tag "Host")
251                          (checklist :inline t
252                                     (cons :format "%v"
253                                           (const :format "" "login")
254                                           (string :format "Login: %v"))
255                                     (cons :format "%v"
256                                           (const :format "" "password")
257                                           (string :format "Password: %v")))))))
258
259 (defcustom nnimap-prune-cache t
260   "If non-nil, nnimap check whether articles still exist on server before using data stored in NOV cache."
261   :type 'boolean)
262
263 (defvar nnimap-request-list-method 'imap-mailbox-list
264   "Method to use to request a list of all folders from the server.
265 If this is 'imap-mailbox-lsub, then use a server-side subscription list to
266 restrict visible folders.")
267
268 ;; Internal variables:
269
270 (defvar nnimap-debug nil);; "*nnimap-debug*")
271 (defvar nnimap-current-move-server nil)
272 (defvar nnimap-current-move-group nil)
273 (defvar nnimap-current-move-article nil)
274 (defvar nnimap-length)
275 (defvar nnimap-progress-chars '(?| ?/ ?- ?\\))
276 (defvar nnimap-progress-how-often 20)
277 (defvar nnimap-counter)
278 (defvar nnimap-callback-callback-function nil
279   "Gnus callback the nnimap asynchronous callback should call.")
280 (defvar nnimap-callback-buffer nil
281   "Which buffer the asynchronous article prefetch callback should work in.")
282 (defvar nnimap-server-buffer-alist nil) ;; Map server name to buffers.
283 (defvar nnimap-current-server nil)      ;; Current server
284 (defvar nnimap-server-buffer nil)       ;; Current servers' buffer
285
286 \f
287
288 (nnoo-define-basics nnimap)
289
290 ;; Utility functions:
291
292 (defsubst nnimap-get-server-buffer (server)
293   "Return buffer for SERVER, if nil use current server."
294   (cadr (assoc (or server nnimap-current-server) nnimap-server-buffer-alist)))
295
296 (defun nnimap-possibly-change-server (server)
297   "Return buffer for SERVER, changing the current server as a side-effect.
298 If SERVER is nil, uses the current server."
299   (setq nnimap-current-server (or server nnimap-current-server)
300         nnimap-server-buffer (nnimap-get-server-buffer nnimap-current-server)))
301
302 (defun nnimap-verify-uidvalidity (group server)
303   "Verify stored uidvalidity match current one in GROUP on SERVER."
304   (let* ((gnusgroup (gnus-group-prefixed-name
305                      group (gnus-server-to-method
306                             (format "nnimap:%s" server))))
307          (new-uidvalidity (imap-mailbox-get 'uidvalidity))
308          (old-uidvalidity (gnus-group-get-parameter gnusgroup 'uidvalidity)))
309     (if old-uidvalidity
310         (if (not (equal old-uidvalidity new-uidvalidity))
311             nil ;; uidvalidity clash
312           (gnus-group-set-parameter gnusgroup 'uidvalidity new-uidvalidity)
313           t)
314       (gnus-group-add-parameter gnusgroup (cons 'uidvalidity new-uidvalidity))
315       t)))
316
317 (defun nnimap-find-minmax-uid (group &optional examine)
318   "Find lowest and highest active article nummber in GROUP.
319 If EXAMINE is non-nil the group is selected read-only."
320   (with-current-buffer nnimap-server-buffer
321     (when (imap-mailbox-select group examine)
322       (let (minuid maxuid)
323         (when (> (imap-mailbox-get 'exists) 0)
324           (imap-fetch "1,*" "UID" nil 'nouidfetch)
325           (imap-message-map (lambda (uid Uid)
326                               (setq minuid (if minuid (min minuid uid) uid)
327                                     maxuid (if maxuid (max maxuid uid) uid)))
328                             'UID))
329         (list (imap-mailbox-get 'exists) minuid maxuid)))))
330   
331 (defun nnimap-possibly-change-group (group &optional server)
332   "Make GROUP the current group, and SERVER the current server."
333   (when (nnimap-possibly-change-server server)
334     (with-current-buffer nnimap-server-buffer
335       (if (or (null group) (imap-current-mailbox-p group))
336           imap-current-mailbox
337         (if (imap-mailbox-select group)
338             (if (or (nnimap-verify-uidvalidity
339                      group (or server nnimap-current-server))
340                     (zerop (imap-mailbox-get 'exists group))
341                     (yes-or-no-p
342                      (format
343                       "nnimap: Group %s is not uidvalid.  Continue? " group)))
344                 imap-current-mailbox
345               (imap-mailbox-unselect)
346               (error "nnimap: Group %s is not uid-valid." group))
347           (nnheader-report 'nnimap (imap-error-text)))))))
348
349 (defun nnimap-replace-whitespace (string)
350   "Return STRING with all whitespace replaced with space."
351   (when string
352     (while (string-match "[\r\n\t]+" string)
353       (setq string (replace-match " " t t string)))
354     string))
355
356 ;; Required backend functions
357
358 (defun nnimap-retrieve-headers-progress ()
359   "Hook to insert NOV line for current article into `nntp-server-buffer'."
360   (and (numberp nnmail-large-newsgroup)
361        (zerop (% (incf nnimap-counter) nnimap-progress-how-often))
362        (> nnimap-length nnmail-large-newsgroup)
363        (nnheader-message 6 "nnimap: Retrieving headers... %c"
364                          (nth (/ (% nnimap-counter
365                                     (* (length nnimap-progress-chars)
366                                        nnimap-progress-how-often))
367                                  nnimap-progress-how-often)
368                               nnimap-progress-chars)))
369   (with-current-buffer nntp-server-buffer
370     (let (headers lines chars uid mbx)
371       (with-current-buffer nnimap-server-buffer
372         (setq uid imap-current-message
373               mbx imap-current-mailbox
374               headers (if (imap-capability 'IMAP4rev1)
375                           ;; xxx don't just use car? alist doesn't contain
376                           ;; anything else now, but it might...
377                           (nth 2 (car (imap-message-get uid 'BODYDETAIL)))
378                         (imap-message-get uid 'RFC822.HEADER))
379               lines (imap-body-lines (imap-message-body imap-current-message))
380               chars (imap-message-get imap-current-message 'RFC822.SIZE)))
381       (nnheader-insert-nov
382        (with-temp-buffer
383          (buffer-disable-undo)
384          (insert headers)
385          (nnheader-fold-continuation-lines)
386          (subst-char-in-region (point-min) (point-max) ?\t ? )
387          (nnheader-ms-strip-cr)
388          (nnheader-fold-continuation-lines)
389          (subst-char-in-region (point-min) (point-max) ?\t ? )
390          (let ((head (nnheader-parse-head 'naked)))
391            (mail-header-set-number head uid)
392            (mail-header-set-chars head chars)
393            (mail-header-set-lines head lines)
394            (mail-header-set-xref
395             head (format "%s %s:%d" (system-name) mbx uid))
396            head))))))
397
398 (defun nnimap-retrieve-which-headers (articles fetch-old)
399   "Get a range of articles to fetch based on ARTICLES and FETCH-OLD."
400   (with-current-buffer nnimap-server-buffer
401     (if (numberp (car-safe articles))
402         (imap-search
403          (concat "UID "
404                  (nnimap-range-to-string
405                   (gnus-compress-sequence
406                    (append (gnus-uncompress-sequence
407                             (and fetch-old
408                                  (cons (if (numberp fetch-old)
409                                            (max 1 (- (car articles) fetch-old))
410                                          1)
411                                        (1- (car articles)))))
412                            articles)))))
413       (mapcar (lambda (msgid)
414                 (imap-search
415                  (format "HEADER Message-Id %s" msgid)))
416               articles))))
417
418 (defun nnimap-group-overview-filename (group server)
419   "Make pathname for GROUP on SERVER."
420   (let ((dir (file-name-as-directory (expand-file-name nnimap-directory)))
421         (file (nnheader-translate-file-chars
422                (concat nnimap-nov-file-name
423                        (if (equal server "")
424                            "unnamed"
425                          server) "." group nnimap-nov-file-name-suffix) t)))
426     (if (or nnmail-use-long-file-names
427             (file-exists-p (concat dir file)))
428         (concat dir file)
429       (concat dir (encode-coding-string
430                    (nnheader-replace-chars-in-string file ?. ?/)
431                    nnmail-pathname-coding-system)))))
432
433 (defun nnimap-retrieve-headers-from-file (group server)
434   (with-current-buffer nntp-server-buffer
435     (let ((nov (nnimap-group-overview-filename group server)))
436       (when (file-exists-p nov)
437         (nnheader-insert-file-contents nov)
438         (set-buffer-modified-p nil)
439         (let ((min (progn (goto-char (point-min))
440                           (when (not (eobp))
441                             (read (current-buffer)))))
442               (max (progn (goto-char (point-max))
443                           (forward-line -1)
444                           (when (not (bobp))
445                             (read (current-buffer))))))
446           (if (and (numberp min) (numberp max))
447               (cons min max)
448             ;; junk, remove it, it's saved later
449             (erase-buffer)
450             nil))))))
451
452 (defun nnimap-retrieve-headers-from-server (articles group server)
453   (with-current-buffer nnimap-server-buffer
454     (let ((imap-fetch-data-hook '(nnimap-retrieve-headers-progress))
455           (nnimap-length (gnus-range-length articles))
456           (nnimap-counter 0))
457       (imap-fetch (nnimap-range-to-string articles)
458                   (concat "(UID RFC822.SIZE BODY "
459                           (let ((headers
460                                  (append '(Subject From Date Message-Id
461                                                    References In-Reply-To Xref)
462                                          (copy-sequence
463                                           nnmail-extra-headers))))
464                             (if (imap-capability 'IMAP4rev1)
465                                 (format "BODY.PEEK[HEADER.FIELDS %s])" headers)
466                               (format "RFC822.HEADER.LINES %s)" headers)))))
467       (and (numberp nnmail-large-newsgroup)
468            (> nnimap-length nnmail-large-newsgroup)
469            (nnheader-message 6 "nnimap: Retrieving headers...done")))))
470
471 (defun nnimap-use-nov-p (group server)
472   (or gnus-nov-is-evil nnimap-nov-is-evil
473       (unless (and (gnus-make-directory
474                     (file-name-directory
475                      (nnimap-group-overview-filename group server)))
476                    (file-writable-p
477                     (nnimap-group-overview-filename group server)))
478         (message "nnimap: Nov cache not writable, %s"
479                  (nnimap-group-overview-filename group server)))))
480
481 (deffoo nnimap-retrieve-headers (articles &optional group server fetch-old)
482   (when (nnimap-possibly-change-group group server)
483     (with-current-buffer nntp-server-buffer
484       (erase-buffer)
485       (if (nnimap-use-nov-p group server)
486           (nnimap-retrieve-headers-from-server
487            (gnus-compress-sequence articles) group server)
488         (let (uids cached low high)
489           (when (setq uids (nnimap-retrieve-which-headers articles fetch-old)
490                       low (car uids)
491                       high (car (last uids)))
492             (if (setq cached (nnimap-retrieve-headers-from-file group server))
493                 (progn
494                   ;; fetch articles with uids before cache block
495                   (when (< low (car cached))
496                     (goto-char (point-min))
497                     (nnimap-retrieve-headers-from-server
498                      (cons low (1- (car cached))) group server))
499                   ;; fetch articles with uids after cache block
500                   (when (> high (cdr cached))
501                     (goto-char (point-max))
502                     (nnimap-retrieve-headers-from-server
503                      (cons (1+ (cdr cached)) high) group server))
504                   (when nnimap-prune-cache
505                     ;; remove nov's for articles which has expired on server
506                     (goto-char (point-min))
507                     (dolist (uid (gnus-set-difference articles uids))
508                       (when (re-search-forward (format "^%d\t" uid) nil t)
509                         (gnus-delete-line)))))
510               ;; nothing cached, fetch whole range from server
511               (nnimap-retrieve-headers-from-server
512                (cons low high) group server))
513             (when (buffer-modified-p)
514               (nnmail-write-region
515                1 (point-max) (nnimap-group-overview-filename group server)
516                nil 'nomesg))
517             (nnheader-nov-delete-outside-range low high))))
518       'nov)))
519
520 (defun nnimap-open-connection (server)
521   (if (not (imap-open nnimap-address nnimap-server-port nnimap-stream
522                       nnimap-authenticator nnimap-server-buffer))
523       (nnheader-report 'nnimap "Can't open connection to server %s" server)
524     (unless (or (imap-capability 'IMAP4 nnimap-server-buffer)
525                 (imap-capability 'IMAP4rev1 nnimap-server-buffer))
526       (imap-close nnimap-server-buffer)
527       (nnheader-report 'nnimap "Server %s is not IMAP4 compliant" server))
528     (let (list alist user passwd)
529       (and (fboundp 'gnus-parse-netrc)
530            (setq list (gnus-parse-netrc nnimap-authinfo-file)
531                  alist (or (and (gnus-netrc-get
532                                  (gnus-netrc-machine list server) "machine")
533                                 (gnus-netrc-machine list server))
534                            (gnus-netrc-machine list nnimap-address))
535                  user (gnus-netrc-get alist "login")
536                  passwd (gnus-netrc-get alist "password")))
537       (if (imap-authenticate user passwd nnimap-server-buffer)
538           (prog1
539               (push (list server nnimap-server-buffer)
540                     nnimap-server-buffer-alist)
541             (nnimap-possibly-change-server server))
542         (imap-close nnimap-server-buffer)
543         (kill-buffer nnimap-server-buffer)
544         (nnheader-report 'nnimap "Could not authenticate to %s" server)))))
545
546 (deffoo nnimap-open-server (server &optional defs)
547   (nnheader-init-server-buffer)
548   (if (nnimap-server-opened server)
549       t
550     (unless (assq 'nnimap-server-buffer defs)
551       (push (list 'nnimap-server-buffer (concat " *nnimap* " server)) defs))
552     ;; translate `nnimap-server-address' to `nnimap-address' in defs
553     ;; for people that configured nnimap with a very old version
554     (unless (assq 'nnimap-address defs)
555       (if (assq 'nnimap-server-address defs)
556           (push (list 'nnimap-address
557                       (cadr (assq 'nnimap-server-address defs))) defs)
558         (push (list 'nnimap-address server) defs)))
559     (nnoo-change-server 'nnimap server defs)
560     (or (and nnimap-server-buffer
561              (imap-opened nnimap-server-buffer))
562         (nnimap-open-connection server))))
563
564 (deffoo nnimap-server-opened (&optional server)
565   "Whether SERVER is opened.
566 If SERVER is the current virtual server, and the connection to the
567 physical server is alive, this function return a non-nil value.  If
568 SERVER is nil, it is treated as the current server."
569   ;; clean up autologouts??
570   (and (or server nnimap-current-server)
571        (nnoo-server-opened 'nnimap (or server nnimap-current-server))
572        (imap-opened (nnimap-get-server-buffer server))))
573
574 (deffoo nnimap-close-server (&optional server)
575   "Close connection to server and free all resources connected to it.
576 Return nil if the server couldn't be closed for some reason."
577   (let ((server (or server nnimap-current-server)))
578     (when (or (nnimap-server-opened server)
579               (imap-opened (nnimap-get-server-buffer server)))
580       (imap-close (nnimap-get-server-buffer server))
581       (kill-buffer (nnimap-get-server-buffer server))
582       (setq nnimap-server-buffer nil
583             nnimap-current-server nil
584             nnimap-server-buffer-alist
585             (delq server nnimap-server-buffer-alist)))
586     (nnoo-close-server 'nnimap server)))
587
588 (deffoo nnimap-request-close ()
589   "Close connection to all servers and free all resources that the backend have reserved.
590 All buffers that have been created by that
591 backend should be killed.  (Not the nntp-server-buffer, though.) This
592 function is generally only called when Gnus is shutting down."
593   (mapcar (lambda (server) (nnimap-close-server (car server)))
594           nnimap-server-buffer-alist)
595   (setq nnimap-server-buffer-alist nil))
596
597 (deffoo nnimap-status-message (&optional server)
598   "This function returns the last error message from server."
599   (when (nnimap-possibly-change-server server)
600     (nnoo-status-message 'nnimap server)))
601
602 (defun nnimap-demule (string)
603   (funcall (if (and (fboundp 'string-as-multibyte)
604                     (subrp (symbol-function 'string-as-multibyte)))
605                'string-as-multibyte
606              'identity)
607            (or string "")))
608
609 (defun nnimap-callback ()
610   (remove-hook 'imap-fetch-data-hook 'nnimap-callback)
611   (with-current-buffer nnimap-callback-buffer
612     (insert
613      (with-current-buffer nnimap-server-buffer
614        (nnimap-demule
615         (if (imap-capability 'IMAP4rev1) 
616             ;; xxx don't just use car? alist doesn't contain
617             ;; anything else now, but it might...
618             (nth 2 (car (imap-message-get (imap-current-message) 'BODYDETAIL)))
619           (imap-message-get (imap-current-message) 'RFC822)))))
620     (nnheader-ms-strip-cr)
621     (funcall nnimap-callback-callback-function t)))
622
623 (defun nnimap-request-article-part (article part prop &optional
624                                             group server to-buffer detail)
625   (when (nnimap-possibly-change-group group server)
626     (let ((article (if (stringp article)
627                        (car-safe (imap-search
628                                   (format "HEADER Message-Id %s" article)
629                                   nnimap-server-buffer))
630                      article)))
631       (when article
632         (gnus-message 9 "nnimap: Fetching (part of) article %d..." article)
633         (if (not nnheader-callback-function)
634             (with-current-buffer (or to-buffer nntp-server-buffer)
635               (erase-buffer)
636               (let ((data (imap-fetch article part prop nil
637                                       nnimap-server-buffer)))
638                 (when data
639                   (insert (nnimap-demule (if detail
640                                              (nth 2 (car data))
641                                            data)))
642                   (nnheader-ms-strip-cr)
643                   (gnus-message 9
644                                 "nnimap: Fetching (part of) article %d...done"
645                                 article)
646                   (if (bobp)
647                       (nnheader-report 'nnimap "No such article: %s"
648                                        (imap-error-text nnimap-server-buffer))
649                     (cons group article)))))
650           (add-hook 'imap-fetch-data-hook 'nnimap-callback)
651           (setq nnimap-callback-callback-function nnheader-callback-function
652                 nnimap-callback-buffer nntp-server-buffer)
653           (imap-fetch-asynch article part nil nnimap-server-buffer)
654           (cons group article))))))
655
656 (deffoo nnimap-asynchronous-p ()
657   t)
658
659 (deffoo nnimap-request-article (article &optional group server to-buffer)
660   (if (imap-capability 'IMAP4rev1 nnimap-server-buffer)
661       (nnimap-request-article-part
662        article "BODY.PEEK[]" 'BODYDETAIL group server to-buffer 'detail)
663     (nnimap-request-article-part
664      article "RFC822.PEEK" 'RFC822 group server to-buffer)))
665
666 (deffoo nnimap-request-head (article &optional group server to-buffer)
667   (if (imap-capability 'IMAP4rev1 nnimap-server-buffer)
668       (nnimap-request-article-part
669        article "BODY.PEEK[HEADER]" 'BODYDETAIL group server to-buffer 'detail)
670     (nnimap-request-article-part
671      article "RFC822.HEADER" 'RFC822.HEADER group server to-buffer)))
672
673 (deffoo nnimap-request-body (article &optional group server to-buffer)
674   (if (imap-capability 'IMAP4rev1 nnimap-server-buffer)
675       (nnimap-request-article-part
676        article "BODY.PEEK[TEXT]" 'BODYDETAIL group server to-buffer 'detail)
677     (nnimap-request-article-part
678      article "RFC822.TEXT.PEEK" 'RFC822.TEXT group server to-buffer)))
679
680 (deffoo nnimap-request-group (group &optional server fast)
681   (nnimap-request-update-info-internal
682    group
683    (gnus-get-info (gnus-group-prefixed-name
684                    group (gnus-server-to-method (format "nnimap:%s" server))))
685    server)
686   (when (nnimap-possibly-change-group group server)
687     (let (info)
688       (cond (fast group)
689             ((null (setq info (nnimap-find-minmax-uid group t)))
690              (nnheader-report 'nnimap "Could not get active info for %s"
691                               group))
692             (t
693              (nnheader-insert "211 %d %d %d %s\n" (or (nth 0 info) 0)
694                               (max 1 (or (nth 1 info) 1))
695                               (or (nth 2 info) 0) group)
696              (nnheader-report 'nnimap "Group %s selected" group)
697              t)))))
698
699 (defun nnimap-close-group (group &optional server)
700   (with-current-buffer nnimap-server-buffer
701     (when (and (imap-opened)
702                (nnimap-possibly-change-group group server))
703       (case nnimap-expunge-on-close
704         ('always (imap-mailbox-expunge)
705                  (imap-mailbox-close))
706         ('ask (if (and (imap-search "DELETED")
707                        (gnus-y-or-n-p (format
708                                        "Expunge articles in group `%s'? "
709                                        imap-current-mailbox)))
710                   (progn (imap-mailbox-expunge)
711                          (imap-mailbox-close))
712                 (imap-mailbox-unselect)))
713         (t (imap-mailbox-unselect)))
714       (not imap-current-mailbox))))
715
716 (defun nnimap-pattern-to-list-arguments (pattern)
717   (mapcar (lambda (p)
718             (cons (car-safe p) (or (cdr-safe p) p)))
719           (if (and (listp pattern)
720                    (listp (cdr pattern)))
721               pattern
722             (list pattern))))
723
724 (deffoo nnimap-request-list (&optional server)
725   (when (nnimap-possibly-change-server server)
726     (with-current-buffer nntp-server-buffer
727       (erase-buffer))
728     (gnus-message 5 "nnimap: Generating active list%s..."
729                   (if (> (length server) 0) (concat " for " server) ""))
730     (with-current-buffer nnimap-server-buffer
731       (dolist (pattern (nnimap-pattern-to-list-arguments nnimap-list-pattern))
732         (dolist (mbx (funcall nnimap-request-list-method
733                               (cdr pattern) (car pattern)))
734           (or (member "\\NoSelect" (imap-mailbox-get 'list-flags mbx))
735               (let ((info (nnimap-find-minmax-uid mbx 'examine)))
736                 (when info
737                   (with-current-buffer nntp-server-buffer
738                    (insert (format "\"%s\" %d %d y\n"
739                                    mbx (or (nth 2 info) 0)
740                                    (max 1 (or (nth 1 info) 1)))))))))))
741     (gnus-message 5 "nnimap: Generating active list%s...done"
742                   (if (> (length server) 0) (concat " for " server) ""))
743     t))
744
745 (deffoo nnimap-request-post (&optional server)
746   (let ((success t))
747     (dolist (mbx (message-unquote-tokens
748                   (message-tokenize-header
749                    (message-fetch-field "Newsgroups") ", ")) success))
750       (let ((to-newsgroup (gnus-group-prefixed-name mbx gnus-command-method)))
751         (or (gnus-active to-newsgroup)
752             (gnus-activate-group to-newsgroup)
753             (if (gnus-y-or-n-p (format "No such group: %s.  Create it? "
754                                        to-newsgroup))
755                 (or (and (gnus-request-create-group
756                           to-newsgroup gnus-command-method)
757                          (gnus-activate-group to-newsgroup nil nil
758                                               gnus-command-method))
759                     (error "Couldn't create group %s" to-newsgroup)))
760             (error "No such group: %s" to-newsgroup))
761         (unless (nnimap-request-accept-article mbx (nth 1 gnus-command-method))
762           (setq success nil))))))
763
764 ;; Optional backend functions
765
766 (deffoo nnimap-retrieve-groups (groups &optional server)
767   (when (nnimap-possibly-change-server server)
768     (gnus-message 5 "nnimap: Checking mailboxes...")
769     (with-current-buffer nntp-server-buffer
770       (erase-buffer)
771       (dolist (group groups)
772         (gnus-message 7 "nnimap: Checking mailbox %s" group)
773         (or (member "\\NoSelect"
774                     (imap-mailbox-get 'list-flags group nnimap-server-buffer))
775             (let ((info (nnimap-find-minmax-uid group 'examine)))
776               (insert (format "\"%s\" %d %d y\n" group
777                               (or (nth 2 info) 0)
778                               (max 1 (or (nth 1 info) 1))))))))
779     (gnus-message 5 "nnimap: Checking mailboxes...done")
780     'active))
781
782 (deffoo nnimap-request-update-info-internal (group info &optional server)
783   (when (nnimap-possibly-change-group group server)
784     (when info;; xxx what does this mean? should we create a info?
785       (with-current-buffer nnimap-server-buffer
786         (gnus-message 5 "nnimap: Updating info for %s..."
787                       (gnus-info-group info))
788         
789         (when (nnimap-mark-permanent-p 'read)
790           (let (seen unseen)
791             ;; read info could contain articles marked unread by other
792             ;; imap clients!  we correct this
793             (setq seen (gnus-uncompress-range (gnus-info-read info))
794                   unseen (imap-search "UNSEEN UNDELETED")
795                   seen (gnus-set-difference seen unseen)
796                   ;; seen might lack articles marked as read by other
797                   ;; imap clients! we correct this
798                   seen (append seen (imap-search "SEEN"))
799                   ;; remove dupes
800                   seen (sort seen '<)
801                   seen (gnus-compress-sequence seen t)
802                   ;; we can't return '(1) since this isn't a "list of ranges",
803                   ;; and we can't return '((1)) since g-list-of-unread-articles
804                   ;; is buggy so we return '((1 . 1)).
805                   seen (if (and (integerp (car seen))
806                                 (null (cdr seen)))
807                            (list (cons (car seen) (car seen)))
808                          seen))
809             (gnus-info-set-read info seen)))
810
811         (mapcar (lambda (pred)
812                   (when (and (nnimap-mark-permanent-p (cdr pred))
813                              (member (nnimap-mark-to-flag (cdr pred))
814                                      (imap-mailbox-get 'flags)))
815                     (gnus-info-set-marks
816                      info
817                      (nnimap-update-alist-soft
818                       (cdr pred)
819                       (gnus-compress-sequence
820                        (imap-search (nnimap-mark-to-predicate (cdr pred))))
821                       (gnus-info-marks info))
822                      t)))
823                 gnus-article-mark-lists)
824         
825         (gnus-message 5 "nnimap: Updating info for %s...done"
826                       (gnus-info-group info))
827
828         info))))
829
830 (deffoo nnimap-request-type (group &optional article)
831   (if (and nnimap-news-groups (string-match nnimap-news-groups group))
832       'news
833     'mail))
834
835 (deffoo nnimap-request-set-mark (group actions &optional server)
836   (when (nnimap-possibly-change-group group server)
837     (with-current-buffer nnimap-server-buffer
838       (let (action)
839         (gnus-message 7 "nnimap: Setting marks in %s..." group)
840         (while (setq action (pop actions))
841           (let ((range (nth 0 action))
842                 (what  (nth 1 action))
843                 (cmdmarks (nth 2 action))
844                 marks)
845             ;; cache flags are pointless on the server
846             (setq cmdmarks (delq 'cache cmdmarks))
847             ;; flag dormant articles as ticked
848             (if (memq 'dormant cmdmarks)
849                 (setq cmdmarks (cons 'tick cmdmarks)))
850             ;; remove stuff we are forbidden to store
851             (mapcar (lambda (mark)
852                       (if (imap-message-flag-permanent-p
853                            (nnimap-mark-to-flag mark))
854                           (setq marks (cons mark marks))))
855                     cmdmarks)
856             (when (and range marks)
857               (cond ((eq what 'del)
858                      (imap-message-flags-del
859                       (nnimap-range-to-string range)
860                       (nnimap-mark-to-flag marks nil t)))
861                     ((eq what 'add)
862                      (imap-message-flags-add
863                       (nnimap-range-to-string range)
864                       (nnimap-mark-to-flag marks nil t)))
865                     ((eq what 'set)
866                      (imap-message-flags-set
867                       (nnimap-range-to-string range)
868                       (nnimap-mark-to-flag marks nil t)))))))
869         (gnus-message 7 "nnimap: Setting marks in %s...done" group))))
870   nil)
871
872 (defun nnimap-split-fancy ()
873   "Like nnmail-split-fancy, but uses nnimap-split-fancy."
874   (let ((nnmail-split-fancy nnimap-split-fancy))
875     (nnmail-split-fancy)))
876
877 (defun nnimap-split-to-groups (rules)
878   ;; tries to match all rules in nnimap-split-rule against content of
879   ;; nntp-server-buffer, returns a list of groups that matched.
880   (with-current-buffer nntp-server-buffer
881     ;; Fold continuation lines.
882     (goto-char (point-min))
883     (while (re-search-forward "\\(\r?\n[ \t]+\\)+" nil t)
884       (replace-match " " t t))
885     (if (functionp rules)
886         (funcall rules)
887       (let (to-groups regrepp)
888         (catch 'split-done
889           (dolist (rule rules to-groups)
890             (let ((group (car rule))
891                   (regexp (cadr rule)))
892               (goto-char (point-min))
893               (when (and (if (stringp regexp)
894                              (progn
895                                (setq regrepp (string-match "\\\\[0-9&]" group))
896                                (re-search-forward regexp nil t))
897                            (funcall regexp group))
898                          ;; Don't enter the article into the same group twice.
899                          (not (assoc group to-groups)))
900                 (push (if regrepp
901                           (nnmail-expand-newtext group)
902                         group)
903                       to-groups)
904                 (or nnimap-split-crosspost
905                     (throw 'split-done to-groups))))))))))
906   
907 (defun nnimap-split-find-rule (server inbox)
908   nnimap-split-rule)
909
910 (defun nnimap-split-find-inbox (server)
911   (if (listp nnimap-split-inbox)
912       nnimap-split-inbox
913     (list nnimap-split-inbox)))
914
915 (defun nnimap-split-articles (&optional group server)
916   (when (nnimap-possibly-change-server server)
917     (with-current-buffer nnimap-server-buffer
918       (let (rule inbox removeorig (inboxes (nnimap-split-find-inbox server)))
919         ;; iterate over inboxes
920         (while (and (setq inbox (pop inboxes))
921                     (nnimap-possibly-change-group inbox));; SELECT
922           ;; find split rule for this server / inbox
923           (when (setq rule (nnimap-split-find-rule server inbox))
924             ;; iterate over articles
925             (dolist (article (imap-search nnimap-split-predicate))
926               (when (nnimap-request-head article)
927                 ;; copy article to right group(s)
928                 (setq removeorig nil)
929                 (dolist (to-group (nnimap-split-to-groups rule))
930                   (if (imap-message-copy (number-to-string article)
931                                          to-group nil 'nocopyuid)
932                       (progn
933                         (message "IMAP split moved %s:%s:%d to %s" server inbox
934                                  article to-group)
935                         (setq removeorig t)
936                         ;; Add the group-art list to the history list.
937                         (push (list (cons to-group 0)) nnmail-split-history))
938                     (message "IMAP split failed to move %s:%s:%d to %s" server
939                              inbox article to-group)))
940                 ;; remove article if it was successfully copied somewhere
941                 (and removeorig
942                      (imap-message-flags-add (format "%d" article)
943                                              "\\Seen \\Deleted")))))
944           (when (imap-mailbox-select inbox);; just in case
945             ;; todo: UID EXPUNGE (if available) to remove splitted articles
946             (imap-mailbox-expunge)
947             (imap-mailbox-close)))
948         t))))
949
950 (deffoo nnimap-request-scan (&optional group server)
951   (nnimap-split-articles group server))
952
953 (deffoo nnimap-request-newgroups (date &optional server)
954   (when (nnimap-possibly-change-server server)
955     (with-current-buffer nntp-server-buffer
956       (gnus-message 5 "nnimap: Listing subscribed mailboxes%s%s..."
957                     (if (> (length server) 0) " on " "") server)
958       (erase-buffer)
959       (dolist (pattern (nnimap-pattern-to-list-arguments
960                         nnimap-list-pattern))
961         (dolist (mbx (imap-mailbox-lsub "*" (car pattern) nil 
962                                         nnimap-server-buffer))
963           (or (member-if (lambda (mailbox)
964                            (string= (downcase mailbox) "\\noselect"))
965                          (imap-mailbox-get 'list-flags mbx
966                                            nnimap-server-buffer))
967               (let ((info (nnimap-find-minmax-uid mbx 'examine)))
968                 (when info
969                   (insert (format "\"%s\" %d %d y\n"
970                                   mbx (or (nth 2 info) 0)
971                                   (max 1 (or (nth 1 info) 1)))))))))
972       (gnus-message 5 "nnimap: Listing subscribed mailboxes%s%s...done"
973                     (if (> (length server) 0) " on " "") server))
974     t))
975       
976 (deffoo nnimap-request-create-group (group &optional server args)
977   (when (nnimap-possibly-change-server server)
978     (or (imap-mailbox-status group 'uidvalidity nnimap-server-buffer)
979         (imap-mailbox-create group nnimap-server-buffer))))
980
981 (defun nnimap-time-substract (time1 time2)
982   "Return TIME for TIME1 - TIME2."
983   (let* ((ms (- (car time1) (car time2)))
984          (ls (- (nth 1 time1) (nth 1 time2))))
985     (if (< ls 0)
986         (list (- ms 1) (+ (expt 2 16) ls))
987       (list ms ls))))
988
989 (defun nnimap-date-days-ago (daysago)
990   "Return date, in format \"3-Aug-1998\", for DAYSAGO days ago."
991   (let ((date (format-time-string "%d-%b-%Y"
992                                   (nnimap-time-substract
993                                    (current-time)
994                                    (days-to-time daysago)))))
995     (if (eq ?0 (string-to-char date))
996         (substring date 1)
997       date)))
998
999 (defun nnimap-request-expire-articles-progress ()
1000   (gnus-message 5 "nnimap: Marking article %d for deletion..."
1001                 imap-current-message))
1002
1003 ;; Notice that we don't actually delete anything, we just mark them deleted.
1004 (deffoo nnimap-request-expire-articles (articles group &optional server force)
1005   (let ((artseq (gnus-compress-sequence articles)))
1006     (when (and artseq (nnimap-possibly-change-group group server))
1007       (with-current-buffer nnimap-server-buffer
1008         (if force
1009             (and (imap-message-flags-add
1010                   (nnimap-range-to-string artseq) "\\Deleted")
1011                  (setq articles nil))
1012           (let ((days (or (and nnmail-expiry-wait-function
1013                                (funcall nnmail-expiry-wait-function group))
1014                           nnmail-expiry-wait)))
1015             (cond ((eq days 'immediate)
1016                    (and (imap-message-flags-add
1017                          (nnimap-range-to-string artseq) "\\Deleted")
1018                         (setq articles nil)))
1019                   ((numberp days)
1020                    (let ((oldarts (imap-search
1021                                    (format "UID %s NOT SINCE %s"
1022                                            (nnimap-range-to-string artseq)
1023                                            (nnimap-date-days-ago days))))
1024                          (imap-fetch-data-hook
1025                           '(nnimap-request-expire-articles-progress)))
1026                      (and oldarts
1027                           (imap-message-flags-add
1028                            (nnimap-range-to-string
1029                             (gnus-compress-sequence oldarts))
1030                            "\\Deleted")
1031                           (setq articles (gnus-set-difference
1032                                           articles oldarts)))))))))))
1033   ;; return articles not deleted
1034   articles)
1035
1036 (deffoo nnimap-request-move-article (article group server
1037                                              accept-form &optional last)
1038   (when (nnimap-possibly-change-server server)
1039     (save-excursion
1040       (let ((buf (get-buffer-create " *nnimap move*"))
1041             (nnimap-current-move-article article)
1042             (nnimap-current-move-group group)
1043             (nnimap-current-move-server nnimap-current-server)
1044             result)
1045         (and (nnimap-request-article article group server)
1046              (save-excursion
1047                (set-buffer buf)
1048                (buffer-disable-undo (current-buffer))
1049                (insert-buffer-substring nntp-server-buffer)
1050                (setq result (eval accept-form))
1051                (kill-buffer buf)
1052                result)
1053              (nnimap-request-expire-articles (list article) group server t))
1054         result))))
1055   
1056 (deffoo nnimap-request-accept-article (group &optional server last)
1057   (when (nnimap-possibly-change-server server)
1058     (let (uid)
1059       (if (setq uid
1060                 (if (string= nnimap-current-server nnimap-current-move-server)
1061                     ;; moving article within same server, speed it up...
1062                     (and (nnimap-possibly-change-group
1063                           nnimap-current-move-group)
1064                          (imap-message-copy (number-to-string
1065                                              nnimap-current-move-article)
1066                                             group 'dontcreate nil
1067                                             nnimap-server-buffer))
1068                   ;; turn into rfc822 format (\r\n eol's)
1069                   (with-current-buffer (current-buffer)
1070                     (goto-char (point-min))
1071                     (while (search-forward "\n" nil t)
1072                       (replace-match "\r\n")))
1073                   ;; this 'or' is for Cyrus server bug
1074                   (or (null (imap-current-mailbox nnimap-server-buffer))
1075                       (imap-mailbox-unselect nnimap-server-buffer))
1076                   (imap-message-append group (current-buffer) nil nil
1077                                        nnimap-server-buffer)))
1078           (cons group (nth 1 uid))
1079         (nnheader-report 'nnimap (imap-error-text nnimap-server-buffer))))))
1080
1081 (deffoo nnimap-request-delete-group (group force &optional server)
1082   (when (nnimap-possibly-change-server server)
1083     (with-current-buffer nnimap-server-buffer
1084       (if force
1085           (or (null (imap-mailbox-status group 'uidvalidity))
1086               (imap-mailbox-delete group))
1087         ;; UNSUBSCRIBE?
1088         t))))
1089
1090 (deffoo nnimap-request-rename-group (group new-name &optional server)
1091   (when (nnimap-possibly-change-server server)
1092     (imap-mailbox-rename group new-name nnimap-server-buffer)))
1093
1094 (defun nnimap-expunge (mailbox server)
1095   (when (nnimap-possibly-change-group mailbox server)
1096     (imap-mailbox-expunge nnimap-server-buffer)))
1097
1098 (defun nnimap-acl-get (mailbox server)
1099   (when (nnimap-possibly-change-server server)
1100     (imap-mailbox-acl-get mailbox nnimap-server-buffer)))
1101
1102 (defun nnimap-acl-edit (mailbox method old-acls new-acls)
1103   (when (nnimap-possibly-change-server (cadr method))
1104     (unless (imap-capability 'ACL nnimap-server-buffer)
1105       (error "Your server does not support ACL editing"))
1106     (with-current-buffer nnimap-server-buffer
1107       ;; delete all removed identifiers
1108       (mapcar (lambda (old-acl)
1109                 (unless (assoc (car old-acl) new-acls)
1110                   (or (imap-mailbox-acl-delete (car old-acl) mailbox)
1111                       (error "Can't delete ACL for %s" (car old-acl)))))
1112               old-acls)
1113       ;; set all changed acl's
1114       (mapcar (lambda (new-acl)
1115                 (let ((new-rights (cdr new-acl))
1116                       (old-rights (cdr (assoc (car new-acl) old-acls))))
1117                   (unless (and old-rights new-rights
1118                                (string= old-rights new-rights))
1119                     (or (imap-mailbox-acl-set (car new-acl) new-rights mailbox)
1120                         (error "Can't set ACL for %s to %s" (car new-acl)
1121                                new-rights)))))
1122               new-acls)
1123       t)))
1124
1125 \f
1126 ;;; Internal functions
1127
1128 ;;
1129 ;; This is confusing.
1130 ;;
1131 ;; mark      => read, tick, draft, reply etc
1132 ;; flag      => "\\Seen", "\\Flagged", "\\Draft", "gnus-expire" etc
1133 ;; predicate => "SEEN", "FLAGGED", "DRAFT", "KEYWORD gnus-expire" etc
1134 ;;
1135 ;; Mark should not really contain 'read since it's not a "mark" in the Gnus
1136 ;; world, but we cheat.  Mark == gnus-article-mark-lists + '(read . read).
1137 ;;
1138
1139 (defconst nnimap-mark-to-predicate-alist
1140   (mapcar
1141    (lambda (pair)                       ; cdr is the mark
1142      (or (assoc (cdr pair)
1143                 '((read . "SEEN")
1144                   (tick . "FLAGGED")
1145                   (draft . "DRAFT")
1146                   (reply . "ANSWERED")))
1147          (cons (cdr pair)
1148                (format "KEYWORD gnus-%s" (symbol-name (cdr pair))))))
1149    (cons '(read . read) gnus-article-mark-lists)))
1150
1151 (defun nnimap-mark-to-predicate (pred)
1152   "Convert a Gnus mark (a symbol such as read, tick, expire) to a IMAP predicate.
1153 This is a string such as \"SEEN\", \"FLAGGED\", \"KEYWORD gnus-expire\",
1154 to be used within a IMAP SEARCH query."
1155   (cdr (assq pred nnimap-mark-to-predicate-alist)))
1156
1157 (defconst nnimap-mark-to-flag-alist
1158   (mapcar
1159    (lambda (pair)
1160      (or (assoc (cdr pair)
1161                 '((read . "\\Seen")
1162                   (tick . "\\Flagged")
1163                   (draft . "\\Draft")
1164                   (reply . "\\Answered")))
1165          (cons (cdr pair)
1166                (format "gnus-%s" (symbol-name (cdr pair))))))
1167    (cons '(read . read) gnus-article-mark-lists)))
1168
1169 (defun nnimap-mark-to-flag-1 (preds)
1170   (if (and (not (null preds)) (listp preds))
1171       (cons (nnimap-mark-to-flag (car preds))
1172             (nnimap-mark-to-flag (cdr preds)))
1173     (cdr (assoc preds nnimap-mark-to-flag-alist))))
1174
1175 (defun nnimap-mark-to-flag (preds &optional always-list make-string)
1176   "Convert a Gnus mark (a symbol such as read, tick, expire) to a IMAP flag.
1177 This is a string such as \"\\Seen\", \"\\Flagged\", \"gnus-expire\", to
1178 be used in a STORE FLAGS command."
1179   (let ((result (nnimap-mark-to-flag-1 preds)))
1180     (setq result (if (and (or make-string always-list)
1181                           (not (listp result)))
1182                      (list result)
1183                    result))
1184     (if make-string
1185         (mapconcat (lambda (flag)
1186                      (if (listp flag)
1187                          (mapconcat 'identity flag " ")
1188                        flag))
1189                    result " ")
1190       result)))
1191
1192 (defun nnimap-mark-permanent-p (mark &optional group)
1193   "Return t iff MARK can be permanently (between IMAP sessions) saved on articles, in GROUP."
1194   (imap-message-flag-permanent-p (nnimap-mark-to-flag mark)))
1195
1196 (defun nnimap-remassoc (key alist)
1197   "Delete by side effect any elements of LIST whose car is `equal' to KEY.
1198 The modified LIST is returned.  If the first member
1199 of LIST has a car that is `equal' to KEY, there is no way to remove it
1200 by side effect; therefore, write `(setq foo (remassoc key foo))' to be
1201 sure of changing the value of `foo'."
1202   (when alist
1203     (if (equal key (caar alist))
1204         (cdr alist)
1205       (setcdr alist (nnimap-remassoc key (cdr alist)))
1206       alist)))
1207   
1208 (defun nnimap-update-alist-soft (key value alist)
1209   (if value
1210       (cons (cons key value) (nnimap-remassoc key alist))
1211     (nnimap-remassoc key alist)))
1212
1213 (defun nnimap-range-to-string (range)
1214   (mapconcat
1215    (lambda (item)
1216      (if (consp item)
1217          (format "%d:%d"
1218                  (car item) (cdr item))
1219        (format "%d" item)))
1220    (if (and (listp range) (not (listp (cdr range))))
1221        (list range);; make (1 . 2) into ((1 . 2))
1222      range)
1223    ","))
1224
1225 (when nnimap-debug
1226   (require 'trace)
1227   (buffer-disable-undo (get-buffer-create nnimap-debug))
1228   (mapcar (lambda (f) (trace-function-background f nnimap-debug))
1229         '(
1230           nnimap-possibly-change-server
1231           nnimap-verify-uidvalidity
1232           nnimap-find-minmax-uid
1233           nnimap-possibly-change-group
1234           ;;nnimap-replace-whitespace
1235           nnimap-retrieve-headers-progress
1236           nnimap-retrieve-which-headers
1237           nnimap-group-overview-filename
1238           nnimap-retrieve-headers-from-file
1239           nnimap-retrieve-headers-from-server
1240           nnimap-retrieve-headers
1241           nnimap-open-connection
1242           nnimap-open-server
1243           nnimap-server-opened
1244           nnimap-close-server
1245           nnimap-request-close
1246           nnimap-status-message
1247           ;;nnimap-demule
1248           nnimap-request-article-part
1249           nnimap-request-article
1250           nnimap-request-head
1251           nnimap-request-body
1252           nnimap-request-group
1253           nnimap-close-group
1254           nnimap-pattern-to-list-arguments
1255           nnimap-request-list
1256           nnimap-request-post
1257           nnimap-retrieve-groups
1258           nnimap-request-update-info-internal
1259           nnimap-request-type
1260           nnimap-request-set-mark
1261           nnimap-split-to-groups
1262           nnimap-split-find-rule
1263           nnimap-split-find-inbox
1264           nnimap-split-articles
1265           nnimap-request-scan
1266           nnimap-request-newgroups
1267           nnimap-request-create-group
1268           nnimap-time-substract
1269           nnimap-date-days-ago
1270           nnimap-request-expire-articles-progress
1271           nnimap-request-expire-articles
1272           nnimap-request-move-article
1273           nnimap-request-accept-article
1274           nnimap-request-delete-group
1275           nnimap-request-rename-group
1276           gnus-group-nnimap-expunge
1277           gnus-group-nnimap-edit-acl
1278           gnus-group-nnimap-edit-acl-done
1279           nnimap-group-mode-hook
1280           nnimap-mark-to-predicate
1281           nnimap-mark-to-flag-1
1282           nnimap-mark-to-flag
1283           nnimap-mark-permanent-p
1284           nnimap-remassoc
1285           nnimap-update-alist-soft
1286           nnimap-range-to-string
1287           )))
1288
1289 (provide 'nnimap)
1290
1291 ;;; nnimap.el ends here