*** empty log message ***
[gnus] / lisp / nnvirtual.el
index 27ce4d7..59dacdc 100644 (file)
@@ -1,7 +1,7 @@
-;;;; nnvirtual.el --- virtual newsgroups access for (ding) Gnus
+;;; nnvirtual.el --- virtual newsgroups access for Gnus
 ;; Copyright (C) 1994,95 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
 
@@ -35,7 +35,7 @@
 
 \f
 
-(defconst nnvirtual-version "nnvirtual 0.0"
+(defconst nnvirtual-version "nnvirtual 1.0"
   "Version number of this version of nnvirtual.")
 
 (defvar nnvirtual-group-alist nil)
 
 ;;; Interface functions.
 
-(defun nnvirtual-retrieve-headers (sequence &optional newsgroup server)
+(defun nnvirtual-retrieve-headers (sequence &optional newsgroup server fetch-old)
   "Retrieve the headers for the articles in SEQUENCE."
   (nnvirtual-possibly-change-newsgroups newsgroup server t)
   (save-excursion
     (set-buffer (get-buffer-create "*virtual headers*"))
     (buffer-disable-undo (current-buffer))
     (erase-buffer)
-    (let ((map nnvirtual-current-mapping)
-         (offset 0)
-         articles beg group active top article result prefix)
-      (while sequence
-       (while (< (car (car map)) (car sequence))
-         (setq offset (car (car map)))
-         (setq map (cdr map)))
-       (setq top (car (car map)))
-       (setq group (nth 1 (car map)))
-       (setq prefix (gnus-group-real-prefix group))
-       (setq active (nth 2 (car map)))
-       (setq articles nil)
-       (while (and sequence (<= (car sequence) top))
-         (setq articles (cons (- (+ active (car sequence)) offset) articles))
-         (setq sequence (cdr sequence)))
-       (setq articles (nreverse articles))
-       (if (and articles
-                (setq result (gnus-retrieve-headers articles group)))
+    (if (stringp (car sequence))
+       'headers
+      (let ((map nnvirtual-current-mapping)
+           (offset 0)
+           articles beg group active top article result prefix
+           fetched-articles group-method)
+       (while sequence
+         (while (< (car (car map)) (car sequence))
+           (setq offset (car (car map)))
+           (setq map (cdr map)))
+         (setq top (car (car map)))
+         (setq group (nth 1 (car map)))
+         (setq prefix (gnus-group-real-prefix group))
+         (setq active (nth 2 (car map)))
+         (setq articles nil)
+         (while (and sequence (<= (car sequence) top))
+           (setq articles (cons (- (+ active (car sequence)) offset) 
+                                articles))
+           (setq sequence (cdr sequence)))
+         (setq articles (nreverse articles))
+         (if (and articles
+                  (setq result 
+                        (progn
+                          (setq group-method 
+                                (gnus-find-method-for-group group))
+                          (and (or (gnus-server-opened group-method)
+                                   (gnus-open-server group-method))
+                               (gnus-request-group group t)
+                               (gnus-retrieve-headers articles group)))))
+             (save-excursion
+               (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.
+               (and (eq result 'headers) (nnvirtual-convert-headers))
+               (goto-char (point-min))
+               (setq fetched-articles nil)
+               (while (not (eobp))
+                 (setq beg (point)
+                       article (read nntp-server-buffer)
+                       fetched-articles (cons article fetched-articles))
+                 (delete-region beg (point))
+                 (insert (int-to-string (+ (- article active) offset)))
+                 (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 (format "Xref: %s %s:%d\t" (system-name) 
+                                       group article)))
+                   (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))))
+         (goto-char (point-max))
+         (insert-buffer-substring nntp-server-buffer)
+         ;; We have now massaged and inserted the headers from one
+         ;; group. In case some of the articles have expired or been
+         ;; cancelled, we have to mark them as read in the component
+         ;; group. 
+         (let ((unfetched (gnus-sorted-complement 
+                           articles (nreverse fetched-articles))))
+           (and unfetched
+                (gnus-group-make-articles-read group 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)
-             ;; 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.
-             (and (eq result 'headers) (nnvirtual-convert-headers))
-             (goto-char (point-min))
-             (while (not (eobp))
-               (setq beg (point))
-               (setq article (read nntp-server-buffer))
-               (delete-region beg (point))
-               (insert (int-to-string (+ (- article active) offset)))
-               (end-of-line)
-               (setq beg (point))
-               (search-backward "\t")
-               (if (not (search-forward "Xref:" beg t))
-                   (progn
-                     (end-of-line)
-                     (or (= (char-after (1- (point))) ?\t)
-                         (insert ?\t))
-                     (insert (format "Xref: %s %s:%d\t" (system-name) 
-                                     group article)))
-                 (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)))))
-               (forward-line 1))))
-       (goto-char (point-max))
-       (insert-buffer-substring nntp-server-buffer))
-      ;; 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 "*virtual headers*")
-           'nov)
-       (kill-buffer (current-buffer))))))
+             (erase-buffer)
+             (insert-buffer-substring "*virtual headers*")
+             'nov)
+         (kill-buffer (current-buffer)))))))
 
 (defun nnvirtual-open-server (newsgroups &optional something)
   "Open a virtual newsgroup that contains NEWSGROUPS."
   "Close news server."
   t)
 
+(defun nnvirtual-request-close ()
+  (setq nnvirtual-current-group nil
+       nnvirtual-current-groups nil
+       nnvirtual-current-mapping nil
+       nnvirtual-group-alist nil)
+  t)
+
 (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 ()
+(defun nnvirtual-status-message (&optional server)
   "Return server status response as string."
   nnvirtual-status-string)
 
@@ -144,10 +182,14 @@ If the stream is opened, return T, otherwise return NIL."
   (nnvirtual-possibly-change-newsgroups newsgroup server t)
   (and (numberp article)
        (let ((map nnvirtual-current-mapping)
-            (offset 0))
+            (offset 0)
+            group-method)
         (while (< (car (car map)) article)
           (setq offset (car (car map)))
           (setq map (cdr map)))
+        (setq group-method (gnus-find-method-for-group (nth 1 (car map))))
+        (or (gnus-server-opened group-method)
+            (gnus-open-server group-method))
         (gnus-request-group (nth 1 (car map)) t)
         (gnus-request-article (- (+ (nth 2 (car map)) article) offset)
                               (nth 1 (car map)) buffer))))
@@ -155,25 +197,37 @@ If the stream is opened, return T, otherwise return NIL."
 (defun nnvirtual-request-group (group &optional server dont-check)
   "Make GROUP the current newsgroup."
   (nnvirtual-possibly-change-newsgroups group server dont-check)
-  (if (not dont-check)
-      (let ((map nnvirtual-current-mapping))
-       (while (cdr map)
-         (setq map (cdr map)))
-       (save-excursion
-         (set-buffer nntp-server-buffer)
-         (erase-buffer)
-         (insert (format "211 %d 1 %d %s\n" (car (car map)) 
-                         (car (car map)) group)))))
-  t)
+  (let ((map nnvirtual-current-mapping))
+    (save-excursion
+      (set-buffer nntp-server-buffer)
+      (erase-buffer)
+      (if map
+         (progn
+           (while (cdr map)
+             (setq map (cdr map)))
+           (insert (format "211 %d 1 %d %s\n" (car (car map)) 
+                           (car (car map)) group))
+           t)
+       (setq nnvirtual-status-string "No component groups")
+       (setq nnvirtual-current-group nil)
+       nil))))
+
+(defun nnvirtual-request-type (group &optional article)
+  (nnvirtual-possibly-change-newsgroups group nil)
+  (if (not article)
+      'unknown
+    (gnus-request-type (car (nnvirtual-art-group article)))))
     
 (defun nnvirtual-close-group (group &optional server)
-  (nnvirtual-possibly-change-newsgroups group server t)
-  (nnvirtual-update-marked)
-  (setq nnvirtual-current-group nil
-       nnvirtual-current-groups nil
-       nnvirtual-current-mapping nil)
-  (setq nnvirtual-group-alist 
-       (delq (assoc group nnvirtual-group-alist) nnvirtual-group-alist)))
+  (if (not nnvirtual-current-group)
+      ()
+    (nnvirtual-possibly-change-newsgroups group server t)
+    (nnvirtual-update-marked)
+    (setq nnvirtual-current-group nil
+         nnvirtual-current-groups nil
+         nnvirtual-current-mapping nil)
+    (setq nnvirtual-group-alist 
+         (delq (assoc group nnvirtual-group-alist) nnvirtual-group-alist))))
 
 (defun nnvirtual-request-list (&optional server) 
   (setq nnvirtual-status-string "nnvirtual: LIST is not implemented.")
@@ -189,33 +243,30 @@ If the stream is opened, return T, otherwise return NIL."
        "nnvirtual: LIST NEWSGROUPS is not implemented.")
   nil)
 
-(fset 'nnvirtual-request-post 'nntp-request-post)
-
-(fset 'nnvirtual-request-post-buffer 'nntp-request-post-buffer)
-
 \f
 ;;; Internal functions.
 
-;; Convert HEAD headers into NOV headers.
 (defun nnvirtual-convert-headers ()
+  "Convert HEAD headers into NOV headers."
   (save-excursion
     (set-buffer nntp-server-buffer)
-    (let* ((gnus-newsgroup-dependencies (make-vector 100 0))
-          (headers (gnus-get-newsgroup-headers))
+    (let* ((dependencies (make-vector 100 0))
+          (headers (gnus-get-newsgroup-headers dependencies))
           header)
       (erase-buffer)
       (while headers
-       (setq header (car headers)
-             headers (cdr headers))
-       (insert (int-to-string (header-number header)) "\t"
-               (or (header-subject header) "") "\t"
-               (or (header-from header) "") "\t"
-               (or (header-date header) "") "\t"
-               (or (header-id header) "") "\t"
-               (or (header-references header) "") "\t"
-               (int-to-string (or (header-chars header) 0)) "\t"
-               (int-to-string (or (header-lines header) 0)) "\t"
-               (or (header-xref header) "") "\n")))))
+       (setq header (pop headers))
+       (insert (int-to-string (mail-header-number header)) "\t"
+               (or (mail-header-subject header) "") "\t"
+               (or (mail-header-from header) "") "\t"
+               (or (mail-header-date header) "") "\t"
+               (or (mail-header-id header) "") "\t"
+               (or (mail-header-references header) "") "\t"
+               (int-to-string (or (mail-header-chars header) 0)) "\t"
+               (int-to-string (or (mail-header-lines header) 0)) "\t"
+               (if (mail-header-xref header) 
+                   (concat "Xref: " (mail-header-xref header) "\t")
+                 "") "\n")))))
 
 (defun nnvirtual-possibly-change-newsgroups (group regexp &optional check)
   (let ((inf t))
@@ -234,10 +285,13 @@ If the stream is opened, return T, otherwise return NIL."
                         (delq inf nnvirtual-group-alist)))
          (setq nnvirtual-current-mapping nil)
          (setq nnvirtual-current-group group)
-         (let ((newsrc gnus-newsrc-assoc))
+         (let ((newsrc gnus-newsrc-alist)
+               (virt-group (gnus-group-prefixed-name 
+                            nnvirtual-current-group '(nnvirtual ""))))
            (setq nnvirtual-current-groups nil)
            (while newsrc
              (and (string-match regexp (car (car newsrc)))
+                  (not (string= (car (car newsrc)) virt-group))
                   (setq nnvirtual-current-groups
                         (cons (car (car newsrc)) nnvirtual-current-groups)))
              (setq newsrc (cdr newsrc))))
@@ -257,10 +311,8 @@ If the stream is opened, return T, otherwise return NIL."
   (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)
         (offset 0)
-        reads unread igroup itotal itreads ireads)
+        reads unread igroup itotal ireads)
     ;; The virtual group doesn't exist. (?)
     (or info (error "No such group: %s" group))
     (setq nnvirtual-current-mapping nil)
@@ -272,36 +324,61 @@ If the stream is opened, return T, otherwise return NIL."
        ;; 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 itotal (1+ (- (cdr active) (car active))))
-       (if (setq ireads (nth 2 info))
-           (let ((itreads
-                  (if (atom (car ireads)) 
-                      (setq ireads (list (cons (car ireads) (cdr ireads))))
-                    (setq ireads (copy-alist ireads)))))
-             (if (< (cdr (car ireads)) (car active))
-                 (setq ireads (setq itreads (cdr ireads))))
-             (if (< (car (car ireads)) (car active)) 
-                 (setcar (car ireads) (1+ (car active))))
-             (while itreads
-               (setcar (car itreads)
-                       (+ (- (car (car itreads)) (car active)) offset))
-               (setcdr (car itreads)
-                       (+ (- (cdr (car itreads)) (car active)) offset))
-               (setq itreads (cdr itreads)))
-             (setq reads (nconc reads ireads))))
-       (setq offset (+ offset (1- itotal)))
-       (setq nnvirtual-current-mapping
-             (cons (list offset igroup (car active)) 
-                   nnvirtual-current-mapping))
+           (if (gnus-activate-group igroup)
+               (progn
+                 (gnus-get-unread-articles-in-group
+                  info (gnus-gethash igroup gnus-active-hashtb))
+                 (setq active (gnus-gethash igroup gnus-active-hashtb)))
+             (message "Couldn't open component group %s" igroup)))
+       (if (null active)
+           ()
+         ;; And then we do the mapping for this component group. If
+         ;; you feel tempted to cast your eyes to the soup below -
+         ;; don't.  It'll hurt your soul.  Suffice to say that it
+         ;; assigns ranges of nnvirtual article numbers to the
+         ;; different component groups.  To get the article number
+         ;; from the nnvirtual number, one does something like
+         ;; (+ (- number offset) (car active)), where `offset' is the
+         ;; slice the mess below assigns, and active is the lowest
+         ;; active article in the component group. 
+         (setq itotal (1+ (- (cdr active) (car active))))
+         (if (setq ireads (nth 2 info))
+             (let ((itreads
+                    (if (not (listp (cdr ireads)))
+                        (setq ireads (list (cons (car ireads) (cdr ireads))))
+                      (setq ireads (copy-alist ireads)))))
+               (if (< (or (and (numberp (car ireads)) (car ireads))
+                          (cdr (car ireads))) (car active))
+                   (setq ireads (setq itreads (cdr ireads))))
+               (if (and ireads (< (or (and (numberp (car ireads))
+                                           (car ireads))
+                                      (car (car ireads))) (car active)))
+                   (setcar (or (and (numberp (car ireads)) ireads)
+                               (car ireads)) (1+ (car active))))
+               (while itreads
+                 (setcar (or (and (numberp (car itreads)) itreads)
+                             (car itreads))
+                         (+ (max 
+                             1 (- (if (numberp (car itreads)) 
+                                      (car itreads)
+                                    (car (car itreads)))
+                                  (car active)))
+                            offset))
+                 (if (not (numberp (car itreads)))
+                     (setcdr (car itreads)
+                             (+ (- (cdr (car itreads)) (car active)) offset)))
+                 (setq itreads (cdr itreads)))
+               (setq reads (nconc reads ireads))))
+         (setq offset (+ offset (1- itotal)))
+         (setq nnvirtual-current-mapping
+               (cons (list offset igroup (car active)) 
+                     nnvirtual-current-mapping)))
        (setq groups (cdr groups))))
     (setq nnvirtual-current-mapping
          (nreverse nnvirtual-current-mapping))
-    (gnus-sethash group (cons 1 offset) gnus-active-hashtb)
+    ;; Set Gnus active info.
+    (gnus-sethash group (cons 1 (1- offset)) gnus-active-hashtb)
+    ;; Set Gnus read info.
     (setcar (nthcdr 2 info) reads)
 
     ;; Then we deal with the marks.
@@ -320,7 +397,8 @@ If the stream is opened, return T, otherwise return NIL."
                      (nconc (mapcar 
                              (lambda (art) 
                                (if (numberp art)
-                                   (+ (- art active) offset)
+                                   (if (< art active)
+                                       0 (+ (- art active) offset))
                                  (cons (+ (- (car art) active) offset)
                                        (cdr art))))
                              (cdr (assq (car m) marked)))
@@ -348,6 +426,7 @@ If the stream is opened, return T, otherwise return NIL."
        marks art-group group-alist g)
     (while mark-lists
       (setq marks (symbol-value (car (car mark-lists))))
+      ;; Find out what groups the mark belong to.
       (while marks
        (setq art-group (nnvirtual-art-group (car marks)))
        (if (setq g (assoc (car art-group) group-alist))
@@ -355,11 +434,18 @@ If the stream is opened, return T, otherwise return NIL."
          (setq group-alist (cons (list (car art-group) (cdr art-group)) 
                                  group-alist)))
        (setq marks (cdr marks)))
+      ;; The groups that don't have marks must have no marks. (Yup.)
+      (let ((groups nnvirtual-current-groups))
+       (while groups
+         (or (assoc (car groups) group-alist)
+             (setq group-alist (cons (list (car groups)) group-alist)))
+         (setq groups (cdr groups))))
+      ;; The we update the list of marks.
       (while group-alist
        (gnus-add-marked-articles 
         (car (car group-alist)) (cdr (car mark-lists)) 
         (cdr (car group-alist)) nil t)
-       (gnus-group-update-group (car (car group-alist)))
+       (gnus-group-update-group (car (car group-alist)) t)
        (setq group-alist (cdr group-alist)))
       (setq mark-lists (cdr mark-lists)))))
 
@@ -375,6 +461,14 @@ If the stream is opened, return T, otherwise return NIL."
            (cons (- (+ (car article) (nth 2 (car map))) offset)
                  (cdr article))))))
 
+(defun nnvirtual-catchup-group (group &optional server all)
+  (nnvirtual-possibly-change-newsgroups group server)
+  (let ((gnus-group-marked nnvirtual-current-groups)
+       (gnus-expert-user t))
+    (save-excursion
+      (set-buffer gnus-group-buffer)
+      (gnus-group-catchup-current nil all))))
+
 (provide 'nnvirtual)
 
 ;;; nnvirtual.el ends here