1 ;;; nnshimbun.el --- interfacing with web newspapers -*- coding: junet; -*-
3 ;; Authors: TSUCHIYA Masatoshi <tsuchiya@namazu.org>,
4 ;; Akihiro Arisawa <ari@atesoft.advantest.co.jp>,
5 ;; Katsumi Yamaoka <yamaoka@jpl.org>,
6 ;; Yuuichi Teranishi <teranisi@gohome.org>
11 ;; This file is a part of Semi-Gnus.
13 ;; This program is free software; you can redistribute it and/or modify
14 ;; it under the terms of the GNU General Public License as published by
15 ;; the Free Software Foundation; either version 2, or (at your option)
18 ;; This program is distributed in the hope that it will be useful,
19 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
20 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 ;; GNU General Public License for more details.
23 ;; You should have received a copy of the GNU General Public License
24 ;; along with this program; if not, you can either send email to this
25 ;; program's maintainer or write to: The Free Software Foundation,
26 ;; Inc.; 59 Temple Place, Suite 330; Boston, MA 02111-1307, USA.
30 ;; Gnus (or gnus) backend to read newspapers on the World Wide Web.
31 ;; This module requires the Emacs-W3M and the external command W3M.
32 ;; Visit the following pages for more information.
34 ;; http://namazu.org/~tsuchiya/emacs-w3m/
35 ;; http://ei5nazha.yz.yamagata-u.ac.jp/~aito/w3m/
37 ;; If you would like to use this module in Gnus (not T-gnus), put this
38 ;; file into the lisp/ directory in the Gnus source tree and run
39 ;; `make install'. And then, copy the function definition of
40 ;; `gnus-group-make-shimbun-group' from the file gnus-group.el of
41 ;; T-gnus to somewhere else, for example .gnus file as follows:
43 ;;(eval-after-load "gnus-group"
44 ;; '(if (not (fboundp 'gnus-group-make-shimbun-group))
45 ;; (defun gnus-group-make-shimbun-group ()
46 ;; "Create a nnshimbun group."
47 ;; [...a function definition...])))
51 (gnus-declare-backend "nnshimbun" 'address)
53 (eval-when-compile (require 'cl))
62 (nnoo-declare nnshimbun)
64 (defvoo nnshimbun-directory (nnheader-concat gnus-directory "shimbun/")
65 "Where nnshimbun will save its files.")
67 (defvoo nnshimbun-nov-is-evil nil
68 "*Non-nil means that nnshimbun will never retrieve NOV headers.")
70 (defvoo nnshimbun-nov-file-name ".overview")
72 (defvoo nnshimbun-pre-fetch-article nil
73 "*Non nil means that nnshimbun fetch unread articles when scanning groups.")
75 (defvoo nnshimbun-use-entire-index t
76 "*Nil means that nnshimbun check the last index of articles.")
78 ;; set by nnshimbun-possibly-change-group
79 (defvoo nnshimbun-buffer nil)
80 (defvoo nnshimbun-current-directory nil)
81 (defvoo nnshimbun-current-group nil)
83 ;; set by nnshimbun-open-server
84 (defvoo nnshimbun-shimbun nil)
85 (defvoo nnshimbun-server-directory nil)
87 (defvoo nnshimbun-status-string "")
88 (defvoo nnshimbun-nov-last-check nil)
89 (defvoo nnshimbun-nov-buffer-alist nil)
90 (defvoo nnshimbun-nov-buffer-file-name nil)
92 (defvoo nnshimbun-keep-backlog 300)
93 (defvoo nnshimbun-backlog-articles nil)
94 (defvoo nnshimbun-backlog-hashtb nil)
97 (defmacro nnshimbun-backlog (&rest form)
98 `(let ((gnus-keep-backlog nnshimbun-keep-backlog)
99 (gnus-backlog-buffer (format " *nnshimbun backlog %s*" (nnoo-current-server 'nnshimbun)))
100 (gnus-backlog-articles nnshimbun-backlog-articles)
101 (gnus-backlog-hashtb nnshimbun-backlog-hashtb))
104 (setq nnshimbun-backlog-articles gnus-backlog-articles
105 nnshimbun-backlog-hashtb gnus-backlog-hashtb))))
106 (put 'nnshimbun-backlog 'lisp-indent-function 0)
107 (put 'nnshimbun-backlog 'edebug-form-spec '(form body))
110 ;;; Interface Functions
111 (nnoo-define-basics nnshimbun)
113 (deffoo nnshimbun-open-server (server &optional defs)
114 (push (list 'nnshimbun-shimbun
116 (shimbun-open server (luna-make-entity 'shimbun-gnus-mua))
117 (error (nnheader-report 'nnshimbun "%s" (error-message-string err)))))
119 ;; Set directory for server working files.
120 (push (list 'nnshimbun-server-directory
121 (file-name-as-directory
122 (expand-file-name server nnshimbun-directory)))
124 (nnoo-change-server 'nnshimbun server defs)
125 (nnshimbun-possibly-change-group nil server)
127 (unless (file-exists-p nnshimbun-directory)
128 (ignore-errors (make-directory nnshimbun-directory t)))
130 ((not (file-exists-p nnshimbun-directory))
131 (nnshimbun-close-server)
132 (nnheader-report 'nnshimbun "Couldn't create directory: %s" nnshimbun-directory))
133 ((not (file-directory-p (file-truename nnshimbun-directory)))
134 (nnshimbun-close-server)
135 (nnheader-report 'nnshimbun "Not a directory: %s" nnshimbun-directory))
137 (unless (file-exists-p nnshimbun-server-directory)
138 (ignore-errors (make-directory nnshimbun-server-directory t)))
140 ((not (file-exists-p nnshimbun-server-directory))
141 (nnshimbun-close-server)
142 (nnheader-report 'nnshimbun "Couldn't create directory: %s" nnshimbun-server-directory))
143 ((not (file-directory-p (file-truename nnshimbun-server-directory)))
144 (nnshimbun-close-server)
145 (nnheader-report 'nnshimbun "Not a directory: %s" nnshimbun-server-directory))
147 (nnheader-report 'nnshimbun "Opened server %s using directory %s"
148 server nnshimbun-server-directory)
151 (deffoo nnshimbun-close-server (&optional server)
152 (shimbun-close nnshimbun-shimbun)
153 (and (nnshimbun-server-opened server)
154 (gnus-buffer-live-p nnshimbun-buffer)
155 (kill-buffer nnshimbun-buffer))
156 (nnshimbun-backlog (gnus-backlog-shutdown))
158 (nnoo-close-server 'nnshimbun server)
161 (defsubst nnshimbun-header-xref (x)
162 (if (and (setq x (mail-header-xref x))
163 (string-match "^Xref: " x))
170 (let ((gnus (locate-library "gnus"))
171 ;; Gnus has mailcap.el in the same directory of gnus.el.
172 (mailcap (locate-library "mailcap")))
174 (string-equal (file-name-directory gnus)
175 (file-name-directory mailcap)))))))
178 (defmacro nnshimbun-mail-header-subject (header)
179 `(mail-header-subject ,header))
180 (defmacro nnshimbun-mail-header-from (header)
181 `(mail-header-from ,header)))
182 (defmacro nnshimbun-mail-header-subject (header)
183 `(mime-entity-fetch-field ,header 'Subject))
184 (defmacro nnshimbun-mail-header-from (header)
185 `(mime-entity-fetch-field ,header 'From)))))
187 (defun nnshimbun-make-shimbun-header (header)
189 (mail-header-number header)
190 (nnshimbun-mail-header-subject header)
191 (nnshimbun-mail-header-from header)
192 (mail-header-date header)
193 (or (cdr (assq 'X-Nnshimbun-Id (mail-header-extra header)))
194 (mail-header-id header))
195 (mail-header-references header)
196 (mail-header-chars header)
197 (mail-header-lines header)
198 (nnshimbun-header-xref header)))
200 (defsubst nnshimbun-check-header (group header)
203 (let ((id (std11-field-body "message-id")))
204 (when (and id (not (string= id (mail-header-id header))))
205 (let ((extra (mail-header-extra header)))
206 (unless (assq 'X-Nnshimbun-Id extra)
207 (push (cons 'X-Nnshimbun-Id (mail-header-id header)) extra)
208 (mail-header-set-extra header extra)))
209 (mail-header-set-id header id)
212 (when (string= "" (mail-header-references header))
213 (let ((refs (std11-field-body "references")))
215 (mail-header-set-references header (std11-unfold-string refs))))
219 (with-current-buffer (nnshimbun-open-nov group)
220 (when (nnheader-find-nov-line (mail-header-number header))
221 (mail-header-set-xref header (nnshimbun-header-xref header))
222 (delete-region (point) (progn (forward-line 1) (point)))
223 (nnheader-insert-nov header))))))
225 (defun nnshimbun-request-article-1 (article &optional group server to-buffer)
226 (if (nnshimbun-backlog
227 (gnus-backlog-request-article
228 group article (or to-buffer nntp-server-buffer)))
230 (let ((header (with-current-buffer (nnshimbun-open-nov group)
231 (and (nnheader-find-nov-line article)
232 (nnheader-parse-nov)))))
234 (with-current-buffer (or to-buffer nntp-server-buffer)
235 (delete-region (point-min) (point-max))
236 (shimbun-article nnshimbun-shimbun
237 (nnshimbun-make-shimbun-header header))
238 (when (> (buffer-size) 0)
239 (nnshimbun-check-header group header)
241 (gnus-backlog-enter-article group article (current-buffer)))
242 (nnheader-report 'nnshimbun "Article %s retrieved"
243 (mail-header-id header))
244 (cons group (mail-header-number header))))))))
246 (deffoo nnshimbun-request-article (article &optional group server to-buffer)
247 (when (nnshimbun-possibly-change-group group server)
248 (when (stringp article)
249 (setq article (nnshimbun-search-id group article)))
250 (if (integerp article)
251 (nnshimbun-request-article-1 article group server to-buffer)
252 (nnheader-report 'nnshimbun "Couldn't retrieve article: %s"
253 (prin1-to-string article))
256 (deffoo nnshimbun-request-group (group &optional server dont-check)
257 (let ((file-name-coding-system nnmail-pathname-coding-system)
258 (pathname-coding-system nnmail-pathname-coding-system))
260 ((not (nnshimbun-possibly-change-group group server))
261 (nnheader-report 'nnshimbun "Invalid group (no such directory)"))
262 ((not (file-exists-p nnshimbun-current-directory))
263 (nnheader-report 'nnshimbun "Directory %s does not exist"
264 nnshimbun-current-directory))
265 ((not (file-directory-p nnshimbun-current-directory))
266 (nnheader-report 'nnshimbun "%s is not a directory" nnshimbun-current-directory))
268 (nnheader-report 'nnshimbun "Group %s selected" group)
273 (set-buffer (nnshimbun-open-nov group))
274 (goto-char (point-min))
275 (setq beg (ignore-errors (read (current-buffer))))
276 (goto-char (point-max))
278 (setq end (ignore-errors (read (current-buffer)))
279 lines (count-lines (point-min) (point-max))))
280 (nnheader-report 'nnshimbunw "Selected group %s" group)
281 (nnheader-insert "211 %d %d %d %s\n"
282 lines (or beg 0) (or end 0) group))))))
284 (deffoo nnshimbun-request-scan (&optional group server)
285 (nnshimbun-possibly-change-group group server)
286 (nnshimbun-generate-nov-database group))
288 (deffoo nnshimbun-close-group (group &optional server)
289 (nnshimbun-write-nov group)
292 (deffoo nnshimbun-request-list (&optional server)
293 (with-current-buffer nntp-server-buffer
294 (delete-region (point-min) (point-max))
295 (dolist (group (shimbun-groups nnshimbun-shimbun))
296 (when (nnshimbun-possibly-change-group group server)
298 (with-current-buffer (nnshimbun-open-nov group)
299 (goto-char (point-min))
300 (setq beg (ignore-errors (read (current-buffer))))
301 (goto-char (point-max))
303 (setq end (ignore-errors (read (current-buffer)))))
304 (insert (format "%s %d %d n\n" group (or end 0) (or beg 0)))))))
307 (deffoo nnshimbun-retrieve-headers (articles &optional group server fetch-old)
308 (when (nnshimbun-possibly-change-group group server)
309 (if (nnshimbun-retrieve-headers-with-nov articles fetch-old)
311 (with-current-buffer nntp-server-buffer
312 (delete-region (point-min) (point-max))
314 (dolist (art articles)
316 (setq art (nnshimbun-search-id group art)))
319 (with-current-buffer (nnshimbun-open-nov group)
320 (and (nnheader-find-nov-line art)
321 (nnheader-parse-nov))))
322 (insert (format "220 %d Article retrieved.\n" art))
323 (shimbun-header-insert
325 (nnshimbun-make-shimbun-header header))
327 (delete-region (point) (point-max))))))
330 (defun nnshimbun-retrieve-headers-with-nov (articles &optional fetch-old)
331 (if (or gnus-nov-is-evil nnshimbun-nov-is-evil)
333 (let ((nov (expand-file-name nnshimbun-nov-file-name nnshimbun-current-directory)))
334 (when (file-exists-p nov)
336 (set-buffer nntp-server-buffer)
338 (nnheader-insert-file-contents nov)
339 (if (and fetch-old (not (numberp fetch-old)))
340 t ; Don't remove anything.
341 (nnheader-nov-delete-outside-range
342 (if fetch-old (max 1 (- (car articles) fetch-old))
344 (and articles (nth (1- (length articles)) articles)))
349 ;;; Nov Database Operations
351 (defun nnshimbun-generate-nov-database (group)
352 (nnshimbun-possibly-change-group group)
354 (with-current-buffer (nnshimbun-open-nov group)
355 (goto-char (point-max))
357 (setq i (or (ignore-errors (read (current-buffer))) 0))
358 (dolist (header (shimbun-headers nnshimbun-shimbun))
359 (unless (nnshimbun-search-id group (shimbun-header-id header))
360 (goto-char (point-max))
362 (make-full-mail-header (setq i (1+ i))
363 (shimbun-header-subject header)
364 (shimbun-header-from header)
365 (shimbun-header-date header)
366 (shimbun-header-id header)
367 (shimbun-header-references header)
368 (shimbun-header-chars header)
369 (shimbun-header-lines header)
370 (shimbun-header-xref header)))
371 (if nnshimbun-pre-fetch-article
372 (nnshimbun-request-article-1 i group nil nnshimbun-buffer)))))
373 (nnshimbun-write-nov group)))
375 (defun nnshimbun-search-id (group id &optional nov)
376 (with-current-buffer (nnshimbun-open-nov group)
377 (goto-char (point-min))
379 (while (and (not found)
380 (search-forward id nil t)) ; We find the ID.
381 ;; And the id is in the fourth field.
382 (if (not (and (search-backward "\t" nil t 4)
383 (not (search-backward "\t" (gnus-point-at-bol) t))))
388 (goto-char (point-min))
389 (setq id (concat "X-Nnshimbun-Id: " id))
390 (while (and (not found)
391 (search-forward id nil t))
392 (if (not (search-backward "\t" (gnus-point-at-bol) t 8))
399 ;; We return the article number.
400 (ignore-errors (read (current-buffer))))))))
402 (defun nnshimbun-open-nov (group)
403 (let ((buffer (cdr (assoc group nnshimbun-nov-buffer-alist))))
404 (if (buffer-live-p buffer)
406 (setq buffer (gnus-get-buffer-create
407 (format " *nnshimbun overview %s %s*"
408 (nnoo-current-server 'nnshimbun) group)))
411 (set (make-local-variable 'nnshimbun-nov-buffer-file-name)
413 nnshimbun-nov-file-name
414 (nnmail-group-pathname group nnshimbun-server-directory)))
416 (when (file-exists-p nnshimbun-nov-buffer-file-name)
417 (nnheader-insert-file-contents nnshimbun-nov-buffer-file-name))
418 (set-buffer-modified-p nil))
419 (push (cons group buffer) nnshimbun-nov-buffer-alist)
422 (defun nnshimbun-write-nov (group)
423 (let ((buffer (cdr (assoc group nnshimbun-nov-buffer-alist))))
424 (when (buffer-live-p buffer)
428 (nnmail-write-region 1 (point-max) nnshimbun-nov-buffer-file-name
431 (defun nnshimbun-save-nov ()
433 (while nnshimbun-nov-buffer-alist
434 (when (buffer-name (cdar nnshimbun-nov-buffer-alist))
435 (set-buffer (cdar nnshimbun-nov-buffer-alist))
436 (when (buffer-modified-p)
437 (nnmail-write-region 1 (point-max) nnshimbun-nov-buffer-file-name
439 (set-buffer-modified-p nil)
440 (kill-buffer (current-buffer)))
441 (setq nnshimbun-nov-buffer-alist (cdr nnshimbun-nov-buffer-alist)))))
445 ;;; Server Initialize
447 (defun nnshimbun-possibly-change-group (group &optional server)
449 (unless (nnshimbun-server-opened server)
450 (nnshimbun-open-server server)))
451 (unless (gnus-buffer-live-p nnshimbun-buffer)
452 (setq nnshimbun-buffer
454 (nnheader-set-temp-buffer
455 (format " *nnshimbun %s*" (nnoo-current-server 'nnshimbun))))))
459 (shimbun-open-group nnshimbun-shimbun group)
460 (error (nnheader-report 'nnshimbun "%s" (error-message-string err))))
461 (let ((pathname (nnmail-group-pathname group nnshimbun-server-directory))
462 (file-name-coding-system nnmail-pathname-coding-system)
463 (pathname-coding-system nnmail-pathname-coding-system))
464 (unless (equal pathname nnshimbun-current-directory)
465 (setq nnshimbun-current-directory pathname
466 nnshimbun-current-group group))
467 (unless (file-exists-p nnshimbun-current-directory)
468 (ignore-errors (make-directory nnshimbun-current-directory t)))
470 ((not (file-exists-p nnshimbun-current-directory))
471 (nnheader-report 'nnshimbun "Couldn't create directory: %s" nnshimbun-current-directory))
472 ((not (file-directory-p (file-truename nnshimbun-current-directory)))
473 (nnheader-report 'nnshimbun "Not a directory: %s" nnshimbun-current-directory))
479 (luna-define-class shimbun-gnus-mua (shimbun-mua) ())
481 (luna-define-method shimbun-mua-search-id ((mua shimbun-gnus-mua) id)
483 (shimbun-current-group-internal (shimbun-mua-shimbun-internal mua))
486 (luna-define-method shimbun-mua-use-entire-index ((mua shimbun-gnus-mua))
487 nnshimbun-use-entire-index)
491 ;;; nnshimbun.el ends here.