home *** CD-ROM | disk | FTP | other *** search
/ NetNews Usenet Archive 1992 #27 / NN_1992_27.iso / spool / gnu / emacs / sources / 801 < prev    next >
Encoding:
Text File  |  1992-11-17  |  20.3 KB  |  592 lines

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