nnspool.el 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457
  1. ;;; nnspool.el --- spool access for GNU Emacs
  2. ;; Copyright (C) 1988-1990, 1993-1998, 2000-2012
  3. ;; Free Software Foundation, Inc.
  4. ;; Author: Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
  5. ;; Lars Magne Ingebrigtsen <larsi@gnus.org>
  6. ;; Keywords: news
  7. ;; This file is part of GNU Emacs.
  8. ;; GNU Emacs is free software: you can redistribute it and/or modify
  9. ;; it under the terms of the GNU General Public License as published by
  10. ;; the Free Software Foundation, either version 3 of the License, or
  11. ;; (at your option) any later version.
  12. ;; GNU Emacs is distributed in the hope that it will be useful,
  13. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. ;; GNU General Public License for more details.
  16. ;; You should have received a copy of the GNU General Public License
  17. ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
  18. ;;; Commentary:
  19. ;;; Code:
  20. (require 'nnheader)
  21. (require 'nntp)
  22. (require 'nnoo)
  23. (eval-when-compile (require 'cl))
  24. (nnoo-declare nnspool)
  25. (defvoo nnspool-inews-program news-inews-program
  26. "Program to post news.
  27. This is most commonly `inews' or `injnews'.")
  28. (defvoo nnspool-inews-switches '("-h" "-S")
  29. "Switches for nnspool-request-post to pass to `inews' for posting news.
  30. If you are using Cnews, you probably should set this variable to nil.")
  31. (defvoo nnspool-spool-directory
  32. (file-name-as-directory (if (boundp 'news-directory)
  33. (symbol-value 'news-directory)
  34. news-path))
  35. "Local news spool directory.")
  36. (defvoo nnspool-nov-directory (concat nnspool-spool-directory "over.view/")
  37. "Local news nov directory.")
  38. (defvoo nnspool-lib-dir
  39. (if (file-exists-p "/usr/lib/news/active")
  40. "/usr/lib/news/"
  41. "/var/lib/news/")
  42. "Where the local news library files are stored.")
  43. (defvoo nnspool-active-file (concat nnspool-lib-dir "active")
  44. "Local news active file.")
  45. (defvoo nnspool-newsgroups-file (concat nnspool-lib-dir "newsgroups")
  46. "Local news newsgroups file.")
  47. (defvoo nnspool-distributions-file (concat nnspool-lib-dir "distribs.pat")
  48. "Local news distributions file.")
  49. (defvoo nnspool-history-file (concat nnspool-lib-dir "history")
  50. "Local news history file.")
  51. (defvoo nnspool-active-times-file (concat nnspool-lib-dir "active.times")
  52. "Local news active date file.")
  53. (defvoo nnspool-large-newsgroup 50
  54. "The number of articles which indicates a large newsgroup.
  55. If the number of articles is greater than the value, verbose
  56. messages will be shown to indicate the current status.")
  57. (defvoo nnspool-nov-is-evil nil
  58. "Non-nil means that nnspool will never return NOV lines instead of headers.")
  59. (defconst nnspool-sift-nov-with-sed nil
  60. "If non-nil, use sed to get the relevant portion from the overview file.
  61. If nil, nnspool will load the entire file into a buffer and process it
  62. there.")
  63. (defvoo nnspool-rejected-article-hook nil
  64. "*A hook that will be run when an article has been rejected by the server.")
  65. (defvoo nnspool-file-coding-system nnheader-file-coding-system
  66. "Coding system for nnspool.")
  67. (defconst nnspool-version "nnspool 2.0"
  68. "Version numbers of this version of NNSPOOL.")
  69. (defvoo nnspool-current-directory nil
  70. "Current news group directory.")
  71. (defvoo nnspool-current-group nil)
  72. (defvoo nnspool-status-string "")
  73. ;;; Interface functions.
  74. (nnoo-define-basics nnspool)
  75. (deffoo nnspool-retrieve-headers (articles &optional group server fetch-old)
  76. "Retrieve the headers of ARTICLES."
  77. (with-current-buffer nntp-server-buffer
  78. (erase-buffer)
  79. (when (nnspool-possibly-change-directory group)
  80. (let* ((number (length articles))
  81. (count 0)
  82. (default-directory nnspool-current-directory)
  83. (do-message (and (numberp nnspool-large-newsgroup)
  84. (> number nnspool-large-newsgroup)))
  85. (nnheader-file-coding-system nnspool-file-coding-system)
  86. file beg article ag)
  87. (if (and (numberp (car articles))
  88. (nnspool-retrieve-headers-with-nov articles fetch-old))
  89. ;; We successfully retrieved the NOV headers.
  90. 'nov
  91. ;; No NOV headers here, so we do it the hard way.
  92. (while (setq article (pop articles))
  93. (if (stringp article)
  94. ;; This is a Message-ID.
  95. (setq ag (nnspool-find-id article)
  96. file (and ag (nnspool-article-pathname
  97. (car ag) (cdr ag)))
  98. article (cdr ag))
  99. ;; This is an article in the current group.
  100. (setq file (int-to-string article)))
  101. ;; Insert the head of the article.
  102. (when (and file
  103. (file-exists-p file))
  104. (insert "221 ")
  105. (princ article (current-buffer))
  106. (insert " Article retrieved.\n")
  107. (setq beg (point))
  108. (inline (nnheader-insert-head file))
  109. (goto-char beg)
  110. (if (search-forward "\n\n" nil t)
  111. (progn
  112. (forward-char -1)
  113. (insert ".\n"))
  114. (goto-char (point-max))
  115. (if (bolp)
  116. (insert ".\n")
  117. (insert "\n.\n")))
  118. (delete-region (point) (point-max)))
  119. (and do-message
  120. (zerop (% (incf count) 20))
  121. (nnheader-message 5 "nnspool: Receiving headers... %d%%"
  122. (/ (* count 100) number))))
  123. (when do-message
  124. (nnheader-message 5 "nnspool: Receiving headers...done"))
  125. ;; Fold continuation lines.
  126. (nnheader-fold-continuation-lines)
  127. 'headers)))))
  128. (deffoo nnspool-open-server (server &optional defs)
  129. (nnoo-change-server 'nnspool server defs)
  130. (cond
  131. ((not (file-exists-p nnspool-spool-directory))
  132. (nnspool-close-server)
  133. (nnheader-report 'nnspool "Spool directory doesn't exist: %s"
  134. nnspool-spool-directory))
  135. ((not (file-directory-p
  136. (directory-file-name
  137. (file-truename nnspool-spool-directory))))
  138. (nnspool-close-server)
  139. (nnheader-report 'nnspool "Not a directory: %s" nnspool-spool-directory))
  140. ((not (file-exists-p nnspool-active-file))
  141. (nnheader-report 'nnspool "The active file doesn't exist: %s"
  142. nnspool-active-file))
  143. (t
  144. (nnheader-report 'nnspool "Opened server %s using directory %s"
  145. server nnspool-spool-directory)
  146. t)))
  147. (deffoo nnspool-request-article (id &optional group server buffer)
  148. "Select article by message ID (or number)."
  149. (nnspool-possibly-change-directory group)
  150. (let ((nntp-server-buffer (or buffer nntp-server-buffer))
  151. file ag)
  152. (if (stringp id)
  153. ;; This is a Message-ID.
  154. (when (setq ag (nnspool-find-id id))
  155. (setq file (nnspool-article-pathname (car ag) (cdr ag))))
  156. (setq file (nnspool-article-pathname nnspool-current-group id)))
  157. (and file
  158. (file-exists-p file)
  159. (not (file-directory-p file))
  160. (save-excursion (nnspool-find-file file))
  161. ;; We return the article number and group name.
  162. (if (numberp id)
  163. (cons nnspool-current-group id)
  164. ag))))
  165. (deffoo nnspool-request-body (id &optional group server)
  166. "Select article body by message ID (or number)."
  167. (nnspool-possibly-change-directory group)
  168. (let ((res (nnspool-request-article id)))
  169. (when res
  170. (with-current-buffer nntp-server-buffer
  171. (goto-char (point-min))
  172. (when (search-forward "\n\n" nil t)
  173. (delete-region (point-min) (point)))
  174. res))))
  175. (deffoo nnspool-request-head (id &optional group server)
  176. "Select article head by message ID (or number)."
  177. (nnspool-possibly-change-directory group)
  178. (let ((res (nnspool-request-article id)))
  179. (when res
  180. (with-current-buffer nntp-server-buffer
  181. (goto-char (point-min))
  182. (when (search-forward "\n\n" nil t)
  183. (delete-region (1- (point)) (point-max)))
  184. (nnheader-fold-continuation-lines)))
  185. res))
  186. (deffoo nnspool-request-group (group &optional server dont-check info)
  187. "Select news GROUP."
  188. (let ((pathname (nnspool-article-pathname group))
  189. dir)
  190. (if (not (file-directory-p pathname))
  191. (nnheader-report
  192. 'nnspool "Invalid group name (no such directory): %s" group)
  193. (setq nnspool-current-directory pathname)
  194. (nnheader-report 'nnspool "Selected group %s" group)
  195. (if dont-check
  196. (progn
  197. (nnheader-report 'nnspool "Selected group %s" group)
  198. t)
  199. ;; Yes, completely empty spool directories *are* possible.
  200. ;; Fix by Sudish Joseph <joseph@cis.ohio-state.edu>
  201. (when (setq dir (directory-files pathname nil "^[0-9]+$" t))
  202. (setq dir (sort (mapcar 'string-to-number dir) '<)))
  203. (if dir
  204. (nnheader-insert
  205. "211 %d %d %d %s\n" (length dir) (car dir)
  206. (car (last dir)) group)
  207. (nnheader-report 'nnspool "Empty group %s" group)
  208. (nnheader-insert "211 0 0 0 %s\n" group))))))
  209. (deffoo nnspool-request-type (group &optional article)
  210. 'news)
  211. (deffoo nnspool-close-group (group &optional server)
  212. t)
  213. (deffoo nnspool-request-list (&optional server)
  214. "List active newsgroups."
  215. (save-excursion
  216. (or (nnspool-find-file nnspool-active-file)
  217. (nnheader-report 'nnspool (nnheader-file-error nnspool-active-file)))))
  218. (deffoo nnspool-request-list-newsgroups (&optional server)
  219. "List newsgroups (defined in NNTP2)."
  220. (save-excursion
  221. (or (nnspool-find-file nnspool-newsgroups-file)
  222. (nnheader-report 'nnspool (nnheader-file-error
  223. nnspool-newsgroups-file)))))
  224. (deffoo nnspool-request-list-distributions (&optional server)
  225. "List distributions (defined in NNTP2)."
  226. (save-excursion
  227. (or (nnspool-find-file nnspool-distributions-file)
  228. (nnheader-report 'nnspool (nnheader-file-error
  229. nnspool-distributions-file)))))
  230. ;; Suggested by Hallvard B Furuseth <h.b.furuseth@usit.uio.no>.
  231. (deffoo nnspool-request-newgroups (date &optional server)
  232. "List groups created after DATE."
  233. (if (nnspool-find-file nnspool-active-times-file)
  234. (save-excursion
  235. ;; Find the last valid line.
  236. (goto-char (point-max))
  237. (while (and (not (looking-at
  238. "\\([^ ]+\\) +\\([0-9]+\\)[0-9][0-9][0-9] "))
  239. (zerop (forward-line -1))))
  240. ;; We require nnheader which requires gnus-util.
  241. (let ((seconds (gnus-float-time (date-to-time date)))
  242. groups)
  243. ;; Go through lines and add the latest groups to a list.
  244. (while (and (looking-at "\\([^ ]+\\) +[0-9]+ ")
  245. (progn
  246. ;; We insert a .0 to make the list reader
  247. ;; interpret the number as a float. It is far
  248. ;; too big to be stored in a lisp integer.
  249. (goto-char (1- (match-end 0)))
  250. (insert ".0")
  251. (> (progn
  252. (goto-char (match-end 1))
  253. (read (current-buffer)))
  254. seconds))
  255. (push (buffer-substring
  256. (match-beginning 1) (match-end 1))
  257. groups)
  258. (zerop (forward-line -1))))
  259. (erase-buffer)
  260. (dolist (group groups)
  261. (insert group " 0 0 y\n")))
  262. t)
  263. nil))
  264. (deffoo nnspool-request-post (&optional server)
  265. "Post a new news in current buffer."
  266. (save-excursion
  267. (let* ((process-connection-type nil) ; t bugs out on Solaris
  268. (inews-buffer (generate-new-buffer " *nnspool post*"))
  269. (proc
  270. (condition-case err
  271. (apply 'start-process "*nnspool inews*" inews-buffer
  272. nnspool-inews-program nnspool-inews-switches)
  273. (error
  274. (nnheader-report 'nnspool "inews error: %S" err)))))
  275. (if (not proc)
  276. ;; The inews program failed.
  277. ()
  278. (nnheader-report 'nnspool "")
  279. (set-process-sentinel proc 'nnspool-inews-sentinel)
  280. (mm-with-unibyte-current-buffer
  281. (process-send-region proc (point-min) (point-max)))
  282. ;; We slap a condition-case around this, because the process may
  283. ;; have exited already...
  284. (ignore-errors
  285. (process-send-eof proc))
  286. t))))
  287. ;;; Internal functions.
  288. (defun nnspool-inews-sentinel (proc status)
  289. (with-current-buffer (process-buffer proc)
  290. (goto-char (point-min))
  291. (if (or (zerop (buffer-size))
  292. (search-forward "spooled" nil t))
  293. (kill-buffer (current-buffer))
  294. ;; Make status message by folding lines.
  295. (while (re-search-forward "[ \t\n]+" nil t)
  296. (replace-match " " t t))
  297. (nnheader-report 'nnspool "%s" (buffer-string))
  298. (nnheader-message 5 "nnspool: %s" nnspool-status-string)
  299. (ding)
  300. (run-hooks 'nnspool-rejected-article-hook))))
  301. (defun nnspool-retrieve-headers-with-nov (articles &optional fetch-old)
  302. (if (or gnus-nov-is-evil nnspool-nov-is-evil)
  303. nil
  304. (let ((nov (nnheader-group-pathname
  305. nnspool-current-group nnspool-nov-directory ".overview"))
  306. (arts articles)
  307. (nnheader-file-coding-system nnspool-file-coding-system)
  308. last)
  309. (if (not (file-exists-p nov))
  310. ()
  311. (with-current-buffer nntp-server-buffer
  312. (erase-buffer)
  313. (if nnspool-sift-nov-with-sed
  314. (nnspool-sift-nov-with-sed articles nov)
  315. (nnheader-insert-file-contents nov)
  316. (if (and fetch-old
  317. (not (numberp fetch-old)))
  318. t ; We want all the headers.
  319. (ignore-errors
  320. ;; Delete unwanted NOV lines.
  321. (nnheader-nov-delete-outside-range
  322. (if fetch-old (max 1 (- (car articles) fetch-old))
  323. (car articles))
  324. (car (last articles)))
  325. ;; If the buffer is empty, this wasn't very successful.
  326. (unless (zerop (buffer-size))
  327. ;; We check what the last article number was.
  328. ;; The NOV file may be out of sync with the articles
  329. ;; in the group.
  330. (forward-line -1)
  331. (setq last (read (current-buffer)))
  332. (if (= last (car articles))
  333. ;; Yup, it's all there.
  334. t
  335. ;; Perhaps not. We try to find the missing articles.
  336. (while (and arts
  337. (<= last (car arts)))
  338. (pop arts))
  339. ;; The articles in `arts' are missing from the buffer.
  340. (mapc 'nnspool-insert-nov-head arts)
  341. t))))))))))
  342. (defun nnspool-insert-nov-head (article)
  343. "Read the head of ARTICLE, convert to NOV headers, and insert."
  344. (save-excursion
  345. (let ((cur (current-buffer))
  346. buf)
  347. (setq buf (nnheader-set-temp-buffer " *nnspool head*"))
  348. (when (nnheader-insert-head
  349. (nnspool-article-pathname nnspool-current-group article))
  350. (nnheader-insert-article-line article)
  351. (goto-char (point-min))
  352. (let ((headers (nnheader-parse-head)))
  353. (set-buffer cur)
  354. (goto-char (point-max))
  355. (nnheader-insert-nov headers)))
  356. (kill-buffer buf))))
  357. (defun nnspool-sift-nov-with-sed (articles file)
  358. (let ((first (car articles))
  359. (last (car (last articles))))
  360. (call-process "awk" nil t nil
  361. (format "BEGIN {firstmsg=%d; lastmsg=%d;}\n $1 >= firstmsg && $1 <= lastmsg {print;}"
  362. (1- first) (1+ last))
  363. file)))
  364. ;; Fixed by fdc@cliwe.ping.de (Frank D. Cringle).
  365. ;; Find out what group an article identified by a Message-ID is in.
  366. (defun nnspool-find-id (id)
  367. (with-temp-buffer
  368. (ignore-errors
  369. (call-process "grep" nil t nil (regexp-quote id) nnspool-history-file))
  370. (goto-char (point-min))
  371. (when (looking-at "<[^>]+>[ \t]+[-0-9~]+[ \t]+\\([^ /\t\n]+\\)/\\([0-9]+\\)[ \t\n]")
  372. (cons (match-string 1) (string-to-number (match-string 2))))))
  373. (defun nnspool-find-file (file)
  374. "Insert FILE in server buffer safely."
  375. (set-buffer nntp-server-buffer)
  376. (erase-buffer)
  377. (condition-case ()
  378. (let ((coding-system-for-read nnspool-file-coding-system))
  379. (mm-insert-file-contents file)
  380. t)
  381. (file-error nil)))
  382. (defun nnspool-possibly-change-directory (group)
  383. (if (not group)
  384. t
  385. (let ((pathname (nnspool-article-pathname group)))
  386. (if (file-directory-p pathname)
  387. (setq nnspool-current-directory pathname
  388. nnspool-current-group group)
  389. (nnheader-report 'nnspool "No such newsgroup: %s" group)))))
  390. (defun nnspool-article-pathname (group &optional article)
  391. "Find the file name for GROUP."
  392. (nnheader-group-pathname group nnspool-spool-directory article))
  393. (provide 'nnspool)
  394. ;;; nnspool.el ends here