(elmo-flatten): Use `append' and `listp' instead of
[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 (defconst elmo-nntp-folder-name-syntax `(group
59                                          (?: [user "^\\([A-Za-z]\\|$\\)"])
60                                          ,@elmo-net-folder-name-syntax))
61
62 (defsubst elmo-nntp-encode-group-string (string)
63   (if elmo-nntp-group-coding-system
64       (encode-coding-string string elmo-nntp-group-coding-system)
65     string))
66
67 (defsubst elmo-nntp-decode-group-string (string)
68   (if elmo-nntp-group-coding-system
69       (decode-coding-string string elmo-nntp-group-coding-system)
70     string))
71
72 ;; For debugging.
73 (defvar elmo-nntp-debug nil
74   "Non-nil forces NNTP folder as debug mode.
75 Debug information is inserted in the buffer \"*NNTP DEBUG*\"")
76
77 ;;; Debug
78 (defsubst elmo-nntp-debug (message &rest args)
79   (if elmo-nntp-debug
80       (let ((biff (string-match "BIFF-" (buffer-name)))
81             pos)
82         (with-current-buffer (get-buffer-create (concat "*NNTP DEBUG*"
83                                                         (if biff "BIFF")))
84           (goto-char (point-max))
85           (setq pos (point))
86           (insert (apply 'format message args) "\n")))))
87
88 ;;; ELMO NNTP folder
89 (eval-and-compile
90   (luna-define-class elmo-nntp-folder (elmo-net-folder)
91                      (group temp-crosses reads))
92   (luna-define-internal-accessors 'elmo-nntp-folder))
93
94 (luna-define-method elmo-folder-initialize ((folder elmo-nntp-folder) name)
95   (let ((elmo-network-stream-type-alist
96          (if elmo-nntp-stream-type-alist
97              (setq elmo-network-stream-type-alist
98                    (append elmo-nntp-stream-type-alist
99                            elmo-network-stream-type-alist))
100            elmo-network-stream-type-alist))
101         tokens)
102     (setq tokens (car (elmo-parse-separated-tokens
103                        name
104                        elmo-nntp-folder-name-syntax)))
105     ;; group
106     (elmo-nntp-folder-set-group-internal folder
107                                          (elmo-nntp-encode-group-string
108                                           (cdr (assq 'group tokens))))
109     ;; user
110     (elmo-net-folder-set-user-internal folder
111                                        (let ((user (cdr (assq 'user tokens))))
112                                          (if user
113                                              (and (> (length user) 0) user)
114                                            elmo-nntp-default-user)))
115     ;; network
116     (elmo-net-folder-set-parameters
117      folder
118      tokens
119      (list :server      elmo-nntp-default-server
120            :port        elmo-nntp-default-port
121            :stream-type
122            (elmo-get-network-stream-type 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 (or (elmo-net-folder-user-internal folder) ""))
591     (unless (string= username (or elmo-nntp-default-user ""))
592       (setq append-serv (concat append-serv
593                                 ":" (elmo-quote-syntactical-element
594                                      username
595                                      'user elmo-nntp-folder-name-syntax))))
596     (unless (string= (elmo-net-folder-server-internal folder)
597                      elmo-nntp-default-server)
598       (setq append-serv (concat append-serv
599                                 "@" (elmo-net-folder-server-internal folder))))
600     (unless (eq (elmo-net-folder-port-internal folder) elmo-nntp-default-port)
601       (setq append-serv (concat append-serv
602                                 ":" (int-to-string
603                                      (elmo-net-folder-port-internal folder)))))
604     (unless (eq (elmo-network-stream-type-symbol
605                  (elmo-net-folder-stream-type-internal folder))
606                 elmo-nntp-default-stream-type)
607       (setq append-serv
608             (concat append-serv
609                     (elmo-network-stream-type-spec-string
610                      (elmo-net-folder-stream-type-internal folder)))))
611     (mapcar (lambda (fld)
612               (if (consp fld)
613                   (list (concat "-" (elmo-nntp-decode-group-string (car fld))
614                                 append-serv))
615                 (concat "-" (elmo-nntp-decode-group-string fld) append-serv)))
616             ret-val)))
617
618 (defun elmo-nntp-make-msglist (beg-str end-str)
619   (elmo-make-number-list (string-to-int beg-str) (string-to-int end-str)))
620
621 (luna-define-method elmo-folder-list-messages-plugged ((folder
622                                                         elmo-nntp-folder)
623                                                        &optional nohide)
624   (let ((session (elmo-nntp-get-session folder))
625         (group   (elmo-nntp-folder-group-internal folder))
626         response numbers use-listgroup)
627     (save-excursion
628       (when (setq use-listgroup (elmo-nntp-listgroup-p session))
629         (elmo-nntp-send-command session
630                                 (format "listgroup %s" group))
631         (if (not (elmo-nntp-read-response session t))
632             (progn
633               (elmo-nntp-set-listgroup session nil)
634               (setq use-listgroup nil))
635           (if (null (setq response (elmo-nntp-read-contents session)))
636               (error "Fetching listgroup failed"))
637           (setq numbers (elmo-string-to-list response))
638           (elmo-nntp-session-set-current-group-internal session
639                                                         group)))
640       (unless use-listgroup
641         (elmo-nntp-send-command session (format "group %s" group))
642         (if (null (setq response (elmo-nntp-read-response session)))
643             (error "Select group failed"))
644         (when (and
645                (string-match
646                 "211 \\([0-9]+\\) \\([0-9]+\\) \\([0-9]+\\) [^.].+$"
647                 response)
648                (> (string-to-int (elmo-match-string 1 response)) 0))
649           (setq numbers (elmo-nntp-make-msglist
650                          (elmo-match-string 2 response)
651                          (elmo-match-string 3 response)))))
652       numbers)))
653
654 (luna-define-method elmo-folder-status ((folder elmo-nntp-folder))
655   (elmo-nntp-folder-status folder))
656
657 (defun elmo-nntp-folder-status (folder)
658   (let ((killed-list (elmo-msgdb-killed-list-load
659                       (elmo-folder-msgdb-path folder)))
660         end-num entry)
661     (if elmo-nntp-groups-async
662         (if (setq entry
663                   (elmo-get-hash-val
664                    (concat (elmo-nntp-folder-group-internal folder)
665                            (elmo-nntp-folder-postfix
666                             (elmo-net-folder-user-internal folder)
667                             (elmo-net-folder-server-internal folder)
668                             (elmo-net-folder-port-internal folder)
669                             (elmo-net-folder-stream-type-internal folder)))
670                    elmo-newsgroups-hashtb))
671             (progn
672               (setq end-num (nth 2 entry))
673               (when (and killed-list
674                          (elmo-number-set-member end-num killed-list))
675                 ;; Max is killed.
676                 (setq end-num nil))
677               (cons end-num (car entry)))
678           (error "No such newsgroup \"%s\""
679                  (elmo-nntp-folder-group-internal folder)))
680       (let ((session (elmo-nntp-get-session folder))
681             response e-num)
682         (if (null session)
683             (error "Connection failed"))
684         (save-excursion
685           (elmo-nntp-send-command session
686                                   (format
687                                    "group %s"
688                                    (elmo-nntp-folder-group-internal folder)))
689           (setq response (elmo-nntp-read-response session))
690           (if (and response
691                    (string-match
692                     "211 \\([0-9]+\\) \\([0-9]+\\) \\([0-9]+\\) [^.].+$"
693                     response))
694               (progn
695                 (setq end-num (string-to-int
696                                (elmo-match-string 3 response)))
697                 (setq e-num (string-to-int
698                              (elmo-match-string 1 response)))
699                 (when (and killed-list
700                            (elmo-number-set-member end-num killed-list))
701                   ;; Max is killed.
702                   (setq end-num nil))
703                 (cons end-num e-num))
704             (if (null response)
705                 (error "Selecting newsgroup \"%s\" failed"
706                        (elmo-nntp-folder-group-internal folder))
707               nil)))))))
708
709 (defconst elmo-nntp-overview-index
710   '(("number" . 0)
711     ("subject" . 1)
712     ("from" . 2)
713     ("date" . 3)
714     ("message-id" . 4)
715     ("references" . 5)
716     ("size" . 6)
717     ("lines" . 7)
718     ("xref" . 8)))
719
720 (defun elmo-nntp-create-msgdb-from-overview-string (folder
721                                                     str
722                                                     flag-table
723                                                     &optional numlist)
724   (let ((new-msgdb (elmo-make-msgdb))
725         ov-list message-id entity
726         ov-entity num
727         field field-index flags)
728     (setq ov-list (elmo-nntp-parse-overview-string str))
729     (while ov-list
730       (setq ov-entity (car ov-list))
731 ;;; INN bug??
732 ;;;   (if (or (> (setq num (string-to-int (aref ov-entity 0)))
733 ;;;              99999)
734 ;;;           (<= num 0))
735 ;;;       (setq num 0))
736 ;;;  (setq num (int-to-string num))
737       (setq num (string-to-int (aref ov-entity 0)))
738       (when (or (null numlist)
739                 (memq num numlist))
740         (setq entity (elmo-msgdb-make-message-entity
741                       (elmo-msgdb-message-entity-handler new-msgdb)
742                       :message-id (aref ov-entity 4)
743                       :number     num
744                       :references (elmo-msgdb-get-last-message-id
745                                     (aref ov-entity 5))
746                       :from       (elmo-with-enable-multibyte
747                                     (eword-decode-string
748                                      (elmo-delete-char  ?\"
749                                                         (or (aref ov-entity 2)
750                                                             elmo-no-from))))
751                       :subject    (or (elmo-with-enable-multibyte
752                                         (eword-decode-string
753                                          (aref ov-entity 1)))
754                                       elmo-no-subject)
755                       :date       (aref ov-entity 3)
756                       :size       (string-to-int (aref ov-entity 6))))
757         (dolist (extra elmo-msgdb-extra-fields)
758           (setq extra (downcase extra))
759           (when (and (setq field-index
760                            (cdr (assoc extra elmo-nntp-overview-index)))
761                      (> (length ov-entity) field-index))
762             (setq field (aref ov-entity field-index))
763             (when (eq field-index 8) ;; xref
764               (setq field (elmo-msgdb-remove-field-string field)))
765             (elmo-message-entity-set-field entity (intern extra) field)))
766         (setq message-id (elmo-message-entity-field entity 'message-id)
767               flags (elmo-flag-table-get flag-table message-id))
768         (elmo-global-flags-set flags folder num message-id)
769         (elmo-msgdb-append-entity new-msgdb entity flags))
770       (setq ov-list (cdr ov-list)))
771     new-msgdb))
772
773 (luna-define-method elmo-folder-msgdb-create ((folder elmo-nntp-folder)
774                                               numbers flag-table)
775   (elmo-nntp-folder-msgdb-create folder numbers flag-table))
776
777 (defun elmo-nntp-folder-msgdb-create (folder numbers flag-table)
778   (let ((filter numbers)
779         (session (elmo-nntp-get-session folder))
780         (new-msgdb (elmo-make-msgdb))
781         beg-num end-num cur length
782         new-msgdb ov-str use-xover dir)
783     (elmo-nntp-select-group session (elmo-nntp-folder-group-internal
784                                      folder))
785     (when (setq use-xover (elmo-nntp-xover-p session))
786       (setq beg-num (car numbers)
787             cur beg-num
788             end-num (nth (1- (length numbers)) numbers)
789             length  (+ (- end-num beg-num) 1))
790       (message "Getting overview...")
791       (while (<= cur end-num)
792         (elmo-nntp-send-command
793          session
794          (format
795           "xover %s-%s"
796           (int-to-string cur)
797           (int-to-string
798            (+ cur
799               elmo-nntp-overview-fetch-chop-length))))
800         (with-current-buffer (elmo-network-session-buffer session)
801           (if ov-str
802               (elmo-msgdb-append
803                new-msgdb
804                (elmo-nntp-create-msgdb-from-overview-string
805                 folder
806                 ov-str
807                 flag-table
808                 filter))))
809         (if (null (elmo-nntp-read-response session t))
810             (progn
811               (setq cur end-num);; exit while loop
812               (elmo-nntp-set-xover session nil)
813               (setq use-xover nil))
814           (if (null (setq ov-str (elmo-nntp-read-contents session)))
815               (error "Fetching overview failed")))
816         (setq cur (+ elmo-nntp-overview-fetch-chop-length cur 1))
817         (when (> length elmo-display-progress-threshold)
818           (elmo-display-progress
819            'elmo-nntp-msgdb-create "Getting overview..."
820            (/ (* (+ (- (min cur end-num)
821                        beg-num) 1) 100) length))))
822       (when (> length elmo-display-progress-threshold)
823         (elmo-display-progress
824          'elmo-nntp-msgdb-create "Getting overview..." 100)))
825     (if (not use-xover)
826         (setq new-msgdb (elmo-nntp-msgdb-create-by-header
827                          session numbers flag-table))
828       (with-current-buffer (elmo-network-session-buffer session)
829         (if ov-str
830             (elmo-msgdb-append
831              new-msgdb
832              (elmo-nntp-create-msgdb-from-overview-string
833               folder
834               ov-str
835               flag-table
836               filter)))))
837     (elmo-folder-set-killed-list-internal
838      folder
839      (nconc
840       (elmo-folder-killed-list-internal folder)
841       (car (elmo-list-diff
842             numbers
843             (elmo-msgdb-list-messages new-msgdb)))))
844     ;; If there are canceled messages, overviews are not obtained
845     ;; to max-number(inn 2.3?).
846     (when (and (elmo-nntp-max-number-precedes-list-active-p)
847                (elmo-nntp-list-active-p session))
848       (elmo-nntp-send-command session
849                               (format "list active %s"
850                                       (elmo-nntp-folder-group-internal
851                                        folder)))
852       (if (null (elmo-nntp-read-response session))
853           (progn
854             (elmo-nntp-set-list-active session nil)
855             (error "NNTP list command failed")))
856       (let ((killed (elmo-nntp-catchup-msgdb
857                      new-msgdb
858                      (nth 1 (read (concat "(" (elmo-nntp-read-contents
859                                                session) ")"))))))
860         (when killed
861           (elmo-folder-kill-messages folder killed))))
862     new-msgdb))
863
864 (luna-define-method elmo-folder-update-number ((folder elmo-nntp-folder))
865   (when (elmo-nntp-max-number-precedes-list-active-p)
866     (let ((session (elmo-nntp-get-session folder)))
867       (when (elmo-nntp-list-active-p session)
868         (let ((numbers (elmo-folder-list-messages folder nil 'in-msgdb))
869               msgdb-max max-number)
870           ;; If there are canceled messages, overviews are not obtained
871           ;; to max-number(inn 2.3?).
872           (elmo-nntp-select-group session
873                                   (elmo-nntp-folder-group-internal folder))
874           (elmo-nntp-send-command session
875                                   (format "list active %s"
876                                           (elmo-nntp-folder-group-internal
877                                            folder)))
878           (if (null (elmo-nntp-read-response session))
879               (error "NNTP list command failed"))
880           (setq max-number
881                 (nth 1 (read (concat "(" (elmo-nntp-read-contents
882                                           session) ")"))))
883           (setq msgdb-max (if numbers (apply #'max numbers) 0))
884           (when (and msgdb-max
885                      max-number
886                      (< msgdb-max max-number))
887             (let ((i (1+ msgdb-max))
888                   killed)
889               (while (<= i max-number)
890                 (setq killed (cons i killed))
891                 (incf i))
892               (elmo-folder-kill-messages folder (nreverse killed)))))))))
893
894 (defun elmo-nntp-msgdb-create-by-header (session numbers flag-table)
895   (with-temp-buffer
896     (elmo-nntp-retrieve-headers session (current-buffer) numbers)
897     (elmo-nntp-msgdb-create-message
898      (length numbers) flag-table)))
899
900 (defun elmo-nntp-parse-xhdr-response (string)
901   (let (response)
902     (with-temp-buffer
903       (insert string)
904       (goto-char (point-min))
905       (while (not (eobp))
906         (if (looking-at "^\\([0-9]+\\) \\(.*\\)$")
907             (setq response (cons (cons (string-to-int (elmo-match-buffer 1))
908                                        (elmo-match-buffer 2))
909                                  response)))
910         (forward-line 1)))
911     (nreverse response)))
912
913 (defun elmo-nntp-parse-overview-string (string)
914   (save-excursion
915     (let ((tmp-buffer (get-buffer-create " *ELMO Overview TMP*"))
916           ret-list ret-val beg)
917       (set-buffer tmp-buffer)
918       (erase-buffer)
919       (set-buffer-multibyte nil)
920       (insert string)
921       (goto-char (point-min))
922       (setq beg (point))
923       (while (not (eobp))
924         (end-of-line)
925         (setq ret-list (save-match-data
926                          (apply 'vector (split-string
927                                          (buffer-substring beg (point))
928                                          "\t"))))
929         (beginning-of-line)
930         (forward-line 1)
931         (setq beg (point))
932         (setq ret-val (nconc ret-val (list ret-list))))
933 ;;;   (kill-buffer tmp-buffer)
934       ret-val)))
935
936 (defun elmo-nntp-get-newsgroup-by-msgid (msgid server user port type)
937   "Get nntp header string."
938   (save-excursion
939     (let ((session (elmo-nntp-get-session
940                     (luna-make-entity
941                      'elmo-nntp-folder
942                      :user user
943                      :server server
944                      :port port
945                      :stream-type type))))
946       (elmo-nntp-send-command session
947                               (format "head %s" msgid))
948       (if (elmo-nntp-read-response session)
949           (elmo-nntp-read-contents session))
950       (with-current-buffer (elmo-network-session-buffer session)
951         (std11-field-body "Newsgroups")))))
952
953 (luna-define-method elmo-message-fetch :around
954   ((folder elmo-nntp-folder) number strategy &optional unread section)
955   (when (luna-call-next-method)
956     (elmo-nntp-setup-crosspost-buffer folder number)
957     (unless unread
958       (elmo-nntp-folder-update-crosspost-message-alist
959        folder (list number)))
960     t))
961
962 (luna-define-method elmo-message-fetch-plugged ((folder elmo-nntp-folder)
963                                                 number strategy
964                                                 &optional section outbuf
965                                                 unread)
966   (elmo-nntp-message-fetch folder number strategy section outbuf unread))
967
968 (defun elmo-nntp-message-fetch (folder number strategy section outbuf unread)
969   (let ((session (elmo-nntp-get-session folder))
970         newsgroups)
971     (with-current-buffer (elmo-network-session-buffer session)
972       (elmo-nntp-select-group session (elmo-nntp-folder-group-internal folder))
973       (elmo-nntp-send-command session (format "article %s" number))
974       (if (null (elmo-nntp-read-response session t))
975           (progn
976             (with-current-buffer outbuf (erase-buffer))
977             (message "Fetching message failed")
978             nil)
979         (prog1 (elmo-nntp-read-body session outbuf)
980           (with-current-buffer outbuf
981             (goto-char (point-min))
982             (while (re-search-forward "^\\." nil t)
983               (replace-match "")
984               (forward-line))
985             (elmo-nntp-setup-crosspost-buffer folder number)
986             (unless unread
987               (elmo-nntp-folder-update-crosspost-message-alist
988                folder (list number)))))))))
989
990 (defun elmo-nntp-post (hostname content-buf)
991   (let ((session (elmo-nntp-get-session
992                   (luna-make-entity
993                    'elmo-nntp-folder
994                    :user elmo-nntp-default-user
995                    :server hostname
996                    :port elmo-nntp-default-port
997                    :stream-type
998                    (elmo-get-network-stream-type
999                     elmo-nntp-default-stream-type))))
1000         response has-message-id)
1001     (save-excursion
1002       (set-buffer content-buf)
1003       (goto-char (point-min))
1004       (if (search-forward mail-header-separator nil t)
1005           (delete-region (match-beginning 0)(match-end 0)))
1006       (setq has-message-id (std11-field-body "message-id"))
1007       (elmo-nntp-send-command session "post")
1008       (if (string-match "^340" (setq response
1009                                      (elmo-nntp-read-raw-response session)))
1010           (if (string-match "recommended ID \\(<[^@]+@[^>]+>\\)" response)
1011               (unless has-message-id
1012                 (goto-char (point-min))
1013                 (insert (concat "Message-ID: "
1014                                 (elmo-match-string 1 response)
1015                                 "\n"))))
1016         (error "POST failed"))
1017       (run-hooks 'elmo-nntp-post-pre-hook)
1018       (elmo-nntp-send-buffer session content-buf)
1019       (elmo-nntp-send-command session ".")
1020 ;;;   (elmo-nntp-read-response buffer process t)
1021       (if (not (string-match
1022                 "^2" (setq response (elmo-nntp-read-raw-response
1023                                      session))))
1024           (error "NNTP error: %s" response)))))
1025
1026 (defsubst elmo-nntp-send-data-line (session line)
1027   "Send LINE to SESSION."
1028   ;; Escape "." at start of a line
1029   (if (eq (string-to-char line) ?.)
1030       (process-send-string (elmo-network-session-process-internal
1031                             session) "."))
1032   (process-send-string (elmo-network-session-process-internal
1033                         session) line)
1034   (process-send-string (elmo-network-session-process-internal
1035                         session) "\r\n"))
1036
1037 (defun elmo-nntp-send-buffer (session databuf)
1038   "Send data content of DATABUF to SESSION."
1039   (let ((data-continue t)
1040         line bol)
1041     (with-current-buffer databuf
1042       (goto-char (point-min))
1043       (while data-continue
1044         (beginning-of-line)
1045         (setq bol (point))
1046         (end-of-line)
1047         (setq line (buffer-substring bol (point)))
1048         (unless (eq (forward-line 1) 0) (setq data-continue nil))
1049         (elmo-nntp-send-data-line session line)))))
1050
1051 (luna-define-method elmo-folder-delete-messages ((folder elmo-nntp-folder)
1052                                                  numbers)
1053   (elmo-folder-kill-messages folder numbers)
1054   t)
1055
1056 (luna-define-method elmo-folder-exists-p-plugged ((folder elmo-nntp-folder))
1057   (let ((session (elmo-nntp-get-session folder)))
1058     (elmo-nntp-send-command
1059      session
1060      (format "group %s"
1061              (elmo-nntp-folder-group-internal folder)))
1062     (elmo-nntp-read-response session)))
1063
1064 (defun elmo-nntp-retrieve-field (spec field from-msgs)
1065   "Retrieve FIELD values from FROM-MSGS.
1066 Returns a list of cons cells like (NUMBER . VALUE)"
1067   (let ((session (elmo-nntp-get-session spec)))
1068     (if (elmo-nntp-xhdr-p session)
1069         (progn
1070           (elmo-nntp-select-group session (elmo-nntp-folder-group-internal spec))
1071           (elmo-nntp-send-command session
1072                                   (format "xhdr %s %s"
1073                                           field
1074                                           (if from-msgs
1075                                               (format
1076                                                "%d-%d"
1077                                                (car from-msgs)
1078                                                (nth
1079                                                 (max
1080                                                  (- (length from-msgs) 1) 0)
1081                                                 from-msgs))
1082                                             "0-")))
1083           (if (elmo-nntp-read-response session t)
1084               (elmo-nntp-parse-xhdr-response
1085                (elmo-nntp-read-contents session))
1086             (elmo-nntp-set-xhdr session nil)
1087             (error "NNTP XHDR command failed"))))))
1088
1089 (defun elmo-nntp-search-primitive (spec condition &optional from-msgs)
1090   (let ((search-key (elmo-filter-key condition)))
1091     (cond
1092      ((string= "last" search-key)
1093       (let ((numbers (or from-msgs (elmo-folder-list-messages spec))))
1094         (nthcdr (max (- (length numbers)
1095                         (string-to-int (elmo-filter-value condition)))
1096                      0)
1097                 numbers)))
1098      ((string= "first" search-key)
1099       (let* ((numbers (or from-msgs (elmo-folder-list-messages spec)))
1100              (rest (nthcdr (string-to-int (elmo-filter-value condition) )
1101                            numbers)))
1102         (mapcar '(lambda (x) (delete x numbers)) rest)
1103         numbers))
1104      ((or (string= "since" search-key)
1105           (string= "before" search-key))
1106       (let* ((specified-date (elmo-date-make-sortable-string
1107                               (elmo-date-get-datevec (elmo-filter-value
1108                                                       condition))))
1109              (since (string= "since" search-key))
1110              field-date  result)
1111         (if (eq (elmo-filter-type condition) 'unmatch)
1112             (setq since (not since)))
1113         (setq result
1114               (delq nil
1115                     (mapcar
1116                      (lambda (pair)
1117                        (setq field-date
1118                              (elmo-date-make-sortable-string
1119                               (timezone-fix-time
1120                                (cdr pair)
1121                                (current-time-zone) nil)))
1122                        (if (if since
1123                                (or (string= specified-date field-date)
1124                                    (string< specified-date field-date))
1125                              (string< field-date
1126                                       specified-date))
1127                            (car pair)))
1128                      (elmo-nntp-retrieve-field spec "date" from-msgs))))
1129         (if from-msgs
1130             (elmo-list-filter from-msgs result)
1131           result)))
1132      ((string= "body" search-key)
1133       nil)
1134      (t
1135       (let ((val (elmo-filter-value condition))
1136             (negative (eq (elmo-filter-type condition) 'unmatch))
1137             (case-fold-search t)
1138             result)
1139         (setq result
1140               (delq nil
1141                     (mapcar
1142                      (lambda (pair)
1143                        (if (string-match val
1144                                          (eword-decode-string
1145                                           (decode-mime-charset-string
1146                                            (cdr pair) elmo-mime-charset)))
1147                            (unless negative (car pair))
1148                          (if negative (car pair))))
1149                      (elmo-nntp-retrieve-field spec search-key
1150                                                from-msgs))))
1151         (if from-msgs
1152             (elmo-list-filter from-msgs result)
1153           result))))))
1154
1155 (defun elmo-nntp-search-internal (folder condition from-msgs)
1156   (let (result)
1157     (cond
1158      ((vectorp condition)
1159       (setq result (elmo-nntp-search-primitive
1160                     folder condition from-msgs)))
1161      ((eq (car condition) 'and)
1162       (setq result (elmo-nntp-search-internal folder
1163                                               (nth 1 condition)
1164                                               from-msgs)
1165             result (elmo-list-filter result
1166                                      (elmo-nntp-search-internal
1167                                       folder (nth 2 condition)
1168                                       from-msgs))))
1169      ((eq (car condition) 'or)
1170       (setq result (elmo-nntp-search-internal folder
1171                                               (nth 1 condition)
1172                                               from-msgs)
1173             result (elmo-uniq-list
1174                     (nconc result
1175                            (elmo-nntp-search-internal folder
1176                                                       (nth 2 condition)
1177                                                       from-msgs)))
1178             result (sort result '<))))))
1179
1180 (defun elmo-nntp-use-server-search-p (condition)
1181   (if (vectorp condition)
1182       (not (string= "body" (elmo-filter-key condition)))
1183     (and (elmo-nntp-use-server-search-p (nth 1 condition))
1184          (elmo-nntp-use-server-search-p (nth 2 condition)))))
1185
1186 (luna-define-method elmo-folder-search :around ((folder elmo-nntp-folder)
1187                                                 condition &optional from-msgs)
1188   (if (and (elmo-folder-plugged-p folder)
1189            (elmo-nntp-use-server-search-p condition))
1190       (elmo-nntp-search-internal folder condition from-msgs)
1191     (luna-call-next-method)))
1192
1193 (defun elmo-nntp-get-folders-info-prepare (folder session-keys)
1194   (condition-case ()
1195       (let ((session (elmo-nntp-get-session folder))
1196             key count)
1197         (with-current-buffer (elmo-network-session-buffer session)
1198           (unless (setq key (assoc session session-keys))
1199             (erase-buffer)
1200             (setq key (cons session
1201                             (vector 0
1202                                     (elmo-net-folder-server-internal folder)
1203                                     (elmo-net-folder-user-internal folder)
1204                                     (elmo-net-folder-port-internal folder)
1205                                     (elmo-net-folder-stream-type-internal
1206                                      folder))))
1207             (setq session-keys (nconc session-keys (list key))))
1208           (elmo-nntp-send-command session
1209                                   (format "group %s"
1210                                           (elmo-nntp-folder-group-internal
1211                                            folder))
1212                                   'noerase)
1213           (if elmo-nntp-get-folders-securely
1214               (accept-process-output
1215                (elmo-network-session-process-internal session)
1216                1))
1217           (setq count (aref (cdr key) 0))
1218           (aset (cdr key) 0 (1+ count))))
1219     (error
1220      (when elmo-auto-change-plugged
1221        (sit-for 1))
1222      nil))
1223   session-keys)
1224
1225 (defun elmo-nntp-get-folders-info (session-keys)
1226   (let ((sessions session-keys)
1227         (cur (get-buffer-create " *ELMO NNTP Temp*")))
1228     (while sessions
1229       (let* ((session (caar sessions))
1230              (key     (cdar sessions))
1231              (count   (aref key 0))
1232              (server  (aref key 1))
1233              (user    (aref key 2))
1234              (port    (aref key 3))
1235              (type    (aref key 4))
1236              (hashtb (or elmo-newsgroups-hashtb
1237                          (setq elmo-newsgroups-hashtb
1238                                (elmo-make-hash count)))))
1239         (save-excursion
1240           (elmo-nntp-groups-read-response session cur count)
1241           (set-buffer cur)
1242           (goto-char (point-min))
1243           (let ((case-replace nil)
1244                 (postfix (elmo-nntp-folder-postfix user server port type)))
1245             (if (not (string= postfix ""))
1246                 (save-excursion
1247                   (while (re-search-forward "^\\(211 [0-9]+ [0-9]+ [0-9]+ [^ \n]+\\)\\(.*\\)$" nil t)
1248                     (replace-match (concat (match-string 1)
1249                                            (elmo-replace-in-string
1250                                             postfix
1251                                             "\\\\" "\\\\\\\\\\\\\\\\")))))))
1252           (let (len min max group)
1253             (while (not (eobp))
1254               (condition-case ()
1255                   (when (= (following-char) ?2)
1256                     (read cur)
1257                     (setq len (read cur)
1258                           min (read cur)
1259                           max (read cur))
1260                     (set (setq group (let ((obarray hashtb)) (read cur)))
1261                          (list len min max)))
1262                 (error (and group (symbolp group) (set group nil))))
1263               (forward-line 1))))
1264         (setq sessions (cdr sessions))))
1265     (kill-buffer cur)))
1266
1267 ;; original is 'nntp-retrieve-groups [Gnus]
1268 (defun elmo-nntp-groups-read-response (session outbuf count)
1269   (let* ((received 0)
1270          (last-point (point-min)))
1271     (with-current-buffer (elmo-network-session-buffer session)
1272       (accept-process-output
1273        (elmo-network-session-process-internal session) 1)
1274       (discard-input)
1275       ;; Wait for all replies.
1276       (message "Getting folders info...")
1277       (while (progn
1278                (goto-char last-point)
1279                ;; Count replies.
1280                (while (re-search-forward "^[0-9]" nil t)
1281                  (setq received
1282                        (1+ received)))
1283                (setq last-point (point))
1284                (< received count))
1285         (accept-process-output (elmo-network-session-process-internal session)
1286                                1)
1287         (discard-input)
1288         (when (> count elmo-display-progress-threshold)
1289           (if (or (zerop (% received 10)) (= received count))
1290               (elmo-display-progress
1291                'elmo-nntp-groups-read-response "Getting folders info..."
1292                (/ (* received 100) count)))))
1293       (when (> count elmo-display-progress-threshold)
1294         (elmo-display-progress
1295          'elmo-nntp-groups-read-response "Getting folders info..." 100))
1296       ;; Wait for the reply from the final command.
1297       (goto-char (point-max))
1298       (re-search-backward "^[0-9]" nil t)
1299       (when (looking-at "^[23]")
1300         (while (progn
1301                  (goto-char (point-max))
1302                  (not (re-search-backward "\r?\n" (- (point) 3) t)))
1303           (accept-process-output
1304            (elmo-network-session-process-internal session) 1)
1305           (discard-input)))
1306       ;; Now all replies are received.  We remove CRs.
1307       (goto-char (point-min))
1308       (while (search-forward "\r" nil t)
1309         (replace-match "" t t))
1310       (copy-to-buffer outbuf (point-min) (point-max)))))
1311
1312 ;; from nntp.el [Gnus]
1313
1314 (defsubst elmo-nntp-next-result-arrived-p ()
1315   (cond
1316    ((eq (following-char) ?2)
1317     (if (re-search-forward "\n\\.\r?\n" nil t)
1318         t
1319       nil))
1320    ((looking-at "[34]")
1321     (if (search-forward "\n" nil t)
1322         t
1323       nil))
1324    (t
1325     nil)))
1326
1327 (defun elmo-nntp-retrieve-headers (session outbuf articles)
1328   "Retrieve the headers of ARTICLES."
1329   (with-current-buffer (elmo-network-session-buffer session)
1330     (erase-buffer)
1331     (let ((number (length articles))
1332           (count 0)
1333           (received 0)
1334           (last-point (point-min))
1335           article)
1336       ;; Send HEAD commands.
1337       (while (setq article (pop articles))
1338         (elmo-nntp-send-command session
1339                                 (format "head %s" article)
1340                                 'noerase)
1341         (setq count (1+ count))
1342         ;; Every 200 requests we have to read the stream in
1343         ;; order to avoid deadlocks.
1344         (when (or (null articles)       ;All requests have been sent.
1345                   (zerop (% count elmo-nntp-header-fetch-chop-length)))
1346           (accept-process-output
1347            (elmo-network-session-process-internal session) 1)
1348           (discard-input)
1349           (while (progn
1350                    (goto-char last-point)
1351                    ;; Count replies.
1352                    (while (elmo-nntp-next-result-arrived-p)
1353                      (setq last-point (point))
1354                      (setq received (1+ received)))
1355                    (< received count))
1356             (when (> number elmo-display-progress-threshold)
1357               (if (or (zerop (% received 20)) (= received number))
1358                   (elmo-display-progress
1359                    'elmo-nntp-retrieve-headers "Getting headers..."
1360                    (/ (* received 100) number))))
1361             (accept-process-output
1362              (elmo-network-session-process-internal session) 1)
1363             (discard-input))))
1364       (when (> number elmo-display-progress-threshold)
1365         (elmo-display-progress
1366          'elmo-nntp-retrieve-headers "Getting headers..." 100))
1367       (message "Getting headers...done")
1368       ;; Replace all CRLF with LF.
1369       (elmo-delete-cr-buffer)
1370       (copy-to-buffer outbuf (point-min) (point-max)))))
1371
1372 ;; end of from Gnus
1373
1374 (defun elmo-nntp-msgdb-create-message (len flag-table)
1375   (save-excursion
1376     (let ((new-msgdb (elmo-make-msgdb))
1377           beg entity i num message-id)
1378       (set-buffer-multibyte nil)
1379       (goto-char (point-min))
1380       (setq i 0)
1381       (message "Creating msgdb...")
1382       (while (not (eobp))
1383         (setq beg (save-excursion (forward-line 1) (point)))
1384         (setq num
1385               (and (looking-at "^2[0-9]*[ ]+\\([0-9]+\\)")
1386                    (string-to-int
1387                     (elmo-match-buffer 1))))
1388         (elmo-nntp-next-result-arrived-p)
1389         (when num
1390           (save-excursion
1391             (forward-line -1)
1392             (save-restriction
1393               (narrow-to-region beg (point))
1394               (setq entity
1395                     (elmo-msgdb-create-message-entity-from-buffer
1396                      (elmo-msgdb-message-entity-handler new-msgdb) num))
1397               (when entity
1398                 (setq message-id
1399                       (elmo-message-entity-field entity 'message-id))
1400                 (elmo-msgdb-append-entity
1401                  new-msgdb
1402                  entity
1403                  (elmo-flag-table-get flag-table message-id))))))
1404         (when (> len elmo-display-progress-threshold)
1405           (setq i (1+ i))
1406           (if (or (zerop (% i 20)) (= i len))
1407               (elmo-display-progress
1408                'elmo-nntp-msgdb-create-message "Creating msgdb..."
1409                (/ (* i 100) len)))))
1410       (when (> len elmo-display-progress-threshold)
1411         (elmo-display-progress
1412          'elmo-nntp-msgdb-create-message "Creating msgdb..." 100))
1413       new-msgdb)))
1414
1415 (luna-define-method elmo-message-use-cache-p ((folder elmo-nntp-folder) number)
1416   elmo-nntp-use-cache)
1417
1418 (defun elmo-nntp-parse-newsgroups (string &optional subscribe-only)
1419   (let ((nglist (elmo-parse string "[ \t\f\r\n,]*\\([^ \t\f\r\n,]+\\)"))
1420         ngs)
1421     (if (not subscribe-only)
1422         nglist
1423       (dolist (ng nglist)
1424         (if (intern-soft ng elmo-newsgroups-hashtb)
1425             (setq ngs (cons ng ngs))))
1426       ngs)))
1427
1428 ;;; Crosspost processing.
1429
1430 ;; 1. setup crosspost alist.
1431 ;;    1.1. When message is fetched and is crossposted message,
1432 ;;         it is remembered in `temp-crosses' slot.
1433 ;;         temp-crosses slot is a list of cons cell:
1434 ;;         (NUMBER . (MESSAGE-ID (LIST-OF-NEWSGROUPS) 'ng))
1435 ;;    1.2. In elmo-folder-close, `temp-crosses' slot is cleared,
1436 ;;    1.3. In elmo-folder-flag-as-read, move crosspost entry
1437 ;;         from `temp-crosses' slot to `elmo-crosspost-message-alist'.
1438
1439 ;; 2. process crosspost alist.
1440 ;;    2.1. At elmo-folder-process-crosspost, setup `reads' slot from
1441 ;;         `elmo-crosspost-message-alist'.
1442 ;;    2.2. remove crosspost entry for current newsgroup from
1443 ;;         `elmo-crosspost-message-alist'.
1444 ;;    2.3. elmo-folder-list-unreads return unread message list according to
1445 ;;         `reads' slot.
1446 ;;         (There's a problem that if `elmo-folder-list-unreads'
1447 ;;           never executed, crosspost information is thrown away.)
1448 ;;    2.4. In elmo-folder-close, `read' slot is cleared,
1449
1450 (defun elmo-nntp-setup-crosspost-buffer (folder number)
1451 ;;    1.1. When message is fetched and is crossposted message,
1452 ;;         it is remembered in `temp-crosses' slot.
1453 ;;         temp-crosses slot is a list of cons cell:
1454 ;;         (NUMBER . (MESSAGE-ID (LIST-OF-NEWSGROUPS) 'ng))
1455   (let (newsgroups crosspost-newsgroups message-id)
1456     (save-restriction
1457       (std11-narrow-to-header)
1458       (setq newsgroups (std11-fetch-field "newsgroups")
1459             message-id (std11-msg-id-string
1460                         (car (std11-parse-msg-id-string
1461                               (std11-fetch-field "message-id"))))))
1462     (when newsgroups
1463       (when (setq crosspost-newsgroups
1464                   (delete
1465                    (elmo-nntp-folder-group-internal folder)
1466                    (elmo-nntp-parse-newsgroups newsgroups t)))
1467         (unless (assq number
1468                       (elmo-nntp-folder-temp-crosses-internal folder))
1469           (elmo-nntp-folder-set-temp-crosses-internal
1470            folder
1471            (cons (cons number (list message-id crosspost-newsgroups 'ng))
1472                  (elmo-nntp-folder-temp-crosses-internal folder))))))))
1473
1474 (luna-define-method elmo-folder-close-internal ((folder elmo-nntp-folder))
1475 ;;    1.2. In elmo-folder-close, `temp-crosses' slot is cleared,
1476   (elmo-nntp-folder-set-temp-crosses-internal folder nil)
1477   (elmo-nntp-folder-set-reads-internal folder nil)
1478   )
1479
1480 (defun elmo-nntp-folder-update-crosspost-message-alist (folder numbers)
1481 ;;    1.3. In elmo-folder-flag-as-read, move crosspost entry
1482 ;;         from `temp-crosses' slot to `elmo-crosspost-message-alist'.
1483   (let (elem)
1484     (dolist (number numbers)
1485       (when (setq elem (assq number
1486                              (elmo-nntp-folder-temp-crosses-internal folder)))
1487         (unless (assoc (cdr (cdr elem)) elmo-crosspost-message-alist)
1488           (setq elmo-crosspost-message-alist
1489                 (cons (cdr elem) elmo-crosspost-message-alist)))
1490         (elmo-nntp-folder-set-temp-crosses-internal
1491          folder
1492          (delq elem (elmo-nntp-folder-temp-crosses-internal folder)))))))
1493
1494 (luna-define-method elmo-folder-set-flag :before ((folder elmo-nntp-folder)
1495                                                   numbers
1496                                                   flag
1497                                                   &optional is-local)
1498   (when (eq flag 'read)
1499     (elmo-nntp-folder-update-crosspost-message-alist folder numbers)))
1500
1501 (luna-define-method elmo-folder-unset-flag :before ((folder elmo-nntp-folder)
1502                                                     numbers
1503                                                     flag
1504                                                     &optional is-local)
1505   (when (eq flag 'unread)
1506     (elmo-nntp-folder-update-crosspost-message-alist folder numbers)))
1507
1508 (defsubst elmo-nntp-folder-process-crosspost (folder)
1509 ;;    2.1. At elmo-folder-process-crosspost, setup `reads' slot from
1510 ;;         `elmo-crosspost-message-alist'.
1511 ;;    2.2. remove crosspost entry for current newsgroup from
1512 ;;         `elmo-crosspost-message-alist'.
1513   (let (cross-deletes reads entity ngs)
1514     (dolist (cross elmo-crosspost-message-alist)
1515       (when (setq entity (elmo-message-entity folder (nth 0 cross)))
1516         (setq reads (cons (elmo-message-entity-number entity) reads)))
1517       (when entity
1518         (if (setq ngs (delete (elmo-nntp-folder-group-internal folder)
1519                               (nth 1 cross)))
1520             (setcar (cdr cross) ngs)
1521           (setq cross-deletes (cons cross cross-deletes)))
1522         (setq elmo-crosspost-message-alist-modified t)))
1523     (dolist (dele cross-deletes)
1524       (setq elmo-crosspost-message-alist (delq
1525                                           dele
1526                                           elmo-crosspost-message-alist)))
1527     (elmo-nntp-folder-set-reads-internal folder reads)))
1528
1529 (luna-define-method elmo-folder-process-crosspost ((folder elmo-nntp-folder))
1530   (elmo-nntp-folder-process-crosspost folder))
1531
1532 (luna-define-method elmo-folder-list-flagged :around ((folder elmo-nntp-folder)
1533                                                       flag &optional in-msgdb)
1534   ;;    2.3. elmo-folder-list-unreads return unread message list according to
1535   ;;         `reads' slot.
1536   (let ((msgs (luna-call-next-method)))
1537     (if in-msgdb
1538         msgs
1539       (case flag
1540         (unread
1541          (elmo-living-messages msgs (elmo-nntp-folder-reads-internal folder)))
1542         ;; Should consider read, digest and any flag?
1543         (otherwise
1544          msgs)))))
1545
1546 (require 'product)
1547 (product-provide (provide 'elmo-nntp) (require 'elmo-version))
1548
1549 ;;; elmo-nntp.el ends here