*** empty log message ***
[gnus] / lisp / nnvirtual.el
index 801af71..7a97ce7 100644 (file)
@@ -1,7 +1,7 @@
-;;;; nnvirtual.el --- Virtual newsgroups access for (ding) Gnus
-;; Copyright (C) 1994,95 Free Software Foundation, Inc.
+;;; nnvirtual.el --- virtual newsgroups access for Gnus
+;; Copyright (C) 1994,95,96 Free Software Foundation, Inc.
 
-;; Author: Lars Ingebrigtsen <larsi@ifi.uio.no>
+;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
 ;;     Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
 ;; Keywords: news
 
 ;; GNU General Public License for more details.
 
 ;; You should have received a copy of the GNU General Public License
-;; along with GNU Emacs; see the file COPYING.  If not, write to
-;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
 
 ;;; 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:
 (require 'nntp)
 (require 'nnheader)
 (require 'gnus)
+(require 'nnoo)
+(require 'gnus-util)
+(require 'gnus-start)
+(require 'gnus-sum)
+(eval-when-compile (require 'cl))
 
-(defconst nnvirtual-version "nnvirtual 0.0"
-  "Version numbers of this version of nnvirual.")
+(nnoo-declare nnvirtual)
 
-(defvar nnvirtual-large-newsgroup 50
-  "The number of the articles which indicates a large newsgroup.
-If the number of the articles is greater than the value, verbose
-messages will be shown to indicate the current status.")
+(defvoo nnvirtual-always-rescan nil
+  "*If non-nil, always scan groups for unread articles when entering a group.
+If this variable is nil (which is the default) and you read articles
+in a component group after the virtual group has been activated, the
+read articles from the component group will show up when you enter the
+virtual group.")
+
+(defvoo nnvirtual-component-regexp nil
+  "*Regexp to match component groups.")
+
+(defvoo nnvirtual-component-groups nil
+  "Component group in this nnvirtual group.")
 
 \f
 
-(defvar nnvirtual-group-alist nil)
-(defvar nnvirtual-current-group nil)
-(defvar nnvirtual-current-groups nil)
-(defvar nnvirtual-current-mapping nil)
+(defconst nnvirtual-version "nnvirtual 1.0")
+
+(defvoo nnvirtual-current-group nil)
+(defvoo nnvirtual-mapping nil)
 
-(defvar nnvirtual-do-not-open nil)
+(defvoo nnvirtual-status-string "")
 
-(defvar nnvirtual-status-string "")
+(eval-and-compile
+  (autoload 'gnus-cache-articles-in-group "gnus-cache"))
 
 \f
 
 ;;; Interface functions.
 
-(defun nnvirtual-retrieve-headers (sequence &optional newsgroup server)
-  "Retrieve the headers for the articles in SEQUENCE."
-  (nnvirtual-possibly-change-newsgroups newsgroup server)
-  (save-excursion
-    (set-buffer (get-buffer-create "*virtual headers*"))
-    (erase-buffer)
-    (let ((number (length sequence))
-         (count 0)
-         (gnus-nov-is-evil t)
-         (i 0)
-         prev articles group-articles beg art-info article group)
-      (if sequence (setq prev (car (aref nnvirtual-current-mapping 
-                                        (car sequence)))))
-      (while sequence
-       (setq art-info (aref nnvirtual-current-mapping (car sequence)))
-       (if (not (equal prev (car art-info)))
-           (progn
-             (setq group-articles (cons (list prev (nreverse articles)) 
-                                        group-articles))
-             (setq articles nil)
-             (setq prev (car art-info))))
-       (setq articles (cons (cdr art-info) articles))
-       (setq sequence (cdr sequence)))
-      (if prev
-         (setq group-articles (cons (list prev (nreverse articles)) 
-                                    group-articles)))
-      (setq group-articles (nreverse group-articles))
-      (while group-articles
-       (setq group (car (car group-articles)))
-       (gnus-retrieve-headers (car (cdr (car group-articles))) group)
-       (save-excursion
-         (set-buffer nntp-server-buffer)
-         (goto-char 1)
-         (insert "\n.\n")
-         (goto-char 1)
-         (while (search-forward "\n.\n" nil t)
-           (if (not (looking-at ".[0-9]+ \\([0-9]+\\) "))
-               ()
-             (setq article (string-to-int (gnus-buffer-substring 1 1)))
-             (setq i 1)
-             (while (/= article (cdr (aref nnvirtual-current-mapping i)))
-               (setq i (1+ i)))
-             (goto-char (match-beginning 1))
-             (looking-at "[0-9]+ ")
-             (replace-match (format "%d " i))
-             (setq beg (point))
-             (search-forward "\n.\n" nil t)
-             (if (not (re-search-backward "^Xref: " beg t))
-                 (progn
-                   (forward-char -2)
-                   (insert (format "Xref: %s %s:%d\n" (system-name) 
-                                   group article))
-                   (forward-char -1)))
-             )))
-       (goto-char (point-max))
-       (insert-buffer-substring nntp-server-buffer 4)
-       (setq group-articles (cdr group-articles)))
-      ;; The headers are ready for reading, so they are inserted into
-      ;; the nntp-server-buffer, which is where Gnus expects to find
-      ;; them.
-      (prog1
-         (save-excursion
-           (if (not nntp-server-buffer)
-               (setq nntp-server-buffer (get-buffer-create " *nntpd*")))
-           (set-buffer nntp-server-buffer)
-           (erase-buffer)
-           (insert-buffer-substring "*virtual headers*")
-           'headers)
-       (kill-buffer (current-buffer))))))
-
-(defun nnvirtual-open-server (newsgroups &optional something)
-  "Open a virtual newsgroup that contains NEWSGROUPS."
-  (nnvirtual-open-server-internal))
-
-(defun nnvirtual-close-server (&rest dum)
-  "Close news server."
-  (nnvirtual-close-server-internal))
-
-(fset 'nnvirtual-request-quit (symbol-function 'nnvirtual-close-server))
-
-(defun nnvirtual-server-opened (&optional server)
-  "Return server process status, T or NIL.
-If the stream is opened, return T, otherwise return NIL."
-  (and nntp-server-buffer
-       (get-buffer nntp-server-buffer)))
-
-(defun nnvirtual-status-message ()
-  "Return server status response as string."
-  nnvirtual-status-string)
-
-(defun nnvirtual-request-article (id &optional newsgroup server buffer)
-  "Select article by message ID (or number)."
-  (nnvirtual-possibly-change-newsgroups newsgroup server)
-  (let (art)
-    (setq art (aref nnvirtual-current-mapping id))
-    (gnus-request-group (car art))
-    (gnus-request-article (cdr art) (car art) buffer)))
-
-(defun nnvirtual-request-group (group &optional server dont-check)
-  "Make GROUP the current newsgroup."
-  (nnvirtual-possibly-change-newsgroups group server dont-check)
-  (let ((total (length nnvirtual-current-mapping)))
+(nnoo-define-basics nnvirtual)
+
+(deffoo nnvirtual-retrieve-headers (articles &optional newsgroup
+                                            server fetch-old)
+  (when (nnvirtual-possibly-change-server server)
     (save-excursion
       (set-buffer nntp-server-buffer)
       (erase-buffer)
-      (insert (format "211 %d %d %d %s\n" total 1 (1- total) group)))
-    t))
-
-(defun nnvirtual-close-group (group &optional server)
-  (nnvirtual-possibly-change-newsgroups group server)
-  (nnvirtual-update-marked)
-  (setq nnvirtual-current-group nil)
-  (setq nnvirtual-current-groups nil)
-  (setq nnvirtual-current-mapping nil)
-  (let ((inf (member group nnvirtual-group-alist)))
-    (setq nnvirtual-group-alist (delq inf nnvirtual-group-alist))))
-
-(defun nnvirtual-request-list (&optional server) 
-  (setq nnvirtual-status-string "nnvirtual: LIST is not implemented.")
-  nil)
-
-(defun nnvirtual-request-newgroups (date &optional server)
-  "List new groups."
-  (setq nnvirtual-status-string "NEWGROUPS is not supported.")
-  nil)
-
-(defun nnvirtual-request-list-newsgroups (&optional server)
-  (setq nnvirtual-status-string "nnvirtual: LIST NEWSGROUPS is not implemented.")
-  nil)
-
-(fset 'nnvirtual-request-post 'nntp-request-post)
+      (if (stringp (car articles))
+         'headers
+       (let ((vbuf (nnheader-set-temp-buffer 
+                    (get-buffer-create " *virtual headers*")))
+             (unfetched (mapcar (lambda (g) (list g))
+                                nnvirtual-component-groups))
+             (system-name (system-name))
+             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))
+             (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)
+                   (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
+         ;; them.
+         (prog1
+             (save-excursion
+               (set-buffer nntp-server-buffer)
+               (erase-buffer)
+               (insert-buffer-substring vbuf)
+               'nov)
+           (kill-buffer vbuf)))))))
+
+(deffoo nnvirtual-request-article (article &optional group server buffer)
+  (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)
+    (push `(nnvirtual-component-regexp ,server)
+         defs))
+  (nnoo-change-server 'nnvirtual server defs)
+  (if nnvirtual-component-groups
+      t
+    (setq nnvirtual-mapping 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)))))))
+    (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
+       (delete (nnvirtual-current-group) nnvirtual-component-groups))
+  (cond
+   ((null nnvirtual-component-groups)
+    (setq nnvirtual-current-group nil)
+    (nnheader-report 'nnvirtual "No component groups in %s" group))
+   (t
+    (unless dont-check
+      (nnvirtual-create-mapping))
+    (setq nnvirtual-current-group 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 (assq article nnvirtual-mapping)))
+      (when mart
+       (gnus-request-type (cadr mart) (car mart))))))
+
+(deffoo nnvirtual-request-update-mark (group article mark)
+  (let* ((nart (assq article nnvirtual-mapping))
+        (cgroup (cadr nart))
+        ;; The component group might be a virtual group.
+        (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))))
+    ;; 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) 
+  (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))))
+
+      ;; Enter these new marks into the info of the group.
+      (if (nthcdr 3 info)
+         (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)
+  (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))))
 
-(fset 'nnvirtual-request-post-buffer 'nntp-request-post-buffer)
+(deffoo nnvirtual-find-group-art (group article)
+  "Return the real group and article for virtual GROUP and ARTICLE."
+  (let ((mart (assq article nnvirtual-mapping)))
+    (when mart
+      (cons (cadr mart) (caddr mart)))))
 
 \f
-;;; Low-level functions.
+;;; Internal functions.
 
-(defun nnvirtual-open-server-internal ()
-  "Fix some internal variables."
+(defun nnvirtual-convert-headers ()
+  "Convert HEAD headers into NOV headers."
   (save-excursion
-    ;; Initialize communication buffer.
-    (setq nntp-server-buffer (get-buffer-create " *nntpd*"))
     (set-buffer nntp-server-buffer)
-    (buffer-disable-undo (current-buffer))
-    (kill-all-local-variables)
-    (setq case-fold-search t)))
-
-(defun nnvirtual-close-server-internal (&rest dum)
-  "Close connection to news server."
-  nil)
-
-(defun nnvirtual-possibly-change-newsgroups (group regexp &optional dont-check)
-  (let (inf)
-    (or (not group)
-       (and nnvirtual-current-group
-            (string= group nnvirtual-current-group))
-       (and (setq inf (member group nnvirtual-group-alist))
-            (string= (nth 3 inf) regexp)
-            (progn
-              (setq nnvirtual-current-group (car inf))
-              (setq nnvirtual-current-groups (nth 1 inf))
-              (setq nnvirtual-current-mapping (nth 2 inf)))))
-    (if (or (not dont-check) (not inf))
-       (progn
-         (and inf (setq nnvirtual-group-alist 
-                        (delq inf nnvirtual-group-alist)))
-         (setq nnvirtual-current-mapping nil)
-         (setq nnvirtual-current-group group)
-         (let ((newsrc gnus-newsrc-assoc))
-           (setq nnvirtual-current-groups nil)
-           (while newsrc
-             (and (string-match regexp (car (car newsrc)))
-                  (setq nnvirtual-current-groups
-                        (cons (car (car newsrc)) nnvirtual-current-groups)))
-             (setq newsrc (cdr newsrc))))
-         (if nnvirtual-current-groups
-             (progn
-               (nnvirtual-create-mapping group)
-               (setq nnvirtual-group-alist
-                     (cons (list group nnvirtual-current-groups 
-                                 nnvirtual-current-mapping regexp)
-                           nnvirtual-group-alist)))
-           (setq nnvirtual-status-string 
-                 (format 
-                  "nnvirtual: No newsgroups for this virtual newsgroup"))))))
-  nnvirtual-current-groups)
-
-(defun nnvirtual-create-mapping (group)
-  (let* ((group (gnus-group-prefixed-name group (list 'nnvirtual "")))
-        (info (nth 2 (gnus-gethash group gnus-newsrc-hashtb)))
-        (groups nnvirtual-current-groups)
-        (i 1)
-        (total 0)
-        unread igroup)
-    ;; The virtual group doesn't exist. (?)
-    (or info (error "No such group: %s" group))
-    ;; Set the list of read articles to nil.
-    (setcar (nthcdr 2 info) nil)
-    (while groups
-      ;; Added by Sudish Joseph <joseph@cis.ohio-state.edu>.
-      (setq igroup (car groups))
-      (let ((info (nth 2 (gnus-gethash igroup gnus-newsrc-hashtb)))
-           (active (gnus-gethash igroup gnus-active-hashtb)))
-       ;; see if the group has had its active list read this session
-       ;; if not, we do it now
-       (if (null active)
-           (if (gnus-activate-newsgroup igroup)
-               (gnus-get-unread-articles-in-group
-                info (gnus-gethash igroup gnus-active-hashtb))
-             (message "Couldn't request newsgroup %s" group)
-             (ding))))
-      (setq unread (car (gnus-gethash (car groups) gnus-newsrc-hashtb)))
-      (setq total (+ total unread))
-      (setq groups (cdr groups)))
-    ;; We create a mapping from nnvirtual article numbers (starting at
-    ;; 1) to the actual groups numbers.
-    (setq nnvirtual-current-mapping (make-vector (1+ total) nil))
-    (let ((groups nnvirtual-current-groups)
-         (marks '(tick dormant reply expire))
-         tick dormant reply expire marked)
-      (while groups
-       (setq igroup (car groups))
-       (setq marked (nth 3 (nth 2 (gnus-gethash igroup gnus-newsrc-hashtb))))
-       (setq unread (gnus-list-of-unread-articles igroup))
-       (while unread
-         (aset nnvirtual-current-mapping i (cons igroup (car unread)))
-         ;; Find out if the article is marked, and enter the marks in
-         ;; the proper lists. 
-         (let ((m marks))
-           (while m
-             (and (memq (car unread) (assq (car m) marked))
-                  (set (car m) (cons i (symbol-value (car m)))))
-             (setq m (cdr m))))
-         (setq i (1+ i))
-         (setq unread (cdr unread)))
-       (setq groups (cdr groups)))
-      ;; Put the list of marked articles in the info of the virtual group.
-      (let ((m marks)
-           marked)
-       (while m
-         (and (symbol-value (car m))
-              (setq marked (cons (cons (car m) (symbol-value (car m)))
-                                 marked)))
-         (setq m (cdr m)))
-       (if (nthcdr 3 info)
-           (setcar (nthcdr 3 info) marked)
-         (setcdr (nthcdr 2 info) (list marked)))))))
+    (let* ((dependencies (make-vector 100 0))
+          (headers (gnus-get-newsgroup-headers dependencies))
+          header)
+      (erase-buffer)
+      (while (setq header (pop headers))
+       (nnheader-insert-nov header)))))
+
+(defun nnvirtual-possibly-change-server (server)
+  (or (not server)
+      (nnoo-current-server-p 'nnvirtual server)
+      (nnvirtual-open-server server)))
 
 (defun nnvirtual-update-marked ()
-  (let ((mark-lists '((gnus-newsgroup-marked . tick)
-                     (gnus-newsgroup-dormant . dormant)
-                     (gnus-newsgroup-expirable . expire)
-                     (gnus-newsgroup-replied . reply)))
-       marks art-group group-alist g)
-    (while mark-lists
-      (setq marks (symbol-value (car (car mark-lists))))
-      (while marks
-       (setq art-group (aref nnvirtual-current-mapping (car marks)))
-       (if (setq g (assoc (car art-group) group-alist))
-           (nconc g (list (cdr art-group)))
-         (setq group-alist (cons (list (car art-group) (cdr art-group)) 
-                                 group-alist)))
-       (setq marks (cdr marks)))
-      (while group-alist
-       (gnus-add-marked-articles (car (car group-alist)) 
-                                 (cdr (car mark-lists))
-                                 (cdr (car group-alist)))
-       (gnus-group-update-group (car (car group-alist)))
-       (setq group-alist (cdr group-alist)))
-      (setq mark-lists (cdr mark-lists)))))
+  "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)))))
+
+(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))
+
+(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 ()
+  "Create an article mapping for the current group."
+  (let* ((div nil)
+        m 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 (incf i)))))
 
 (provide 'nnvirtual)