Add sanity check to not delete marks outside the active range.
[gnus] / lisp / nnvirtual.el
index acd4088..88ff852 100644 (file)
@@ -1,16 +1,19 @@
 ;;; nnvirtual.el --- virtual newsgroups access for Gnus
-;; Copyright (C) 1994,95,96 Free Software Foundation, Inc.
 
-;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
-;;     Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
+;; Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+;;   2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
+
+;; Author: David Moore <dmoore@ucsd.edu>
+;;     Lars Magne Ingebrigtsen <larsi@gnus.org>
+;;     Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
 ;; Keywords: news
 
 ;; This file is part of GNU Emacs.
 
-;; GNU Emacs is free software; you can redistribute it and/or modify
+;; GNU Emacs is free software: you can redistribute it and/or modify
 ;; it under the terms of the GNU General Public License as published by
-;; the Free Software Foundation; either version 2, or (at your option)
-;; any later version.
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
 
 ;; GNU Emacs is distributed in the hope that it will be useful,
 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
 ;; 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, Inc., 59 Temple Place - Suite 330,
-;; Boston, MA 02111-1307, USA.
+;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
 
 ;;; 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 'nnheader)
 (require 'gnus)
 (require 'nnoo)
+(require 'gnus-util)
+(require 'gnus-start)
+(require 'gnus-sum)
+(require 'gnus-msg)
 (eval-when-compile (require 'cl))
 
 (nnoo-declare nnvirtual)
 
-(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-always-rescan t
+  "If non-nil, always scan groups for unread articles when entering a group.
+If this variable is nil 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.")
+  "Regexp to match component groups.")
+
+(defvoo nnvirtual-component-groups nil
+  "Component group in this nnvirtual group.")
 
 \f
 
-(defconst nnvirtual-version "nnvirtual 1.0")
+(defconst nnvirtual-version "nnvirtual 1.1")
 
 (defvoo nnvirtual-current-group nil)
-(defvoo nnvirtual-component-groups nil)
-(defvoo nnvirtual-mapping 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-status-string "")
 
-(eval-and-compile
-  (autoload 'gnus-cache-articles-in-group "gnus-cache"))
+(autoload 'gnus-cache-articles-in-group "gnus-cache")
 
 \f
 
@@ -67,119 +89,136 @@ virtual group.")
 
 (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)
+    (with-current-buffer nntp-server-buffer
       (erase-buffer)
       (if (stringp (car articles))
          'headers
-       (let ((vbuf (nnheader-set-temp-buffer 
+       (let ((vbuf (nnheader-set-temp-buffer
                     (get-buffer-create " *virtual headers*")))
-             (unfetched (mapcar (lambda (g) (list g))
-                                nnvirtual-component-groups))
+             (carticles (nnvirtual-partition-sequence articles))
              (system-name (system-name))
-             cgroup article result prefix)
-         (while articles
-           (setq article (assq (pop articles) nnvirtual-mapping))
-           (when (and (setq cgroup (cadr article))
+             cgroup carticle article result prefix)
+         (while carticles
+           (setq cgroup (caar carticles))
+           (setq articles (cdar carticles))
+           (pop carticles)
+           (when (and articles
                       (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)))
+                      (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
+                     (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))
+           )
 
          ;; 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)
+             (with-current-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 (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)))))))
+  (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* ((gnus-override-method nil)
+                  (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
+             (with-current-buffer buffer
+               ;; We bind this here to avoid double decoding.
+               (let ((gnus-article-decode-hook nil))
+                 (gnus-request-article-this-buffer (cdr amap) cgroup)))
+           (gnus-request-article (cdr amap) cgroup))))))))
+
 
 (deffoo nnvirtual-open-server (server &optional defs)
   (unless (assq 'nnvirtual-component-regexp defs)
@@ -188,231 +227,577 @@ virtual group.")
   (nnoo-change-server 'nnvirtual server defs)
   (if nnvirtual-component-groups
       t
-    (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)))
-       (and (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-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)))))))
     (if (not nnvirtual-component-groups)
        (nnheader-report 'nnvirtual "No component groups: %s" server)
       t)))
 
-(deffoo nnvirtual-request-group (group &optional server dont-check)
+
+(deffoo nnvirtual-request-group (group &optional server dont-check info)
   (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)