Mercurial > hg > xemacs-beta
comparison lisp/gnus/nnvirtual.el @ 16:0293115a14e9 r19-15b91
Import from CVS: tag r19-15b91
author | cvs |
---|---|
date | Mon, 13 Aug 2007 08:49:20 +0200 |
parents | 376386a54a3c |
children | d95e72db5c07 |
comparison
equal
deleted
inserted
replaced
15:ad457d5f7d04 | 16:0293115a14e9 |
---|---|
1 ;;; nnvirtual.el --- virtual newsgroups access for Gnus | 1 ;;; nnvirtual.el --- virtual newsgroups access for Gnus |
2 ;; Copyright (C) 1994,95,96 Free Software Foundation, Inc. | 2 ;; Copyright (C) 1994,95,96,97 Free Software Foundation, Inc. |
3 | 3 |
4 ;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no> | 4 ;; Author: David Moore <dmoore@ucsd.edu> |
5 ;; Lars Magne Ingebrigtsen <larsi@ifi.uio.no> | |
5 ;; Masanobu UMEDA <umerin@flab.flab.fujitsu.junet> | 6 ;; Masanobu UMEDA <umerin@flab.flab.fujitsu.junet> |
6 ;; Keywords: news | 7 ;; Keywords: news |
7 | 8 |
8 ;; This file is part of GNU Emacs. | 9 ;; This file is part of GNU Emacs. |
9 | 10 |
23 ;; Boston, MA 02111-1307, USA. | 24 ;; Boston, MA 02111-1307, USA. |
24 | 25 |
25 ;;; Commentary: | 26 ;;; Commentary: |
26 | 27 |
27 ;; The other access methods (nntp, nnspool, etc) are general news | 28 ;; The other access methods (nntp, nnspool, etc) are general news |
28 ;; access methods. This module relies on Gnus and can not be used | 29 ;; access methods. This module relies on Gnus and can not be used |
29 ;; separately. | 30 ;; separately. |
30 | 31 |
31 ;;; Code: | 32 ;;; Code: |
32 | 33 |
33 (require 'nntp) | 34 (require 'nntp) |
34 (require 'nnheader) | 35 (require 'nnheader) |
35 (require 'gnus) | 36 (require 'gnus) |
36 (require 'nnoo) | 37 (require 'nnoo) |
38 (require 'gnus-util) | |
39 (require 'gnus-start) | |
40 (require 'gnus-sum) | |
37 (eval-when-compile (require 'cl)) | 41 (eval-when-compile (require 'cl)) |
38 | 42 |
39 (nnoo-declare nnvirtual) | 43 (nnoo-declare nnvirtual) |
40 | 44 |
41 (defvoo nnvirtual-always-rescan nil | 45 (defvoo nnvirtual-always-rescan nil |
46 virtual group.") | 50 virtual group.") |
47 | 51 |
48 (defvoo nnvirtual-component-regexp nil | 52 (defvoo nnvirtual-component-regexp nil |
49 "*Regexp to match component groups.") | 53 "*Regexp to match component groups.") |
50 | 54 |
55 (defvoo nnvirtual-component-groups nil | |
56 "Component group in this nnvirtual group.") | |
57 | |
51 | 58 |
52 | 59 |
53 (defconst nnvirtual-version "nnvirtual 1.0") | 60 (defconst nnvirtual-version "nnvirtual 1.1") |
54 | 61 |
55 (defvoo nnvirtual-current-group nil) | 62 (defvoo nnvirtual-current-group nil) |
56 (defvoo nnvirtual-component-groups nil) | 63 |
57 (defvoo nnvirtual-mapping nil) | 64 (defvoo nnvirtual-mapping-table nil |
65 "Table of rules on how to map between component group and article number | |
66 to virtual article number.") | |
67 | |
68 (defvoo nnvirtual-mapping-offsets nil | |
69 "Table indexed by component group to an offset to be applied to article numbers in that group.") | |
70 | |
71 (defvoo nnvirtual-mapping-len 0 | |
72 "Number of articles in this virtual group.") | |
73 | |
74 (defvoo nnvirtual-mapping-reads nil | |
75 "Compressed sequence of read articles on the virtual group as computed from the unread status of individual component groups.") | |
76 | |
77 (defvoo nnvirtual-mapping-marks nil | |
78 "Compressed marks alist for the virtual group as computed from the marks of individual component groups.") | |
79 | |
80 (defvoo nnvirtual-info-installed nil | |
81 "T if we have already installed the group info for this group, and shouldn't blast over it again.") | |
58 | 82 |
59 (defvoo nnvirtual-status-string "") | 83 (defvoo nnvirtual-status-string "") |
60 | 84 |
61 (eval-and-compile | 85 (eval-and-compile |
62 (autoload 'gnus-cache-articles-in-group "gnus-cache")) | 86 (autoload 'gnus-cache-articles-in-group "gnus-cache")) |
64 | 88 |
65 | 89 |
66 ;;; Interface functions. | 90 ;;; Interface functions. |
67 | 91 |
68 (nnoo-define-basics nnvirtual) | 92 (nnoo-define-basics nnvirtual) |
93 | |
69 | 94 |
70 (deffoo nnvirtual-retrieve-headers (articles &optional newsgroup | 95 (deffoo nnvirtual-retrieve-headers (articles &optional newsgroup |
71 server fetch-old) | 96 server fetch-old) |
72 (when (nnvirtual-possibly-change-server server) | 97 (when (nnvirtual-possibly-change-server server) |
73 (save-excursion | 98 (save-excursion |
75 (erase-buffer) | 100 (erase-buffer) |
76 (if (stringp (car articles)) | 101 (if (stringp (car articles)) |
77 'headers | 102 'headers |
78 (let ((vbuf (nnheader-set-temp-buffer | 103 (let ((vbuf (nnheader-set-temp-buffer |
79 (get-buffer-create " *virtual headers*"))) | 104 (get-buffer-create " *virtual headers*"))) |
80 (unfetched (mapcar (lambda (g) (list g)) | 105 (carticles (nnvirtual-partition-sequence articles)) |
81 nnvirtual-component-groups)) | |
82 (system-name (system-name)) | 106 (system-name (system-name)) |
83 cgroup article result prefix) | 107 cgroup carticle article result prefix) |
84 (while articles | 108 (while carticles |
85 (setq article (assq (pop articles) nnvirtual-mapping)) | 109 (setq cgroup (caar carticles)) |
86 (when (and (setq cgroup (cadr article)) | 110 (setq articles (cdar carticles)) |
111 (pop carticles) | |
112 (when (and articles | |
87 (gnus-check-server | 113 (gnus-check-server |
88 (gnus-find-method-for-group cgroup) t) | 114 (gnus-find-method-for-group cgroup) t) |
89 (gnus-request-group cgroup t)) | 115 (gnus-request-group cgroup t) |
90 (setq prefix (gnus-group-real-prefix cgroup)) | 116 (setq prefix (gnus-group-real-prefix cgroup)) |
91 (when (setq result (gnus-retrieve-headers | 117 ;; FIX FIX FIX we want to check the cache! |
92 (list (caddr article)) cgroup nil)) | 118 ;; This is probably evil if people have set |
93 (set-buffer nntp-server-buffer) | 119 ;; gnus-use-cache to nil themselves, but I |
94 (if (zerop (buffer-size)) | 120 ;; have no way of finding the true value of it. |
95 (nconc (assq cgroup unfetched) (list (caddr article))) | 121 (let ((gnus-use-cache t)) |
96 ;; If we got HEAD headers, we convert them into NOV | 122 (setq result (gnus-retrieve-headers |
97 ;; headers. This is slow, inefficient and, come to think | 123 articles cgroup nil)))) |
98 ;; of it, downright evil. So sue me. I couldn't be | 124 (set-buffer nntp-server-buffer) |
99 ;; bothered to write a header parse routine that could | 125 ;; If we got HEAD headers, we convert them into NOV |
100 ;; parse a mixed HEAD/NOV buffer. | 126 ;; headers. This is slow, inefficient and, come to think |
101 (when (eq result 'headers) | 127 ;; of it, downright evil. So sue me. I couldn't be |
102 (nnvirtual-convert-headers)) | 128 ;; bothered to write a header parse routine that could |
103 (goto-char (point-min)) | 129 ;; parse a mixed HEAD/NOV buffer. |
104 (while (not (eobp)) | 130 (when (eq result 'headers) |
105 (delete-region | 131 (nnvirtual-convert-headers)) |
106 (point) (progn (read nntp-server-buffer) (point))) | 132 (goto-char (point-min)) |
107 (princ (car article) (current-buffer)) | 133 (while (not (eobp)) |
134 (delete-region (point) | |
135 (progn | |
136 (setq carticle (read nntp-server-buffer)) | |
137 (point))) | |
138 | |
139 ;; We remove this article from the articles list, if | |
140 ;; anything is left in the articles list after going through | |
141 ;; the entire buffer, then those articles have been | |
142 ;; expired or canceled, so we appropriately update the | |
143 ;; component group below. They should be coming up | |
144 ;; generally in order, so this shouldn't be slow. | |
145 (setq articles (delq carticle articles)) | |
146 | |
147 (setq article (nnvirtual-reverse-map-article cgroup carticle)) | |
148 (if (null article) | |
149 ;; This line has no reverse mapping, that means it | |
150 ;; was an extra article reference returned by nntp. | |
151 (progn | |
108 (beginning-of-line) | 152 (beginning-of-line) |
109 (looking-at | 153 (delete-region (point) (progn (forward-line 1) (point)))) |
110 "[^\t]*\t[^\t]*\t[^\t]*\t[^\t]*\t[^\t]*\t[^\t]*\t[^\t]*\t") | 154 ;; Otherwise insert the virtual article number, |
111 (goto-char (match-end 0)) | 155 ;; and clean up the xrefs. |
112 (or (search-forward | 156 (princ article nntp-server-buffer) |
113 "\t" (save-excursion (end-of-line) (point)) t) | 157 (nnvirtual-update-xref-header cgroup carticle |
114 (end-of-line)) | 158 prefix system-name) |
115 (while (= (char-after (1- (point))) ? ) | 159 (forward-line 1)) |
116 (forward-char -1) | 160 ) |
117 (delete-char 1)) | 161 |
118 (if (eolp) | 162 (set-buffer vbuf) |
119 (progn | 163 (goto-char (point-max)) |
120 (end-of-line) | 164 (insert-buffer-substring nntp-server-buffer)) |
121 (or (= (char-after (1- (point))) ?\t) | 165 ;; Anything left in articles is expired or canceled. |
122 (insert ?\t)) | 166 ;; Could be smart and not tell it about articles already known? |
123 (insert "Xref: " system-name " " cgroup ":") | 167 (when articles |
124 (princ (caddr article) (current-buffer)) | 168 (gnus-group-make-articles-read cgroup articles)) |
125 (insert "\t")) | 169 ) |
126 (insert "Xref: " system-name " " cgroup ":") | |
127 (princ (caddr article) (current-buffer)) | |
128 (insert " ") | |
129 (if (not (string= "" prefix)) | |
130 (while (re-search-forward | |
131 "[^ ]+:[0-9]+" | |
132 (save-excursion (end-of-line) (point)) t) | |
133 (save-excursion | |
134 (goto-char (match-beginning 0)) | |
135 (insert prefix)))) | |
136 (end-of-line) | |
137 (or (= (char-after (1- (point))) ?\t) | |
138 (insert ?\t))) | |
139 (forward-line 1)) | |
140 (set-buffer vbuf) | |
141 (goto-char (point-max)) | |
142 (insert-buffer-substring nntp-server-buffer))))) | |
143 | |
144 ;; In case some of the articles have expired or been | |
145 ;; cancelled, we have to mark them as read in the | |
146 ;; component group. | |
147 (while unfetched | |
148 (when (cdar unfetched) | |
149 (gnus-group-make-articles-read | |
150 (caar unfetched) (sort (cdar unfetched) '<))) | |
151 (setq unfetched (cdr unfetched))) | |
152 | 170 |
153 ;; The headers are ready for reading, so they are inserted into | 171 ;; The headers are ready for reading, so they are inserted into |
154 ;; the nntp-server-buffer, which is where Gnus expects to find | 172 ;; the nntp-server-buffer, which is where Gnus expects to find |
155 ;; them. | 173 ;; them. |
156 (prog1 | 174 (prog1 |
157 (save-excursion | 175 (save-excursion |
158 (set-buffer nntp-server-buffer) | 176 (set-buffer nntp-server-buffer) |
159 (erase-buffer) | 177 (erase-buffer) |
160 (insert-buffer-substring vbuf) | 178 (insert-buffer-substring vbuf) |
179 ;; FIX FIX FIX, we should be able to sort faster than | |
180 ;; this if needed, since each cgroup is sorted, we just | |
181 ;; need to merge | |
182 (sort-numeric-fields 1 (point-min) (point-max)) | |
161 'nov) | 183 'nov) |
162 (kill-buffer vbuf))))))) | 184 (kill-buffer vbuf))))))) |
185 | |
186 | |
163 | 187 |
164 (deffoo nnvirtual-request-article (article &optional group server buffer) | 188 (deffoo nnvirtual-request-article (article &optional group server buffer) |
165 (when (and (nnvirtual-possibly-change-server server) | 189 (when (and (nnvirtual-possibly-change-server server) |
166 (numberp article)) | 190 (numberp article)) |
167 (let* ((amap (assq article nnvirtual-mapping)) | 191 (let* ((amap (nnvirtual-map-article article)) |
168 (cgroup (cadr amap))) | 192 (cgroup (car amap))) |
169 (cond | 193 (cond |
170 ((not amap) | 194 ((not amap) |
171 (nnheader-report 'nnvirtual "No such article: %s" article)) | 195 (nnheader-report 'nnvirtual "No such article: %s" article)) |
172 ((not (gnus-check-group cgroup)) | 196 ((not (gnus-check-group cgroup)) |
173 (nnheader-report | 197 (nnheader-report |
176 (nnheader-report 'nnvirtual "Can't open component group %s" cgroup)) | 200 (nnheader-report 'nnvirtual "Can't open component group %s" cgroup)) |
177 (t | 201 (t |
178 (if buffer | 202 (if buffer |
179 (save-excursion | 203 (save-excursion |
180 (set-buffer buffer) | 204 (set-buffer buffer) |
181 (gnus-request-article-this-buffer (caddr amap) cgroup)) | 205 (gnus-request-article-this-buffer (cdr amap) cgroup)) |
182 (gnus-request-article (caddr amap) cgroup))))))) | 206 (gnus-request-article (cdr amap) cgroup))))))) |
207 | |
183 | 208 |
184 (deffoo nnvirtual-open-server (server &optional defs) | 209 (deffoo nnvirtual-open-server (server &optional defs) |
185 (unless (assq 'nnvirtual-component-regexp defs) | 210 (unless (assq 'nnvirtual-component-regexp defs) |
186 (push `(nnvirtual-component-regexp ,server) | 211 (push `(nnvirtual-component-regexp ,server) |
187 defs)) | 212 defs)) |
188 (nnoo-change-server 'nnvirtual server defs) | 213 (nnoo-change-server 'nnvirtual server defs) |
189 (if nnvirtual-component-groups | 214 (if nnvirtual-component-groups |
190 t | 215 t |
191 (setq nnvirtual-mapping nil) | 216 (setq nnvirtual-mapping-table nil |
192 ;; Go through the newsrc alist and find all component groups. | 217 nnvirtual-mapping-offsets nil |
193 (let ((newsrc (cdr gnus-newsrc-alist)) | 218 nnvirtual-mapping-len 0 |
194 group) | 219 nnvirtual-mapping-reads nil |
195 (while (setq group (car (pop newsrc))) | 220 nnvirtual-mapping-marks nil |
196 (when (string-match nnvirtual-component-regexp group) ; Match | 221 nnvirtual-info-installed nil) |
197 ;; Add this group to the list of component groups. | 222 (when nnvirtual-component-regexp |
198 (setq nnvirtual-component-groups | 223 ;; Go through the newsrc alist and find all component groups. |
199 (cons group (delete group nnvirtual-component-groups)))))) | 224 (let ((newsrc (cdr gnus-newsrc-alist)) |
225 group) | |
226 (while (setq group (car (pop newsrc))) | |
227 (when (string-match nnvirtual-component-regexp group) ; Match | |
228 ;; Add this group to the list of component groups. | |
229 (setq nnvirtual-component-groups | |
230 (cons group (delete group nnvirtual-component-groups))))))) | |
200 (if (not nnvirtual-component-groups) | 231 (if (not nnvirtual-component-groups) |
201 (nnheader-report 'nnvirtual "No component groups: %s" server) | 232 (nnheader-report 'nnvirtual "No component groups: %s" server) |
202 t))) | 233 t))) |
234 | |
203 | 235 |
204 (deffoo nnvirtual-request-group (group &optional server dont-check) | 236 (deffoo nnvirtual-request-group (group &optional server dont-check) |
205 (nnvirtual-possibly-change-server server) | 237 (nnvirtual-possibly-change-server server) |
206 (setq nnvirtual-component-groups | 238 (setq nnvirtual-component-groups |
207 (delete (nnvirtual-current-group) nnvirtual-component-groups)) | 239 (delete (nnvirtual-current-group) nnvirtual-component-groups)) |
208 (cond | 240 (cond |
209 ((null nnvirtual-component-groups) | 241 ((null nnvirtual-component-groups) |
210 (setq nnvirtual-current-group nil) | 242 (setq nnvirtual-current-group nil) |
211 (nnheader-report 'nnvirtual "No component groups in %s" group)) | 243 (nnheader-report 'nnvirtual "No component groups in %s" group)) |
212 (t | 244 (t |
213 (unless dont-check | 245 (when (or (not dont-check) |
246 nnvirtual-always-rescan) | |
214 (nnvirtual-create-mapping)) | 247 (nnvirtual-create-mapping)) |
215 (setq nnvirtual-current-group group) | 248 (setq nnvirtual-current-group group) |
216 (let ((len (length nnvirtual-mapping))) | 249 (nnheader-insert "211 %d 1 %d %s\n" |
217 (nnheader-insert "211 %d 1 %d %s\n" len len group))))) | 250 nnvirtual-mapping-len nnvirtual-mapping-len group)))) |
251 | |
218 | 252 |
219 (deffoo nnvirtual-request-type (group &optional article) | 253 (deffoo nnvirtual-request-type (group &optional article) |
220 (if (not article) | 254 (if (not article) |
221 'unknown | 255 'unknown |
222 (let ((mart (assq article nnvirtual-mapping))) | 256 (let ((mart (nnvirtual-map-article article))) |
223 (when mart | 257 (when mart |
224 (gnus-request-type (cadr mart) (car mart)))))) | 258 (gnus-request-type (car mart) (cdr mart)))))) |
225 | 259 |
226 (deffoo nnvirtual-request-update-mark (group article mark) | 260 (deffoo nnvirtual-request-update-mark (group article mark) |
227 (let* ((nart (assq article nnvirtual-mapping)) | 261 (let* ((nart (nnvirtual-map-article article)) |
228 (cgroup (cadr nart)) | 262 (cgroup (car nart)) |
229 ;; The component group might be a virtual group. | 263 ;; The component group might be a virtual group. |
230 (nmark (gnus-request-update-mark cgroup (caddr nart) mark))) | 264 (nmark (gnus-request-update-mark cgroup (cdr nart) mark))) |
231 (when (and nart | 265 (when (and nart |
232 (= mark nmark) | 266 (= mark nmark) |
233 (gnus-group-auto-expirable-p cgroup)) | 267 (gnus-group-auto-expirable-p cgroup)) |
234 (setq mark gnus-expirable-mark))) | 268 (setq mark gnus-expirable-mark))) |
235 mark) | 269 mark) |
270 | |
236 | 271 |
237 (deffoo nnvirtual-close-group (group &optional server) | 272 (deffoo nnvirtual-close-group (group &optional server) |
238 (when (nnvirtual-possibly-change-server server) | 273 (when (and (nnvirtual-possibly-change-server server) |
239 ;; Copy (un)read articles. | 274 (not (gnus-ephemeral-group-p (nnvirtual-current-group)))) |
240 (nnvirtual-update-reads) | 275 (nnvirtual-update-read-and-marked t t)) |
241 ;; We copy the marks from this group to the component | |
242 ;; groups here. | |
243 (nnvirtual-update-marked)) | |
244 t) | 276 t) |
245 | 277 |
246 (deffoo nnvirtual-request-list (&optional server) | 278 |
279 (deffoo nnvirtual-request-list (&optional server) | |
247 (nnheader-report 'nnvirtual "LIST is not implemented.")) | 280 (nnheader-report 'nnvirtual "LIST is not implemented.")) |
281 | |
248 | 282 |
249 (deffoo nnvirtual-request-newgroups (date &optional server) | 283 (deffoo nnvirtual-request-newgroups (date &optional server) |
250 (nnheader-report 'nnvirtual "NEWGROUPS is not supported.")) | 284 (nnheader-report 'nnvirtual "NEWGROUPS is not supported.")) |
251 | 285 |
286 | |
252 (deffoo nnvirtual-request-list-newsgroups (&optional server) | 287 (deffoo nnvirtual-request-list-newsgroups (&optional server) |
253 (nnheader-report 'nnvirtual "LIST NEWSGROUPS is not implemented.")) | 288 (nnheader-report 'nnvirtual "LIST NEWSGROUPS is not implemented.")) |
254 | 289 |
290 | |
255 (deffoo nnvirtual-request-update-info (group info &optional server) | 291 (deffoo nnvirtual-request-update-info (group info &optional server) |
256 (when (nnvirtual-possibly-change-server server) | 292 (when (and (nnvirtual-possibly-change-server server) |
257 (let ((map nnvirtual-mapping) | 293 (not nnvirtual-info-installed)) |
258 (marks (mapcar (lambda (m) (list (cdr m))) gnus-article-mark-lists)) | 294 ;; Install the precomputed lists atomically, so the virtual group |
259 reads mr m op) | 295 ;; is not left in a half-way state in case of C-g. |
260 ;; Go through the mapping. | 296 (gnus-atomic-progn |
261 (while map | 297 (setcar (cddr info) nnvirtual-mapping-reads) |
262 (unless (nth 3 (setq m (pop map))) | |
263 ;; Read article. | |
264 (push (car m) reads)) | |
265 ;; Copy marks. | |
266 (when (setq mr (nth 4 m)) | |
267 (while mr | |
268 (setcdr (setq op (assq (pop mr) marks)) (cons (car m) (cdr op)))))) | |
269 ;; Compress the marks and the reads. | |
270 (setq mr marks) | |
271 (while mr | |
272 (setcdr (car mr) (gnus-compress-sequence (sort (cdr (pop mr)) '<)))) | |
273 (setcar (cddr info) (gnus-compress-sequence (nreverse reads))) | |
274 ;; Remove empty marks lists. | |
275 (while (and marks (not (cdar marks))) | |
276 (setq marks (cdr marks))) | |
277 (setq mr marks) | |
278 (while (cdr mr) | |
279 (if (cdadr mr) | |
280 (setq mr (cdr mr)) | |
281 (setcdr mr (cddr mr)))) | |
282 | |
283 ;; Enter these new marks into the info of the group. | |
284 (if (nthcdr 3 info) | 298 (if (nthcdr 3 info) |
285 (setcar (nthcdr 3 info) marks) | 299 (setcar (nthcdr 3 info) nnvirtual-mapping-marks) |
286 ;; Add the marks lists to the end of the info. | 300 (when nnvirtual-mapping-marks |
287 (when marks | 301 (setcdr (nthcdr 2 info) (list nnvirtual-mapping-marks)))) |
288 (setcdr (nthcdr 2 info) (list marks)))) | 302 (setq nnvirtual-info-installed t)) |
289 t))) | 303 t)) |
304 | |
290 | 305 |
291 (deffoo nnvirtual-catchup-group (group &optional server all) | 306 (deffoo nnvirtual-catchup-group (group &optional server all) |
292 (nnvirtual-possibly-change-server server) | 307 (when (and (nnvirtual-possibly-change-server server) |
293 (let ((gnus-group-marked (copy-sequence nnvirtual-component-groups)) | 308 (not (gnus-ephemeral-group-p (nnvirtual-current-group)))) |
294 (gnus-expert-user t)) | 309 ;; copy over existing marks first, in case they set anything |
295 ;; Make sure all groups are activated. | 310 (nnvirtual-update-read-and-marked nil nil) |
296 (mapcar | 311 ;; do a catchup on all component groups |
297 (lambda (g) | 312 (let ((gnus-group-marked (copy-sequence nnvirtual-component-groups)) |
298 (when (not (numberp (car (gnus-gethash g gnus-newsrc-hashtb)))) | 313 (gnus-expert-user t)) |
299 (gnus-activate-group g))) | 314 ;; Make sure all groups are activated. |
300 nnvirtual-component-groups) | 315 (mapcar |
301 (save-excursion | 316 (lambda (g) |
302 (set-buffer gnus-group-buffer) | 317 (when (not (numberp (car (gnus-gethash g gnus-newsrc-hashtb)))) |
303 (gnus-group-catchup-current nil all)))) | 318 (gnus-activate-group g))) |
319 nnvirtual-component-groups) | |
320 (save-excursion | |
321 (set-buffer gnus-group-buffer) | |
322 (gnus-group-catchup-current nil all))))) | |
323 | |
304 | 324 |
305 (deffoo nnvirtual-find-group-art (group article) | 325 (deffoo nnvirtual-find-group-art (group article) |
306 "Return the real group and article for virtual GROUP and ARTICLE." | 326 "Return the real group and article for virtual GROUP and ARTICLE." |
307 (let ((mart (assq article nnvirtual-mapping))) | 327 (nnvirtual-map-article article)) |
308 (when mart | |
309 (cons (cadr mart) (caddr mart))))) | |
310 | 328 |
311 | 329 |
312 ;;; Internal functions. | 330 ;;; Internal functions. |
313 | 331 |
314 (defun nnvirtual-convert-headers () | 332 (defun nnvirtual-convert-headers () |
320 header) | 338 header) |
321 (erase-buffer) | 339 (erase-buffer) |
322 (while (setq header (pop headers)) | 340 (while (setq header (pop headers)) |
323 (nnheader-insert-nov header))))) | 341 (nnheader-insert-nov header))))) |
324 | 342 |
343 | |
344 (defun nnvirtual-update-xref-header (group article prefix system-name) | |
345 "Edit current NOV header in current buffer to have an xref to the component group, and also server prefix any existing xref lines." | |
346 ;; Move to beginning of Xref field, creating a slot if needed. | |
347 (beginning-of-line) | |
348 (looking-at | |
349 "[^\t]*\t[^\t]*\t[^\t]*\t[^\t]*\t[^\t]*\t[^\t]*\t[^\t]*\t") | |
350 (goto-char (match-end 0)) | |
351 (unless (search-forward "\t" (point-at-eol) 'move) | |
352 (insert "\t")) | |
353 | |
354 ;; Remove any spaces at the beginning of the Xref field. | |
355 (while (= (char-after (1- (point))) ? ) | |
356 (forward-char -1) | |
357 (delete-char 1)) | |
358 | |
359 (insert "Xref: " system-name " " group ":") | |
360 (princ article (current-buffer)) | |
361 | |
362 ;; If there were existing xref lines, clean them up to have the correct | |
363 ;; component server prefix. | |
364 (let ((xref-end (save-excursion | |
365 (search-forward "\t" (point-at-eol) 'move) | |
366 (point))) | |
367 (len (length prefix))) | |
368 (unless (= (point) xref-end) | |
369 (insert " ") | |
370 (when (not (string= "" prefix)) | |
371 (while (re-search-forward "[^ ]+:[0-9]+" xref-end t) | |
372 (save-excursion | |
373 (goto-char (match-beginning 0)) | |
374 (insert prefix)) | |
375 (setq xref-end (+ xref-end len))) | |
376 ))) | |
377 | |
378 ;; Ensure a trailing \t. | |
379 (end-of-line) | |
380 (or (= (char-after (1- (point))) ?\t) | |
381 (insert ?\t))) | |
382 | |
383 | |
325 (defun nnvirtual-possibly-change-server (server) | 384 (defun nnvirtual-possibly-change-server (server) |
326 (or (not server) | 385 (or (not server) |
327 (nnoo-current-server-p 'nnvirtual server) | 386 (nnoo-current-server-p 'nnvirtual server) |
328 (nnvirtual-open-server server))) | 387 (nnvirtual-open-server server))) |
329 | 388 |
330 (defun nnvirtual-update-marked () | 389 |
331 "Copy marks from the virtual group to the component groups." | 390 (defun nnvirtual-update-read-and-marked (read-p update-p) |
332 (let ((mark-lists gnus-article-mark-lists) | 391 "Copy marks from the virtual group to the component groups. |
333 (marks (gnus-info-marks (gnus-get-info (nnvirtual-current-group)))) | 392 If READ-P is not nil, update the (un)read status of the components. |
334 type list mart cgroups) | 393 If UPDATE-P is not nil, call gnus-group-update-group on the components." |
335 (while (setq type (cdr (pop mark-lists))) | 394 (let ((unreads (and read-p |
336 (setq list (gnus-uncompress-range (cdr (assq type marks)))) | 395 (nnvirtual-partition-sequence |
337 (setq cgroups | 396 (gnus-list-of-unread-articles |
338 (mapcar (lambda (g) (list g)) nnvirtual-component-groups)) | 397 (nnvirtual-current-group))))) |
339 (while list | 398 (type-marks (mapcar (lambda (ml) |
340 (nconc (assoc (cadr (setq mart (assq (pop list) nnvirtual-mapping))) | 399 (cons (car ml) |
341 cgroups) | 400 (nnvirtual-partition-sequence (cdr ml)))) |
342 (list (caddr mart)))) | 401 (gnus-info-marks (gnus-get-info |
343 (while cgroups | 402 (nnvirtual-current-group))))) |
344 (gnus-add-marked-articles | 403 mark type groups carticles info entry) |
345 (caar cgroups) type (cdar cgroups) nil t) | 404 |
346 (gnus-group-update-group (car (pop cgroups)) t))))) | 405 ;; Ok, atomically move all of the (un)read info, clear any old |
347 | 406 ;; marks, and move all of the current marks. This way if someone |
348 (defun nnvirtual-update-reads () | 407 ;; hits C-g, you won't leave the component groups in a half-way state. |
349 "Copy (un)reads from the current group to the component groups." | 408 (gnus-atomic-progn |
350 (let ((groups (mapcar (lambda (g) (list g)) nnvirtual-component-groups)) | 409 ;; move (un)read |
351 (articles (gnus-list-of-unread-articles | 410 (let ((gnus-newsgroup-active nil)) ;workaround guns-update-read-articles |
352 (nnvirtual-current-group))) | 411 (while (setq entry (pop unreads)) |
353 m) | 412 (gnus-update-read-articles (car entry) (cdr entry)))) |
354 (while articles | 413 |
355 (setq m (assq (pop articles) nnvirtual-mapping)) | 414 ;; clear all existing marks on the component groups |
356 (nconc (assoc (nth 1 m) groups) (list (nth 2 m)))) | 415 (setq groups nnvirtual-component-groups) |
357 (while groups | 416 (while groups |
358 (gnus-update-read-articles (caar groups) (cdr (pop groups)))))) | 417 (when (and (setq info (gnus-get-info (pop groups))) |
418 (gnus-info-marks info)) | |
419 (gnus-info-set-marks info nil))) | |
420 | |
421 ;; Ok, currently type-marks is an assq list with keys of a mark type, | |
422 ;; with data of an assq list with keys of component group names | |
423 ;; and the articles which correspond to that key/group pair. | |
424 (while (setq mark (pop type-marks)) | |
425 (setq type (car mark)) | |
426 (setq groups (cdr mark)) | |
427 (while (setq carticles (pop groups)) | |
428 (gnus-add-marked-articles (car carticles) type (cdr carticles) | |
429 nil t)))) | |
430 | |
431 ;; possibly update the display, it is really slow | |
432 (when update-p | |
433 (setq groups nnvirtual-component-groups) | |
434 (while groups | |
435 (gnus-group-update-group (pop groups) t))) | |
436 )) | |
437 | |
359 | 438 |
360 (defun nnvirtual-current-group () | 439 (defun nnvirtual-current-group () |
361 "Return the prefixed name of the current nnvirtual group." | 440 "Return the prefixed name of the current nnvirtual group." |
362 (concat "nnvirtual:" nnvirtual-current-group)) | 441 (concat "nnvirtual:" nnvirtual-current-group)) |
363 | 442 |
364 (defsubst nnvirtual-marks (article marks) | 443 |
365 "Return a list of mark types for ARTICLE." | 444 |
366 (let (out) | 445 ;;; This is currently O(kn^2) to merge n lists of length k. |
367 (while marks | 446 ;;; You could do it in O(knlogn), but we have a small n, and the |
368 (when (memq article (cdar marks)) | 447 ;;; overhead of the other approach is probably greater. |
369 (push (caar marks) out)) | 448 (defun nnvirtual-merge-sorted-lists (&rest lists) |
370 (setq marks (cdr marks))) | 449 "Merge many sorted lists of numbers." |
371 out)) | 450 (if (null (cdr lists)) |
451 (car lists) | |
452 (apply 'nnvirtual-merge-sorted-lists | |
453 (merge 'list (car lists) (cadr lists) '<) | |
454 (cddr lists)))) | |
455 | |
456 | |
457 | |
458 ;;; We map between virtual articles and real articles in a manner | |
459 ;;; which keeps the size of the virtual active list the same as | |
460 ;;; the sum of the component active lists. | |
461 ;;; To achieve fair mixing of the groups, the last article in | |
462 ;;; each of N component groups will be in the the last N articles | |
463 ;;; in the virtual group. | |
464 | |
465 ;;; If you have 3 components A, B and C, with articles 1-8, 1-5, and 6-7 | |
466 ;;; resprectively, then the virtual article numbers look like: | |
467 ;;; | |
468 ;;; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
469 ;;; A1 A2 A3 A4 B1 A5 B2 A6 B3 A7 B4 C6 A8 B5 C7 | |
470 | |
471 ;;; To compute these mappings we generate a couple tables and then | |
472 ;;; do some fast operations on them. Tables for the example above: | |
473 ;;; | |
474 ;;; Offsets - [(A 0) (B -3) (C -1)] | |
475 ;;; | |
476 ;;; a b c d e | |
477 ;;; Mapping - ([ 3 0 1 3 0 ] | |
478 ;;; [ 6 3 2 9 3 ] | |
479 ;;; [ 8 6 3 15 9 ]) | |
480 ;;; | |
481 ;;; (note column 'e' is different in real algorithm, which is slightly | |
482 ;;; different than described here, but this gives you the methodology.) | |
483 ;;; | |
484 ;;; The basic idea is this, when going from component->virtual, apply | |
485 ;;; the appropriate offset to the article number. Then search the first | |
486 ;;; column of the table for a row where 'a' is less than or equal to the | |
487 ;;; modified number. You can see that only group A can therefore go to | |
488 ;;; the first row, groups A and B to the second, and all to the last. | |
489 ;;; The third column of the table is telling us the number of groups | |
490 ;;; which might be able to reach that row (it might increase by more than | |
491 ;;; 1 if several groups have the same size). | |
492 ;;; Then column 'b' provides an additional offset you apply when you have | |
493 ;;; found the correct row. You then multiply by 'c' and add on the groups | |
494 ;;; _position_ in the offset table. The basic idea here is that on | |
495 ;;; any given row we are going to map back and forth using X'=X*c+Y and | |
496 ;;; X=(X'/c), Y=(X' mod c). Then once you've done this transformation, | |
497 ;;; you apply a final offset from column 'e' to give the virtual article. | |
498 ;;; | |
499 ;;; Going the other direction, you instead search on column 'd' instead | |
500 ;;; of 'a', and apply everything in reverse order. | |
501 | |
502 ;;; Convert component -> virtual: | |
503 ;;; set num = num - Offset(group) | |
504 ;;; find first row in Mapping where num <= 'a' | |
505 ;;; num = (num-'b')*c + Position(group) + 'e' | |
506 | |
507 ;;; Convert virtual -> component: | |
508 ;;; find first row in Mapping where num <= 'd' | |
509 ;;; num = num - 'e' | |
510 ;;; group_pos = num mod 'c' | |
511 ;;; num = (num / 'c') + 'b' + Offset(group_pos) | |
512 | |
513 ;;; Easy no? :) | |
514 ;;; | |
515 ;;; Well actually, you need to keep column e offset smaller by the 'c' | |
516 ;;; column for that line, and always add 1 more when going from | |
517 ;;; component -> virtual. Otherwise you run into a problem with | |
518 ;;; unique reverse mapping. | |
519 | |
520 (defun nnvirtual-map-article (article) | |
521 "Return a cons of the component group and article corresponding to the given virtual ARTICLE." | |
522 (let ((table nnvirtual-mapping-table) | |
523 entry group-pos) | |
524 (while (and table | |
525 (> article (aref (car table) 3))) | |
526 (setq table (cdr table))) | |
527 (when (and table | |
528 (> article 0)) | |
529 (setq entry (car table)) | |
530 (setq article (- article (aref entry 4) 1)) | |
531 (setq group-pos (mod article (aref entry 2))) | |
532 (cons (car (aref nnvirtual-mapping-offsets group-pos)) | |
533 (+ (/ article (aref entry 2)) | |
534 (aref entry 1) | |
535 (cdr (aref nnvirtual-mapping-offsets group-pos))) | |
536 )) | |
537 )) | |
538 | |
539 | |
540 | |
541 (defun nnvirtual-reverse-map-article (group article) | |
542 "Return the virtual article number corresponding to the given component GROUP and ARTICLE." | |
543 (let ((table nnvirtual-mapping-table) | |
544 (group-pos 0) | |
545 entry) | |
546 (while (not (string= group (car (aref nnvirtual-mapping-offsets | |
547 group-pos)))) | |
548 (setq group-pos (1+ group-pos))) | |
549 (setq article (- article (cdr (aref nnvirtual-mapping-offsets | |
550 group-pos)))) | |
551 (while (and table | |
552 (> article (aref (car table) 0))) | |
553 (setq table (cdr table))) | |
554 (setq entry (car table)) | |
555 (when (and entry | |
556 (> article 0) | |
557 (< group-pos (aref entry 2))) ; article not out of range below | |
558 (+ (aref entry 4) | |
559 group-pos | |
560 (* (- article (aref entry 1)) | |
561 (aref entry 2)) | |
562 1)) | |
563 )) | |
564 | |
565 | |
566 (defun nnvirtual-reverse-map-sequence (group articles) | |
567 "Return list of virtual article numbers for all ARTICLES in GROUP. | |
568 The ARTICLES should be sorted, and can be a compressed sequence. | |
569 If any of the article numbers has no corresponding virtual article, | |
570 then it is left out of the result." | |
571 (when (numberp (cdr-safe articles)) | |
572 (setq articles (list articles))) | |
573 (let (result a i j new-a) | |
574 (while (setq a (pop articles)) | |
575 (if (atom a) | |
576 (setq i a | |
577 j a) | |
578 (setq i (car a) | |
579 j (cdr a))) | |
580 (while (<= i j) | |
581 ;; If this is slow, you can optimize by moving article checking | |
582 ;; into here. You don't have to recompute the group-pos, | |
583 ;; nor scan the table every time. | |
584 (when (setq new-a (nnvirtual-reverse-map-article group i)) | |
585 (push new-a result)) | |
586 (setq i (1+ i)))) | |
587 (nreverse result))) | |
588 | |
589 | |
590 (defun nnvirtual-partition-sequence (articles) | |
591 "Return an association list of component article numbers. | |
592 These are indexed by elements of nnvirtual-component-groups, based on | |
593 the sequence ARTICLES of virtual article numbers. ARTICLES should be | |
594 sorted, and can be a compressed sequence. If any of the article | |
595 numbers has no corresponding component article, then it is left out of | |
596 the result." | |
597 (when (numberp (cdr-safe articles)) | |
598 (setq articles (list articles))) | |
599 (let ((carticles (mapcar (lambda (g) (list g)) | |
600 nnvirtual-component-groups)) | |
601 a i j article entry) | |
602 (while (setq a (pop articles)) | |
603 (if (atom a) | |
604 (setq i a | |
605 j a) | |
606 (setq i (car a) | |
607 j (cdr a))) | |
608 (while (<= i j) | |
609 (when (setq article (nnvirtual-map-article i)) | |
610 (setq entry (assoc (car article) carticles)) | |
611 (setcdr entry (cons (cdr article) (cdr entry)))) | |
612 (setq i (1+ i)))) | |
613 (mapc '(lambda (x) (setcdr x (nreverse (cdr x)))) | |
614 carticles) | |
615 carticles)) | |
616 | |
372 | 617 |
373 (defun nnvirtual-create-mapping () | 618 (defun nnvirtual-create-mapping () |
374 "Create an article mapping for the current group." | 619 "Build the tables necessary to map between component (group, article) to virtual article. |
375 (let* ((div nil) | 620 Generate the set of read messages and marks for the virtual group |
376 m marks list article unreads marks active | 621 based on the marks on the component groups." |
377 (map (sort | 622 (let ((cnt 0) |
378 (apply | 623 (tot 0) |
379 'nconc | 624 (M 0) |
380 (mapcar | 625 (i 0) |
381 (lambda (g) | 626 actives all-unreads all-marks |
382 (when (and (setq active (gnus-activate-group g)) | 627 active min max size unreads marks |
383 (> (cdr active) (car active))) | 628 next-M next-tot |
384 (setq unreads (gnus-list-of-unread-articles g) | 629 reads beg) |
385 marks (gnus-uncompress-marks | 630 ;; Ok, we loop over all component groups and collect a lot of |
386 (gnus-info-marks (gnus-get-info g)))) | 631 ;; information: |
387 (when gnus-use-cache | 632 ;; Into actives we place (g size max), where size is max-min+1. |
388 (push (cons 'cache (gnus-cache-articles-in-group g)) | 633 ;; Into all-unreads we put (g unreads). |
389 marks)) | 634 ;; Into all-marks we put (g marks). |
390 (setq div (/ (float (car active)) | 635 ;; We also increment cnt and tot here, and compute M (max of sizes). |
391 (if (zerop (cdr active)) | 636 (mapc (lambda (g) |
392 1 (cdr active)))) | 637 (setq active (gnus-activate-group g) |
393 (mapcar (lambda (n) | 638 min (car active) |
394 (list (* div (- n (car active))) | 639 max (cdr active)) |
395 g n (and (memq n unreads) t) | 640 (when (and active (>= max min) (not (zerop max))) |
396 (inline (nnvirtual-marks n marks)))) | 641 ;; store active information |
397 (gnus-uncompress-range active)))) | 642 (push (list g (- max min -1) max) actives) |
398 nnvirtual-component-groups)) | 643 ;; collect unread/mark info for later |
399 (lambda (m1 m2) | 644 (setq unreads (gnus-list-of-unread-articles g)) |
400 (< (car m1) (car m2))))) | 645 (setq marks (gnus-info-marks (gnus-get-info g))) |
401 (i 0)) | 646 (when gnus-use-cache |
402 (setq nnvirtual-mapping map) | 647 (push (cons 'cache |
403 ;; Set the virtual article numbers. | 648 (gnus-cache-articles-in-group g)) |
404 (while (setq m (pop map)) | 649 marks)) |
405 (setcar m (setq article (incf i)))))) | 650 (push (cons g unreads) all-unreads) |
651 (push (cons g marks) all-marks) | |
652 ;; count groups, total #articles, and max size | |
653 (setq size (- max min -1)) | |
654 (setq cnt (1+ cnt) | |
655 tot (+ tot size) | |
656 M (max M size)))) | |
657 nnvirtual-component-groups) | |
658 | |
659 ;; Number of articles in the virtual group. | |
660 (setq nnvirtual-mapping-len tot) | |
661 | |
662 | |
663 ;; We want the actives list sorted by size, to build the tables. | |
664 (setq actives (sort actives (lambda (g1 g2) (< (nth 1 g1) (nth 1 g2))))) | |
665 | |
666 ;; Build the offset table. Largest sized groups are at the front. | |
667 (setq nnvirtual-mapping-offsets | |
668 (vconcat | |
669 (nreverse | |
670 (mapcar (lambda (entry) | |
671 (cons (nth 0 entry) | |
672 (- (nth 2 entry) M))) | |
673 actives)))) | |
674 | |
675 ;; Build the mapping table. | |
676 (setq nnvirtual-mapping-table nil) | |
677 (setq actives (mapcar (lambda (entry) (nth 1 entry)) actives)) | |
678 (while actives | |
679 (setq size (car actives)) | |
680 (setq next-M (- M size)) | |
681 (setq next-tot (- tot (* cnt size))) | |
682 ;; make current row in table | |
683 (push (vector M next-M cnt tot (- next-tot cnt)) | |
684 nnvirtual-mapping-table) | |
685 ;; update M and tot | |
686 (setq M next-M) | |
687 (setq tot next-tot) | |
688 ;; subtract the current size from all entries. | |
689 (setq actives (mapcar (lambda (x) (- x size)) actives)) | |
690 ;; remove anything that went to 0. | |
691 (while (and actives | |
692 (= (car actives) 0)) | |
693 (pop actives) | |
694 (setq cnt (- cnt 1)))) | |
695 | |
696 | |
697 ;; Now that the mapping tables are generated, we can convert | |
698 ;; and combine the separate component unreads and marks lists | |
699 ;; into single lists of virtual article numbers. | |
700 (setq unreads (apply 'nnvirtual-merge-sorted-lists | |
701 (mapcar (lambda (x) | |
702 (nnvirtual-reverse-map-sequence | |
703 (car x) (cdr x))) | |
704 all-unreads))) | |
705 (setq marks (mapcar | |
706 (lambda (type) | |
707 (cons (cdr type) | |
708 (gnus-compress-sequence | |
709 (apply | |
710 'nnvirtual-merge-sorted-lists | |
711 (mapcar (lambda (x) | |
712 (nnvirtual-reverse-map-sequence | |
713 (car x) | |
714 (cdr (assq (cdr type) (cdr x))))) | |
715 all-marks))))) | |
716 gnus-article-mark-lists)) | |
717 | |
718 ;; Remove any empty marks lists, and store. | |
719 (setq nnvirtual-mapping-marks (delete-if-not 'cdr marks)) | |
720 | |
721 ;; We need to convert the unreads to reads. We compress the | |
722 ;; sequence as we go, otherwise it could be huge. | |
723 (while (and (<= (incf i) nnvirtual-mapping-len) | |
724 unreads) | |
725 (if (= i (car unreads)) | |
726 (setq unreads (cdr unreads)) | |
727 ;; try to get a range. | |
728 (setq beg i) | |
729 (while (and (<= (incf i) nnvirtual-mapping-len) | |
730 (not (= i (car unreads))))) | |
731 (setq i (- i 1)) | |
732 (if (= i beg) | |
733 (push i reads) | |
734 (push (cons beg i) reads)) | |
735 )) | |
736 (when (<= i nnvirtual-mapping-len) | |
737 (if (= i nnvirtual-mapping-len) | |
738 (push i reads) | |
739 (push (cons i nnvirtual-mapping-len) reads))) | |
740 | |
741 ;; Store the reads list for later use. | |
742 (setq nnvirtual-mapping-reads (nreverse reads)) | |
743 | |
744 ;; Throw flag to show we changed the info. | |
745 (setq nnvirtual-info-installed nil) | |
746 )) | |
406 | 747 |
407 (provide 'nnvirtual) | 748 (provide 'nnvirtual) |
408 | 749 |
409 ;;; nnvirtual.el ends here | 750 ;;; nnvirtual.el ends here |