From: rob@agate.berkeley.edu (Rob Robertson)
Newsgroups: gnu.emacs.gnus
Subject: XOVER support for GNUS
Date: 22 Jan 93 15:00:47
Organization: University of California, Bezerkeley
NNTP-Posting-Host: hanalei.berkeley.edu


I've cooked the the following hacks so that GNUS can support the XOVER
command that will be available in INN 1.3.  It dramatically speeds up
transfer times.  note these are just hacks, ideally nntp.el would probe
for XOVER and automagically use it if it found it.

To use it just set the following hook:

(setq nntp-server-hook
      (function
       (lambda ()
	 (require 'xover)
	 (progn
	   (fset 'gnus-retrieve-headers 'xover-retrieve-headers)
	   (setq gnus-Article-prepare-hook
		 (function
		  (lambda ()
		    (xover-get-xrefs-on-fly))))))))

and byte compile xover.el, and put it in yer lisp path.

note that since the Overview database doesn't have the Xref: line,
marking of Xrefs happens when you look at the article, so if you catch
up inside *Subject* mode or do a kill, the other cross references
won't be marked as read automagically...

rob
			  william robertson
			rob@agate.berkeley.edu

     "when i was your age, i walked 10 miles to school, unarmed."

;; hooks for GNUS to use the XOVER command to quickly download
;; headers.  
;;
;; $Header: /home/agate_a/rob/src/elisp/RCS/xover.el,v 1.2 1993/01/17 05:13:14 rob Exp $
;;
;; by William Robertson, rob@agate.berkeley.edu
;; based on code from nntp.el in the GNUS distribution,
;; by Masanobu UMEDA

;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY.  No author or distributor
;; accepts responsibility to anyone for the consequences of using it
;; or for whether it serves any particular purpose or works at all,
;; unless he says so in writing.  Refer to the GNU Emacs General Public
;; License for full details.

;; Everyone is granted permission to copy, modify and redistribute
;; GNU Emacs, but only under the conditions described in the
;; GNU Emacs General Public License.   A copy of this license is
;; supposed to have been given to you along with GNU Emacs so you
;; can know your rights and responsibilities.  It should be in a
;; file named COPYING.  Among other things, the copyright notice
;; and this notice must be preserved on all copies.

;; this file consists of two parts:

;; one a replacement for nntp-retrieve-headers that uses 
;; XOVER to download the headers in the group

;; and since the overview database doesn't normally contain 
;; the Xref data, we have to get it when we actually look at 
;; articles.  'xover-get-xrefs-on-fly does that when called 
;; from 'gnus-Article-prepare-hook.

;; this file wants to be byte-compiled

;; i use the following hook to load xover.elc in.  in the future 
;; i'd like to write sumpin that checks the server and sees if it
;; supports XOVER and does the right thing.
;;
;;(setq nntp-server-hook
;;      (function
;;       (lambda ()
;;	 (require 'xover)
;;	 (if (string-match "^agate" nntp-server-name)
;;	     (progn
;;	       (fset 'gnus-retrieve-headers 'xover-retrieve-headers)
;;	       (setq gnus-Article-prepare-hook
;;		     (function
;;		      (lambda ()
;;			(xover-get-xrefs-on-fly)))))))))
(provide 'xover)

;;(defun xover-test-server ()
;;  "Test if a server supports the XOVER command
;;To be called before we do a GROUP command"
;;  (progn
;;    (nntp-send-command "^[2345].*$" "XOVER" "1-1")
;;    (string-match "^[4][0-9][0-9][ \t]" nntp-status-string)))

(defmacro nntp-set-header-xref (header xref)
  "Set article xref of HEADER to xref."
  (` (aset (, header) 3 (, xref))))

;;	to be called by gnus-Article-prepare-hook so that it
;;	adds the xrefs in to the header list of the articles 
;;	we've looked at, so that we won't get shown them again.

(defun xover-get-xrefs-on-fly ()
  "Get Xrefs as we read the article, and place a reference in HEADER"
  (save-restriction
    (goto-char (point-min))
    (search-forward "\n\n" nil 'move)
    (narrow-to-region (point-min) (point))
    (goto-char (point-min))
    (if (re-search-forward "\nXref:[ \t]*\\(.*\\)\n" nil t) 
	(nntp-set-header-xref gnus-current-headers
			      (buffer-substring 
			       (match-beginning 1) (match-end 1))))))

(defmacro xover-sequence-to-range (sequence)
  "Convert a SEQUENCE to nntp ranges"
  (` (let ((first (car sequence))
	(last (car sequence)))
    (while (and sequence (or (= last (car sequence)) ;Omit duplicated number
			     (= (1+ last) (car sequence)))) ;Still in sequence
      (setq last (car sequence))
      (setq sequence (cdr sequence)))
    (if (= first last)
	(int-to-string first)
      (concat (int-to-string first) "-" 
				 (int-to-string last))))))

;;	the function 'xover-retrieve-headers is a replacement for
;;	nntp-retrieve-headers, and uses the XOVER command to get the
;;	headers.  the syntax of the XOVER command is 
;;
;;		XOVER RANGE
;;
;;	where 
;;
;;		RANGE :  [0-9]+ | [0-9]+-[0-9]+
;;
;;	it returns on line per available article:
;;
;;	messno\tsubject\tfrom\tdate\tmsg-id\trefs\tlines\tbytes\n
;;
;;	the reply is terminated by a .\r\n

(defun xover-retrieve-headers (sequence)
  "Return list of article headers specified by SEQUENCE of article id.
The format of list is
 `([NUMBER SUBJECT FROM XREF LINES DATE MESSAGE-ID REFERENCES] ...)'.
Reader macros for the vector are defined as `nntp-header-FIELD'.
Writer macros for the vector are defined as `nntp-set-header-FIELD'.
News group must be selected before calling me."
  (save-excursion
    (set-buffer nntp-server-buffer)
    (erase-buffer)
    (let ((last-point (point-min))
	  (number (length sequence))
	  (received 0)
	  (count 0)
	  (headers nil)			;Result list.
	  (article 0)
	  (subject nil)
	  (message-id)
	  (from nil)
	  (xref nil)
	  (lines 0)
	  (date nil)
	  (references nil)
	  (spin '(?- ?\ ?| ?/ )) 
	  (index 0))
      ;; Send XOVER command.
      (while sequence
	(nntp-send-strings-to-server "XOVER" (xover-sequence-to-range sequence))
	(setq count (1+ count))
	;; Every 400 header requests we have to read stream in order
	;;  to avoid deadlock.
	(if (or (null sequence)		;All requests have been sent.
		(zerop (% count nntp-maximum-request)))
	    (progn
	      (accept-process-output)
	      (while (progn
		       (goto-char last-point)
		       ;; Count replies.
		       (while (re-search-forward "^\\.\r$" nil t)
			 (setq received (1+ received)))
		       (setq last-point (point))
		       (< received count))
		;; If number of headers is greater than 100, give
		;;  informative messages.
		(and (numberp nntp-large-newsgroup)
		     (> number nntp-large-newsgroup)
		     (message "NNTP: Transfering %c" (nth index spin))
		     (setq index (% (1+ index) (length spin))))
		(nntp-accept-response))
	      ))
	)
      ;; Wait for text of last command.
      (goto-char (point-max))
      (re-search-backward "^[0-9]" nil t)
      (if (looking-at "^[23]")
	  (while (progn
		   (goto-char (- (point-max) 3))
		   (not (looking-at "^\\.\r$")))
	    (nntp-accept-response)))
      (and (numberp nntp-large-newsgroup)
	   (> number nntp-large-newsgroup)
	   (message "NNTP: 100%% of headers received."))
      ;; Now all of replies are received.
      (setq received number)
      (and (numberp nntp-large-newsgroup)
	   (> number nntp-large-newsgroup)
	   (message "NNTP: Parsing headers..."))
      ;; Then examines replies.
      (goto-char (point-min))
      (while (not (eobp))
	(if (looking-at "\\([0-9]+\\)\t\\(.*\\)\t\\(.*\\)\t\\(.*\\)\t\\(.*\\)\t\\(.*\\)\t\\(.*\\)\t\\(.*\\)\r$") 
	    (progn
	      (setq article
		    (string-to-int
		     (buffer-substring (match-beginning 1) (match-end 1))))
	      (setq subject
		    (buffer-substring (match-beginning 2) (match-end 2)))
	      (setq from
		    (buffer-substring (match-beginning 3) (match-end 3)))
	      (setq date
		    (buffer-substring (match-beginning 4) (match-end 4)))
	      (setq message-id
		    (buffer-substring (match-beginning 5) (match-end 5)))
	      (setq references
		    (buffer-substring (match-beginning 6) (match-end 6)))
	      (setq lines (string-to-int
			   (buffer-substring (match-beginning 8) (match-end 8))))
	      ;; Set default value.
	      (setq xref nil)
	      ;; Finished to parse one header.
	      (if (string= subject "")
		  (setq subject "(None)"))
	      (if (string= from "")
		  (setq from "(Unknown User)"))
	      (setq headers
		    (cons (vector article subject from
				  xref lines date
				  message-id references) headers))
	      (setq received (1- received))
	      (and (numberp nntp-large-newsgroup)
		   (> number nntp-large-newsgroup)
		   (zerop (% received 20))
		   (message "NNTP: Parsing headers... %d%%"
			    (/ (* received 100) number)))))
	(forward-line 1))
      (and (numberp nntp-large-newsgroup)
	   (> number nntp-large-newsgroup)
	   (message "NNTP: Parsing headers... done"))
      (nreverse headers))))