(elmo-folder-synchronize): Ignore `mask' when
[elisp/wanderlust.git] / elmo / elmo-nntp.el
1 ;;; elmo-nntp.el --- NNTP Interface for ELMO.
2
3 ;; Copyright (C) 1998,1999,2000 Yuuichi Teranishi <teranisi@gohome.org>
4 ;; Copyright (C) 1998,1999,2000 Masahiro MURATA <muse@ba2.so-net.ne.jp>
5 ;; Copyright (C) 1999,2000      Kenichi OKADA <okada@opaopa.org>
6
7 ;; Author: Yuuichi Teranishi <teranisi@gohome.org>
8 ;;      Masahiro MURATA <muse@ba2.so-net.ne.jp>
9 ;;      Kenichi OKADA <okada@opaopa.org>
10 ;; Keywords: mail, net news
11
12 ;; This file is part of ELMO (Elisp Library for Message Orchestration).
13
14 ;; This program is free software; you can redistribute it and/or modify
15 ;; it under the terms of the GNU General Public License as published by
16 ;; the Free Software Foundation; either version 2, or (at your option)
17 ;; any later version.
18 ;;
19 ;; This program is distributed in the hope that it will be useful,
20 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
21 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22 ;; GNU General Public License for more details.
23 ;;
24 ;; You should have received a copy of the GNU General Public License
25 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
26 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
27 ;; Boston, MA 02111-1307, USA.
28 ;;
29
30 ;;; Commentary:
31 ;;
32
33 ;;; Code:
34 ;;
35 (eval-when-compile (require 'cl))
36
37 (require 'elmo-vars)
38 (require 'elmo-util)
39 (require 'elmo-date)
40 (require 'elmo-msgdb)
41 (require 'elmo-cache)
42 (require 'elmo)
43 (require 'elmo-net)
44
45 (defvar elmo-nntp-overview-fetch-chop-length 200
46  "*Number of overviews to fetch in one request in nntp.")
47
48 (defvar elmo-nntp-use-cache t
49   "Use cache in nntp folder.")
50
51 (defvar elmo-nntp-max-number-precedes-list-active nil
52   "Non-nil means max number of msgdb is set as the max number of `list active'.
53 \(Needed for inn 2.3 or later?\).")
54
55 (defvar elmo-nntp-group-coding-system nil
56   "A coding system for newsgroup string.")
57
58 (defsubst elmo-nntp-encode-group-string (string)
59   (if elmo-nntp-group-coding-system
60       (encode-coding-string string elmo-nntp-group-coding-system)
61     string))
62
63 (defsubst elmo-nntp-decode-group-string (string)
64   (if elmo-nntp-group-coding-system
65       (decode-coding-string string elmo-nntp-group-coding-system)
66     string))
67
68 ;; For debugging.
69 (defvar elmo-nntp-debug nil
70   "Non-nil forces NNTP folder as debug mode.
71 Debug information is inserted in the buffer \"*NNTP DEBUG*\"")
72
73 ;;; Debug
74 (defsubst elmo-nntp-debug (message &rest args)
75   (if elmo-nntp-debug
76       (let ((biff (string-match "BIFF-" (buffer-name)))
77             pos)
78         (with-current-buffer (get-buffer-create (concat "*NNTP DEBUG*"
79                                                         (if biff "BIFF")))
80           (goto-char (point-max))
81           (setq pos (point))
82           (insert (apply 'format message args) "\n")))))
83
84 ;;; ELMO NNTP folder
85 (eval-and-compile
86   (luna-define-class elmo-nntp-folder (elmo-net-folder)
87                      (group temp-crosses reads))
88   (luna-define-internal-accessors 'elmo-nntp-folder))
89
90 (luna-define-method elmo-folder-initialize :around ((folder
91                                                      elmo-nntp-folder)
92                                                     name)
93   (let ((elmo-network-stream-type-alist
94          (if elmo-nntp-stream-type-alist
95              (setq elmo-network-stream-type-alist
96                    (append elmo-nntp-stream-type-alist
97                            elmo-network-stream-type-alist))
98            elmo-network-stream-type-alist))
99         explicit-user parse)
100     (setq name (luna-call-next-method))
101     (setq parse (elmo-parse-token name ":"))
102     (elmo-nntp-folder-set-group-internal folder
103                                          (elmo-nntp-encode-group-string
104                                           (car parse)))
105     (setq explicit-user (eq ?: (string-to-char (cdr parse))))
106     (setq parse (elmo-parse-prefixed-element ?: (cdr parse)))
107     (elmo-net-folder-set-user-internal folder
108                                        (if (eq (length (car parse)) 0)
109                                            (unless explicit-user
110                                              elmo-nntp-default-user)
111                                          (car parse)))
112     (unless (elmo-net-folder-server-internal folder)
113       (elmo-net-folder-set-server-internal folder
114                                            elmo-nntp-default-server))
115     (unless (elmo-net-folder-port-internal folder)
116       (elmo-net-folder-set-port-internal folder
117                                          elmo-nntp-default-port))
118     (unless (elmo-net-folder-stream-type-internal folder)
119       (elmo-net-folder-set-stream-type-internal
120        folder
121        (elmo-get-network-stream-type
122         elmo-nntp-default-stream-type)))
123     folder))
124
125 (luna-define-method elmo-folder-expand-msgdb-path ((folder elmo-nntp-folder))
126   (convert-standard-filename
127    (expand-file-name
128     (elmo-nntp-folder-group-internal folder)
129     (expand-file-name (or (elmo-net-folder-server-internal folder) "nowhere")
130                       (expand-file-name "nntp"
131                                         elmo-msgdb-directory)))))
132
133 (luna-define-method elmo-folder-newsgroups ((folder elmo-nntp-folder))
134   (list (elmo-nntp-folder-group-internal folder)))
135
136 ;;; NNTP Session
137 (eval-and-compile
138   (luna-define-class elmo-nntp-session (elmo-network-session)
139                      (current-group))
140   (luna-define-internal-accessors 'elmo-nntp-session))
141
142 ;;
143 ;; internal variables
144 ;;
145
146 (defvar elmo-nntp-connection-cache nil
147   "Cache of NNTP connection.")
148 ;; buffer local variable
149
150 (defvar elmo-nntp-list-folders-use-cache 600
151   "*Time to cache of list folders, as the number of seconds.
152 Don't cache if nil.")
153
154 (defvar elmo-nntp-list-folders-cache nil)
155
156 (defvar elmo-nntp-groups-async nil)
157 (defvar elmo-nntp-header-fetch-chop-length 200)
158
159 (defvar elmo-nntp-read-point 0)
160
161 (defvar elmo-nntp-send-mode-reader t)
162
163 (defvar elmo-nntp-opened-hook nil)
164
165 (defvar elmo-nntp-get-folders-securely nil)
166
167 (defvar elmo-nntp-default-use-xover t)
168
169 (defvar elmo-nntp-default-use-listgroup t)
170
171 (defvar elmo-nntp-default-use-list-active t)
172
173 (defvar elmo-nntp-default-use-xhdr t)
174
175 (defvar elmo-nntp-server-command-alist nil)
176
177
178 (defconst elmo-nntp-server-command-index '((xover . 0)
179                                            (listgroup . 1)
180                                            (list-active . 2)
181                                            (xhdr . 3)))
182
183 (defmacro elmo-nntp-get-server-command (session)
184   (` (assoc (cons (elmo-network-session-server-internal (, session))
185                   (elmo-network-session-port-internal (, session)))
186             elmo-nntp-server-command-alist)))
187
188 (defmacro elmo-nntp-set-server-command (session com value)
189   (` (let (entry)
190        (unless (setq entry (cdr (elmo-nntp-get-server-command
191                                  (, session))))
192          (setq elmo-nntp-server-command-alist
193                (nconc elmo-nntp-server-command-alist
194                       (list (cons
195                              (cons
196                               (elmo-network-session-server-internal (, session))
197                               (elmo-network-session-port-internal (, session)))
198                              (setq entry
199                                    (vector
200                                     elmo-nntp-default-use-xover
201                                     elmo-nntp-default-use-listgroup
202                                     elmo-nntp-default-use-list-active
203                                     elmo-nntp-default-use-xhdr)))))))
204        (aset entry
205              (cdr (assq (, com) elmo-nntp-server-command-index))
206              (, value)))))
207
208 (defmacro elmo-nntp-xover-p (session)
209   (` (let ((entry (elmo-nntp-get-server-command (, session))))
210        (if entry
211            (aref (cdr entry)
212                  (cdr (assq 'xover elmo-nntp-server-command-index)))
213          elmo-nntp-default-use-xover))))
214
215 (defmacro elmo-nntp-set-xover (session value)
216   (` (elmo-nntp-set-server-command (, session) 'xover (, value))))
217
218 (defmacro elmo-nntp-listgroup-p (session)
219   (` (let ((entry (elmo-nntp-get-server-command (, session))))
220        (if entry
221            (aref (cdr entry)
222                  (cdr (assq 'listgroup elmo-nntp-server-command-index)))
223          elmo-nntp-default-use-listgroup))))
224
225 (defmacro elmo-nntp-set-listgroup (session value)
226   (` (elmo-nntp-set-server-command (, session) 'listgroup (, value))))
227
228 (defmacro elmo-nntp-list-active-p (session)
229   (` (let ((entry (elmo-nntp-get-server-command (, session))))
230        (if entry
231            (aref (cdr entry)
232                  (cdr (assq 'list-active elmo-nntp-server-command-index)))
233          elmo-nntp-default-use-list-active))))
234
235 (defmacro elmo-nntp-set-list-active (session value)
236   (` (elmo-nntp-set-server-command (, session) 'list-active (, value))))
237
238 (defmacro elmo-nntp-xhdr-p (session)
239   (` (let ((entry (elmo-nntp-get-server-command (, session))))
240        (if entry
241            (aref (cdr entry)
242                  (cdr (assq 'xhdr elmo-nntp-server-command-index)))
243          elmo-nntp-default-use-xhdr))))
244
245 (defmacro elmo-nntp-set-xhdr (session value)
246   (` (elmo-nntp-set-server-command (, session) 'xhdr (, value))))
247
248 (defsubst elmo-nntp-max-number-precedes-list-active-p ()
249   elmo-nntp-max-number-precedes-list-active)
250
251 (defsubst elmo-nntp-folder-postfix (user server port type)
252   (concat
253    (and user (concat ":" user))
254    (if (and server
255             (null (string= server elmo-nntp-default-server)))
256        (concat "@" server))
257    (if (and port
258             (null (eq port elmo-nntp-default-port)))
259        (concat ":" (if (numberp port)
260                        (int-to-string port) port)))
261    (unless (eq (elmo-network-stream-type-symbol type)
262                elmo-nntp-default-stream-type)
263      (elmo-network-stream-type-spec-string type))))
264
265 (defun elmo-nntp-get-session (folder &optional if-exists)
266   (elmo-network-get-session
267    'elmo-nntp-session
268    (concat
269     (if (elmo-folder-biff-internal folder)
270         "BIFF-")
271     "NNTP")
272    folder
273    if-exists))
274
275 (luna-define-method elmo-network-initialize-session ((session
276                                                       elmo-nntp-session))
277   (let ((process (elmo-network-session-process-internal session))
278         response)
279     (set-process-filter (elmo-network-session-process-internal session)
280                         'elmo-nntp-process-filter)
281     (with-current-buffer (elmo-network-session-buffer session)
282       (setq elmo-nntp-read-point (point-min))
283       ;; Skip garbage output from process before greeting.
284       (while (and (memq (process-status process) '(open run))
285                   (goto-char (point-max))
286                   (forward-line -1)
287                   (not (looking-at "^[2-5][0-9][0-9]")))
288         (accept-process-output process 1))
289       (setq elmo-nntp-read-point (point))
290       (setq response (elmo-nntp-read-response session t t))
291       (unless (car response)
292           (signal 'elmo-open-error (list (cdr response))))
293       (if elmo-nntp-send-mode-reader
294           (elmo-nntp-send-mode-reader session))
295       (when (eq (elmo-network-stream-type-symbol
296                  (elmo-network-session-stream-type-internal session))
297                 'starttls)
298         (elmo-nntp-send-command session "starttls")
299         (or (elmo-nntp-read-response session)
300             (error "Cannot open starttls session"))
301         (starttls-negotiate process)))))
302
303 (luna-define-method elmo-network-authenticate-session ((session
304                                                         elmo-nntp-session))
305   (with-current-buffer (elmo-network-session-buffer session)
306     (when (elmo-network-session-user-internal session)
307       (elmo-nntp-send-command session
308                               (format "authinfo user %s"
309                                       (elmo-network-session-user-internal
310                                        session))
311                               nil
312                               'no-log)
313       (or (elmo-nntp-read-response session)
314           (signal 'elmo-authenticate-error '(authinfo)))
315       (elmo-nntp-send-command
316        session
317        (format "authinfo pass %s"
318                (elmo-get-passwd (elmo-network-session-password-key session)))
319        nil
320        'no-log)
321       (or (elmo-nntp-read-response session)
322           (signal 'elmo-authenticate-error '(authinfo))))))
323
324 (luna-define-method elmo-network-setup-session ((session
325                                                  elmo-nntp-session))
326   (run-hooks 'elmo-nntp-opened-hook))
327
328 (defun elmo-nntp-process-filter (process output)
329   (when (buffer-live-p (process-buffer process))
330     (with-current-buffer (process-buffer process)
331       (goto-char (point-max))
332       (insert output)
333       (elmo-nntp-debug "RECEIVED: %s\n" output))))
334
335 (defun elmo-nntp-send-mode-reader (session)
336   (elmo-nntp-send-command session "mode reader")
337   (if (null (elmo-nntp-read-response session t))
338       (message "Mode reader failed")))
339
340 (defun elmo-nntp-send-command (session command &optional noerase no-log)
341   (with-current-buffer (elmo-network-session-buffer session)
342     (unless noerase
343       (erase-buffer)
344       (goto-char (point-min)))
345     (setq elmo-nntp-read-point (point))
346     (elmo-nntp-debug "SEND: %s\n" (if no-log "<NO LOGGING>" command))
347     (process-send-string (elmo-network-session-process-internal
348                           session) command)
349     (process-send-string (elmo-network-session-process-internal
350                           session) "\r\n")))
351
352 (defun elmo-nntp-read-response (session &optional not-command error-msg)
353   (with-current-buffer (elmo-network-session-buffer session)
354     (let ((process (elmo-network-session-process-internal session))
355           (case-fold-search nil)
356           (response-string nil)
357           (response-continue t)
358           response match-end)
359       (while response-continue
360         (goto-char elmo-nntp-read-point)
361         (while (not (search-forward "\r\n" nil t))
362           (accept-process-output process)
363           (goto-char elmo-nntp-read-point))
364         (setq match-end (point))
365         (setq response-string
366               (buffer-substring elmo-nntp-read-point (- match-end 2)))
367         (goto-char elmo-nntp-read-point)
368         (if (looking-at "[23][0-9]+ .*$")
369             (progn (setq response-continue nil)
370                    (setq elmo-nntp-read-point match-end)
371                    (setq response
372                          (if response
373                              (concat response "\n" response-string)
374                            response-string)))
375           (if (looking-at "[^23][0-9]+ .*$")
376               (progn (setq response-continue nil)
377                      (setq elmo-nntp-read-point match-end)
378                      (setq response nil))
379             (setq elmo-nntp-read-point match-end)
380             (if not-command
381                 (setq response-continue nil))
382             (setq response
383                   (if response
384                       (concat response "\n" response-string)
385                     response-string)))
386           (setq elmo-nntp-read-point match-end)))
387       (if error-msg
388           (cons response response-string)
389         response))))
390
391 (defun elmo-nntp-read-raw-response (session)
392   (with-current-buffer (elmo-network-session-buffer session)
393     (goto-char elmo-nntp-read-point)
394     (while (not (search-forward "\r\n" nil t))
395       (accept-process-output (elmo-network-session-process-internal
396                               session))
397       (goto-char elmo-nntp-read-point))
398     (buffer-substring elmo-nntp-read-point (- (point) 2))))
399
400 (defun elmo-nntp-read-contents (session)
401   (with-current-buffer (elmo-network-session-buffer session)
402     (goto-char elmo-nntp-read-point)
403     (while (not (re-search-forward "^\\.\r\n" nil t))
404       (accept-process-output (elmo-network-session-process-internal
405                               session))
406       (goto-char elmo-nntp-read-point))
407     (elmo-delete-cr
408      (buffer-substring elmo-nntp-read-point
409                        (- (point) 3)))))
410
411 (defun elmo-nntp-read-body (session outbuf)
412   (with-current-buffer (elmo-network-session-buffer session)
413     (goto-char elmo-nntp-read-point)
414     (while (not (re-search-forward "^\\.\r\n" nil t))
415       (accept-process-output (elmo-network-session-process-internal session))
416       (goto-char elmo-nntp-read-point))
417     (let ((start elmo-nntp-read-point)
418           (end  (point)))
419       (with-current-buffer outbuf
420         (erase-buffer)
421         (insert-buffer-substring (elmo-network-session-buffer session)
422                                  start (- end 3))
423         (elmo-delete-cr-buffer)))
424     t))
425
426 (defun elmo-nntp-select-group (session group &optional force)
427   (let (response)
428     (when (or force
429               (not (string= (elmo-nntp-session-current-group-internal session)
430                             group)))
431       (unwind-protect
432           (progn
433             (elmo-nntp-send-command session (format "group %s" group))
434             (setq response (elmo-nntp-read-response session)))
435         (elmo-nntp-session-set-current-group-internal session
436                                                       (and response group))
437         response))))
438
439 (defun elmo-nntp-list-folders-get-cache (group server buf)
440   (when (and elmo-nntp-list-folders-use-cache
441              elmo-nntp-list-folders-cache
442              (string-match (concat "^"
443                                    (regexp-quote
444                                     (or
445                                      (nth 1 elmo-nntp-list-folders-cache)
446                                      "")))
447                            (or group ""))
448              (string-match (concat "^"
449                                    (regexp-quote
450                                     (or
451                                      (nth 2 elmo-nntp-list-folders-cache)
452                                      "")))
453                            (or server "")))
454     (let* ((cache-time (car elmo-nntp-list-folders-cache)))
455       (unless (elmo-time-expire cache-time
456                                 elmo-nntp-list-folders-use-cache)
457         (save-excursion
458           (set-buffer buf)
459           (erase-buffer)
460           (insert (nth 3 elmo-nntp-list-folders-cache))
461           (goto-char (point-min))
462           (or (string= group "")
463               (and group
464                    (keep-lines (concat "^" (regexp-quote group) "\\."))))
465           t
466           )))))
467
468 (defsubst elmo-nntp-catchup-msgdb (msgdb max-number)
469   (let ((numbers (elmo-msgdb-list-messages msgdb))
470         msgdb-max)
471     (setq msgdb-max (if numbers (apply #'max numbers) 0))
472     (when (and msgdb-max
473                max-number
474                (< msgdb-max max-number))
475       (let ((i (1+ msgdb-max))
476             killed)
477         (while (<= i max-number)
478           (setq killed (cons i killed))
479           (incf i))
480         (nreverse killed)))))
481
482 (luna-define-method elmo-folder-list-subfolders ((folder elmo-nntp-folder)
483                                                  &optional one-level)
484   (elmo-nntp-folder-list-subfolders folder one-level))
485
486 (defun elmo-nntp-folder-list-subfolders (folder one-level)
487   (let ((session (elmo-nntp-get-session folder))
488         (case-fold-search nil)
489         response ret-val top-ng username append-serv use-list-active start)
490     (with-temp-buffer
491       (set-buffer-multibyte nil)
492       (if (and (elmo-nntp-folder-group-internal folder)
493                (elmo-nntp-select-group
494                 session
495                 (elmo-nntp-folder-group-internal folder)))
496           ;; add top newsgroups
497           (setq ret-val (list (elmo-nntp-folder-group-internal folder))))
498       (unless (setq response (elmo-nntp-list-folders-get-cache
499                               (elmo-nntp-folder-group-internal folder)
500                               (elmo-net-folder-server-internal folder)
501                               (current-buffer)))
502         (when (setq use-list-active (elmo-nntp-list-active-p session))
503           (elmo-nntp-send-command
504            session
505            (concat "list"
506                    (if (and (elmo-nntp-folder-group-internal folder)
507                             (not (string= (elmo-nntp-folder-group-internal
508                                            folder) "")))
509                        (concat " active"
510                                (format
511                                 " %s.*"
512                                 (elmo-nntp-folder-group-internal folder))))))
513           (if (elmo-nntp-read-response session t)
514               (if (null (setq response (elmo-nntp-read-contents session)))
515                   (error "NNTP List folders failed")
516                 (when elmo-nntp-list-folders-use-cache
517                   (setq elmo-nntp-list-folders-cache
518                         (list (current-time)
519                               (elmo-nntp-folder-group-internal folder)
520                               (elmo-net-folder-server-internal folder)
521                               response)))
522                 (erase-buffer)
523                 (insert response))
524             (elmo-nntp-set-list-active session nil)
525             (setq use-list-active nil)))
526         (when (null use-list-active)
527           (elmo-nntp-send-command session "list")
528           (if (null (and (elmo-nntp-read-response session t)
529                          (setq response (elmo-nntp-read-contents session))))
530               (error "NNTP List folders failed"))
531           (when elmo-nntp-list-folders-use-cache
532             (setq elmo-nntp-list-folders-cache
533                   (list (current-time) nil nil response)))
534           (erase-buffer)
535           (setq start nil)
536           (while (string-match (concat "^"
537                                        (regexp-quote
538                                         (or
539                                          (elmo-nntp-folder-group-internal
540                                           folder)
541                                          "")) ".*$")
542                                response start)
543             (insert (match-string 0 response) "\n")
544             (setq start (match-end 0)))))
545       (goto-char (point-min))
546       (let ((len (count-lines (point-min) (point-max)))
547             (i 0) regexp)
548         (if one-level
549             (progn
550               (setq regexp
551                     (format "^\\(%s[^. ]+\\)\\([. ]\\).*\n"
552                             (if (and (elmo-nntp-folder-group-internal folder)
553                                      (null (string=
554                                             (elmo-nntp-folder-group-internal
555                                              folder) "")))
556                                 (concat (elmo-nntp-folder-group-internal
557                                          folder)
558                                         "\\.")
559                               "")))
560               (while (looking-at regexp)
561                 (setq top-ng (elmo-match-buffer 1))
562                 (if (string= (elmo-match-buffer 2) " ")
563                     (if (not (or (member top-ng ret-val)
564                                  (assoc top-ng ret-val)))
565                         (setq ret-val (nconc ret-val (list top-ng))))
566                   (if (member top-ng ret-val)
567                       (setq ret-val (delete top-ng ret-val)))
568                   (if (not (assoc top-ng ret-val))
569                       (setq ret-val (nconc ret-val (list (list top-ng))))))
570                 (when (> len elmo-display-progress-threshold)
571                   (setq i (1+ i))
572                   (if (or (zerop (% i 10)) (= i len))
573                       (elmo-display-progress
574                        'elmo-nntp-list-folders "Parsing active..."
575                        (/ (* i 100) len))))
576                 (forward-line 1)))
577           (while (re-search-forward "\\([^ ]+\\) .*\n" nil t)
578             (setq ret-val (nconc ret-val
579                                  (list (elmo-match-buffer 1))))
580             (when (> len elmo-display-progress-threshold)
581               (setq i (1+ i))
582               (if (or (zerop (% i 10)) (= i len))
583                   (elmo-display-progress
584                    'elmo-nntp-list-folders "Parsing active..."
585                    (/ (* i 100) len))))))
586         (when (> len elmo-display-progress-threshold)
587           (elmo-display-progress
588            'elmo-nntp-list-folders "Parsing active..." 100))))
589
590     (setq username (elmo-net-folder-user-internal folder))
591     (when (and username
592                elmo-nntp-default-user
593                (string= username elmo-nntp-default-user))
594       (setq username nil))
595
596     (when (or username ; XXX: ad-hoc fix against username includes "@"
597               (not (string= (elmo-net-folder-server-internal folder)
598                             elmo-nntp-default-server)))
599       (setq append-serv (concat "@" (elmo-net-folder-server-internal
600                                      folder))))
601     (unless (eq (elmo-net-folder-port-internal folder) elmo-nntp-default-port)
602       (setq append-serv (concat append-serv
603                                 ":" (int-to-string
604                                      (elmo-net-folder-port-internal folder)))))
605     (unless (eq (elmo-network-stream-type-symbol
606                  (elmo-net-folder-stream-type-internal folder))
607                 elmo-nntp-default-stream-type)
608       (setq append-serv
609             (concat append-serv
610                     (elmo-network-stream-type-spec-string
611                      (elmo-net-folder-stream-type-internal folder)))))
612     (mapcar '(lambda (fld)
613                (if (consp fld)
614                    (list (concat "-" (elmo-nntp-decode-group-string (car fld))
615                                  (and username
616                                       (concat
617                                        ":"
618                                        username))
619                                  (and append-serv
620                                       (concat append-serv))))
621                  (concat "-" (elmo-nntp-decode-group-string fld)
622                          (and username
623                               (concat ":" username))
624                          (and append-serv
625                               (concat append-serv)))))
626             ret-val)))
627
628 (defun elmo-nntp-make-msglist (beg-str end-str)
629   (elmo-make-number-list (string-to-int beg-str) (string-to-int end-str)))
630
631 (luna-define-method elmo-folder-list-messages-plugged ((folder
632                                                         elmo-nntp-folder)
633                                                        &optional nohide)
634   (let ((session (elmo-nntp-get-session folder))
635         (group   (elmo-nntp-folder-group-internal folder))
636         response numbers use-listgroup)
637     (save-excursion
638       (when (setq use-listgroup (elmo-nntp-listgroup-p session))
639         (elmo-nntp-send-command session
640                                 (format "listgroup %s" group))
641         (if (not (elmo-nntp-read-response session t))
642             (progn
643               (elmo-nntp-set-listgroup session nil)
644               (setq use-listgroup nil))
645           (if (null (setq response (elmo-nntp-read-contents session)))
646               (error "Fetching listgroup failed"))
647           (setq numbers (elmo-string-to-list response))
648           (elmo-nntp-session-set-current-group-internal session
649                                                         group)))
650       (unless use-listgroup
651         (elmo-nntp-send-command session (format "group %s" group))
652         (if (null (setq response (elmo-nntp-read-response session)))
653             (error "Select group failed"))
654         (when (and
655                (string-match
656                 "211 \\([0-9]+\\) \\([0-9]+\\) \\([0-9]+\\) [^.].+$"
657                 response)
658                (> (string-to-int (elmo-match-string 1 response)) 0))
659           (setq numbers (elmo-nntp-make-msglist
660                          (elmo-match-string 2 response)
661                          (elmo-match-string 3 response)))))
662       numbers)))
663
664 (luna-define-method elmo-folder-status ((folder elmo-nntp-folder))
665   (elmo-nntp-folder-status folder))
666
667 (defun elmo-nntp-folder-status (folder)
668   (let ((killed-list (elmo-msgdb-killed-list-load
669                       (elmo-folder-msgdb-path folder)))
670         end-num entry)
671     (if elmo-nntp-groups-async
672         (if (setq entry
673                   (elmo-get-hash-val
674                    (concat (elmo-nntp-folder-group-internal folder)
675                            (elmo-nntp-folder-postfix
676                             (elmo-net-folder-user-internal folder)
677                             (elmo-net-folder-server-internal folder)
678                             (elmo-net-folder-port-internal folder)
679                             (elmo-net-folder-stream-type-internal folder)))
680                    elmo-newsgroups-hashtb))
681             (progn
682               (setq end-num (nth 2 entry))
683               (when (and killed-list
684                          (elmo-number-set-member end-num killed-list))
685                 ;; Max is killed.
686                 (setq end-num nil))
687               (cons end-num (car entry)))
688           (error "No such newsgroup \"%s\""
689                  (elmo-nntp-folder-group-internal folder)))
690       (let ((session (elmo-nntp-get-session folder))
691             response e-num)
692         (if (null session)
693             (error "Connection failed"))
694         (save-excursion
695           (elmo-nntp-send-command session
696                                   (format
697                                    "group %s"
698                                    (elmo-nntp-folder-group-internal folder)))
699           (setq response (elmo-nntp-read-response session))
700           (if (and response
701                    (string-match
702                     "211 \\([0-9]+\\) \\([0-9]+\\) \\([0-9]+\\) [^.].+$"
703                     response))
704               (progn
705                 (setq end-num (string-to-int
706                                (elmo-match-string 3 response)))
707                 (setq e-num (string-to-int
708                              (elmo-match-string 1 response)))
709                 (when (and killed-list
710                            (elmo-number-set-member end-num killed-list))
711                   ;; Max is killed.
712                   (setq end-num nil))
713                 (cons end-num e-num))
714             (if (null response)
715                 (error "Selecting newsgroup \"%s\" failed"
716                        (elmo-nntp-folder-group-internal folder))
717               nil)))))))
718
719 (defconst elmo-nntp-overview-index
720   '(("number" . 0)
721     ("subject" . 1)
722     ("from" . 2)
723     ("date" . 3)
724     ("message-id" . 4)
725     ("references" . 5)
726     ("size" . 6)
727     ("lines" . 7)
728     ("xref" . 8)))
729
730 (defun elmo-nntp-create-msgdb-from-overview-string (folder
731                                                     str
732                                                     flag-table
733                                                     &optional numlist)
734   (let ((new-msgdb (elmo-make-msgdb))
735         ov-list message-id entity
736         ov-entity num
737         extras extra ext field field-index flags)
738     (setq ov-list (elmo-nntp-parse-overview-string str))
739     (while ov-list
740       (setq ov-entity (car ov-list))
741 ;;; INN bug??
742 ;;;   (if (or (> (setq num (string-to-int (aref ov-entity 0)))
743 ;;;              99999)
744 ;;;           (<= num 0))
745 ;;;       (setq num 0))
746 ;;;  (setq num (int-to-string num))
747       (setq num (string-to-int (aref ov-entity 0)))
748       (when (or (null numlist)
749                 (memq num numlist))
750         (setq extras elmo-msgdb-extra-fields
751               extra nil)
752         (while extras
753           (setq ext (downcase (car extras)))
754           (when (setq field-index (cdr (assoc ext elmo-nntp-overview-index)))
755             (when (> (length ov-entity) field-index)
756               (setq field (aref ov-entity field-index))
757               (when (eq field-index 8) ;; xref
758                 (setq field (elmo-msgdb-remove-field-string field)))
759               (setq extra (cons (cons ext field) extra))))
760           (setq extras (cdr extras)))
761         (setq entity (elmo-msgdb-make-message-entity
762                       (elmo-msgdb-message-entity-handler new-msgdb)
763                       :message-id (aref ov-entity 4)
764                       :number     num
765                       :references (elmo-msgdb-get-last-message-id
766                                     (aref ov-entity 5))
767                       :from       (elmo-mime-string (elmo-delete-char
768                                                      ?\"
769                                                      (or
770                                                       (aref ov-entity 2)
771                                                       elmo-no-from) 'uni))
772                       :subject    (elmo-mime-string (or (aref ov-entity 1)
773                                                         elmo-no-subject))
774                       :date       (aref ov-entity 3)
775                       :size       (string-to-int (aref ov-entity 6))
776                       :extra      extra))
777         (setq message-id (elmo-message-entity-field entity 'message-id)
778               flags (elmo-flag-table-get flag-table message-id))
779         (elmo-global-flags-set flags folder num message-id)
780         (elmo-msgdb-append-entity new-msgdb entity flags))
781       (setq ov-list (cdr ov-list)))
782     new-msgdb))
783
784 (luna-define-method elmo-folder-msgdb-create ((folder elmo-nntp-folder)
785                                               numbers flag-table)
786   (elmo-nntp-folder-msgdb-create folder numbers flag-table))
787
788 (defun elmo-nntp-folder-msgdb-create (folder numbers flag-table)
789   (let ((filter numbers)
790         (session (elmo-nntp-get-session folder))
791         (new-msgdb (elmo-make-msgdb))
792         beg-num end-num cur length
793         new-msgdb ov-str use-xover dir)
794     (elmo-nntp-select-group session (elmo-nntp-folder-group-internal
795                                      folder))
796     (when (setq use-xover (elmo-nntp-xover-p session))
797       (setq beg-num (car numbers)
798             cur beg-num
799             end-num (nth (1- (length numbers)) numbers)
800             length  (+ (- end-num beg-num) 1))
801       (message "Getting overview...")
802       (while (<= cur end-num)
803         (elmo-nntp-send-command
804          session
805          (format
806           "xover %s-%s"
807           (int-to-string cur)
808           (int-to-string
809            (+ cur
810               elmo-nntp-overview-fetch-chop-length))))
811         (with-current-buffer (elmo-network-session-buffer session)
812           (if ov-str
813               (elmo-msgdb-append
814                new-msgdb
815                (elmo-nntp-create-msgdb-from-overview-string
816                 folder
817                 ov-str
818                 flag-table
819                 filter))))
820         (if (null (elmo-nntp-read-response session t))
821             (progn
822               (setq cur end-num);; exit while loop
823               (elmo-nntp-set-xover session nil)
824               (setq use-xover nil))
825           (if (null (setq ov-str (elmo-nntp-read-contents session)))
826               (error "Fetching overview failed")))
827         (setq cur (+ elmo-nntp-overview-fetch-chop-length cur 1))
828         (when (> length elmo-display-progress-threshold)
829           (elmo-display-progress
830            'elmo-nntp-msgdb-create "Getting overview..."
831            (/ (* (+ (- (min cur end-num)
832                        beg-num) 1) 100) length))))
833       (when (> length elmo-display-progress-threshold)
834         (elmo-display-progress
835          'elmo-nntp-msgdb-create "Getting overview..." 100)))
836     (if (not use-xover)
837         (setq new-msgdb (elmo-nntp-msgdb-create-by-header
838                          session numbers flag-table))
839       (with-current-buffer (elmo-network-session-buffer session)
840         (if ov-str
841             (elmo-msgdb-append
842              new-msgdb
843              (elmo-nntp-create-msgdb-from-overview-string
844               folder
845               ov-str
846               flag-table
847               filter)))))
848     (elmo-folder-set-killed-list-internal
849      folder
850      (nconc
851       (elmo-folder-killed-list-internal folder)
852       (car (elmo-list-diff
853             numbers
854             (elmo-msgdb-list-messages new-msgdb)))))
855     ;; If there are canceled messages, overviews are not obtained
856     ;; to max-number(inn 2.3?).
857     (when (and (elmo-nntp-max-number-precedes-list-active-p)
858                (elmo-nntp-list-active-p session))
859       (elmo-nntp-send-command session
860                               (format "list active %s"
861                                       (elmo-nntp-folder-group-internal
862                                        folder)))
863       (if (null (elmo-nntp-read-response session))
864           (progn
865             (elmo-nntp-set-list-active session nil)
866             (error "NNTP list command failed")))
867       (let ((killed (elmo-nntp-catchup-msgdb
868                      new-msgdb
869                      (nth 1 (read (concat "(" (elmo-nntp-read-contents
870                                                session) ")"))))))
871         (when killed
872           (elmo-folder-kill-messages folder killed))))
873     new-msgdb))
874
875 (luna-define-method elmo-folder-update-number ((folder elmo-nntp-folder))
876   (when (elmo-nntp-max-number-precedes-list-active-p)
877     (let ((session (elmo-nntp-get-session folder)))
878       (when (elmo-nntp-list-active-p session)
879         (let ((numbers (elmo-folder-list-messages folder nil 'in-msgdb))
880               msgdb-max max-number)
881           ;; If there are canceled messages, overviews are not obtained
882           ;; to max-number(inn 2.3?).
883           (elmo-nntp-select-group session
884                                   (elmo-nntp-folder-group-internal folder))
885           (elmo-nntp-send-command session
886                                   (format "list active %s"
887                                           (elmo-nntp-folder-group-internal
888                                            folder)))
889           (if (null (elmo-nntp-read-response session))
890               (error "NNTP list command failed"))
891           (setq max-number
892                 (nth 1 (read (concat "(" (elmo-nntp-read-contents
893                                           session) ")"))))
894           (setq msgdb-max (if numbers (apply #'max numbers) 0))
895           (when (and msgdb-max
896                      max-number
897                      (< msgdb-max max-number))
898             (let ((i (1+ msgdb-max))
899                   killed)
900               (while (<= i max-number)
901                 (setq killed (cons i killed))
902                 (incf i))
903               (elmo-folder-kill-messages folder (nreverse killed)))))))))
904
905 (defun elmo-nntp-msgdb-create-by-header (session numbers flag-table)
906   (with-temp-buffer
907     (elmo-nntp-retrieve-headers session (current-buffer) numbers)
908     (elmo-nntp-msgdb-create-message
909      (length numbers) flag-table)))
910
911 (defun elmo-nntp-parse-xhdr-response (string)
912   (let (response)
913     (with-temp-buffer
914       (insert string)
915       (goto-char (point-min))
916       (while (not (eobp))
917         (if (looking-at "^\\([0-9]+\\) \\(.*\\)$")
918             (setq response (cons (cons (string-to-int (elmo-match-buffer 1))
919                                        (elmo-match-buffer 2))
920                                  response)))
921         (forward-line 1)))
922     (nreverse response)))
923
924 (defun elmo-nntp-parse-overview-string (string)
925   (save-excursion
926     (let ((tmp-buffer (get-buffer-create " *ELMO Overview TMP*"))
927           ret-list ret-val beg)
928       (set-buffer tmp-buffer)
929       (erase-buffer)
930       (set-buffer-multibyte nil)
931       (insert string)
932       (goto-char (point-min))
933       (setq beg (point))
934       (while (not (eobp))
935         (end-of-line)
936         (setq ret-list (save-match-data
937                          (apply 'vector (split-string
938                                          (buffer-substring beg (point))
939                                          "\t"))))
940         (beginning-of-line)
941         (forward-line 1)
942         (setq beg (point))
943         (setq ret-val (nconc ret-val (list ret-list))))
944 ;;;   (kill-buffer tmp-buffer)
945       ret-val)))
946
947 (defun elmo-nntp-get-newsgroup-by-msgid (msgid server user port type)
948   "Get nntp header string."
949   (save-excursion
950     (let ((session (elmo-nntp-get-session
951                     (luna-make-entity
952                      'elmo-nntp-folder
953                      :user user
954                      :server server
955                      :port port
956                      :stream-type type))))
957       (elmo-nntp-send-command session
958                               (format "head %s" msgid))
959       (if (elmo-nntp-read-response session)
960           (elmo-nntp-read-contents session))
961       (with-current-buffer (elmo-network-session-buffer session)
962         (std11-field-body "Newsgroups")))))
963
964 (luna-define-method elmo-message-fetch :around
965   ((folder elmo-nntp-folder) number strategy &optional unread section)
966   (when (luna-call-next-method)
967     (elmo-nntp-setup-crosspost-buffer folder number)
968     (unless unread
969       (elmo-nntp-folder-update-crosspost-message-alist
970        folder (list number)))
971     t))
972
973 (luna-define-method elmo-message-fetch-plugged ((folder elmo-nntp-folder)
974                                                 number strategy
975                                                 &optional section outbuf
976                                                 unread)
977   (elmo-nntp-message-fetch folder number strategy section outbuf unread))
978
979 (defun elmo-nntp-message-fetch (folder number strategy section outbuf unread)
980   (let ((session (elmo-nntp-get-session folder))
981         newsgroups)
982     (with-current-buffer (elmo-network-session-buffer session)
983       (elmo-nntp-select-group session (elmo-nntp-folder-group-internal folder))
984       (elmo-nntp-send-command session (format "article %s" number))
985       (if (null (elmo-nntp-read-response session t))
986           (progn
987             (with-current-buffer outbuf (erase-buffer))
988             (message "Fetching message failed")
989             nil)
990         (prog1 (elmo-nntp-read-body session outbuf)
991           (with-current-buffer outbuf
992             (goto-char (point-min))
993             (while (re-search-forward "^\\." nil t)
994               (replace-match "")
995               (forward-line))
996             (elmo-nntp-setup-crosspost-buffer folder number)
997             (unless unread
998               (elmo-nntp-folder-update-crosspost-message-alist
999                folder (list number)))))))))
1000
1001 (defun elmo-nntp-post (hostname content-buf)
1002   (let ((session (elmo-nntp-get-session
1003                   (luna-make-entity
1004                    'elmo-nntp-folder
1005                    :user elmo-nntp-default-user
1006                    :server hostname
1007                    :port elmo-nntp-default-port
1008                    :stream-type
1009                    (elmo-get-network-stream-type
1010                     elmo-nntp-default-stream-type))))
1011         response has-message-id)
1012     (save-excursion
1013       (set-buffer content-buf)
1014       (goto-char (point-min))
1015       (if (search-forward mail-header-separator nil t)
1016           (delete-region (match-beginning 0)(match-end 0)))
1017       (setq has-message-id (std11-field-body "message-id"))
1018       (elmo-nntp-send-command session "post")
1019       (if (string-match "^340" (setq response
1020                                      (elmo-nntp-read-raw-response session)))
1021           (if (string-match "recommended ID \\(<[^@]+@[^>]+>\\)" response)
1022               (unless has-message-id
1023                 (goto-char (point-min))
1024                 (insert (concat "Message-ID: "
1025                                 (elmo-match-string 1 response)
1026                                 "\n"))))
1027         (error "POST failed"))
1028       (run-hooks 'elmo-nntp-post-pre-hook)
1029       (elmo-nntp-send-buffer session content-buf)
1030       (elmo-nntp-send-command session ".")
1031 ;;;   (elmo-nntp-read-response buffer process t)
1032       (if (not (string-match
1033                 "^2" (setq response (elmo-nntp-read-raw-response
1034                                      session))))
1035           (error "NNTP error: %s" response)))))
1036
1037 (defsubst elmo-nntp-send-data-line (session line)
1038   "Send LINE to SESSION."
1039   ;; Escape "." at start of a line
1040   (if (eq (string-to-char line) ?.)
1041       (process-send-string (elmo-network-session-process-internal
1042                             session) "."))
1043   (process-send-string (elmo-network-session-process-internal
1044                         session) line)
1045   (process-send-string (elmo-network-session-process-internal
1046                         session) "\r\n"))
1047
1048 (defun elmo-nntp-send-buffer (session databuf)
1049   "Send data content of DATABUF to SESSION."
1050   (let ((data-continue t)
1051         line bol)
1052     (with-current-buffer databuf
1053       (goto-char (point-min))
1054       (while data-continue
1055         (beginning-of-line)
1056         (setq bol (point))
1057         (end-of-line)
1058         (setq line (buffer-substring bol (point)))
1059         (unless (eq (forward-line 1) 0) (setq data-continue nil))
1060         (elmo-nntp-send-data-line session line)))))
1061
1062 (luna-define-method elmo-folder-delete-messages ((folder elmo-nntp-folder)
1063                                                  numbers)
1064   (elmo-folder-kill-messages folder numbers)
1065   t)
1066
1067 (luna-define-method elmo-folder-exists-p-plugged ((folder elmo-nntp-folder))
1068   (let ((session (elmo-nntp-get-session folder)))
1069     (elmo-nntp-send-command
1070      session
1071      (format "group %s"
1072              (elmo-nntp-folder-group-internal folder)))
1073     (elmo-nntp-read-response session)))
1074
1075 (defun elmo-nntp-retrieve-field (spec field from-msgs)
1076   "Retrieve FIELD values from FROM-MSGS.
1077 Returns a list of cons cells like (NUMBER . VALUE)"
1078   (let ((session (elmo-nntp-get-session spec)))
1079     (if (elmo-nntp-xhdr-p session)
1080         (progn
1081           (elmo-nntp-select-group session (elmo-nntp-folder-group-internal spec))
1082           (elmo-nntp-send-command session
1083                                   (format "xhdr %s %s"
1084                                           field
1085                                           (if from-msgs
1086                                               (format
1087                                                "%d-%d"
1088                                                (car from-msgs)
1089                                                (nth
1090                                                 (max
1091                                                  (- (length from-msgs) 1) 0)
1092                                                 from-msgs))
1093                                             "0-")))
1094           (if (elmo-nntp-read-response session t)
1095               (elmo-nntp-parse-xhdr-response
1096                (elmo-nntp-read-contents session))
1097             (elmo-nntp-set-xhdr session nil)
1098             (error "NNTP XHDR command failed"))))))
1099
1100 (defun elmo-nntp-search-primitive (spec condition &optional from-msgs)
1101   (let ((search-key (elmo-filter-key condition)))
1102     (cond
1103      ((string= "last" search-key)
1104       (let ((numbers (or from-msgs (elmo-folder-list-messages spec))))
1105         (nthcdr (max (- (length numbers)
1106                         (string-to-int (elmo-filter-value condition)))
1107                      0)
1108                 numbers)))
1109      ((string= "first" search-key)
1110       (let* ((numbers (or from-msgs (elmo-folder-list-messages spec)))
1111              (rest (nthcdr (string-to-int (elmo-filter-value condition) )
1112                            numbers)))
1113         (mapcar '(lambda (x) (delete x numbers)) rest)
1114         numbers))
1115      ((or (string= "since" search-key)
1116           (string= "before" search-key))
1117       (let* ((specified-date (elmo-date-make-sortable-string
1118                               (elmo-date-get-datevec (elmo-filter-value
1119                                                       condition))))
1120              (since (string= "since" search-key))
1121              field-date  result)
1122         (if (eq (elmo-filter-type condition) 'unmatch)
1123             (setq since (not since)))
1124         (setq result
1125               (delq nil
1126                     (mapcar
1127                      (lambda (pair)
1128                        (setq field-date
1129                              (elmo-date-make-sortable-string
1130                               (timezone-fix-time
1131                                (cdr pair)
1132                                (current-time-zone) nil)))
1133                        (if (if since
1134                                (or (string= specified-date field-date)
1135                                    (string< specified-date field-date))
1136                              (string< field-date
1137                                       specified-date))
1138                            (car pair)))
1139                      (elmo-nntp-retrieve-field spec "date" from-msgs))))
1140         (if from-msgs
1141             (elmo-list-filter from-msgs result)
1142           result)))
1143      ((string= "body" search-key)
1144       nil)
1145      (t
1146       (let ((val (elmo-filter-value condition))
1147             (negative (eq (elmo-filter-type condition) 'unmatch))
1148             (case-fold-search t)
1149             result)
1150         (setq result
1151               (delq nil
1152                     (mapcar
1153                      (lambda (pair)
1154                        (if (string-match val
1155                                          (eword-decode-string
1156                                           (decode-mime-charset-string
1157                                            (cdr pair) elmo-mime-charset)))
1158                            (unless negative (car pair))
1159                          (if negative (car pair))))
1160                      (elmo-nntp-retrieve-field spec search-key
1161                                                from-msgs))))
1162         (if from-msgs
1163             (elmo-list-filter from-msgs result)
1164           result))))))
1165
1166 (defun elmo-nntp-search-internal (folder condition from-msgs)
1167   (let (result)
1168     (cond
1169      ((vectorp condition)
1170       (setq result (elmo-nntp-search-primitive
1171                     folder condition from-msgs)))
1172      ((eq (car condition) 'and)
1173       (setq result (elmo-nntp-search-internal folder
1174                                               (nth 1 condition)
1175                                               from-msgs)
1176             result (elmo-list-filter result
1177                                      (elmo-nntp-search-internal
1178                                       folder (nth 2 condition)
1179                                       from-msgs))))
1180      ((eq (car condition) 'or)
1181       (setq result (elmo-nntp-search-internal folder
1182                                               (nth 1 condition)
1183                                               from-msgs)
1184             result (elmo-uniq-list
1185                     (nconc result
1186                            (elmo-nntp-search-internal folder
1187                                                       (nth 2 condition)
1188                                                       from-msgs)))
1189             result (sort result '<))))))
1190
1191 (luna-define-method elmo-folder-search :around ((folder elmo-nntp-folder)
1192                                                 condition &optional from-msgs)
1193   (if (and (elmo-folder-plugged-p folder)
1194            (not (string= "body" (elmo-filter-key condition))))
1195       (elmo-nntp-search-internal folder condition from-msgs)
1196     (luna-call-next-method)))
1197
1198 (defun elmo-nntp-get-folders-info-prepare (folder session-keys)
1199   (condition-case ()
1200       (let ((session (elmo-nntp-get-session folder))
1201             key count)
1202         (with-current-buffer (elmo-network-session-buffer session)
1203           (unless (setq key (assoc session session-keys))
1204             (erase-buffer)
1205             (setq key (cons session
1206                             (vector 0
1207                                     (elmo-net-folder-server-internal folder)
1208                                     (elmo-net-folder-user-internal folder)
1209                                     (elmo-net-folder-port-internal folder)
1210                                     (elmo-net-folder-stream-type-internal
1211                                      folder))))
1212             (setq session-keys (nconc session-keys (list key))))
1213           (elmo-nntp-send-command session
1214                                   (format "group %s"
1215                                           (elmo-nntp-folder-group-internal
1216                                            folder))
1217                                   'noerase)
1218           (if elmo-nntp-get-folders-securely
1219               (accept-process-output
1220                (elmo-network-session-process-internal session)
1221                1))
1222           (setq count (aref (cdr key) 0))
1223           (aset (cdr key) 0 (1+ count))))
1224     (error
1225      (when elmo-auto-change-plugged
1226        (sit-for 1))
1227      nil))
1228   session-keys)
1229
1230 (defun elmo-nntp-get-folders-info (session-keys)
1231   (let ((sessions session-keys)
1232         (cur (get-buffer-create " *ELMO NNTP Temp*")))
1233     (while sessions
1234       (let* ((session (caar sessions))
1235              (key     (cdar sessions))
1236              (count   (aref key 0))
1237              (server  (aref key 1))
1238              (user    (aref key 2))
1239              (port    (aref key 3))
1240              (type    (aref key 4))
1241              (hashtb (or elmo-newsgroups-hashtb
1242                          (setq elmo-newsgroups-hashtb
1243                                (elmo-make-hash count)))))
1244         (save-excursion
1245           (elmo-nntp-groups-read-response session cur count)
1246           (set-buffer cur)
1247           (goto-char (point-min))
1248           (let ((case-replace nil)
1249                 (postfix (elmo-nntp-folder-postfix user server port type)))
1250             (if (not (string= postfix ""))
1251                 (save-excursion
1252                   (while (re-search-forward "^\\(211 [0-9]+ [0-9]+ [0-9]+ [^ \n]+\\)\\(.*\\)$" nil t)
1253                     (replace-match (concat (match-string 1)
1254                                            (elmo-replace-in-string
1255                                             postfix
1256                                             "\\\\" "\\\\\\\\\\\\\\\\")))))))
1257           (let (len min max group)
1258             (while (not (eobp))
1259               (condition-case ()
1260                   (when (= (following-char) ?2)
1261                     (read cur)
1262                     (setq len (read cur)
1263                           min (read cur)
1264                           max (read cur))
1265                     (set (setq group (let ((obarray hashtb)) (read cur)))
1266                          (list len min max)))
1267                 (error (and group (symbolp group) (set group nil))))
1268               (forward-line 1))))
1269         (setq sessions (cdr sessions))))
1270     (kill-buffer cur)))
1271
1272 ;; original is 'nntp-retrieve-groups [Gnus]
1273 (defun elmo-nntp-groups-read-response (session outbuf count)
1274   (let* ((received 0)
1275          (last-point (point-min)))
1276     (with-current-buffer (elmo-network-session-buffer session)
1277       (accept-process-output
1278        (elmo-network-session-process-internal session) 1)
1279       (discard-input)
1280       ;; Wait for all replies.
1281       (message "Getting folders info...")
1282       (while (progn
1283                (goto-char last-point)
1284                ;; Count replies.
1285                (while (re-search-forward "^[0-9]" nil t)
1286                  (setq received
1287                        (1+ received)))
1288                (setq last-point (point))
1289                (< received count))
1290         (accept-process-output (elmo-network-session-process-internal session)
1291                                1)
1292         (discard-input)
1293         (when (> count elmo-display-progress-threshold)
1294           (if (or (zerop (% received 10)) (= received count))
1295               (elmo-display-progress
1296                'elmo-nntp-groups-read-response "Getting folders info..."
1297                (/ (* received 100) count)))))
1298       (when (> count elmo-display-progress-threshold)
1299         (elmo-display-progress
1300          'elmo-nntp-groups-read-response "Getting folders info..." 100))
1301       ;; Wait for the reply from the final command.
1302       (goto-char (point-max))
1303       (re-search-backward "^[0-9]" nil t)
1304       (when (looking-at "^[23]")
1305         (while (progn
1306                  (goto-char (point-max))
1307                  (not (re-search-backward "\r?\n" (- (point) 3) t)))
1308           (accept-process-output
1309            (elmo-network-session-process-internal session) 1)
1310           (discard-input)))
1311       ;; Now all replies are received.  We remove CRs.
1312       (goto-char (point-min))
1313       (while (search-forward "\r" nil t)
1314         (replace-match "" t t))
1315       (copy-to-buffer outbuf (point-min) (point-max)))))
1316
1317 ;; from nntp.el [Gnus]
1318
1319 (defsubst elmo-nntp-next-result-arrived-p ()
1320   (cond
1321    ((eq (following-char) ?2)
1322     (if (re-search-forward "\n\\.\r?\n" nil t)
1323         t
1324       nil))
1325    ((looking-at "[34]")
1326     (if (search-forward "\n" nil t)
1327         t
1328       nil))
1329    (t
1330     nil)))
1331
1332 (defun elmo-nntp-retrieve-headers (session outbuf articles)
1333   "Retrieve the headers of ARTICLES."
1334   (with-current-buffer (elmo-network-session-buffer session)
1335     (erase-buffer)
1336     (let ((number (length articles))
1337           (count 0)
1338           (received 0)
1339           (last-point (point-min))
1340           article)
1341       ;; Send HEAD commands.
1342       (while (setq article (pop articles))
1343         (elmo-nntp-send-command session
1344                                 (format "head %s" article)
1345                                 'noerase)
1346         (setq count (1+ count))
1347         ;; Every 200 requests we have to read the stream in
1348         ;; order to avoid deadlocks.
1349         (when (or (null articles)       ;All requests have been sent.
1350                   (zerop (% count elmo-nntp-header-fetch-chop-length)))
1351           (accept-process-output
1352            (elmo-network-session-process-internal session) 1)
1353           (discard-input)
1354           (while (progn
1355                    (goto-char last-point)
1356                    ;; Count replies.
1357                    (while (elmo-nntp-next-result-arrived-p)
1358                      (setq last-point (point))
1359                      (setq received (1+ received)))
1360                    (< received count))
1361             (when (> number elmo-display-progress-threshold)
1362               (if (or (zerop (% received 20)) (= received number))
1363                   (elmo-display-progress
1364                    'elmo-nntp-retrieve-headers "Getting headers..."
1365                    (/ (* received 100) number))))
1366             (accept-process-output
1367              (elmo-network-session-process-internal session) 1)
1368             (discard-input))))
1369       (when (> number elmo-display-progress-threshold)
1370         (elmo-display-progress
1371          'elmo-nntp-retrieve-headers "Getting headers..." 100))
1372       (message "Getting headers...done")
1373       ;; Replace all CRLF with LF.
1374       (elmo-delete-cr-buffer)
1375       (copy-to-buffer outbuf (point-min) (point-max)))))
1376
1377 ;; end of from Gnus
1378
1379 (defun elmo-nntp-msgdb-create-message (len flag-table)
1380   (save-excursion
1381     (let ((new-msgdb (elmo-make-msgdb))
1382           beg entity i num message-id)
1383       (set-buffer-multibyte nil)
1384       (goto-char (point-min))
1385       (setq i 0)
1386       (message "Creating msgdb...")
1387       (while (not (eobp))
1388         (setq beg (save-excursion (forward-line 1) (point)))
1389         (setq num
1390               (and (looking-at "^2[0-9]*[ ]+\\([0-9]+\\)")
1391                    (string-to-int
1392                     (elmo-match-buffer 1))))
1393         (elmo-nntp-next-result-arrived-p)
1394         (when num
1395           (save-excursion
1396             (forward-line -1)
1397             (save-restriction
1398               (narrow-to-region beg (point))
1399               (setq entity
1400                     (elmo-msgdb-create-message-entity-from-buffer
1401                      (elmo-msgdb-message-entity-handler new-msgdb) num))
1402               (when entity
1403                 (setq message-id
1404                       (elmo-message-entity-field entity 'message-id))
1405                 (elmo-msgdb-append-entity
1406                  new-msgdb
1407                  entity
1408                  (elmo-flag-table-get flag-table message-id))))))
1409         (when (> len elmo-display-progress-threshold)
1410           (setq i (1+ i))
1411           (if (or (zerop (% i 20)) (= i len))
1412               (elmo-display-progress
1413                'elmo-nntp-msgdb-create-message "Creating msgdb..."
1414                (/ (* i 100) len)))))
1415       (when (> len elmo-display-progress-threshold)
1416         (elmo-display-progress
1417          'elmo-nntp-msgdb-create-message "Creating msgdb..." 100))
1418       new-msgdb)))
1419
1420 (luna-define-method elmo-message-use-cache-p ((folder elmo-nntp-folder) number)
1421   elmo-nntp-use-cache)
1422
1423 (defun elmo-nntp-parse-newsgroups (string &optional subscribe-only)
1424   (let ((nglist (elmo-parse string "[ \t\f\r\n,]*\\([^ \t\f\r\n,]+\\)"))
1425         ngs)
1426     (if (not subscribe-only)
1427         nglist
1428       (dolist (ng nglist)
1429         (if (intern-soft ng elmo-newsgroups-hashtb)
1430             (setq ngs (cons ng ngs))))
1431       ngs)))
1432
1433 ;;; Crosspost processing.
1434
1435 ;; 1. setup crosspost alist.
1436 ;;    1.1. When message is fetched and is crossposted message,
1437 ;;         it is remembered in `temp-crosses' slot.
1438 ;;         temp-crosses slot is a list of cons cell:
1439 ;;         (NUMBER . (MESSAGE-ID (LIST-OF-NEWSGROUPS) 'ng))
1440 ;;    1.2. In elmo-folder-close, `temp-crosses' slot is cleared,
1441 ;;    1.3. In elmo-folder-flag-as-read, move crosspost entry
1442 ;;         from `temp-crosses' slot to `elmo-crosspost-message-alist'.
1443
1444 ;; 2. process crosspost alist.
1445 ;;    2.1. At elmo-folder-process-crosspost, setup `reads' slot from
1446 ;;         `elmo-crosspost-message-alist'.
1447 ;;    2.2. remove crosspost entry for current newsgroup from
1448 ;;         `elmo-crosspost-message-alist'.
1449 ;;    2.3. elmo-folder-list-unreads return unread message list according to
1450 ;;         `reads' slot.
1451 ;;         (There's a problem that if `elmo-folder-list-unreads'
1452 ;;           never executed, crosspost information is thrown away.)
1453 ;;    2.4. In elmo-folder-close, `read' slot is cleared,
1454
1455 (defun elmo-nntp-setup-crosspost-buffer (folder number)
1456 ;;    1.1. When message is fetched and is crossposted message,
1457 ;;         it is remembered in `temp-crosses' slot.
1458 ;;         temp-crosses slot is a list of cons cell:
1459 ;;         (NUMBER . (MESSAGE-ID (LIST-OF-NEWSGROUPS) 'ng))
1460   (let (newsgroups crosspost-newsgroups message-id)
1461     (save-restriction
1462       (std11-narrow-to-header)
1463       (setq newsgroups (std11-fetch-field "newsgroups")
1464             message-id (std11-msg-id-string
1465                         (car (std11-parse-msg-id-string
1466                               (std11-fetch-field "message-id"))))))
1467     (when newsgroups
1468       (when (setq crosspost-newsgroups
1469                   (delete
1470                    (elmo-nntp-folder-group-internal folder)
1471                    (elmo-nntp-parse-newsgroups newsgroups t)))
1472         (unless (assq number
1473                       (elmo-nntp-folder-temp-crosses-internal folder))
1474           (elmo-nntp-folder-set-temp-crosses-internal
1475            folder
1476            (cons (cons number (list message-id crosspost-newsgroups 'ng))
1477                  (elmo-nntp-folder-temp-crosses-internal folder))))))))
1478
1479 (luna-define-method elmo-folder-close-internal ((folder elmo-nntp-folder))
1480 ;;    1.2. In elmo-folder-close, `temp-crosses' slot is cleared,
1481   (elmo-nntp-folder-set-temp-crosses-internal folder nil)
1482   (elmo-nntp-folder-set-reads-internal folder nil)
1483   )
1484
1485 (defun elmo-nntp-folder-update-crosspost-message-alist (folder numbers)
1486 ;;    1.3. In elmo-folder-flag-as-read, move crosspost entry
1487 ;;         from `temp-crosses' slot to `elmo-crosspost-message-alist'.
1488   (let (elem)
1489     (dolist (number numbers)
1490       (when (setq elem (assq number
1491                              (elmo-nntp-folder-temp-crosses-internal folder)))
1492         (unless (assoc (cdr (cdr elem)) elmo-crosspost-message-alist)
1493           (setq elmo-crosspost-message-alist
1494                 (cons (cdr elem) elmo-crosspost-message-alist)))
1495         (elmo-nntp-folder-set-temp-crosses-internal
1496          folder
1497          (delq elem (elmo-nntp-folder-temp-crosses-internal folder)))))))
1498
1499 (luna-define-method elmo-folder-set-flag :before ((folder elmo-nntp-folder)
1500                                                   numbers
1501                                                   flag
1502                                                   &optional is-local)
1503   (when (eq flag 'read)
1504     (elmo-nntp-folder-update-crosspost-message-alist folder numbers)))
1505
1506 (luna-define-method elmo-folder-unset-flag :before ((folder elmo-nntp-folder)
1507                                                     numbers
1508                                                     flag
1509                                                     &optional is-local)
1510   (when (eq flag 'unread)
1511     (elmo-nntp-folder-update-crosspost-message-alist folder numbers)))
1512
1513 (defsubst elmo-nntp-folder-process-crosspost (folder)
1514 ;;    2.1. At elmo-folder-process-crosspost, setup `reads' slot from
1515 ;;         `elmo-crosspost-message-alist'.
1516 ;;    2.2. remove crosspost entry for current newsgroup from
1517 ;;         `elmo-crosspost-message-alist'.
1518   (let (cross-deletes reads entity ngs)
1519     (dolist (cross elmo-crosspost-message-alist)
1520       (when (setq entity (elmo-message-entity folder (nth 0 cross)))
1521         (setq reads (cons (elmo-message-entity-number entity) reads)))
1522       (when entity
1523         (if (setq ngs (delete (elmo-nntp-folder-group-internal folder)
1524                               (nth 1 cross)))
1525             (setcar (cdr cross) ngs)
1526           (setq cross-deletes (cons cross cross-deletes)))
1527         (setq elmo-crosspost-message-alist-modified t)))
1528     (dolist (dele cross-deletes)
1529       (setq elmo-crosspost-message-alist (delq
1530                                           dele
1531                                           elmo-crosspost-message-alist)))
1532     (elmo-nntp-folder-set-reads-internal folder reads)))
1533
1534 (luna-define-method elmo-folder-process-crosspost ((folder elmo-nntp-folder))
1535   (elmo-nntp-folder-process-crosspost folder))
1536
1537 (luna-define-method elmo-folder-list-flagged :around ((folder elmo-nntp-folder)
1538                                                       flag &optional in-msgdb)
1539   ;;    2.3. elmo-folder-list-unreads return unread message list according to
1540   ;;         `reads' slot.
1541   (let ((msgs (luna-call-next-method)))
1542     (if in-msgdb
1543         msgs
1544       (case flag
1545         (unread
1546          (elmo-living-messages msgs (elmo-nntp-folder-reads-internal folder)))
1547         ;; Should consider read, digest and any flag?
1548         (otherwise
1549          msgs)))))
1550
1551 (require 'product)
1552 (product-provide (provide 'elmo-nntp) (require 'elmo-version))
1553
1554 ;;; elmo-nntp.el ends here