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)
164 (let ((gnus (locate-library "gnus"))
165 ;; Gnus has mailcap.el in the same directory of gnus.el.
166 (mailcap (locate-library "mailcap")))
168 (string-equal (file-name-directory gnus)
169 (file-name-directory mailcap)))))))
172 (defmacro nnshimbun-mail-header-subject (header)
173 `(mail-header-subject ,header))
174 (defmacro nnshimbun-mail-header-from (header)
175 `(mail-header-from ,header)))
176 (defmacro nnshimbun-mail-header-subject (header)
177 `(mime-entity-fetch-field ,header 'Subject))
178 (defmacro nnshimbun-mail-header-from (header)
179 `(mime-entity-fetch-field ,header 'From)))))
181 (defun nnshimbun-make-shimbun-header (header)
183 (mail-header-number header)
184 (nnshimbun-mail-header-subject header)
185 (nnshimbun-mail-header-from header)
186 (mail-header-date header)
187 (or (cdr (assq 'X-Nnshimbun-Id (mail-header-extra header)))
188 (mail-header-id header))
189 (mail-header-references header)
190 (mail-header-chars header)
191 (mail-header-lines header)
192 (let ((xref (mail-header-xref header)))
193 (if (and xref (string-match "^Xref: " xref))
197 (defun nnshimbun-request-article-1 (article &optional group server to-buffer)
198 (if (nnshimbun-backlog
199 (gnus-backlog-request-article
200 group article (or to-buffer nntp-server-buffer)))
202 (let* ((header (with-current-buffer (nnshimbun-open-nov group)
203 (and (nnheader-find-nov-line article)
204 (nnshimbun-make-shimbun-header
205 (nnheader-parse-nov)))))
206 (original-id (shimbun-header-id header)))
208 (with-current-buffer (or to-buffer nntp-server-buffer)
209 (delete-region (point-min) (point-max))
210 (shimbun-article nnshimbun-shimbun header)
211 (when (> (buffer-size) 0)
212 (nnshimbun-replace-nov-entry group article header original-id)
214 (gnus-backlog-enter-article group article (current-buffer)))
215 (nnheader-report 'nnshimbun "Article %s retrieved"
216 (shimbun-header-id header))
217 (cons group article)))))))
219 (deffoo nnshimbun-request-article (article &optional group server to-buffer)
220 (when (nnshimbun-possibly-change-group group server)
221 (when (stringp article)
222 (setq article (nnshimbun-search-id group article)))
223 (if (integerp article)
224 (nnshimbun-request-article-1 article group server to-buffer)
225 (nnheader-report 'nnshimbun "Couldn't retrieve article: %s"
226 (prin1-to-string article))
229 (deffoo nnshimbun-request-group (group &optional server dont-check)
230 (let ((file-name-coding-system nnmail-pathname-coding-system)
231 (pathname-coding-system nnmail-pathname-coding-system))
233 ((not (nnshimbun-possibly-change-group group server))
234 (nnheader-report 'nnshimbun "Invalid group (no such directory)"))
235 ((not (file-exists-p nnshimbun-current-directory))
236 (nnheader-report 'nnshimbun "Directory %s does not exist"
237 nnshimbun-current-directory))
238 ((not (file-directory-p nnshimbun-current-directory))
239 (nnheader-report 'nnshimbun "%s is not a directory" nnshimbun-current-directory))
241 (nnheader-report 'nnshimbun "Group %s selected" group)
245 (with-current-buffer (nnshimbun-open-nov group)
246 (goto-char (point-min))
247 (setq beg (ignore-errors (read (current-buffer))))
248 (goto-char (point-max))
250 (setq end (ignore-errors (read (current-buffer)))
251 lines (count-lines (point-min) (point-max))))
252 (nnheader-report 'nnshimbunw "Selected group %s" group)
253 (nnheader-insert "211 %d %d %d %s\n"
254 lines (or beg 0) (or end 0) group))))))
256 (deffoo nnshimbun-request-scan (&optional group server)
257 (nnshimbun-possibly-change-group group server)
258 (nnshimbun-generate-nov-database group))
260 (deffoo nnshimbun-close-group (group &optional server)
261 (nnshimbun-write-nov group)
264 (deffoo nnshimbun-request-list (&optional server)
265 (with-current-buffer nntp-server-buffer
266 (delete-region (point-min) (point-max))
267 (dolist (group (shimbun-groups nnshimbun-shimbun))
268 (when (nnshimbun-possibly-change-group group server)
270 (with-current-buffer (nnshimbun-open-nov group)
271 (goto-char (point-min))
272 (setq beg (ignore-errors (read (current-buffer))))
273 (goto-char (point-max))
275 (setq end (ignore-errors (read (current-buffer)))))
276 (insert (format "%s %d %d n\n" group (or end 0) (or beg 0)))))))
279 (deffoo nnshimbun-retrieve-headers (articles &optional group server fetch-old)
280 (when (nnshimbun-possibly-change-group group server)
281 (if (nnshimbun-retrieve-headers-with-nov articles fetch-old)
283 (with-current-buffer nntp-server-buffer
284 (delete-region (point-min) (point-max))
286 (dolist (art articles)
288 (setq art (nnshimbun-search-id group art)))
291 (with-current-buffer (nnshimbun-open-nov group)
292 (and (nnheader-find-nov-line art)
293 (nnheader-parse-nov))))
294 (insert (format "220 %d Article retrieved.\n" art))
295 (shimbun-header-insert
297 (nnshimbun-make-shimbun-header header))
299 (delete-region (point) (point-max))))))
302 (defun nnshimbun-retrieve-headers-with-nov (articles &optional fetch-old)
303 (if (or gnus-nov-is-evil nnshimbun-nov-is-evil)
305 (let ((nov (expand-file-name nnshimbun-nov-file-name nnshimbun-current-directory)))
306 (when (file-exists-p nov)
308 (set-buffer nntp-server-buffer)
310 (nnheader-insert-file-contents nov)
311 (if (and fetch-old (not (numberp fetch-old)))
312 t ; Don't remove anything.
313 (nnheader-nov-delete-outside-range
314 (if fetch-old (max 1 (- (car articles) fetch-old))
316 (and articles (nth (1- (length articles)) articles)))
321 ;;; Nov Database Operations
323 (defsubst nnshimbun-insert-nov (number header &optional id)
324 (unless (and (stringp id)
325 (not (string= id (shimbun-header-id header))))
327 (princ number (current-buffer))
331 (or (shimbun-header-subject header) "(none)") "\t"
332 (or (shimbun-header-from header) "(nobody)") "\t"
333 (or (shimbun-header-date header) "") "\t"
334 (or (shimbun-header-id header) (nnmail-message-id)) "\t"
335 (or (shimbun-header-references header) "") "\t")
336 (princ (or (shimbun-header-chars header) 0) (current-buffer))
338 (princ (or (shimbun-header-lines header) 0) (current-buffer))
340 (when (shimbun-header-xref header)
341 (insert "Xref: " (shimbun-header-xref header)))
342 (when (or (shimbun-header-xref header) id)
345 (insert "X-Nnshimbun-Id: " id "\t"))
348 (while (search-backward "\n" p t)
352 (defun nnshimbun-generate-nov-database (group)
353 (nnshimbun-possibly-change-group group)
354 (with-current-buffer (nnshimbun-open-nov group)
355 (goto-char (point-max))
357 (let ((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))
361 (nnshimbun-insert-nov (setq i (1+ i)) header)
362 (when nnshimbun-pre-fetch-article
363 (nnshimbun-request-article-1 i group nil nnshimbun-buffer)))))
364 (nnshimbun-write-nov group)))
366 (defun nnshimbun-replace-nov-entry (group article header &optional id)
367 (with-current-buffer (nnshimbun-open-nov group)
368 (when (nnheader-find-nov-line article)
369 (delete-region (point) (progn (forward-line 1) (point)))
370 (nnshimbun-insert-nov article header id))))
372 (defun nnshimbun-search-id (group id &optional nov)
373 (with-current-buffer (nnshimbun-open-nov group)
374 (goto-char (point-min))
376 (while (and (not found)
377 (search-forward id nil t)) ; We find the ID.
378 ;; And the id is in the fourth field.
379 (if (not (and (search-backward "\t" nil t 4)
380 (not (search-backward "\t" (gnus-point-at-bol) t))))
385 (goto-char (point-min))
386 (setq id (concat "X-Nnshimbun-Id: " id))
387 (while (and (not found)
388 (search-forward id nil t))
389 (if (not (search-backward "\t" (gnus-point-at-bol) t 8))
396 ;; We return the article number.
397 (ignore-errors (read (current-buffer))))))))
399 (defun nnshimbun-open-nov (group)
400 (let ((buffer (cdr (assoc group nnshimbun-nov-buffer-alist))))
401 (if (buffer-live-p buffer)
403 (setq buffer (gnus-get-buffer-create
404 (format " *nnshimbun overview %s %s*"
405 (nnoo-current-server 'nnshimbun) group)))
408 (set (make-local-variable 'nnshimbun-nov-buffer-file-name)
410 nnshimbun-nov-file-name
411 (nnmail-group-pathname group nnshimbun-server-directory)))
413 (when (file-exists-p nnshimbun-nov-buffer-file-name)
414 (nnheader-insert-file-contents nnshimbun-nov-buffer-file-name))
415 (set-buffer-modified-p nil))
416 (push (cons group buffer) nnshimbun-nov-buffer-alist)
419 (defun nnshimbun-write-nov (group)
420 (let ((buffer (cdr (assoc group nnshimbun-nov-buffer-alist))))
421 (when (buffer-live-p buffer)
425 (nnmail-write-region 1 (point-max) nnshimbun-nov-buffer-file-name
428 (defun nnshimbun-save-nov ()
430 (while nnshimbun-nov-buffer-alist
431 (when (buffer-name (cdar nnshimbun-nov-buffer-alist))
432 (set-buffer (cdar nnshimbun-nov-buffer-alist))
433 (when (buffer-modified-p)
434 (nnmail-write-region 1 (point-max) nnshimbun-nov-buffer-file-name
436 (set-buffer-modified-p nil)
437 (kill-buffer (current-buffer)))
438 (setq nnshimbun-nov-buffer-alist (cdr nnshimbun-nov-buffer-alist)))))
442 ;;; Server Initialize
444 (defun nnshimbun-possibly-change-group (group &optional server)
446 (unless (nnshimbun-server-opened server)
447 (nnshimbun-open-server server)))
448 (unless (gnus-buffer-live-p nnshimbun-buffer)
449 (setq nnshimbun-buffer
451 (nnheader-set-temp-buffer
452 (format " *nnshimbun %s*" (nnoo-current-server 'nnshimbun))))))
456 (shimbun-open-group nnshimbun-shimbun group)
457 (error (nnheader-report 'nnshimbun "%s" (error-message-string err))))
458 (let ((pathname (nnmail-group-pathname group nnshimbun-server-directory))
459 (file-name-coding-system nnmail-pathname-coding-system)
460 (pathname-coding-system nnmail-pathname-coding-system))
461 (unless (equal pathname nnshimbun-current-directory)
462 (setq nnshimbun-current-directory pathname
463 nnshimbun-current-group group))
464 (unless (file-exists-p nnshimbun-current-directory)
465 (ignore-errors (make-directory nnshimbun-current-directory t)))
467 ((not (file-exists-p nnshimbun-current-directory))
468 (nnheader-report 'nnshimbun "Couldn't create directory: %s" nnshimbun-current-directory))
469 ((not (file-directory-p (file-truename nnshimbun-current-directory)))
470 (nnheader-report 'nnshimbun "Not a directory: %s" nnshimbun-current-directory))
476 (luna-define-class shimbun-gnus-mua (shimbun-mua) ())
478 (luna-define-method shimbun-mua-search-id ((mua shimbun-gnus-mua) id)
480 (shimbun-current-group-internal (shimbun-mua-shimbun-internal mua))
483 (luna-define-method shimbun-mua-use-entire-index ((mua shimbun-gnus-mua))
484 nnshimbun-use-entire-index)
488 ;;; nnshimbun.el ends here.