home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Source Code 1993 July / THE_SOURCE_CODE_CD_ROM.iso / languages / elisp / interfaces / nnspool.el < prev    next >
Encoding:
Text File  |  1992-11-25  |  20.4 KB  |  589 lines

  1. ; Path: hal.com!olivea!uunet!hotmomma!sdb
  2. ; From: sdb@ssr.com (Scott Ballantyne)
  3. ; Newsgroups: gnu.emacs.sources
  4. ; Subject: NEW nnspool.el (uses CNews nov database)
  5. ; Date: 17 Nov 92 00:25:20 GMT
  6. ; Organization: ScotSoft Research
  7. ; One of the developers of CNews (Geoff Collyer) has implemented a
  8. ; common newsreader database which will be standard with all future
  9. ; CNews distributions. On the whole, this is a great improvement,
  10. ; certainly better than groveling through the spool or developing yet
  11. ; another newsreader database implementation.
  12. ; Below the 'snip' line, is a version of nnspool.el which causes emacs
  13. ; to default to the use of the overview database, but remains compatible
  14. ; with all news systems (B-News, and CNews without the news database).
  15. ; If this database is not found, then it will try to use the gnusgethdrs
  16. ; program (an earlier speedup to nnspool) and if that is not found, will
  17. ; use the original (quite slow) method.
  18. ; I'm including the entire file below, the diffs were almost the same
  19. ; size. If anyone wants the source to gnusgethdrs.c, send me mail.
  20. ; Enjoy,
  21. ; Scott
  22. ; ---
  23. ; sdb@ssr.com
  24. ; ---------------------------------snip---------------------------------
  25. ;;; Spool access using NNTP for GNU Emacs
  26. ;; Copyright (C) 1988, 1989 Fujitsu Laboratories LTD.
  27. ;; Copyright (C) 1988, 1989, 1990 Masanobu UMEDA
  28. ;; $Header: nnspool.el,v 1.10 90/03/23 13:25:25 umerin Locked $
  29. ;;
  30. ;; LCD Archive Entry:
  31. ;; nnspool|Scott Ballantyne|sdb@ssr.com|
  32. ;; Spool access using NNTP for GNU Emacs.|
  33. ;; 92-11-17||~/interfaces/nnspool.el.Z|
  34. ;;
  35. ;; Modifications Copright (C) 1991, 1992 Scott Ballantyne, and
  36. ;; released under the same conditions as the original.
  37. ;;
  38. ;; Newest modification: Use new .overview databse in new C-News version,
  39. ;; if no .overview file found, try header frobnicator and then original
  40. ;; method (in that order).
  41. ;;
  42. ;; Earlier modifications:
  43. ;; nnspool-replace-chars-in-string: Slightly faster than original
  44. ;; nnspool-find-article-by-message-id: Uses a database lookup - much faster.
  45. ;; nnspool-retrieve-headers: Uses a C program to scan the spool, this
  46. ;; avoids having to read in the entire article just to get at the headers.
  47. ;; Other changes make this function considerably faster.  
  48. ;;
  49. ;; NOTE: These modifications were tested under C-News, and in fact the
  50. ;;       c program 'gnusgethdrs.c' requires libcnews.a and some of the
  51. ;;       C-News headers, so it is not likely to work if you are using
  52. ;;       B-News.  The old functions are still here, and you can still
  53. ;;       have them called by setting the values of certain variables
  54. ;;       appropriately. See the documentation for:
  55. ;;
  56. ;; nnspool-dbm-program
  57. ;; nnspool-dbm-args
  58. ;; nnspool-header-frobnicator
  59. ;; 
  60. ;; to see how these variables affect the operation of gnus.
  61. ;;
  62. ;; Scott (sdb@ssr.com)
  63.  
  64. ;; GNU Emacs is distributed in the hope that it will be useful,
  65. ;; but WITHOUT ANY WARRANTY.  No author or distributor
  66. ;; accepts responsibility to anyone for the consequences of using it
  67. ;; or for whether it serves any particular purpose or works at all,
  68. ;; unless he says so in writing.  Refer to the GNU Emacs General Public
  69. ;; License for full details.
  70.  
  71. ;; Everyone is granted permission to copy, modify and redistribute
  72. ;; GNU Emacs, but only under the conditions described in the
  73. ;; GNU Emacs General Public License.   A copy of this license is
  74. ;; supposed to have been given to you along with GNU Emacs so you
  75. ;; can know your rights and responsibilities.  It should be in a
  76. ;; file named COPYING.  Among other things, the copyright notice
  77. ;; and this notice must be preserved on all copies.
  78.  
  79. (provide 'nnspool)
  80. (require 'nntp)
  81.  
  82. (defvar nnspool-inews-program news-inews-program
  83.   "*Program to post news.")
  84.  
  85. (defvar nnspool-inews-switches '("-h")
  86.   "*Switches for nnspool-request-post to pass to `inews' for posting news.")
  87.  
  88. (defvar nnspool-spool-directory news-path
  89.   "*Local news spool directory.")
  90.  
  91. (defvar nnspool-active-file "/usr/lib/news/active"
  92.   "*Local news active file.")
  93.  
  94. (defvar nnspool-history-file "/usr/lib/news/history"
  95.   "*Local news history file.")
  96.  
  97. (defvar nnspool-dbm-program "/usr/lib/newsbin/dbz"
  98.   "*Program used to search history database")
  99.  
  100. (defvar nnspool-dbm-args "-ix"
  101.   "*Arguments to pass to nnspool-dbm-program")
  102.  
  103. (defvar nnspool-header-frobnicator "/usr/local/bin/gnusgethdrs"
  104.   "*Program to retrieve headers from spool.")
  105.  
  106.  
  107. (defconst nnspool-version "NNSPOOL 1.2x"
  108.   "Version numbers of this version of NNSPOOL.")
  109.  
  110. (defvar nnspool-current-directory nil
  111.   "Current news group directory.")
  112.  
  113. ;;;
  114. ;;; Replacement of Extended Command for retrieving many headers.
  115. ;;;
  116.  
  117. (defun nnspool-retrieve-headers (sequence)
  118.   "Return list of article headers specified by SEQUENCE of article numbers.
  119. The format of list is
  120.  `([NUMBER SUBJECT FROM XREF LINES DATE MESSAGE-ID REFERENCES] ...)'.
  121. Reader macros for the vector are defined as `nntp-header-FIELD'.
  122. Writer macros for the vector are defined as `nntp-set-header-FIELD'.
  123. News group must be selected before calling me."
  124.   (let ((overviewfile (concat (file-name-as-directory nnspool-current-directory) ".overview"))
  125.     (number (length sequence))
  126.     (count 0)
  127.     (headers nil)
  128.     (article 0)
  129.     (subject nil)
  130.     (message-id nil)
  131.     (from nil)
  132.     (xref nil)
  133.     (lines 0)
  134.     (date nil)
  135.     (references nil))
  136.  
  137.     ;; If no .overview file found, use try to use the header frobnicator, if
  138.     ;; that's not available, revert to the original approach.
  139.     ;; Hopefully this will allow this module to remain compatible with all
  140.     ;; news systems.
  141.     ;; One optimization which is useful: For single article requests,
  142.     ;; *DO* use one of the older methods, since that is less overhead than
  143.     ;; retrieving the entire .overview file
  144.     (if (or (= 1 number) (not (file-readable-p overviewfile)))
  145.     (if (null nnspool-header-frobnicator)
  146.         (nnspool-slowly-retrieve-headers)
  147.       (nnspool-use-header-frobnicator))
  148.       (save-excursion
  149.     (set-buffer nntp-server-buffer)
  150.     (erase-buffer)
  151.     (message "Retrieving headers...")
  152.     (insert-file-contents overviewfile)
  153.     (message "Searching for headers...")
  154.     ;;
  155.     ;; Delete all lines that do not refer to articles we want.
  156.     ;; This is a performance win for the most common case, where someone is reading
  157.     ;; a big gob of articles in the 'middle' of the newsgroup.
  158.     ;;
  159.     (delete-region (point-min)
  160.                (progn (re-search-forward
  161.                    (concat "^" (int-to-string (apply 'min sequence)) "\t") nil t)
  162.                   (beginning-of-line) (point)))
  163.     
  164.     (goto-char (point-max))
  165.     (delete-region (progn (re-search-backward (concat "^" (int-to-string (apply 'max sequence)) "\t") nil t)
  166.                   (forward-line 1) (beginning-of-line) (point))
  167.                (point-max))
  168.     (goto-char (point-min))
  169.     (while (not (eobp))
  170.       (if (memq (string-to-int (buffer-substring (point) (save-excursion (forward-word 1) (point)))) sequence)
  171.           (forward-line 1)
  172.         (delete-region (point) (1+ (save-excursion (end-of-line) (point))))))
  173.     ;;
  174.     ;; Now stuff articles into vector
  175.     ;;
  176.     ;; overview file has (separated by tabs)
  177.     ;; number subj author date msgid ref bytecount linecount
  178.     ;; we want
  179.     ;; NUMBER SUBJ authr  xref lines date msgid references
  180.     (goto-char (point-min))
  181.     (while (not (eobp))
  182.       (beginning-of-line)        ; make sure we are start of line
  183.       (setq position (point))
  184.       (setq article  (string-to-int (buffer-substring position (progn (search-forward "\t") (1- (point))))))
  185.       (setq position (point))
  186.       (setq subject (buffer-substring position (progn (search-forward "\t") (1- (point)))))
  187.       (setq position (point))
  188.       (setq from (buffer-substring position (progn (search-forward "\t") (1- (point)))))
  189.       (setq position (point))
  190.       (setq date (buffer-substring position (progn (search-forward "\t") (1- (point)))))
  191.       (setq position (point))
  192.       (setq message-id (buffer-substring position (progn (search-forward "\t") (1- (point)))))
  193.       (setq position (point))
  194.       (if (/= (following-char) '?\t)
  195.           (setq references (buffer-substring position (progn (search-forward "\t") (1- (point)))))
  196.         (setq references nil)
  197.         (forward-char 1))
  198.       (search-forward "\t")        ;skip byte-count for now
  199.       (if (memq (following-char) '(?\t ?\n))
  200.           (setq lines 0)
  201.         (setq lines (string-to-int (buffer-substring (point) (progn (forward-word 1) (point))))))
  202.       (setq headers (cons (vector article subject from xref lines date message-id references) headers))
  203.       (setq count (1+ count))
  204.       (forward-line 1)
  205.       (and (numberp nntp-large-newsgroup)
  206.            (> number nntp-large-newsgroup)
  207.            (zerop (% count 20))
  208.            (message "%d%% done." (/ (* count 100) number))))
  209.       (message "")
  210.       (and (numberp nntp-large-newsgroup)
  211.            (> number nntp-large-newsgroup)
  212.            (message "100%% of headers received."))
  213.       (nreverse headers)))))
  214.  
  215.       
  216. (defun nnspool-use-header-frobnicator ()
  217.   "Process articles by calling an external frobnicator program"
  218.   (save-excursion
  219.     (set-buffer nntp-server-buffer)
  220.     (erase-buffer)
  221.     (let ((process-connection-type nil) ;use a pipe
  222.       (number (length sequence))
  223.       (count 0)
  224.       (headers nil))     ; result list
  225.       (message "Searching %s for headers." nnspool-current-directory)
  226.       (apply 'call-process nnspool-header-frobnicator nil t nil
  227.          nnspool-current-directory
  228.          (mapcar 'int-to-string sequence))
  229.       (goto-char (point-min))
  230.       (while (not (eobp))
  231.     (let ((hdrs nil)
  232.           (i 0))
  233.       (while (< i 8)
  234.         (setq hdrs
  235.           (cons (cond ((= (following-char) 10) nil)
  236.                   ((or (= 3 i) (= 7 i))
  237.                    (string-to-int (buffer-substring
  238.                            (point) (save-excursion (end-of-line) (point)))))
  239.                   (t (buffer-substring
  240.                   (point) (save-excursion (end-of-line) (point))))) hdrs))
  241.         (forward-line 1)
  242.         (setq i (1+ i)))
  243.       (setq headers (cons (apply 'vector hdrs) headers)))
  244.     (setq count (1+ count))
  245.     (and (numberp nntp-large-newsgroup)
  246.          (> number nntp-large-newsgroup)
  247.          (zerop (% count 20))
  248.          (message "NNSPOOL: %d%% of headers received."
  249.               (/ (* count 100) number))))
  250.       (message "")    ; needed to remove Searching
  251.       (and (numberp nntp-large-newsgroup)
  252.        (> number nntp-large-newsgroup)
  253.        (message "NNSPOOL: 100%% of headers received."))
  254.       (nreverse headers))))
  255.  
  256. (defun nnspool-slowly-retrieve-headers ()
  257.   "This is the original form of nnspool-retrieve-headers.
  258. It is called when the variable nnspool-dbm-program is nil.
  259. For details, see the documentation for nnspool-retrieve-headers."
  260.   (save-excursion
  261.     (set-buffer nntp-server-buffer)
  262.     ;;(erase-buffer)
  263.     (let ((file nil))
  264.       (while sequence
  265.     ;;(nntp-send-strings-to-server "HEAD" (car sequence))
  266.     (setq article (car sequence))
  267.     (setq file
  268.           (concat nnspool-current-directory (prin1-to-string article)))
  269.     (if (and (file-exists-p file)
  270.          (not (file-directory-p file)))
  271.         (progn
  272.           (erase-buffer)
  273.           (insert-file-contents file)
  274.           ;; Make message body invisible.
  275.           (goto-char (point-min))
  276.           (search-forward "\n\n" nil 'move)
  277.           (narrow-to-region (point-min) (point))
  278.           ;; Fold continuation lines.
  279.           (goto-char (point-min))
  280.           (while (re-search-forward "\\(\r?\n[ \t]+\\)+" nil t)
  281.         (replace-match " " t t))
  282.           ;; Make it possible to search for `\nFIELD'.
  283.           (goto-char (point-min))
  284.           (insert "\n")
  285.           ;; Extract From:
  286.           (goto-char (point-min))
  287.           (if (search-forward "\nFrom: " nil t)
  288.           (setq from (buffer-substring
  289.                   (point)
  290.                   (save-excursion (end-of-line) (point))))
  291.         (setq from "(Unknown User)"))
  292.           ;; Extract Subject:
  293.           (goto-char (point-min))
  294.           (if (search-forward "\nSubject: " nil t)
  295.           (setq subject (buffer-substring
  296.                  (point)
  297.                  (save-excursion (end-of-line) (point))))
  298.         (setq subject "(None)"))
  299.           ;; Extract Message-ID:
  300.           (goto-char (point-min))
  301.           (if (search-forward "\nMessage-ID: " nil t)
  302.           (setq message-id (buffer-substring
  303.                     (point)
  304.                     (save-excursion (end-of-line) (point))))
  305.         (setq message-id nil))
  306.           ;; Extract Date:
  307.           (goto-char (point-min))
  308.           (if (search-forward "\nDate: " nil t)
  309.           (setq date (buffer-substring
  310.                   (point)
  311.                   (save-excursion (end-of-line) (point))))
  312.         (setq date nil))
  313.           ;; Extract Lines:
  314.           (goto-char (point-min))
  315.           (if (search-forward "\nLines: " nil t)
  316.           (setq lines (string-to-int
  317.                    (buffer-substring
  318.                 (point)
  319.                 (save-excursion (end-of-line) (point)))))
  320.         (setq lines 0))
  321.           ;; Extract Xref:
  322.           (goto-char (point-min))
  323.           (if (search-forward "\nXref: " nil t)
  324.           (setq xref (buffer-substring
  325.                   (point)
  326.                   (save-excursion (end-of-line) (point))))
  327.         (setq xref nil))
  328.           ;; Extract References:
  329.           (goto-char (point-min))
  330.           (if (search-forward "\nReferences: " nil t)
  331.           (setq references (buffer-substring
  332.                     (point)
  333.                     (save-excursion (end-of-line) (point))))
  334.         (setq references nil))
  335.           (setq headers
  336.             (cons (vector article subject from
  337.                   xref lines date
  338.                   message-id references) headers))
  339.           ))
  340.     (setq sequence (cdr sequence))
  341.     (setq count (1+ count))
  342.     (and (numberp nntp-large-newsgroup)
  343.          (> number nntp-large-newsgroup)
  344.          (zerop (% count 20))
  345.          (message "NNSPOOL: %d%% of headers received."
  346.               (/ (* count 100) number)))
  347.     )
  348.       (and (numberp nntp-large-newsgroup)
  349.        (> number nntp-large-newsgroup)
  350.        (message "NNSPOOL: 100%% of headers received."))
  351.       (nreverse headers))))
  352.  
  353.  
  354. ;;;
  355. ;;; Replacement of NNTP Raw Interface.
  356. ;;;
  357.  
  358. (defun nnspool-open-server (host &optional service)
  359.   "Open news server on HOST.
  360. If HOST is nil, use value of environment variable `NNTPSERVER'.
  361. If optional argument SERVICE is non-nil, open by the service name."
  362.   (let ((host (or host (getenv "NNTPSERVER")))
  363.     (status nil))
  364.     (setq nntp-status-string "")
  365.     (cond ((and (file-directory-p nnspool-spool-directory)
  366.         (file-exists-p nnspool-active-file)
  367.         (string-equal host (system-name)))
  368.        (setq status (nnspool-open-server-internal host service)))
  369.       ((string-equal host (system-name))
  370.        (setq nntp-status-string
  371.          (format "%s has no news spool.  Goodbye." host)))
  372.       ((null host)
  373.        (setq nntp-status-string "NNTP server is not specified."))
  374.       (t
  375.        (setq nntp-status-string
  376.          (format "NNSPOOL: cannot talk to %s." host)))
  377.       )
  378.     status
  379.     ))
  380.  
  381. (defun nnspool-close-server ()
  382.   "Close news server."
  383.   (nnspool-close-server-internal))
  384.  
  385. (fset 'nnspool-request-quit (symbol-function 'nnspool-close-server))
  386.  
  387. (defun nnspool-server-opened ()
  388.   "Return server process status, T or NIL.
  389. If the stream is opened, return T, otherwise return NIL."
  390.   (and nntp-server-buffer
  391.        (get-buffer nntp-server-buffer)))
  392.  
  393. (defun nnspool-status-message ()
  394.   "Return server status response as string."
  395.   nntp-status-string
  396.   )
  397.  
  398. (defun nnspool-request-article (id)
  399.   "Select article by message ID (or number)."
  400.   (let ((file (if (stringp id)
  401.           (nnspool-find-article-by-message-id id)
  402.         (concat nnspool-current-directory (prin1-to-string id)))))
  403.     (if (and (stringp file)
  404.          (file-exists-p file)
  405.          (not (file-directory-p file)))
  406.     (save-excursion
  407.       (nnspool-find-file file)))
  408.     ))
  409.  
  410. (defun nnspool-request-body (id)
  411.   "Select article body by message ID (or number)."
  412.   (if (nnspool-request-article id)
  413.       (save-excursion
  414.     (set-buffer nntp-server-buffer)
  415.     (goto-char (point-min))
  416.     (if (search-forward "\n\n" nil t)
  417.         (delete-region (point-min) (point)))
  418.     t
  419.     )
  420.     ))
  421.  
  422. (defun nnspool-request-head (id)
  423.   "Select article head by message ID (or number)."
  424.   (if (nnspool-request-article id)
  425.       (save-excursion
  426.     (set-buffer nntp-server-buffer)
  427.     (goto-char (point-min))
  428.     (if (search-forward "\n\n" nil t)
  429.         (delete-region (1- (point)) (point-max)))
  430.     t
  431.     )
  432.     ))
  433.  
  434. (defun nnspool-request-stat (id)
  435.   "Select article by message ID (or number)."
  436.   (error "NNSPOOL: STAT is not implemented."))
  437.  
  438. (defun nnspool-request-group (group)
  439.   "Select news GROUP."
  440.   (let ((pathname (nnspool-article-pathname
  441.            (nnspool-replace-chars-in-string group ?. ?/))))
  442.     (if (file-directory-p pathname)
  443.     (setq nnspool-current-directory pathname))
  444.     ))
  445.  
  446. (defun nnspool-request-list ()
  447.   "List valid newsgoups."
  448.   (save-excursion
  449.     (nnspool-find-file nnspool-active-file)))
  450.  
  451. (defun nnspool-request-last ()
  452.   "Set current article pointer to the previous article
  453. in the current news group."
  454.   (error "NNSPOOL: LAST is not implemented."))
  455.  
  456. (defun nnspool-request-next ()
  457.   "Advance current article pointer."
  458.   (error "NNSPOOL: NEXT is not implemented."))
  459.  
  460. (defun nnspool-request-post ()
  461.   "Post a new news in current buffer."
  462.   (save-excursion
  463.     ;; We have to work in the server buffer because of NEmacs hack.
  464.     (copy-to-buffer nntp-server-buffer (point-min) (point-max))
  465.     (set-buffer nntp-server-buffer)
  466.     (apply 'call-process-region
  467.        (point-min) (point-max)
  468.        nnspool-inews-program 'delete t nil nnspool-inews-switches)
  469.     (prog1
  470.     (or (zerop (buffer-size))
  471.         ;; If inews returns strings, it must be error message 
  472.         ;;  unless SPOOLNEWS is defined.  
  473.         ;; This condition is very weak, but there is no good rule 
  474.         ;;  identifying errors when SPOOLNEWS is defined.  
  475.         ;; Suggested by ohm@kaba.junet.
  476.         (string-match "spooled" (buffer-string)))
  477.       ;; Make status message by unfolding lines.
  478.       (subst-char-in-region (point-min) (point-max) ?\n ?\\ 'noundo)
  479.       (setq nntp-status-string (buffer-string))
  480.       (erase-buffer))
  481.     ))
  482.  
  483.  
  484. ;;;
  485. ;;; Replacement of Low-Level Interface to NNTP Server.
  486. ;;; 
  487.  
  488. (defun nnspool-open-server-internal (host &optional service)
  489.   "Open connection to news server on HOST by SERVICE (default is nntp)."
  490.   (save-excursion
  491.     (if (not (string-equal host (system-name)))
  492.     (error "NNSPOOL: cannot talk to %s." host))
  493.     ;; Initialize communication buffer.
  494.     (setq nntp-server-buffer (get-buffer-create " *nntpd*"))
  495.     (set-buffer nntp-server-buffer)
  496.     (buffer-flush-undo (current-buffer))
  497.     (erase-buffer)
  498.     (kill-all-local-variables)
  499.     (setq case-fold-search t)        ;Should ignore case.
  500.     (setq nntp-server-process nil)
  501.     (setq nntp-server-name host)
  502.     ;; It is possible to change kanji-fileio-code in this hook.
  503.     (run-hooks 'nntp-server-hook)
  504.     t
  505.     ))
  506.  
  507. (defun nnspool-close-server-internal ()
  508.   "Close connection to news server."
  509.   (if (get-file-buffer nnspool-history-file)
  510.       (kill-buffer (get-file-buffer nnspool-history-file)))
  511.   (if nntp-server-buffer
  512.       (kill-buffer nntp-server-buffer))
  513.   (setq nntp-server-buffer nil)
  514.   (setq nntp-server-process nil))
  515.  
  516. ;; This uses a fast dbm lookup, instead of groveling through the
  517. ;; history file itself.
  518. (defun nnspool-find-article-by-message-id (id)
  519.   "Return full pathname of an article identified by message-ID."
  520.     (if (null nnspool-dbm-program)
  521.     (nnspool-slowly-find-article-by-message-id id)
  522.       (save-excursion
  523.     (set-buffer nntp-server-buffer)
  524.     (erase-buffer)
  525.     (insert id)
  526.     (call-process-region (point-min) (point-max)
  527.                  nnspool-dbm-program t t nil
  528.                  nnspool-dbm-args
  529.                  nnspool-history-file)
  530.     (goto-char (point-max))
  531.     ; Note that this is based on the C-News format history file.
  532.     ; I don't think (but dunno) if B-Bews used the same format.
  533.     (if (re-search-backward "[ \t]\\([a-z.]+/[0-9]+$\\)" nil t)
  534.         (concat (file-name-as-directory nnspool-spool-directory)
  535.              (nnspool-replace-chars-in-string
  536.               (buffer-substring (match-beginning 1) (match-end 1)) ?. ?/))))))
  537.  
  538. ;; Original version                
  539. (defun nnspool-slowly-find-article-by-message-id (id)
  540.   "Slowly return full pathname of an artilce identified by message-ID."
  541.   (save-excursion
  542.     (let ((buffer (get-file-buffer nnspool-history-file)))
  543.       (if buffer
  544.       (set-buffer buffer)
  545.     ;; Finding history file may take lots of time.
  546.     (message "Reading history file...")
  547.     (set-buffer (find-file-noselect nnspool-history-file))
  548.     (message "Reading history file... done")))
  549.     ;; Search from end of the file. I think this is much faster than
  550.     ;; do from the beginning of the file.
  551.     (goto-char (point-max))
  552.     (if (re-search-backward
  553.      (concat "^" (regexp-quote id)
  554.          "[ \t].*[ \t]\\([^ \t/]+\\)/\\([0-9]+\\)[ \t]*$") nil t)
  555.     (let ((group (buffer-substring (match-beginning 1) (match-end 1)))
  556.           (number (buffer-substring (match-beginning 2) (match-end 2))))
  557.       (concat (nnspool-article-pathname
  558.            (nnspool-replace-chars-in-string group ?. ?/))
  559.           number))
  560.       )))
  561.  
  562. (defun nnspool-find-file (file)
  563.   "Insert FILE in server buffer safely."
  564.   (set-buffer nntp-server-buffer)
  565.   (erase-buffer)
  566.   (condition-case ()
  567.       (progn (insert-file-contents file) t)
  568.     (file-error nil)
  569.     ))
  570.  
  571. (defun nnspool-article-pathname (group)
  572.   "Make pathname for GROUP."
  573.   (concat (file-name-as-directory nnspool-spool-directory) group "/"))
  574.  
  575. (defun nnspool-replace-chars-in-string (string from to)
  576.   "Replace characters in STRING from FROM to TO."
  577.   (let ((string (copy-sequence string))
  578.     (len (length string)))
  579.     (while (> len 0)
  580.       (setq len (1- len))
  581.       (if (= (aref string len) from)
  582.       (aset string len to)))
  583.     string))
  584.