* nnmail.el (nnmail-split-it): Revoke the change of 1999-08-19.
[elisp/gnus.git-] / lisp / nnshimbun.el
index 3c48b1a..87675c9 100644 (file)
@@ -1,4 +1,6 @@
-;;; nnshimbun.el --- interfacing with web newspapers -*- coding: junet; -*-
+;;; nnshimbun.el --- interfacing with web newspapers
+
+;; Copyright (C) 2000,2001,2002 TSUCHIYA Masatoshi <tsuchiya@namazu.org>
 
 ;; Authors: TSUCHIYA Masatoshi <tsuchiya@namazu.org>,
 ;;          Akihiro Arisawa    <ari@atesoft.advantest.co.jp>,
@@ -6,8 +8,6 @@
 ;;          Yuuichi Teranishi  <teranisi@gohome.org>
 ;; Keywords: news
 
-;;; Copyright:
-
 ;; This file is a part of Semi-Gnus.
 
 ;; This program is free software; you can redistribute it and/or modify
 ;;; Commentary:
 
 ;; Gnus (or gnus) backend to read newspapers on the World Wide Web.
-;; This module requires the Emacs-W3M and the external command W3M.
+;; This module requires the emacs-w3m and the external command w3m.
 ;; Visit the following pages for more information.
 ;;
-;;     http://namazu.org/~tsuchiya/emacs-w3m/
-;;     http://ei5nazha.yz.yamagata-u.ac.jp/~aito/w3m/
+;;     http://emacs-w3m.namazu.org/
+;;     http://w3m.sourceforge.net/
 
 ;; If you would like to use this module in Gnus (not T-gnus), put this
-;; file into the lisp/ directory in the Gnus source tree and run
-;; `make install'.  And then, copy the function definition of
-;; `gnus-group-make-shimbun-group' from the file gnus-group.el of
-;; T-gnus to somewhere else, for example .gnus file as follows:
+;; file into the lisp/ directory in the Gnus source tree and run `make
+;; install'.  And then, put the following expression into your ~/.gnus.
 ;;
-;;(eval-after-load "gnus-group"
-;;  '(if (not (fboundp 'gnus-group-make-shimbun-group))
-;;       (defun gnus-group-make-shimbun-group ()
-;;         "Create a nnshimbun group."
-;;         [...a function definition...])))
+;; (autoload 'gnus-group-make-shimbun-group "nnshimbun" nil t)
 
-;;; Definitions:
 
-(gnus-declare-backend "nnshimbun" 'address)
+;;; Definitions:
 
 (eval-when-compile (require 'cl))
-
+(require 'nnoo)
 (require 'nnheader)
 (require 'nnmail)
-(require 'nnoo)
 (require 'gnus-bcklg)
 (require 'shimbun)
 
 
+;; Customize variables
+(defgroup nnshimbun nil
+  "Reading Web Newspapers with Gnus."
+  :group 'gnus)
+
+(defvar nnshimbun-group-parameters-custom
+  '(list :format "%v"
+        (checklist :inline t
+                   (list :inline t :format "%v"
+                         (const :format "" index-range)
+                         (choice :tag "Index range"
+                                 :value all
+                                 (const all)
+                                 (const last)
+                                 (integer :tag "days")))
+                   (list :inline t :format "%v"
+                         (const :format "" prefetch-articles)
+                         (choice :tag "Prefetch articles"
+                                 :value off
+                                 (const on)
+                                 (const off)))
+                   (list :inline t :format "%v"
+                         (const :format "" encapsulate-images)
+                         (choice :tag "Encapsulate article"
+                                 :value on
+                                 (const on)
+                                 (const off)))
+                   (list :inline t :format "%v"
+                         (const :format "" expiry-wait)
+                         (choice :tag "Expire wait"
+                                 :value never
+                                 (const never)
+                                 (const immediate)
+                                 (number :tag "days"))))
+        (repeat :inline t :tag "Others"
+                (list :inline t :format "%v"
+                      (symbol :tag "Keyword")
+                      (sexp :tag "Value"))))
+  "A type definition for customizing the nnshimbun group parameters.")
+
+;; The following definition provides the group parameter
+;; `nnshimbun-group-parameters', the user option
+;; `nnshimbun-group-parameters-alist' and the function
+;; `nnshimbun-find-group-parameters'.
+;; The group parameter `nnshimbun-group-parameters' will have a
+;; property list like the following:
+;;
+;; '(index-range all prefetch-articles off encapsulate-images on
+;;               expiry-wait 6)
+
+(unless (fboundp 'gnus-define-group-parameter)
+  (defmacro gnus-define-group-parameter (&rest args) nil)
+  (defun nnshimbun-find-group-parameters (name)
+    "Return a nnshimbun GROUP's group parameters."
+    (when name
+      (or (gnus-group-find-parameter name 'nnshimbun-group-parameters t)
+         (assoc-default name
+                        (and (boundp 'nnshimbun-group-parameters-alist)
+                             (symbol-value 'nnshimbun-group-parameters-alist))
+                        (function string-match))))))
+
+(gnus-define-group-parameter
+ nnshimbun-group-parameters
+ :type list
+ :function nnshimbun-find-group-parameters
+ :function-document "\
+Return a nnshimbun GROUP's group parameters."
+ :variable nnshimbun-group-parameters-alist
+ :variable-default nil
+ :variable-document "\
+Alist of nnshimbun group parameters.  Each element should be a cons of
+a group name regexp and a plist which consists of a keyword and a value
+pairs like the following:
+
+'(\"^nnshimbun\\\\+asahi:\" index-range all prefetch-articles off
+  encapsulate-images on expiry-wait 6)
+
+`index-range' specifies a range of header indices as described below:
+      all: Retrieve all header indices.
+     last: Retrieve the last header index.
+integer N: Retrieve N pages of header indices.
+
+`prefetch-articles' specifies whether to pre-fetch the unread articles
+when scanning the group.
+
+`encapsulate-images' specifies whether inline images in the shimbun
+article are encapsulated.
+
+`expiry-wait' is similar to the generic group parameter `expiry-wait',
+but it has a preference."
+ :variable-group nnshimbun
+ :variable-type `(repeat (cons :format "%v" (regexp :tag "Group name regexp"
+                                                   :value "^nnshimbun\\+")
+                              ,nnshimbun-group-parameters-custom))
+ :parameter-type nnshimbun-group-parameters-custom
+ :parameter-document "\
+Group parameters for the nnshimbun group.
+
+`Index range' specifies a range of header indices as described below:
+      all: Retrieve all header indices.
+     last: Retrieve the last header index.
+integer N: Retrieve N pages of header indices.
+
+`Prefetch articles' specifies whether to pre-fetch the unread articles
+when scanning the group.
+
+`Encapsulate article' specifies whether inline images in the shimbun
+article are encapsulated.
+
+`Expire wait' is similar to the generic group parameter `expiry-wait',
+but it has a preference.")
+
+(defcustom nnshimbun-keep-unparsable-dated-articles t "\
+*If non-nil, nnshimbun will never delete articles whose NOV date is unparsable."
+  :group 'nnshimbun
+  :type 'boolean)
+
+
+;; Define backend
+(gnus-declare-backend "nnshimbun" 'address)
 (nnoo-declare nnshimbun)
 
 (defvoo nnshimbun-directory (nnheader-concat gnus-directory "shimbun/")
 
 (defvoo nnshimbun-nov-file-name ".overview")
 
-(defvoo nnshimbun-pre-fetch-article nil
-  "*Non nil means that nnshimbun fetch unread articles when scanning groups.")
+(defvoo nnshimbun-pre-fetch-article 'off
+  "*If it is neither `off' nor nil, nnshimbun fetch unread articles when
+scanning groups.  Note that this variable has just a default value for
+all the nnshimbun groups.  You can specify the nnshimbun group
+parameter `prefecth-articles' for each nnshimbun group.")
+
+(defvoo nnshimbun-encapsulate-images shimbun-encapsulate-images
+  "*If it is neither `off' nor nil, inline images will be encapsulated in
+the articles.  Note that this variable has just a default value for
+all the nnshimbun groups.  You can specify the nnshimbun group
+parameter `encapsulate-images' for each nnshimbun group.")
 
-(defvoo nnshimbun-use-entire-index t
-  "*Nil means that nnshimbun check the last index of articles.")
+(defvoo nnshimbun-index-range nil
+  "*Range of indices to detect new pages.  Note that this variable has
+just a default value for all the nnshimbun groups.  You can specify
+the nnshimbun group parameter `index-range' for each nnshimbun group.")
 
-;; set by nnshimbun-possibly-change-group
-(defvoo nnshimbun-buffer nil)
-(defvoo nnshimbun-current-directory nil)
-(defvoo nnshimbun-current-group nil)
 
 ;; set by nnshimbun-open-server
 (defvoo nnshimbun-shimbun nil)
-(defvoo nnshimbun-server-directory nil)
 
 (defvoo nnshimbun-status-string "")
-(defvoo nnshimbun-nov-last-check nil)
-(defvoo nnshimbun-nov-buffer-alist nil)
-(defvoo nnshimbun-nov-buffer-file-name nil)
-
 (defvoo nnshimbun-keep-backlog 300)
 (defvoo nnshimbun-backlog-articles nil)
 (defvoo nnshimbun-backlog-hashtb nil)
 
+
 ;;; backlog
+(defmacro nnshimbun-current-server ()
+  '(nnoo-current-server 'nnshimbun))
+
+(defmacro nnshimbun-server-directory (&optional server)
+  `(nnmail-group-pathname ,(or server '(nnshimbun-current-server))
+                         nnshimbun-directory))
+
+(defmacro nnshimbun-current-group ()
+  '(shimbun-current-group-internal nnshimbun-shimbun))
+
+(defmacro nnshimbun-current-directory (&optional group)
+  `(nnmail-group-pathname ,(or group '(nnshimbun-current-group))
+                         (nnshimbun-server-directory)))
+
 (defmacro nnshimbun-backlog (&rest form)
   `(let ((gnus-keep-backlog nnshimbun-keep-backlog)
-        (gnus-backlog-buffer (format " *nnshimbun backlog %s*" (nnoo-current-server 'nnshimbun)))
+        (gnus-backlog-buffer
+         (format " *nnshimbun backlog %s*" (nnshimbun-current-server)))
         (gnus-backlog-articles nnshimbun-backlog-articles)
         (gnus-backlog-hashtb nnshimbun-backlog-hashtb))
      (unwind-protect
        (setq nnshimbun-backlog-articles gnus-backlog-articles
             nnshimbun-backlog-hashtb gnus-backlog-hashtb))))
 (put 'nnshimbun-backlog 'lisp-indent-function 0)
-(put 'nnshimbun-backlog 'edebug-form-spec '(form body))
+(put 'nnshimbun-backlog 'edebug-form-spec t)
+
+
+;;; Group parameter
+(defmacro nnshimbun-find-parameter (group symbol &optional full-name-p)
+  "Return the value of a nnshimbun group parameter for GROUP which is
+associated with SYMBOL.  If FULL-NAME-P is non-nil, it treats that
+GROUP has a full name."
+  (let ((name (if full-name-p
+                 group
+               `(concat "nnshimbun+" (nnshimbun-current-server) ":" ,group))))
+    (cond ((eq 'index-range (eval symbol))
+          `(or (plist-get (nnshimbun-find-group-parameters ,name)
+                          'index-range)
+               nnshimbun-index-range))
+         ((eq 'prefetch-articles (eval symbol))
+          `(let ((val (or (plist-get (nnshimbun-find-group-parameters ,name)
+                                     'prefetch-articles)
+                          nnshimbun-pre-fetch-article)))
+             (if (eq 'off val)
+                 nil
+               val)))
+         ((eq 'encapsulate-images (eval symbol))
+          `(let ((val (or (plist-get (nnshimbun-find-group-parameters ,name)
+                                     'encapsulate-images)
+                          nnshimbun-encapsulate-images)))
+             (if (eq 'off val)
+                 nil
+               val)))
+         ((eq 'expiry-wait (eval symbol))
+          (if full-name-p
+              `(or (plist-get (nnshimbun-find-group-parameters ,group)
+                              'expiry-wait)
+                   (gnus-group-find-parameter ,group 'expiry-wait))
+            `(let ((name ,name))
+               (or (plist-get (nnshimbun-find-group-parameters name)
+                              'expiry-wait)
+                   (gnus-group-find-parameter name 'expiry-wait)))))
+         (t
+          `(plist-get (nnshimbun-find-group-parameters ,name) ,symbol)))))
 
 
 ;;; Interface Functions
 (nnoo-define-basics nnshimbun)
 
+(defun nnshimbun-possibly-change-group (group &optional server)
+  (when (if server
+           (nnshimbun-open-server server)
+         nnshimbun-shimbun)
+    (or (not group)
+       (when (condition-case err
+                 (shimbun-open-group nnshimbun-shimbun group)
+               (error
+                (nnheader-report 'nnshimbun "%s" (error-message-string err))))
+         (let ((file-name-coding-system nnmail-pathname-coding-system)
+               (pathname-coding-system nnmail-pathname-coding-system)
+               (dir (nnshimbun-current-directory group)))
+           (or (file-directory-p dir)
+               (ignore-errors
+                 (make-directory dir)
+                 (file-directory-p dir))
+               (nnheader-report 'nnshimbun
+                                (if (file-exists-p dir)
+                                    "Not a directory: %s"
+                                  "Couldn't create directory: %s")
+                                dir)))))))
+
 (deffoo nnshimbun-open-server (server &optional defs)
-  (push (list 'nnshimbun-shimbun
-             (condition-case err
-                 (shimbun-open server (luna-make-entity 'shimbun-gnus-mua))
-               (error (nnheader-report 'nnshimbun "%s" (error-message-string err)))))
-       defs)
-  ;; Set directory for server working files.
-  (push (list 'nnshimbun-server-directory
-             (file-name-as-directory
-              (expand-file-name server nnshimbun-directory)))
-       defs)
-  (nnoo-change-server 'nnshimbun server defs)
-  (nnshimbun-possibly-change-group nil server)
-  ;; Make directories.
-  (unless (file-exists-p nnshimbun-directory)
-    (ignore-errors (make-directory nnshimbun-directory t)))
-  (cond
-   ((not (file-exists-p nnshimbun-directory))
-    (nnshimbun-close-server)
-    (nnheader-report 'nnshimbun "Couldn't create directory: %s" nnshimbun-directory))
-   ((not (file-directory-p (file-truename nnshimbun-directory)))
-    (nnshimbun-close-server)
-    (nnheader-report 'nnshimbun "Not a directory: %s" nnshimbun-directory))
-   (t
-    (unless (file-exists-p nnshimbun-server-directory)
-      (ignore-errors (make-directory nnshimbun-server-directory t)))
-    (cond
-     ((not (file-exists-p nnshimbun-server-directory))
-      (nnshimbun-close-server)
-      (nnheader-report 'nnshimbun "Couldn't create directory: %s" nnshimbun-server-directory))
-     ((not (file-directory-p (file-truename nnshimbun-server-directory)))
-      (nnshimbun-close-server)
-      (nnheader-report 'nnshimbun "Not a directory: %s" nnshimbun-server-directory))
-     (t
-      (nnheader-report 'nnshimbun "Opened server %s using directory %s"
-                      server nnshimbun-server-directory)
-      t)))))
+  (or (nnshimbun-server-opened server)
+      (let ((file-name-coding-system nnmail-pathname-coding-system)
+           (pathname-coding-system nnmail-pathname-coding-system)
+           (shimbun))
+       (when (condition-case err
+                 (setq shimbun
+                       (shimbun-open server
+                                     (luna-make-entity 'shimbun-gnus-mua)))
+               (error
+                (nnheader-report 'nnshimbun "%s" (error-message-string err))))
+         (nnoo-change-server 'nnshimbun server
+                             (cons (list 'nnshimbun-shimbun shimbun) defs))
+         (when (or (file-directory-p nnshimbun-directory)
+                   (ignore-errors
+                     (make-directory nnshimbun-directory)
+                     (file-directory-p nnshimbun-directory))
+                   (progn
+                     (nnshimbun-close-server)
+                     (nnheader-report 'nnshimbun
+                                      (if (file-exists-p nnshimbun-directory)
+                                          "Not a directory: %s"
+                                        "Couldn't create directory: %s")
+                                      nnshimbun-directory)))
+           (let ((dir (nnshimbun-server-directory server)))
+             (when (or (file-directory-p dir)
+                       (ignore-errors
+                         (make-directory dir)
+                         (file-directory-p dir))
+                       (progn
+                         (nnshimbun-close-server)
+                         (nnheader-report 'nnshimbun
+                                          (if (file-exists-p dir)
+                                              "Not a directory: %s"
+                                            "Couldn't create directory: %s")
+                                          dir)))
+               (nnheader-report 'nnshimbun
+                                "Opened server %s using directory %s"
+                                server dir)
+               t)))))))
 
 (deffoo nnshimbun-close-server (&optional server)
-  (shimbun-close nnshimbun-shimbun)
-  (and (nnshimbun-server-opened server)
-       (gnus-buffer-live-p nnshimbun-buffer)
-       (kill-buffer nnshimbun-buffer))
+  (when (nnshimbun-server-opened server)
+    (when nnshimbun-shimbun
+      (dolist (group (shimbun-groups nnshimbun-shimbun))
+       (nnshimbun-write-nov group t))
+      (shimbun-close nnshimbun-shimbun)))
   (nnshimbun-backlog (gnus-backlog-shutdown))
-  (nnshimbun-save-nov)
   (nnoo-close-server 'nnshimbun server)
   t)
 
-(eval-and-compile
-  (let ((Gnus-p
-        (eval-when-compile
-          (let ((gnus (locate-library "gnus"))
-                ;; Gnus has mailcap.el in the same directory of gnus.el.
-                (mailcap (locate-library "mailcap")))
-            (and gnus mailcap
-                 (string-equal (file-name-directory gnus)
-                               (file-name-directory mailcap)))))))
-    (if Gnus-p
-       (progn
-         (defmacro nnshimbun-mail-header-subject (header)
-           `(mail-header-subject ,header))
-         (defmacro nnshimbun-mail-header-from (header)
-           `(mail-header-from ,header)))
-      (defmacro nnshimbun-mail-header-subject (header)
-       `(mime-entity-fetch-field ,header 'Subject))
-      (defmacro nnshimbun-mail-header-from (header)
-       `(mime-entity-fetch-field ,header 'From)))))
-
-(defun nnshimbun-make-shimbun-header (header)
-  (shimbun-make-header
-   (mail-header-number header)
-   (nnshimbun-mail-header-subject header)
-   (nnshimbun-mail-header-from header)
-   (mail-header-date header)
-   (or (cdr (assq 'X-Nnshimbun-Id (mail-header-extra header)))
-       (mail-header-id header))
-   (mail-header-references header)
-   (mail-header-chars header)
-   (mail-header-lines header)
-   (let ((xref (mail-header-xref header)))
-     (if (and xref (string-match "^Xref: " xref))
-        (substring xref 6)
-       xref))))
+(eval-when-compile
+  (require 'gnus-sum)) ;; For the macro `gnus-summary-article-header'.
 
 (defun nnshimbun-request-article-1 (article &optional group server to-buffer)
   (if (nnshimbun-backlog
       (cons group article)
     (let* ((header (with-current-buffer (nnshimbun-open-nov group)
                     (and (nnheader-find-nov-line article)
-                         (nnshimbun-make-shimbun-header
-                          (nnheader-parse-nov)))))
+                         (nnshimbun-parse-nov))))
           (original-id (shimbun-header-id header)))
       (when header
        (with-current-buffer (or to-buffer nntp-server-buffer)
-         (delete-region (point-min) (point-max))
-         (shimbun-article nnshimbun-shimbun header)
+         (erase-buffer)
+         (let ((shimbun-encapsulate-images
+                (nnshimbun-find-parameter group 'encapsulate-images)))
+           (shimbun-article nnshimbun-shimbun header))
          (when (> (buffer-size) 0)
+           ;; Kludge! replace a date string in `gnus-newsgroup-data'
+           ;; based on the newly retrieved article.
+           (let ((x (gnus-summary-article-header article)))
+             (when x
+               ;; Trick to suppress byte compile of mail-header-set-date(),
+               ;; in order to keep compatibility between T-gnus and Oort Gnus.
+               (eval
+                `(mail-header-set-date ,x ,(shimbun-header-date header)))))
            (nnshimbun-replace-nov-entry group article header original-id)
            (nnshimbun-backlog
              (gnus-backlog-enter-article group article (current-buffer)))
 
 (deffoo nnshimbun-request-article (article &optional group server to-buffer)
   (when (nnshimbun-possibly-change-group group server)
-    (when (stringp article)
-      (setq article (nnshimbun-search-id group article)))
-    (if (integerp article)
+    (if (or (integerp article)
+           (when (stringp article)
+             (setq article
+                   (or (when (or group (setq group (nnshimbun-current-group)))
+                         (nnshimbun-search-id group article))
+                       (catch 'found
+                         (dolist (x (shimbun-groups nnshimbun-shimbun))
+                           (and (nnshimbun-possibly-change-group x)
+                                (setq x (nnshimbun-search-id x article))
+                                (throw 'found x))))))))
        (nnshimbun-request-article-1 article group server to-buffer)
       (nnheader-report 'nnshimbun "Couldn't retrieve article: %s"
-                      (prin1-to-string article))
-      nil)))
+                      (prin1-to-string article)))))
 
 (deffoo nnshimbun-request-group (group &optional server dont-check)
-  (let ((file-name-coding-system nnmail-pathname-coding-system)
-       (pathname-coding-system nnmail-pathname-coding-system))
-    (cond
-     ((not (nnshimbun-possibly-change-group group server))
-      (nnheader-report 'nnshimbun "Invalid group (no such directory)"))
-     ((not (file-exists-p nnshimbun-current-directory))
-      (nnheader-report 'nnshimbun "Directory %s does not exist"
-                      nnshimbun-current-directory))
-     ((not (file-directory-p nnshimbun-current-directory))
-      (nnheader-report 'nnshimbun "%s is not a directory" nnshimbun-current-directory))
-     (dont-check
-      (nnheader-report 'nnshimbun "Group %s selected" group)
-      t)
-     (t
-      (let (beg end lines)
-       (with-current-buffer (nnshimbun-open-nov group)
-         (goto-char (point-min))
-         (setq beg (ignore-errors (read (current-buffer))))
-         (goto-char (point-max))
-         (forward-line -1)
-         (setq end (ignore-errors (read (current-buffer)))
-               lines (count-lines (point-min) (point-max))))
-       (nnheader-report 'nnshimbunw "Selected group %s" group)
-       (nnheader-insert "211 %d %d %d %s\n"
-                        lines (or beg 0) (or end 0) group))))))
+  (if (not (nnshimbun-possibly-change-group group server))
+      (nnheader-report 'nnshimbun "Invalid group (no such directory)")
+    (let (beg end lines)
+      (with-current-buffer (nnshimbun-open-nov group)
+       (goto-char (point-min))
+       (setq beg (ignore-errors (read (current-buffer))))
+       (goto-char (point-max))
+       (forward-line -1)
+       (setq end (ignore-errors (read (current-buffer)))
+             lines (count-lines (point-min) (point-max))))
+      (nnheader-report 'nnshimbun "Selected group %s" group)
+      (nnheader-insert "211 %d %d %d %s\n"
+                      lines (or beg 0) (or end 0) group))))
 
 (deffoo nnshimbun-request-scan (&optional group server)
-  (nnshimbun-possibly-change-group group server)
-  (nnshimbun-generate-nov-database group))
+  (when (nnshimbun-possibly-change-group nil server)
+    (if group
+       (nnshimbun-generate-nov-database group)
+      (dolist (group (shimbun-groups nnshimbun-shimbun))
+       (nnshimbun-generate-nov-database group)))))
 
 (deffoo nnshimbun-close-group (group &optional server)
   (nnshimbun-write-nov group)
   t)
 
 (deffoo nnshimbun-request-list (&optional server)
-  (with-current-buffer nntp-server-buffer
-    (delete-region (point-min) (point-max))
-    (dolist (group (shimbun-groups nnshimbun-shimbun))
-      (when (nnshimbun-possibly-change-group group server)
-       (let (beg end)
-         (with-current-buffer (nnshimbun-open-nov group)
-           (goto-char (point-min))
-           (setq beg (ignore-errors (read (current-buffer))))
-           (goto-char (point-max))
-           (forward-line -1)
-           (setq end (ignore-errors (read (current-buffer)))))
-         (insert (format "%s %d %d n\n" group (or end 0) (or beg 0)))))))
-  t) ; return value
+  (when (nnshimbun-possibly-change-group nil server)
+    (with-current-buffer nntp-server-buffer
+      (erase-buffer)
+      (dolist (group (shimbun-groups nnshimbun-shimbun))
+       (when (nnshimbun-possibly-change-group group)
+         (let (beg end)
+           (with-current-buffer (nnshimbun-open-nov group)
+             (goto-char (point-min))
+             (setq beg (ignore-errors (read (current-buffer))))
+             (goto-char (point-max))
+             (forward-line -1)
+             (setq end (ignore-errors (read (current-buffer)))))
+           (insert (format "%s %d %d n\n" group (or end 0) (or beg 0)))))))
+    t)) ; return value
 
 (deffoo nnshimbun-retrieve-headers (articles &optional group server fetch-old)
   (when (nnshimbun-possibly-change-group group server)
-    (if (nnshimbun-retrieve-headers-with-nov articles fetch-old)
+    (if (nnshimbun-retrieve-headers-with-nov articles group fetch-old)
        'nov
       (with-current-buffer nntp-server-buffer
-       (delete-region (point-min) (point-max))
+       (erase-buffer)
        (let (header)
          (dolist (art articles)
-           (if (stringp art)
-               (setq art (nnshimbun-search-id group art)))
-           (if (integerp art)
-               (when (setq header
-                           (with-current-buffer (nnshimbun-open-nov group)
-                             (and (nnheader-find-nov-line art)
-                                  (nnheader-parse-nov))))
-                 (insert (format "220 %d Article retrieved.\n" art))
-                 (shimbun-header-insert
-                  nnshimbun-shimbun
-                  (nnshimbun-make-shimbun-header header))
-                 (insert ".\n")
-                 (delete-region (point) (point-max))))))
+           (when (and (if (stringp art)
+                          (setq art (nnshimbun-search-id group art))
+                        (integerp art))
+                      (setq header
+                            (with-current-buffer (nnshimbun-open-nov group)
+                              (and (nnheader-find-nov-line art)
+                                   (nnshimbun-parse-nov)))))
+             (insert (format "220 %d Article retrieved.\n" art))
+             (shimbun-header-insert nnshimbun-shimbun header)
+             (insert ".\n")
+             (delete-region (point) (point-max)))))
        'header))))
 
-(defun nnshimbun-retrieve-headers-with-nov (articles &optional fetch-old)
-  (if (or gnus-nov-is-evil nnshimbun-nov-is-evil)
-      nil
-    (let ((nov (expand-file-name nnshimbun-nov-file-name nnshimbun-current-directory)))
-      (when (file-exists-p nov)
-       (save-excursion
-         (set-buffer nntp-server-buffer)
-         (erase-buffer)
-         (nnheader-insert-file-contents nov)
-         (if (and fetch-old (not (numberp fetch-old)))
-             t                         ; Don't remove anything.
-           (nnheader-nov-delete-outside-range
-            (if fetch-old (max 1 (- (car articles) fetch-old))
-              (car articles))
-            (and articles (nth (1- (length articles)) articles)))
-           t))))))
-
+(defun nnshimbun-retrieve-headers-with-nov (articles &optional group fetch-old)
+  (unless (or gnus-nov-is-evil nnshimbun-nov-is-evil)
+    (with-current-buffer nntp-server-buffer
+      (erase-buffer)
+      (insert-buffer (nnshimbun-open-nov group))
+      (unless (and fetch-old (not (numberp fetch-old)))
+       (nnheader-nov-delete-outside-range
+        (if fetch-old
+            (max 1 (- (car articles) fetch-old))
+          (car articles))
+        (nth (1- (length articles)) articles)))
+      t)))
 
 
 ;;; Nov Database Operations
+(defvar nnshimbun-tmp-string nil
+  "Internal variable used to just a rest for a temporary string.  The
+macro `nnshimbun-string-or' uses it exclusively.")
+
+(defmacro nnshimbun-string-or (&rest strings)
+  "Return the first element of STRINGS that is a non-blank string.  It
+should run fast, especially if two strings are given.  Each string can
+also be nil."
+  (cond ((null strings)
+        nil)
+       ((= 1 (length strings))
+        ;; Return irregularly nil if one blank string is given.
+        `(unless (zerop (length (setq nnshimbun-tmp-string ,(car strings))))
+           nnshimbun-tmp-string))
+       ((= 2 (length strings))
+        ;; Return the second string when the first string is blank.
+        `(if (zerop (length (setq nnshimbun-tmp-string ,(car strings))))
+             ,(cadr strings)
+           nnshimbun-tmp-string))
+       (t
+        `(let ((strings (list ,@strings)))
+           (while strings
+             (setq strings (if (zerop (length (setq nnshimbun-tmp-string
+                                                    (car strings))))
+                               (cdr strings))))
+           nnshimbun-tmp-string))))
+
+(autoload 'message-make-date "message")
 
 (defsubst nnshimbun-insert-nov (number header &optional id)
-  (unless (and (stringp id)
-              (not (string= id (shimbun-header-id header))))
-    (setq id nil))
-  (princ number (current-buffer))
-  (let ((p (point)))
+  (insert "\n")
+  (backward-char 1)
+  (let ((header-id (nnshimbun-string-or (shimbun-header-id header)))
+       ;; Force `princ' to work in the current buffer.
+       (standard-output (current-buffer))
+       (xref (nnshimbun-string-or (shimbun-header-xref header)))
+       (start (point)))
+    (and (stringp id)
+        header-id
+        (string-equal id header-id)
+        (setq id nil))
+    (princ number)
     (insert
      "\t"
-     (or (shimbun-header-subject header) "(none)") "\t"
-     (or (shimbun-header-from header) "(nobody)") "\t"
-     (or (shimbun-header-date header) "") "\t"
-     (or (shimbun-header-id header) (nnmail-message-id)) "\t"
+     (nnshimbun-string-or (shimbun-header-subject header) "(none)") "\t"
+     (nnshimbun-string-or (shimbun-header-from header) "(nobody)") "\t"
+     (nnshimbun-string-or (shimbun-header-date header) (message-make-date))
+     "\t"
+     (or header-id (nnmail-message-id)) "\t"
      (or (shimbun-header-references header) "") "\t")
-    (princ (or (shimbun-header-chars header) 0) (current-buffer))
+    (princ (or (shimbun-header-chars header) 0))
     (insert "\t")
-    (princ (or (shimbun-header-lines header) 0) (current-buffer))
+    (princ (or (shimbun-header-lines header) 0))
     (insert "\t")
-    (when (shimbun-header-xref header)
-      (insert "Xref: " (shimbun-header-xref header)))
-    (when (or (shimbun-header-xref header) id)
-      (insert "\t"))
-    (when id
-      (insert "X-Nnshimbun-Id: " id "\t"))
-    (insert "\n")
-    (backward-char 1)
-    (while (search-backward "\n" p t)
-      (delete-char 1))
+    (if xref
+       (progn
+         (insert "Xref: " xref "\t")
+         (when id
+           (insert "X-Nnshimbun-Id: " id "\t")))
+      (when id
+       (insert "\tX-Nnshimbun-Id: " id "\t")))
+    ;; Replace newlines with spaces in the current NOV line.
+    (while (progn
+            (forward-line 0)
+            (> (point) start))
+      (backward-delete-char 1)
+      (insert " "))
     (forward-line 1)))
 
 (defun nnshimbun-generate-nov-database (group)
-  (nnshimbun-possibly-change-group group)
-  (with-current-buffer (nnshimbun-open-nov group)
-    (goto-char (point-max))
-    (forward-line -1)
-    (let ((i (or (ignore-errors (read (current-buffer))) 0)))
-      (dolist (header (shimbun-headers nnshimbun-shimbun))
-       (unless (nnshimbun-search-id group (shimbun-header-id header))
-         (goto-char (point-max))
-         (nnshimbun-insert-nov (setq i (1+ i)) header)
-         (when nnshimbun-pre-fetch-article
-           (nnshimbun-request-article-1 i group nil nnshimbun-buffer)))))
-  (nnshimbun-write-nov group)))
+  (when (nnshimbun-possibly-change-group group)
+    (with-current-buffer (nnshimbun-open-nov group)
+      (goto-char (point-max))
+      (forward-line -1)
+      (let* ((i (or (ignore-errors (read (current-buffer))) 0))
+            (name (concat "nnshimbun+" (nnshimbun-current-server) ":" group))
+            (pre-fetch (nnshimbun-find-parameter name 'prefetch-articles t)))
+       (dolist (header
+                (shimbun-headers nnshimbun-shimbun
+                                 (nnshimbun-find-parameter name
+                                                           'index-range t)))
+         (unless (nnshimbun-search-id group (shimbun-header-id header))
+           (goto-char (point-max))
+           (nnshimbun-insert-nov (setq i (1+ i)) header)
+           (when pre-fetch
+             (with-temp-buffer
+               (nnshimbun-request-article-1 i group nil (current-buffer)))))))
+      (nnshimbun-write-nov group))))
 
 (defun nnshimbun-replace-nov-entry (group article header &optional id)
   (with-current-buffer (nnshimbun-open-nov group)
       (delete-region (point) (progn (forward-line 1) (point)))
       (nnshimbun-insert-nov article header id))))
 
-(defun nnshimbun-search-id (group id &optional nov)
+(defun nnshimbun-search-id (group id)
   (with-current-buffer (nnshimbun-open-nov group)
     (goto-char (point-min))
     (let (found)
              (forward-line 1)
            (forward-line 0)
            (setq found t))))
-      (if found
-         (if nov
-             (nnheader-parse-nov)
-           ;; We return the article number.
-           (ignore-errors (read (current-buffer))))))))
+      (when found
+       (ignore-errors (read (current-buffer)))))))
+
+;; This function is defined as an alternative of `nnheader-parse-nov',
+;; in order to keep compatibility between T-gnus and Oort Gnus.
+(defun nnshimbun-parse-nov ()
+  (let ((eol (gnus-point-at-eol)))
+    (let ((number  (nnheader-nov-read-integer))
+         (subject (nnheader-nov-field))
+         (from    (nnheader-nov-field))
+         (date    (nnheader-nov-field))
+         (id      (nnheader-nov-read-message-id))
+         (refs    (nnheader-nov-field))
+         (chars   (nnheader-nov-read-integer))
+         (lines   (nnheader-nov-read-integer))
+         (xref    (unless (eq (char-after) ?\n)
+                    (when (looking-at "Xref: ")
+                      (goto-char (match-end 0)))
+                    (nnheader-nov-field)))
+         (extra   (nnheader-nov-parse-extra)))
+      (shimbun-make-header number subject from date
+                          (or (cdr (assq 'X-Nnshimbun-Id extra)) id)
+                          refs chars lines xref))))
+
+(defsubst nnshimbun-nov-buffer-name (&optional group)
+  (format " *nnshimbun overview %s %s*"
+         (nnshimbun-current-server)
+         (or group (nnshimbun-current-group))))
+
+(defsubst nnshimbun-nov-file-name (&optional group)
+  (nnmail-group-pathname (or group (nnshimbun-current-group))
+                        (nnshimbun-server-directory)
+                        nnshimbun-nov-file-name))
 
 (defun nnshimbun-open-nov (group)
-  (let ((buffer (cdr (assoc group nnshimbun-nov-buffer-alist))))
-    (if (buffer-live-p buffer)
-       buffer
-      (setq buffer (gnus-get-buffer-create
-                   (format " *nnshimbun overview %s %s*"
-                           (nnoo-current-server 'nnshimbun) group)))
-      (save-excursion
-       (set-buffer buffer)
-       (set (make-local-variable 'nnshimbun-nov-buffer-file-name)
-            (expand-file-name
-             nnshimbun-nov-file-name
-             (nnmail-group-pathname group nnshimbun-server-directory)))
+  (let ((buffer (nnshimbun-nov-buffer-name group)))
+    (unless (gnus-buffer-live-p buffer)
+      (with-current-buffer (gnus-get-buffer-create buffer)
        (erase-buffer)
-       (when (file-exists-p nnshimbun-nov-buffer-file-name)
-         (nnheader-insert-file-contents nnshimbun-nov-buffer-file-name))
-       (set-buffer-modified-p nil))
-      (push (cons group buffer) nnshimbun-nov-buffer-alist)
-      buffer)))
-
-(defun nnshimbun-write-nov (group)
-  (let ((buffer (cdr (assoc group nnshimbun-nov-buffer-alist))))
-    (when (buffer-live-p buffer)
-      (save-excursion
-       (set-buffer buffer)
-       (buffer-modified-p)
-       (nnmail-write-region 1 (point-max) nnshimbun-nov-buffer-file-name
-                            nil 'nomesg)))))
-
-(defun nnshimbun-save-nov ()
-  (save-excursion
-    (while nnshimbun-nov-buffer-alist
-      (when (buffer-name (cdar nnshimbun-nov-buffer-alist))
-       (set-buffer (cdar nnshimbun-nov-buffer-alist))
-       (when (buffer-modified-p)
-         (nnmail-write-region 1 (point-max) nnshimbun-nov-buffer-file-name
-                              nil 'nomesg))
-       (set-buffer-modified-p nil)
-       (kill-buffer (current-buffer)))
-      (setq nnshimbun-nov-buffer-alist (cdr nnshimbun-nov-buffer-alist)))))
-
-
-
-;;; Server Initialize
-
-(defun nnshimbun-possibly-change-group (group &optional server)
-  (when server
-    (unless (nnshimbun-server-opened server)
-      (nnshimbun-open-server server)))
-  (unless (gnus-buffer-live-p nnshimbun-buffer)
-    (setq nnshimbun-buffer
-         (save-excursion
-           (nnheader-set-temp-buffer
-            (format " *nnshimbun %s*" (nnoo-current-server 'nnshimbun))))))
-  (if (not group)
-      t
-    (condition-case err
-       (shimbun-open-group nnshimbun-shimbun group)
-      (error (nnheader-report 'nnshimbun "%s" (error-message-string err))))
-    (let ((pathname (nnmail-group-pathname group nnshimbun-server-directory))
-         (file-name-coding-system nnmail-pathname-coding-system)
-         (pathname-coding-system nnmail-pathname-coding-system))
-      (unless (equal pathname nnshimbun-current-directory)
-       (setq nnshimbun-current-directory pathname
-             nnshimbun-current-group group))
-      (unless (file-exists-p nnshimbun-current-directory)
-       (ignore-errors (make-directory nnshimbun-current-directory t)))
-      (cond
-       ((not (file-exists-p nnshimbun-current-directory))
-       (nnheader-report 'nnshimbun "Couldn't create directory: %s" nnshimbun-current-directory))
-       ((not (file-directory-p (file-truename nnshimbun-current-directory)))
-       (nnheader-report 'nnshimbun "Not a directory: %s" nnshimbun-current-directory))
-       (t t)))))
-
+       (let ((file-name-coding-system nnmail-pathname-coding-system)
+             (pathname-coding-system nnmail-pathname-coding-system)
+             (nov (nnshimbun-nov-file-name group)))
+         (when (file-exists-p nov)
+           (nnheader-insert-file-contents nov)))
+       (set-buffer-modified-p nil)))
+    buffer))
+
+(defun nnshimbun-write-nov (group &optional close)
+  (let ((buffer (nnshimbun-nov-buffer-name group)))
+    (when (gnus-buffer-live-p buffer)
+      (with-current-buffer buffer
+       (let ((file-name-coding-system nnmail-pathname-coding-system)
+             (pathname-coding-system nnmail-pathname-coding-system)
+             (nov (nnshimbun-nov-file-name group)))
+         (when (and (buffer-modified-p)
+                    (or (> (buffer-size) 0)
+                        (file-exists-p nov)))
+           (nnmail-write-region 1 (point-max) nov nil 'nomesg)
+           (set-buffer-modified-p nil))))
+      (when close
+       (kill-buffer buffer)))))
+
+(deffoo nnshimbun-request-expire-articles (articles group
+                                                   &optional server force)
+  "Do expiration for the specified ARTICLES in the nnshimbun GROUP.
+Notice that nnshimbun does not actually delete any articles, it just
+delete the corresponding entries in the NOV database locally.  The
+optional fourth argument FORCE is ignored."
+  (when (nnshimbun-possibly-change-group group server)
+    (let* ((expirable (copy-sequence articles))
+          (name (concat "nnshimbun+" (nnshimbun-current-server) ":" group))
+          ;; If the group's parameter `expiry-wait' is non-nil, the
+          ;; value of the option `nnmail-expiry-wait' will be bound
+          ;; to that value, and the value of the option
+          ;; `nnmail-expiry-wait-function' will be bound to nil.  See
+          ;; the source code of `gnus-summary-expire-articles' how
+          ;; does it work.  If the group's parameter is not specified
+          ;; by user, the shimbun's default value will be used.
+          (expiry-wait
+           (or (nnshimbun-find-parameter name 'expiry-wait t)
+               (shimbun-article-expiration-days nnshimbun-shimbun)))
+          (nnmail-expiry-wait (or expiry-wait nnmail-expiry-wait))
+          (nnmail-expiry-wait-function (if expiry-wait
+                                           nil
+                                         nnmail-expiry-wait-function))
+          article end time)
+      (with-current-buffer (nnshimbun-open-nov group)
+       (while expirable
+         (setq article (pop expirable))
+         (when (and (nnheader-find-nov-line article)
+                    (setq end (gnus-point-at-eol))
+                    (not (= (point-max) (1+ end))))
+           (setq time (and (search-forward "\t" end t)
+                           (search-forward "\t" end t)
+                           (search-forward "\t" end t)
+                           (parse-time-string
+                            (buffer-substring
+                             (point)
+                             (if (search-forward "\t" end t)
+                                 (1- (point))
+                               end)))))
+           (when (if (setq time (condition-case nil
+                                    (apply 'encode-time time)
+                                  (error nil)))
+                     (nnmail-expired-article-p name time nil)
+                   ;; Inhibit expiration if there's no parsable date
+                   ;; and the following option is non-nil.
+                   (not nnshimbun-keep-unparsable-dated-articles))
+             (forward-line 0)
+             (delete-region (point) (1+ end))
+             (setq articles (delq article articles)))))
+       (nnshimbun-write-nov group))
+      articles)))
 
 
 ;;; shimbun-gnus-mua
    (shimbun-current-group-internal (shimbun-mua-shimbun-internal mua))
    id))
 
-(luna-define-method shimbun-mua-use-entire-index ((mua shimbun-gnus-mua))
-  nnshimbun-use-entire-index)
+
+;;; Command to create nnshimbun group
+(defvar nnshimbun-server-history nil)
+
+;;;###autoload
+(defun gnus-group-make-shimbun-group ()
+  "Create a nnshimbun group."
+  (interactive)
+  (let* ((minibuffer-setup-hook
+         (append minibuffer-setup-hook '(beginning-of-line)))
+        (alist
+         (apply 'nconc
+                (mapcar
+                 (lambda (d)
+                   (and (stringp d)
+                        (file-directory-p d)
+                        (delq nil
+                              (mapcar
+                               (lambda (f)
+                                 (and (string-match "^sb-\\(.*\\)\\.el$" f)
+                                      (list (match-string 1 f))))
+                               (directory-files d)))))
+                 load-path)))
+        (server (completing-read
+                 "Shimbun address: "
+                 alist nil t
+                 (or (car nnshimbun-server-history)
+                     (caar alist))
+                 'nnshimbun-server-history))
+        (groups)
+        (nnshimbun-pre-fetch-article))
+    (if (setq groups (shimbun-groups (shimbun-open server)))
+       (gnus-group-make-group
+        (completing-read "Group name: " (mapcar 'list groups) nil t nil)
+        (list 'nnshimbun server))
+      (error "%s" "Can't find group"))))
 
 
 (provide 'nnshimbun)
-;;; nnshimbun.el ends here.
+
+;;; nnshimbun.el ends here