Mercurial > hg > xemacs-beta
diff lisp/gnus/nnvirtual.el @ 70:131b0175ea99 r20-0b30
Import from CVS: tag r20-0b30
author | cvs |
---|---|
date | Mon, 13 Aug 2007 09:02:59 +0200 |
parents | e04119814345 |
children | 0d2f883870bc |
line wrap: on
line diff
--- a/lisp/gnus/nnvirtual.el Mon Aug 13 09:00:04 2007 +0200 +++ b/lisp/gnus/nnvirtual.el Mon Aug 13 09:02:59 2007 +0200 @@ -1,8 +1,7 @@ ;;; nnvirtual.el --- virtual newsgroups access for Gnus -;; Copyright (C) 1994,95,96,97 Free Software Foundation, Inc. +;; Copyright (C) 1994,95,96 Free Software Foundation, Inc. -;; Author: David Moore <dmoore@ucsd.edu> -;; Lars Magne Ingebrigtsen <larsi@ifi.uio.no> +;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no> ;; Masanobu UMEDA <umerin@flab.flab.fujitsu.junet> ;; Keywords: news @@ -26,7 +25,7 @@ ;;; Commentary: ;; The other access methods (nntp, nnspool, etc) are general news -;; access methods. This module relies on Gnus and can not be used +;; access methods. This module relies on Gnus and can not be used ;; separately. ;;; Code: @@ -35,9 +34,6 @@ (require 'nnheader) (require 'gnus) (require 'nnoo) -(require 'gnus-util) -(require 'gnus-start) -(require 'gnus-sum) (eval-when-compile (require 'cl)) (nnoo-declare nnvirtual) @@ -52,33 +48,13 @@ (defvoo nnvirtual-component-regexp nil "*Regexp to match component groups.") -(defvoo nnvirtual-component-groups nil - "Component group in this nnvirtual group.") - -(defconst nnvirtual-version "nnvirtual 1.1") +(defconst nnvirtual-version "nnvirtual 1.0") (defvoo nnvirtual-current-group nil) - -(defvoo nnvirtual-mapping-table nil - "Table of rules on how to map between component group and article number -to virtual article number.") - -(defvoo nnvirtual-mapping-offsets nil - "Table indexed by component group to an offset to be applied to article numbers in that group.") - -(defvoo nnvirtual-mapping-len 0 - "Number of articles in this virtual group.") - -(defvoo nnvirtual-mapping-reads nil - "Compressed sequence of read articles on the virtual group as computed from the unread status of individual component groups.") - -(defvoo nnvirtual-mapping-marks nil - "Compressed marks alist for the virtual group as computed from the marks of individual component groups.") - -(defvoo nnvirtual-info-installed nil - "T if we have already installed the group info for this group, and shouldn't blast over it again.") +(defvoo nnvirtual-component-groups nil) +(defvoo nnvirtual-mapping nil) (defvoo nnvirtual-status-string "") @@ -91,7 +67,6 @@ (nnoo-define-basics nnvirtual) - (deffoo nnvirtual-retrieve-headers (articles &optional newsgroup server fetch-old) (when (nnvirtual-possibly-change-server server) @@ -100,73 +75,80 @@ (erase-buffer) (if (stringp (car articles)) 'headers - (let ((vbuf (nnheader-set-temp-buffer + (let ((vbuf (nnheader-set-temp-buffer (get-buffer-create " *virtual headers*"))) - (carticles (nnvirtual-partition-sequence articles)) + (unfetched (mapcar (lambda (g) (list g)) + nnvirtual-component-groups)) (system-name (system-name)) - cgroup carticle article result prefix) - (while carticles - (setq cgroup (caar carticles)) - (setq articles (cdar carticles)) - (pop carticles) - (when (and articles + cgroup article result prefix) + (while articles + (setq article (assq (pop articles) nnvirtual-mapping)) + (when (and (setq cgroup (cadr article)) (gnus-check-server (gnus-find-method-for-group cgroup) t) - (gnus-request-group cgroup t) - (setq prefix (gnus-group-real-prefix cgroup)) - ;; FIX FIX FIX we want to check the cache! - ;; This is probably evil if people have set - ;; gnus-use-cache to nil themselves, but I - ;; have no way of finding the true value of it. - (let ((gnus-use-cache t)) - (setq result (gnus-retrieve-headers - articles cgroup nil)))) - (set-buffer nntp-server-buffer) - ;; If we got HEAD headers, we convert them into NOV - ;; headers. This is slow, inefficient and, come to think - ;; of it, downright evil. So sue me. I couldn't be - ;; bothered to write a header parse routine that could - ;; parse a mixed HEAD/NOV buffer. - (when (eq result 'headers) - (nnvirtual-convert-headers)) - (goto-char (point-min)) - (while (not (eobp)) - (delete-region (point) - (progn - (setq carticle (read nntp-server-buffer)) - (point))) - - ;; We remove this article from the articles list, if - ;; anything is left in the articles list after going through - ;; the entire buffer, then those articles have been - ;; expired or canceled, so we appropriately update the - ;; component group below. They should be coming up - ;; generally in order, so this shouldn't be slow. - (setq articles (delq carticle articles)) - - (setq article (nnvirtual-reverse-map-article cgroup carticle)) - (if (null article) - ;; This line has no reverse mapping, that means it - ;; was an extra article reference returned by nntp. - (progn + (gnus-request-group cgroup t)) + (setq prefix (gnus-group-real-prefix cgroup)) + (when (setq result (gnus-retrieve-headers + (list (caddr article)) cgroup nil)) + (set-buffer nntp-server-buffer) + (if (zerop (buffer-size)) + (nconc (assq cgroup unfetched) (list (caddr article))) + ;; If we got HEAD headers, we convert them into NOV + ;; headers. This is slow, inefficient and, come to think + ;; of it, downright evil. So sue me. I couldn't be + ;; bothered to write a header parse routine that could + ;; parse a mixed HEAD/NOV buffer. + (when (eq result 'headers) + (nnvirtual-convert-headers)) + (goto-char (point-min)) + (while (not (eobp)) + (delete-region + (point) (progn (read nntp-server-buffer) (point))) + (princ (car article) (current-buffer)) (beginning-of-line) - (delete-region (point) (progn (forward-line 1) (point)))) - ;; Otherwise insert the virtual article number, - ;; and clean up the xrefs. - (princ article nntp-server-buffer) - (nnvirtual-update-xref-header cgroup carticle - prefix system-name) - (forward-line 1)) - ) - - (set-buffer vbuf) - (goto-char (point-max)) - (insert-buffer-substring nntp-server-buffer)) - ;; Anything left in articles is expired or canceled. - ;; Could be smart and not tell it about articles already known? - (when articles - (gnus-group-make-articles-read cgroup articles)) - ) + (looking-at + "[^\t]*\t[^\t]*\t[^\t]*\t[^\t]*\t[^\t]*\t[^\t]*\t[^\t]*\t") + (goto-char (match-end 0)) + (or (search-forward + "\t" (save-excursion (end-of-line) (point)) t) + (end-of-line)) + (while (= (char-after (1- (point))) ? ) + (forward-char -1) + (delete-char 1)) + (if (eolp) + (progn + (end-of-line) + (or (= (char-after (1- (point))) ?\t) + (insert ?\t)) + (insert "Xref: " system-name " " cgroup ":") + (princ (caddr article) (current-buffer)) + (insert "\t")) + (insert "Xref: " system-name " " cgroup ":") + (princ (caddr article) (current-buffer)) + (insert " ") + (if (not (string= "" prefix)) + (while (re-search-forward + "[^ ]+:[0-9]+" + (save-excursion (end-of-line) (point)) t) + (save-excursion + (goto-char (match-beginning 0)) + (insert prefix)))) + (end-of-line) + (or (= (char-after (1- (point))) ?\t) + (insert ?\t))) + (forward-line 1)) + (set-buffer vbuf) + (goto-char (point-max)) + (insert-buffer-substring nntp-server-buffer))))) + + ;; In case some of the articles have expired or been + ;; cancelled, we have to mark them as read in the + ;; component group. + (while unfetched + (when (cdar unfetched) + (gnus-group-make-articles-read + (caar unfetched) (sort (cdar unfetched) '<))) + (setq unfetched (cdr unfetched))) ;; The headers are ready for reading, so they are inserted into ;; the nntp-server-buffer, which is where Gnus expects to find @@ -176,51 +158,28 @@ (set-buffer nntp-server-buffer) (erase-buffer) (insert-buffer-substring vbuf) - ;; FIX FIX FIX, we should be able to sort faster than - ;; this if needed, since each cgroup is sorted, we just - ;; need to merge - (sort-numeric-fields 1 (point-min) (point-max)) 'nov) (kill-buffer vbuf))))))) - -(defvoo nnvirtual-last-accessed-component-group nil) - (deffoo nnvirtual-request-article (article &optional group server buffer) - (when (nnvirtual-possibly-change-server server) - (if (stringp article) - ;; This is a fetch by Message-ID. - (cond - ((not nnvirtual-last-accessed-component-group) - (nnheader-report - 'nnvirtual "Don't know what server to request from")) - (t - (save-excursion - (when buffer - (set-buffer buffer)) - (let ((method (gnus-find-method-for-group - nnvirtual-last-accessed-component-group))) - (funcall (gnus-get-function method 'request-article) - article nil (nth 1 method) buffer))))) - ;; This is a fetch by number. - (let* ((amap (nnvirtual-map-article article)) - (cgroup (car amap))) - (cond - ((not amap) - (nnheader-report 'nnvirtual "No such article: %s" article)) - ((not (gnus-check-group cgroup)) - (nnheader-report - 'nnvirtual "Can't open server where %s exists" cgroup)) - ((not (gnus-request-group cgroup t)) - (nnheader-report 'nnvirtual "Can't open component group %s" cgroup)) - (t - (setq nnvirtual-last-accessed-component-group cgroup) - (if buffer - (save-excursion - (set-buffer buffer) - (gnus-request-article-this-buffer (cdr amap) cgroup)) - (gnus-request-article (cdr amap) cgroup)))))))) - + (when (and (nnvirtual-possibly-change-server server) + (numberp article)) + (let* ((amap (assq article nnvirtual-mapping)) + (cgroup (cadr amap))) + (cond + ((not amap) + (nnheader-report 'nnvirtual "No such article: %s" article)) + ((not (gnus-check-group cgroup)) + (nnheader-report + 'nnvirtual "Can't open server where %s exists" cgroup)) + ((not (gnus-request-group cgroup t)) + (nnheader-report 'nnvirtual "Can't open component group %s" cgroup)) + (t + (if buffer + (save-excursion + (set-buffer buffer) + (gnus-request-article-this-buffer (caddr amap) cgroup)) + (gnus-request-article (caddr amap) cgroup))))))) (deffoo nnvirtual-open-server (server &optional defs) (unless (assq 'nnvirtual-component-regexp defs) @@ -229,26 +188,19 @@ (nnoo-change-server 'nnvirtual server defs) (if nnvirtual-component-groups t - (setq nnvirtual-mapping-table nil - nnvirtual-mapping-offsets nil - nnvirtual-mapping-len 0 - nnvirtual-mapping-reads nil - nnvirtual-mapping-marks nil - nnvirtual-info-installed nil) - (when nnvirtual-component-regexp - ;; Go through the newsrc alist and find all component groups. - (let ((newsrc (cdr gnus-newsrc-alist)) - group) - (while (setq group (car (pop newsrc))) - (when (string-match nnvirtual-component-regexp group) ; Match - ;; Add this group to the list of component groups. - (setq nnvirtual-component-groups - (cons group (delete group nnvirtual-component-groups))))))) + (setq nnvirtual-mapping nil) + ;; Go through the newsrc alist and find all component groups. + (let ((newsrc (cdr gnus-newsrc-alist)) + group) + (while (setq group (car (pop newsrc))) + (when (string-match nnvirtual-component-regexp group) ; Match + ;; Add this group to the list of component groups. + (setq nnvirtual-component-groups + (cons group (delete group nnvirtual-component-groups)))))) (if (not nnvirtual-component-groups) (nnheader-report 'nnvirtual "No component groups: %s" server) t))) - (deffoo nnvirtual-request-group (group &optional server dont-check) (nnvirtual-possibly-change-server server) (setq nnvirtual-component-groups @@ -258,89 +210,103 @@ (setq nnvirtual-current-group nil) (nnheader-report 'nnvirtual "No component groups in %s" group)) (t - (when (or (not dont-check) - nnvirtual-always-rescan) + (unless dont-check (nnvirtual-create-mapping)) (setq nnvirtual-current-group group) - (nnheader-insert "211 %d 1 %d %s\n" - nnvirtual-mapping-len nnvirtual-mapping-len group)))) - + (let ((len (length nnvirtual-mapping))) + (nnheader-insert "211 %d 1 %d %s\n" len len group))))) (deffoo nnvirtual-request-type (group &optional article) (if (not article) 'unknown - (let ((mart (nnvirtual-map-article article))) + (let ((mart (assq article nnvirtual-mapping))) (when mart - (gnus-request-type (car mart) (cdr mart)))))) + (gnus-request-type (cadr mart) (car mart)))))) (deffoo nnvirtual-request-update-mark (group article mark) - (let* ((nart (nnvirtual-map-article article)) - (cgroup (car nart)) + (let* ((nart (assq article nnvirtual-mapping)) + (cgroup (cadr nart)) ;; The component group might be a virtual group. - (nmark (gnus-request-update-mark cgroup (cdr nart) mark))) + (nmark (gnus-request-update-mark cgroup (caddr nart) mark))) (when (and nart (= mark nmark) (gnus-group-auto-expirable-p cgroup)) (setq mark gnus-expirable-mark))) mark) - - + (deffoo nnvirtual-close-group (group &optional server) - (when (and (nnvirtual-possibly-change-server server) - (not (gnus-ephemeral-group-p (nnvirtual-current-group)))) - (nnvirtual-update-read-and-marked t t)) + (when (nnvirtual-possibly-change-server server) + ;; Copy (un)read articles. + (nnvirtual-update-reads) + ;; We copy the marks from this group to the component + ;; groups here. + (nnvirtual-update-marked)) t) - - -(deffoo nnvirtual-request-list (&optional server) + +(deffoo nnvirtual-request-list (&optional server) (nnheader-report 'nnvirtual "LIST is not implemented.")) - (deffoo nnvirtual-request-newgroups (date &optional server) (nnheader-report 'nnvirtual "NEWGROUPS is not supported.")) - (deffoo nnvirtual-request-list-newsgroups (&optional server) (nnheader-report 'nnvirtual "LIST NEWSGROUPS is not implemented.")) +(deffoo nnvirtual-request-update-info (group info &optional server) + (when (nnvirtual-possibly-change-server server) + (let ((map nnvirtual-mapping) + (marks (mapcar (lambda (m) (list (cdr m))) gnus-article-mark-lists)) + reads mr m op) + ;; Go through the mapping. + (while map + (unless (nth 3 (setq m (pop map))) + ;; Read article. + (push (car m) reads)) + ;; Copy marks. + (when (setq mr (nth 4 m)) + (while mr + (setcdr (setq op (assq (pop mr) marks)) (cons (car m) (cdr op)))))) + ;; Compress the marks and the reads. + (setq mr marks) + (while mr + (setcdr (car mr) (gnus-compress-sequence (sort (cdr (pop mr)) '<)))) + (setcar (cddr info) (gnus-compress-sequence (nreverse reads))) + ;; Remove empty marks lists. + (while (and marks (not (cdar marks))) + (setq marks (cdr marks))) + (setq mr marks) + (while (cdr mr) + (if (cdadr mr) + (setq mr (cdr mr)) + (setcdr mr (cddr mr)))) -(deffoo nnvirtual-request-update-info (group info &optional server) - (when (and (nnvirtual-possibly-change-server server) - (not nnvirtual-info-installed)) - ;; Install the precomputed lists atomically, so the virtual group - ;; is not left in a half-way state in case of C-g. - (gnus-atomic-progn - (setcar (cddr info) nnvirtual-mapping-reads) + ;; Enter these new marks into the info of the group. (if (nthcdr 3 info) - (setcar (nthcdr 3 info) nnvirtual-mapping-marks) - (when nnvirtual-mapping-marks - (setcdr (nthcdr 2 info) (list nnvirtual-mapping-marks)))) - (setq nnvirtual-info-installed t)) - t)) - + (setcar (nthcdr 3 info) marks) + ;; Add the marks lists to the end of the info. + (when marks + (setcdr (nthcdr 2 info) (list marks)))) + t))) (deffoo nnvirtual-catchup-group (group &optional server all) - (when (and (nnvirtual-possibly-change-server server) - (not (gnus-ephemeral-group-p (nnvirtual-current-group)))) - ;; copy over existing marks first, in case they set anything - (nnvirtual-update-read-and-marked nil nil) - ;; do a catchup on all component groups - (let ((gnus-group-marked (copy-sequence nnvirtual-component-groups)) - (gnus-expert-user t)) - ;; Make sure all groups are activated. - (mapcar - (lambda (g) - (when (not (numberp (car (gnus-gethash g gnus-newsrc-hashtb)))) - (gnus-activate-group g))) - nnvirtual-component-groups) - (save-excursion - (set-buffer gnus-group-buffer) - (gnus-group-catchup-current nil all))))) - + (nnvirtual-possibly-change-server server) + (let ((gnus-group-marked (copy-sequence nnvirtual-component-groups)) + (gnus-expert-user t)) + ;; Make sure all groups are activated. + (mapcar + (lambda (g) + (when (not (numberp (car (gnus-gethash g gnus-newsrc-hashtb)))) + (gnus-activate-group g))) + nnvirtual-component-groups) + (save-excursion + (set-buffer gnus-group-buffer) + (gnus-group-catchup-current nil all)))) (deffoo nnvirtual-find-group-art (group article) "Return the real group and article for virtual GROUP and ARTICLE." - (nnvirtual-map-article article)) + (let ((mart (assq article nnvirtual-mapping))) + (when mart + (cons (cadr mart) (caddr mart))))) ;;; Internal functions. @@ -356,410 +322,87 @@ (while (setq header (pop headers)) (nnheader-insert-nov header))))) - -(defun nnvirtual-update-xref-header (group article prefix system-name) - "Edit current NOV header in current buffer to have an xref to the component group, and also server prefix any existing xref lines." - ;; Move to beginning of Xref field, creating a slot if needed. - (beginning-of-line) - (looking-at - "[^\t]*\t[^\t]*\t[^\t]*\t[^\t]*\t[^\t]*\t[^\t]*\t[^\t]*\t") - (goto-char (match-end 0)) - (unless (search-forward "\t" (gnus-point-at-eol) 'move) - (insert "\t")) - - ;; Remove any spaces at the beginning of the Xref field. - (while (= (char-after (1- (point))) ? ) - (forward-char -1) - (delete-char 1)) - - (insert "Xref: " system-name " " group ":") - (princ article (current-buffer)) - - ;; If there were existing xref lines, clean them up to have the correct - ;; component server prefix. - (let ((xref-end (save-excursion - (search-forward "\t" (gnus-point-at-eol) 'move) - (point))) - (len (length prefix))) - (unless (= (point) xref-end) - (insert " ") - (when (not (string= "" prefix)) - (while (re-search-forward "[^ ]+:[0-9]+" xref-end t) - (save-excursion - (goto-char (match-beginning 0)) - (insert prefix)) - (setq xref-end (+ xref-end len))) - ))) - - ;; Ensure a trailing \t. - (end-of-line) - (or (= (char-after (1- (point))) ?\t) - (insert ?\t))) - - (defun nnvirtual-possibly-change-server (server) (or (not server) (nnoo-current-server-p 'nnvirtual server) (nnvirtual-open-server server))) - -(defun nnvirtual-update-read-and-marked (read-p update-p) - "Copy marks from the virtual group to the component groups. -If READ-P is not nil, update the (un)read status of the components. -If UPDATE-P is not nil, call gnus-group-update-group on the components." - (when nnvirtual-current-group - (let ((unreads (and read-p - (nnvirtual-partition-sequence - (gnus-list-of-unread-articles - (nnvirtual-current-group))))) - (type-marks (mapcar (lambda (ml) - (cons (car ml) - (nnvirtual-partition-sequence (cdr ml)))) - (gnus-info-marks (gnus-get-info - (nnvirtual-current-group))))) - mark type groups carticles info entry) +(defun nnvirtual-update-marked () + "Copy marks from the virtual group to the component groups." + (let ((mark-lists gnus-article-mark-lists) + (marks (gnus-info-marks (gnus-get-info (nnvirtual-current-group)))) + type list mart cgroups) + (while (setq type (cdr (pop mark-lists))) + (setq list (gnus-uncompress-range (cdr (assq type marks)))) + (setq cgroups + (mapcar (lambda (g) (list g)) nnvirtual-component-groups)) + (while list + (nconc (assoc (cadr (setq mart (assq (pop list) nnvirtual-mapping))) + cgroups) + (list (caddr mart)))) + (while cgroups + (gnus-add-marked-articles + (caar cgroups) type (cdar cgroups) nil t) + (gnus-group-update-group (car (pop cgroups)) t))))) - ;; Ok, atomically move all of the (un)read info, clear any old - ;; marks, and move all of the current marks. This way if someone - ;; hits C-g, you won't leave the component groups in a half-way state. - (gnus-atomic-progn - ;; move (un)read - (let ((gnus-newsgroup-active nil)) ;workaround guns-update-read-articles - (while (setq entry (pop unreads)) - (gnus-update-read-articles (car entry) (cdr entry)))) - - ;; clear all existing marks on the component groups - (setq groups nnvirtual-component-groups) - (while groups - (when (and (setq info (gnus-get-info (pop groups))) - (gnus-info-marks info)) - (gnus-info-set-marks info nil))) - - ;; Ok, currently type-marks is an assq list with keys of a mark type, - ;; with data of an assq list with keys of component group names - ;; and the articles which correspond to that key/group pair. - (while (setq mark (pop type-marks)) - (setq type (car mark)) - (setq groups (cdr mark)) - (while (setq carticles (pop groups)) - (gnus-add-marked-articles (car carticles) type (cdr carticles) - nil t)))) - - ;; possibly update the display, it is really slow - (when update-p - (setq groups nnvirtual-component-groups) - (while groups - (gnus-group-update-group (pop groups) t)))))) - +(defun nnvirtual-update-reads () + "Copy (un)reads from the current group to the component groups." + (let ((groups (mapcar (lambda (g) (list g)) nnvirtual-component-groups)) + (articles (gnus-list-of-unread-articles + (nnvirtual-current-group))) + m) + (while articles + (setq m (assq (pop articles) nnvirtual-mapping)) + (nconc (assoc (nth 1 m) groups) (list (nth 2 m)))) + (while groups + (gnus-update-read-articles (caar groups) (cdr (pop groups)))))) (defun nnvirtual-current-group () "Return the prefixed name of the current nnvirtual group." (concat "nnvirtual:" nnvirtual-current-group)) - - -;;; This is currently O(kn^2) to merge n lists of length k. -;;; You could do it in O(knlogn), but we have a small n, and the -;;; overhead of the other approach is probably greater. -(defun nnvirtual-merge-sorted-lists (&rest lists) - "Merge many sorted lists of numbers." - (if (null (cdr lists)) - (car lists) - (apply 'nnvirtual-merge-sorted-lists - (merge 'list (car lists) (cadr lists) '<) - (cddr lists)))) - - - -;;; We map between virtual articles and real articles in a manner -;;; which keeps the size of the virtual active list the same as -;;; the sum of the component active lists. -;;; To achieve fair mixing of the groups, the last article in -;;; each of N component groups will be in the the last N articles -;;; in the virtual group. - -;;; If you have 3 components A, B and C, with articles 1-8, 1-5, and 6-7 -;;; resprectively, then the virtual article numbers look like: -;;; -;;; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 -;;; A1 A2 A3 A4 B1 A5 B2 A6 B3 A7 B4 C6 A8 B5 C7 - -;;; To compute these mappings we generate a couple tables and then -;;; do some fast operations on them. Tables for the example above: -;;; -;;; Offsets - [(A 0) (B -3) (C -1)] -;;; -;;; a b c d e -;;; Mapping - ([ 3 0 1 3 0 ] -;;; [ 6 3 2 9 3 ] -;;; [ 8 6 3 15 9 ]) -;;; -;;; (note column 'e' is different in real algorithm, which is slightly -;;; different than described here, but this gives you the methodology.) -;;; -;;; The basic idea is this, when going from component->virtual, apply -;;; the appropriate offset to the article number. Then search the first -;;; column of the table for a row where 'a' is less than or equal to the -;;; modified number. You can see that only group A can therefore go to -;;; the first row, groups A and B to the second, and all to the last. -;;; The third column of the table is telling us the number of groups -;;; which might be able to reach that row (it might increase by more than -;;; 1 if several groups have the same size). -;;; Then column 'b' provides an additional offset you apply when you have -;;; found the correct row. You then multiply by 'c' and add on the groups -;;; _position_ in the offset table. The basic idea here is that on -;;; any given row we are going to map back and forth using X'=X*c+Y and -;;; X=(X'/c), Y=(X' mod c). Then once you've done this transformation, -;;; you apply a final offset from column 'e' to give the virtual article. -;;; -;;; Going the other direction, you instead search on column 'd' instead -;;; of 'a', and apply everything in reverse order. - -;;; Convert component -> virtual: -;;; set num = num - Offset(group) -;;; find first row in Mapping where num <= 'a' -;;; num = (num-'b')*c + Position(group) + 'e' - -;;; Convert virtual -> component: -;;; find first row in Mapping where num <= 'd' -;;; num = num - 'e' -;;; group_pos = num mod 'c' -;;; num = (num / 'c') + 'b' + Offset(group_pos) - -;;; Easy no? :) -;;; -;;; Well actually, you need to keep column e offset smaller by the 'c' -;;; column for that line, and always add 1 more when going from -;;; component -> virtual. Otherwise you run into a problem with -;;; unique reverse mapping. - -(defun nnvirtual-map-article (article) - "Return a cons of the component group and article corresponding to the given virtual ARTICLE." - (let ((table nnvirtual-mapping-table) - entry group-pos) - (while (and table - (> article (aref (car table) 3))) - (setq table (cdr table))) - (when (and table - (> article 0)) - (setq entry (car table)) - (setq article (- article (aref entry 4) 1)) - (setq group-pos (mod article (aref entry 2))) - (cons (car (aref nnvirtual-mapping-offsets group-pos)) - (+ (/ article (aref entry 2)) - (aref entry 1) - (cdr (aref nnvirtual-mapping-offsets group-pos))) - )) - )) - - - -(defun nnvirtual-reverse-map-article (group article) - "Return the virtual article number corresponding to the given component GROUP and ARTICLE." - (let ((table nnvirtual-mapping-table) - (group-pos 0) - entry) - (while (not (string= group (car (aref nnvirtual-mapping-offsets - group-pos)))) - (setq group-pos (1+ group-pos))) - (setq article (- article (cdr (aref nnvirtual-mapping-offsets - group-pos)))) - (while (and table - (> article (aref (car table) 0))) - (setq table (cdr table))) - (setq entry (car table)) - (when (and entry - (> article 0) - (< group-pos (aref entry 2))) ; article not out of range below - (+ (aref entry 4) - group-pos - (* (- article (aref entry 1)) - (aref entry 2)) - 1)) - )) - - -(defsubst nnvirtual-reverse-map-sequence (group articles) - "Return list of virtual article numbers for all ARTICLES in GROUP. -The ARTICLES should be sorted, and can be a compressed sequence. -If any of the article numbers has no corresponding virtual article, -then it is left out of the result." - (when (numberp (cdr-safe articles)) - (setq articles (list articles))) - (let (result a i j new-a) - (while (setq a (pop articles)) - (if (atom a) - (setq i a - j a) - (setq i (car a) - j (cdr a))) - (while (<= i j) - ;; If this is slow, you can optimize by moving article checking - ;; into here. You don't have to recompute the group-pos, - ;; nor scan the table every time. - (when (setq new-a (nnvirtual-reverse-map-article group i)) - (push new-a result)) - (setq i (1+ i)))) - (nreverse result))) - - -(defun nnvirtual-partition-sequence (articles) - "Return an association list of component article numbers. -These are indexed by elements of nnvirtual-component-groups, based on -the sequence ARTICLES of virtual article numbers. ARTICLES should be -sorted, and can be a compressed sequence. If any of the article -numbers has no corresponding component article, then it is left out of -the result." - (when (numberp (cdr-safe articles)) - (setq articles (list articles))) - (let ((carticles (mapcar (lambda (g) (list g)) - nnvirtual-component-groups)) - a i j article entry) - (while (setq a (pop articles)) - (if (atom a) - (setq i a - j a) - (setq i (car a) - j (cdr a))) - (while (<= i j) - (when (setq article (nnvirtual-map-article i)) - (setq entry (assoc (car article) carticles)) - (setcdr entry (cons (cdr article) (cdr entry)))) - (setq i (1+ i)))) - (mapc '(lambda (x) (setcdr x (nreverse (cdr x)))) - carticles) - carticles)) - +(defsubst nnvirtual-marks (article marks) + "Return a list of mark types for ARTICLE." + (let (out) + (while marks + (when (memq article (cdar marks)) + (push (caar marks) out)) + (setq marks (cdr marks))) + out)) (defun nnvirtual-create-mapping () - "Build the tables necessary to map between component (group, article) to virtual article. -Generate the set of read messages and marks for the virtual group -based on the marks on the component groups." - (let ((cnt 0) - (tot 0) - (M 0) - (i 0) - actives all-unreads all-marks - active min max size unreads marks - next-M next-tot - reads beg) - ;; Ok, we loop over all component groups and collect a lot of - ;; information: - ;; Into actives we place (g size max), where size is max-min+1. - ;; Into all-unreads we put (g unreads). - ;; Into all-marks we put (g marks). - ;; We also increment cnt and tot here, and compute M (max of sizes). - (mapc (lambda (g) - (setq active (gnus-activate-group g) - min (car active) - max (cdr active)) - (when (and active (>= max min) (not (zerop max))) - ;; store active information - (push (list g (- max min -1) max) actives) - ;; collect unread/mark info for later - (setq unreads (gnus-list-of-unread-articles g)) - (setq marks (gnus-info-marks (gnus-get-info g))) - (when gnus-use-cache - (push (cons 'cache - (gnus-cache-articles-in-group g)) - marks)) - (push (cons g unreads) all-unreads) - (push (cons g marks) all-marks) - ;; count groups, total #articles, and max size - (setq size (- max min -1)) - (setq cnt (1+ cnt) - tot (+ tot size) - M (max M size)))) - nnvirtual-component-groups) - - ;; Number of articles in the virtual group. - (setq nnvirtual-mapping-len tot) - - - ;; We want the actives list sorted by size, to build the tables. - (setq actives (sort actives (lambda (g1 g2) (< (nth 1 g1) (nth 1 g2))))) - - ;; Build the offset table. Largest sized groups are at the front. - (setq nnvirtual-mapping-offsets - (vconcat - (nreverse - (mapcar (lambda (entry) - (cons (nth 0 entry) - (- (nth 2 entry) M))) - actives)))) - - ;; Build the mapping table. - (setq nnvirtual-mapping-table nil) - (setq actives (mapcar (lambda (entry) (nth 1 entry)) actives)) - (while actives - (setq size (car actives)) - (setq next-M (- M size)) - (setq next-tot (- tot (* cnt size))) - ;; make current row in table - (push (vector M next-M cnt tot (- next-tot cnt)) - nnvirtual-mapping-table) - ;; update M and tot - (setq M next-M) - (setq tot next-tot) - ;; subtract the current size from all entries. - (setq actives (mapcar (lambda (x) (- x size)) actives)) - ;; remove anything that went to 0. - (while (and actives - (= (car actives) 0)) - (pop actives) - (setq cnt (- cnt 1)))) - - - ;; Now that the mapping tables are generated, we can convert - ;; and combine the separate component unreads and marks lists - ;; into single lists of virtual article numbers. - (setq unreads (apply 'nnvirtual-merge-sorted-lists - (mapcar (lambda (x) - (nnvirtual-reverse-map-sequence - (car x) (cdr x))) - all-unreads))) - (setq marks (mapcar - (lambda (type) - (cons (cdr type) - (gnus-compress-sequence - (apply - 'nnvirtual-merge-sorted-lists - (mapcar (lambda (x) - (nnvirtual-reverse-map-sequence - (car x) - (cdr (assq (cdr type) (cdr x))))) - all-marks))))) - gnus-article-mark-lists)) - - ;; Remove any empty marks lists, and store. - (setq nnvirtual-mapping-marks (delete-if-not 'cdr marks)) - - ;; We need to convert the unreads to reads. We compress the - ;; sequence as we go, otherwise it could be huge. - (while (and (<= (incf i) nnvirtual-mapping-len) - unreads) - (if (= i (car unreads)) - (setq unreads (cdr unreads)) - ;; try to get a range. - (setq beg i) - (while (and (<= (incf i) nnvirtual-mapping-len) - (not (= i (car unreads))))) - (setq i (- i 1)) - (if (= i beg) - (push i reads) - (push (cons beg i) reads)) - )) - (when (<= i nnvirtual-mapping-len) - (if (= i nnvirtual-mapping-len) - (push i reads) - (push (cons i nnvirtual-mapping-len) reads))) - - ;; Store the reads list for later use. - (setq nnvirtual-mapping-reads (nreverse reads)) - - ;; Throw flag to show we changed the info. - (setq nnvirtual-info-installed nil) - )) + "Create an article mapping for the current group." + (let* ((div nil) + m marks list article unreads marks active + (map (sort + (apply + 'nconc + (mapcar + (lambda (g) + (when (and (setq active (gnus-activate-group g)) + (> (cdr active) (car active))) + (setq unreads (gnus-list-of-unread-articles g) + marks (gnus-uncompress-marks + (gnus-info-marks (gnus-get-info g)))) + (when gnus-use-cache + (push (cons 'cache (gnus-cache-articles-in-group g)) + marks)) + (setq div (/ (float (car active)) + (if (zerop (cdr active)) + 1 (cdr active)))) + (mapcar (lambda (n) + (list (* div (- n (car active))) + g n (and (memq n unreads) t) + (inline (nnvirtual-marks n marks)))) + (gnus-uncompress-range active)))) + nnvirtual-component-groups)) + (lambda (m1 m2) + (< (car m1) (car m2))))) + (i 0)) + (setq nnvirtual-mapping map) + ;; Set the virtual article numbers. + (while (setq m (pop map)) + (setcar m (setq article (incf i)))))) (provide 'nnvirtual)