e76b980db695b6e2457b077675bc41e838d089f1
[gnus] / lisp / nnvirtual.el
1 ;;; nnvirtual.el --- virtual newsgroups access for Gnus
2 ;; Copyright (C) 1994,95,96 Free Software Foundation, Inc.
3
4 ;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
5 ;;      Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
6 ;; Keywords: news
7
8 ;; This file is part of GNU Emacs.
9
10 ;; GNU Emacs is free software; you can redistribute it and/or modify
11 ;; it under the terms of the GNU General Public License as published by
12 ;; the Free Software Foundation; either version 2, or (at your option)
13 ;; any later version.
14
15 ;; GNU Emacs is distributed in the hope that it will be useful,
16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 ;; GNU General Public License for more details.
19
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with GNU Emacs; see the file COPYING.  If not, write to
22 ;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
23
24 ;;; Commentary:
25
26 ;; The other access methods (nntp, nnspool, etc) are general news
27 ;; access methods. This module relies on Gnus and can not be used
28 ;; separately.
29
30 ;;; Code:
31
32 (require 'nntp)
33 (require 'nnheader)
34 (require 'gnus)
35
36 (defvar nnvirtual-always-rescan nil
37   "*If non-nil, always scan groups for unread articles when entering a group.
38 If this variable is nil (which is the default) and you read articles
39 in a component group after the virtual group has been activated, the
40 read articles from the component group will show up when you enter the
41 virtual group.")
42
43 \f
44
45 (defconst nnvirtual-version "nnvirtual 1.0"
46   "Version number of this version of nnvirtual.")
47
48 (defvar nnvirtual-group-alist nil)
49 (defvar nnvirtual-current-group nil)
50 (defvar nnvirtual-component-groups nil)
51 (defvar nnvirtual-mapping nil)
52
53 (defvar nnvirtual-status-string "")
54
55 (eval-and-compile
56   (autoload 'gnus-cache-articles-in-group "gnus-cache"))
57
58 \f
59
60 ;;; Interface functions.
61
62 (defun nnvirtual-retrieve-headers (articles &optional newsgroup server fetch-old)
63   (when (nnvirtual-possibly-change-group newsgroup server t)
64     (save-excursion
65       (if (stringp (car articles))
66           'headers
67         (let ((map nnvirtual-mapping)
68               (vbuf (nnheader-set-temp-buffer 
69                      (get-buffer-create " *virtual headers*")))
70               (unfetched (mapcar (lambda (g) (list g))
71                                  nnvirtual-component-groups))
72               beg cgroup active article result prefix)
73           (while articles
74             (setq article (assq (pop articles) nnvirtual-mapping))
75             (setq cgroup (cadr article))
76             (gnus-request-group cgroup t)
77             (setq prefix (gnus-group-real-prefix cgroup))
78             (when (setq result (gnus-retrieve-headers 
79                                 (list (caddr article)) cgroup))
80               (set-buffer nntp-server-buffer)
81               (if (zerop (buffer-size))
82                   (nconc (assq cgroup unfetched) (caddr article))
83                 ;; If we got HEAD headers, we convert them into NOV
84                 ;; headers.  This is slow, inefficient and, come to think
85                 ;; of it, downright evil.  So sue me.  I couldn't be
86                 ;; bothered to write a header parse routine that could
87                 ;; parse a mixed HEAD/NOV buffer.
88                 (when (eq result 'headers)
89                   (nnvirtual-convert-headers))
90                 (goto-char (point-min))
91                 (while (not (eobp))
92                   (delete-region 
93                    (point) (progn (read nntp-server-buffer) (point)))
94                   (insert (int-to-string (car article)))
95                   (beginning-of-line)
96                   (looking-at 
97                    "[^\t]*\t[^\t]*\t[^\t]*\t[^\t]*\t[^\t]*\t[^\t]*\t[^\t]*\t")
98                   (goto-char (match-end 0))
99                   (or (search-forward 
100                        "\t" (save-excursion (end-of-line) (point)) t)
101                       (end-of-line))
102                   (while (= (char-after (1- (point))) ? )
103                     (forward-char -1)
104                     (delete-char 1))
105                   (if (eolp)
106                       (progn
107                         (end-of-line)
108                         (or (= (char-after (1- (point))) ?\t)
109                             (insert ?\t))
110                         (insert (format "Xref: %s %s:%d\t" (system-name) 
111                                         cgroup (caddr article))))
112                     (if (not (string= "" prefix))
113                         (while (re-search-forward 
114                                 "[^ ]+:[0-9]+"
115                                 (save-excursion (end-of-line) (point)) t)
116                           (save-excursion
117                             (goto-char (match-beginning 0))
118                             (insert prefix))))
119                     (end-of-line)
120                     (or (= (char-after (1- (point))) ?\t)
121                         (insert ?\t)))
122                   (forward-line 1))
123                 (set-buffer vbuf)
124                 (goto-char (point-max))
125                 (insert-buffer-substring nntp-server-buffer))))
126           
127           ;; In case some of the articles have expired or been
128           ;; cancelled, we have to mark them as read in the
129           ;; component group.
130           (while unfetched
131             (when (cdar unfetched)
132               (gnus-group-make-articles-read 
133                (caar unfetched) (sort (cdar unfetched) '<)))
134             (setq unfetched (cdr unfetched)))
135
136           ;; The headers are ready for reading, so they are inserted into
137           ;; the nntp-server-buffer, which is where Gnus expects to find
138           ;; them.
139           (prog1
140               (save-excursion
141                 (set-buffer nntp-server-buffer)
142                 (erase-buffer)
143                 (insert-buffer-substring vbuf)
144                 'nov)
145             (kill-buffer vbuf)))))))
146
147 (defun nnvirtual-open-server (server &optional something)
148   (nnheader-init-server-buffer))
149
150 (defun nnvirtual-close-server (&rest dum)
151   t)
152
153 (defun nnvirtual-request-close ()
154   (setq nnvirtual-current-group nil
155         nnvirtual-component-groups nil
156         nnvirtual-mapping nil
157         nnvirtual-group-alist nil)
158   t)
159
160 (defun nnvirtual-server-opened (&optional server)
161   (and nntp-server-buffer
162        (get-buffer nntp-server-buffer)))
163
164 (defun nnvirtual-status-message (&optional server)
165   nnvirtual-status-string)
166
167 (defun nnvirtual-request-article (article &optional group server buffer)
168   (when (and (nnvirtual-possibly-change-group group server t)
169              (numberp article))
170     (let* ((amap (assq article nnvirtual-mapping))
171            (cgroup (cadr amap)))
172       (cond
173        ((not amap)
174         (nnheader-report 'nnvirtual "No such article: %s" article))
175        ((not (gnus-check-group cgroup))
176         (nnheader-report
177          'nnvirtual "Can't open server where %s exists" cgroup))
178        ((not (gnus-request-group cgroup t))
179         (nnheader-report 'nnvirtual "Can't open component group %s" cgroup))
180        (t
181         (if buffer 
182             (save-excursion
183               (set-buffer buffer)
184               (gnus-request-article-this-buffer (caddr amap) cgroup))
185           (gnus-request-article (caddr amap) cgroup)))))))
186
187 (defun nnvirtual-request-group (group &optional server dont-check)
188   (cond
189    ((null (nnvirtual-possibly-change-group
190            group server 
191            (if nnvirtual-always-rescan nil dont-check)))
192     (setq nnvirtual-current-group nil)
193     (nnheader-report 'nnvirtual "No component groups in %s" group))
194    (t
195     (let ((len (length nnvirtual-mapping)))