*** empty log message ***
[gnus] / lisp / nnml.el
index 25dc798..ed3e365 100644 (file)
@@ -1,7 +1,7 @@
 ;;; nnml.el --- mail spool access for Gnus
-;; Copyright (C) 1995,96 Free Software Foundation, Inc.
+;; Copyright (C) 1995,96,97,98,99 Free Software Foundation, Inc.
 
-;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
 ;;     Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
 ;; Keywords: news, mail
 
 
 ;; Based on nnspool.el by Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>.
 ;; For an overview of what the interface functions do, please see the
-;; Gnus sources.  
+;; Gnus sources.
 
 ;;; Code:
 
 (require 'nnheader)
 (require 'nnmail)
 (require 'nnoo)
-(require 'cl)
+(eval-when-compile (require 'cl))
 
 (nnoo-declare nnml)
 
 (defvoo nnml-directory message-directory
-  "Mail spool directory.")
+  "Spool directory for the nnml mail backend.")
 
-(defvoo nnml-active-file 
+(defvoo nnml-active-file
   (concat (file-name-as-directory nnml-directory) "active")
   "Mail active file.")
 
-(defvoo nnml-newsgroups-file 
+(defvoo nnml-newsgroups-file
   (concat (file-name-as-directory nnml-directory) "newsgroups")
   "Mail newsgroups description file.")
 
@@ -84,68 +84,66 @@ all.  This may very well take some time.")
 
 (defvoo nnml-generate-active-function 'nnml-generate-active-info)
 
+(defvar nnml-nov-buffer-file-name nil)
+
+(defvoo nnml-file-coding-system nnmail-file-coding-system-1)
+
 \f
 
 ;;; Interface functions.
 
 (nnoo-define-basics nnml)
 
-(deffoo nnml-retrieve-headers (sequence &optional newsgroup server fetch-old)
-  (save-excursion
-    (set-buffer nntp-server-buffer)
-    (erase-buffer)
-    (let ((file nil)
-         (number (length sequence))
-         (count 0)
-         beg article)
-      (if (stringp (car sequence))
-         'headers
-       (nnml-possibly-change-directory newsgroup server)
-       (unless nnml-article-file-alist
-         (setq nnml-article-file-alist
-               (nnheader-article-to-file-alist nnml-current-directory)))
-       (if (nnml-retrieve-headers-with-nov sequence fetch-old)
-           'nov
-         (while sequence
-           (setq article (car sequence))
-           (setq file 
-                 (concat nnml-current-directory 
-                         (or (cdr (assq article nnml-article-file-alist))
-                             "")))
-           (when (and (file-exists-p file)
-                      (not (file-directory-p file)))
-             (insert (format "221 %d Article retrieved.\n" article))
-             (setq beg (point))
-             (nnheader-insert-head file)
-             (goto-char beg)
-             (if (search-forward "\n\n" nil t)
-                 (forward-char -1)
-               (goto-char (point-max))
-               (insert "\n\n"))
-             (insert ".\n")
-             (delete-region (point) (point-max)))
-           (setq sequence (cdr sequence))
-           (setq count (1+ count))
+(deffoo nnml-retrieve-headers (sequence &optional group server fetch-old)
+  (when (nnml-possibly-change-directory group server)
+    (save-excursion
+      (set-buffer nntp-server-buffer)
+      (erase-buffer)
+      (let ((file nil)
+           (number (length sequence))
+           (count 0)
+           (pathname-coding-system 'binary)
+           beg article)
+       (if (stringp (car sequence))
+           'headers
+         (if (nnml-retrieve-headers-with-nov sequence fetch-old)
+             'nov
+           (while sequence
+             (setq article (car sequence))
+             (setq file (nnml-article-to-file article))
+             (when (and file
+                        (file-exists-p file)
+                        (not (file-directory-p file)))
+               (insert (format "221 %d Article retrieved.\n" article))
+               (setq beg (point))
+               (nnheader-insert-head file)
+               (goto-char beg)
+               (if (search-forward "\n\n" nil t)
+                   (forward-char -1)
+                 (goto-char (point-max))
+                 (insert "\n\n"))
+               (insert ".\n")
+               (delete-region (point) (point-max)))
+             (setq sequence (cdr sequence))
+             (setq count (1+ count))
+             (and (numberp nnmail-large-newsgroup)
+                  (> number nnmail-large-newsgroup)
+                  (zerop (% count 20))
+                  (nnheader-message 6 "nnml: Receiving headers... %d%%"
+                                    (/ (* count 100) number))))
+
            (and (numberp nnmail-large-newsgroup)
                 (> number nnmail-large-newsgroup)
-                (zerop (% count 20))
-                (nnheader-message 6 "nnml: Receiving headers... %d%%"
-                                  (/ (* count 100) number))))
-
-         (and (numberp nnmail-large-newsgroup)
-              (> number nnmail-large-newsgroup)
-              (nnheader-message 6 "nnml: Receiving headers...done"))
+                (nnheader-message 6 "nnml: Receiving headers...done"))
 
-         (nnheader-fold-continuation-lines)
-         'headers)))))
+           (nnheader-fold-continuation-lines)
+           'headers))))))
 
 (deffoo nnml-open-server (server &optional defs)
   (nnoo-change-server 'nnml server defs)
   (when (not (file-exists-p nnml-directory))
-    (condition-case ()
-       (make-directory nnml-directory t)
-      (error)))
-  (cond 
+    (ignore-errors (make-directory nnml-directory t)))
+  (cond
    ((not (file-exists-p nnml-directory))
     (nnml-close-server)
     (nnheader-report 'nnml "Couldn't create directory: %s" nnml-directory))
@@ -159,11 +157,13 @@ all.  This may very well take some time.")
 
 (defun nnml-request-regenerate (server)
   (nnml-possibly-change-directory nil server)
-  (nnml-generate-nov-databases))
+  (nnml-generate-nov-databases)
+  t)
 
-(deffoo nnml-request-article (id &optional newsgroup server buffer)
-  (nnml-possibly-change-directory newsgroup server)
+(deffoo nnml-request-article (id &optional group server buffer)
+  (nnml-possibly-change-directory group server)
   (let* ((nntp-server-buffer (or buffer nntp-server-buffer))
+        (pathname-coding-system 'binary)
         path gpath group-num)
     (if (stringp id)
        (when (and (setq group-num (nnml-find-group-number id))
@@ -176,45 +176,50 @@ all.  This may very well take some time.")
                                  nnml-directory))))))
          (setq path (concat gpath (int-to-string (cdr group-num)))))
       (setq path (nnml-article-to-file id)))
-    (cond 
+    (cond
      ((not path)
       (nnheader-report 'nnml "No such article: %s" id))
      ((not (file-exists-p path))
       (nnheader-report 'nnml "No such file: %s" path))
      ((file-directory-p path)
       (nnheader-report 'nnml "File is a directory: %s" path))
-     ((not (save-excursion (nnmail-find-file path)))
+     ((not (save-excursion (let ((nnmail-file-coding-system
+                                 nnml-file-coding-system))
+                            (nnmail-find-file path))))
       (nnheader-report 'nnml "Couldn't read file: %s" path))
      (t
       (nnheader-report 'nnml "Article %s retrieved" id)
       ;; We return the article number.
-      (cons newsgroup (string-to-int (file-name-nondirectory path)))))))
+      (cons (if group-num (car group-num) group)
+           (string-to-int (file-name-nondirectory path)))))))
 
 (deffoo nnml-request-group (group &optional server dont-check)
-  (cond 
-   ((not (nnml-possibly-change-directory group server))
-    (nnheader-report 'nnml "Invalid group (no such directory)"))
-   ((not (file-exists-p nnml-current-directory))
-    (nnheader-report 'nnml "Directory %s does not exist"
-                    nnml-current-directory))
-   ((not (file-directory-p nnml-current-directory))
-    (nnheader-report 'nnml "%s is not a directory" nnml-current-directory))
-   (dont-check 
-    (nnheader-report 'nnml "Group %s selected" group)
-    t)
-   (t
-    (nnheader-re-read-dir nnml-current-directory)
-    (nnmail-activate 'nnml)
-    (let ((active (nth 1 (assoc group nnml-group-alist))))
-      (if (not active)
-         (nnheader-report 'nnml "No such group: %s" group)
-       (nnheader-report 'nnml "Selected group %s" group)
-       (nnheader-insert "211 %d %d %d %s\n" 
-                        (max (1+ (- (cdr active) (car active))) 0)
-                        (car active) (cdr active) group))))))
+  (let ((pathname-coding-system 'binary))
+    (cond
+     ((not (nnml-possibly-change-directory group server))
+      (nnheader-report 'nnml "Invalid group (no such directory)"))
+     ((not (file-exists-p nnml-current-directory))
+      (nnheader-report 'nnml "Directory %s does not exist"
+                      nnml-current-directory))
+     ((not (file-directory-p nnml-current-directory))
+      (nnheader-report 'nnml "%s is not a directory" nnml-current-directory))
+     (dont-check
+      (nnheader-report 'nnml "Group %s selected" group)
+      t)
+     (t
+      (nnheader-re-read-dir nnml-current-directory)
+      (nnmail-activate 'nnml)
+      (let ((active (nth 1 (assoc group nnml-group-alist))))
+       (if (not active)
+           (nnheader-report 'nnml "No such group: %s" group)
+         (nnheader-report 'nnml "Selected group %s" group)
+         (nnheader-insert "211 %d %d %d %s\n"
+                          (max (1+ (- (cdr active) (car active))) 0)
+                          (car active) (cdr active) group)))))))
 
 (deffoo nnml-request-scan (&optional group server)
   (setq nnml-article-file-alist nil)
+  (nnml-possibly-change-directory group server)
   (nnmail-get-new-mail 'nnml 'nnml-save-nov nnml-directory group))
 
 (deffoo nnml-close-group (group &optional server)
@@ -222,8 +227,16 @@ all.  This may very well take some time.")
   t)
 
 (deffoo nnml-request-create-group (group &optional server args)
+  (nnml-possibly-change-directory nil server)
   (nnmail-activate 'nnml)
-  (unless (assoc group nnml-group-alist)
+  (cond
+   ((assoc group nnml-group-alist)
+    t)
+   ((and (file-exists-p (nnmail-group-pathname group nnml-directory))
+        (not (file-directory-p (nnmail-group-pathname group nnml-directory))))
+    (nnheader-report 'nnml "%s is a file"
+                    (nnmail-group-pathname group nnml-directory)))
+   (t
     (let (active)
       (push (list group (setq active (cons 1 0)))
            nnml-group-alist)
@@ -233,12 +246,14 @@ all.  This may very well take some time.")
        (when articles
          (setcar active (apply 'min articles))
          (setcdr active (apply 'max articles))))
-      (nnmail-save-active nnml-group-alist nnml-active-file)))
-  t)
+      (nnmail-save-active nnml-group-alist nnml-active-file)
+      t))))
 
 (deffoo nnml-request-list (&optional server)
   (save-excursion
-    (nnmail-find-file nnml-active-file)
+    (let ((nnmail-file-coding-system nnmail-active-file-coding-system)
+         (pathname-coding-system 'binary))
+      (nnmail-find-file nnml-active-file))
     (setq nnml-group-alist (nnmail-get-active))
     t))
 
@@ -249,56 +264,53 @@ all.  This may very well take some time.")
   (save-excursion
     (nnmail-find-file nnml-newsgroups-file)))
 
-(deffoo nnml-request-expire-articles (articles newsgroup &optional server force)
-  (nnml-possibly-change-directory newsgroup server)
-  (let* ((active-articles 
-         (nnheader-directory-articles nnml-current-directory))
-        (is-old t)
-        article rest mod-time number)
+(deffoo nnml-request-expire-articles (articles group
+                                              &optional server force)
+  (nnml-possibly-change-directory group server)
+  (let ((active-articles
+        (nnheader-directory-articles nnml-current-directory))
+       (is-old t)
+       article rest mod-time number)
     (nnmail-activate 'nnml)
 
-    (unless nnml-article-file-alist
-      (setq nnml-article-file-alist
-           (nnheader-article-to-file-alist nnml-current-directory)))
+    (setq active-articles (sort active-articles '<))
+    ;; Articles not listed in active-articles are already gone,
+    ;; so don't try to expire them.
+    (setq articles (gnus-sorted-intersection articles active-articles))
 
     (while (and articles is-old)
-      (setq article (concat nnml-current-directory 
-                           (int-to-string 
-                            (setq number (pop articles)))))
-      (when (setq mod-time (nth 5 (file-attributes article)))
-       (if (and (nnml-deletable-article-p newsgroup number)
-                (setq is-old 
-                      (nnmail-expired-article-p newsgroup mod-time force
-                                                nnml-inhibit-expiry)))
-           (progn
-             (nnheader-message 5 "Deleting article %s in %s..."
-                               article newsgroup)
-             (condition-case ()
-                 (funcall nnmail-delete-file-function article)
-               (file-error
-                (push number rest)))
-             (setq active-articles (delq number active-articles))
-             (nnml-nov-delete-article newsgroup number))
-         (push number rest))))
-    (let ((active (nth 1 (assoc newsgroup nnml-group-alist))))
+      (when (setq article (nnml-article-to-file (setq number (pop articles))))
+       (when (setq mod-time (nth 5 (file-attributes article)))
+         (if (and (nnml-deletable-article-p group number)
+                  (setq is-old
+                        (nnmail-expired-article-p group mod-time force
+                                                  nnml-inhibit-expiry)))
+             (progn
+               (nnheader-message 5 "Deleting article %s in %s"
+                                 article group)
+               (condition-case ()
+                   (funcall nnmail-delete-file-function article)
+                 (file-error
+                  (push number rest)))
+               (setq active-articles (delq number active-articles))
+               (nnml-nov-delete-article group number))
+           (push number rest)))))
+    (let ((active (nth 1 (assoc group nnml-group-alist))))
       (when active
        (setcar active (or (and active-articles
                                (apply 'min active-articles))
                           (1+ (cdr active)))))
       (nnmail-save-active nnml-group-alist nnml-active-file))
     (nnml-save-nov)
-    (message "")
     (nconc rest articles)))
 
-(deffoo nnml-request-move-article 
+(deffoo nnml-request-move-article
   (article group server accept-form &optional last)
   (let ((buf (get-buffer-create " *nnml move*"))
        result)
     (nnml-possibly-change-directory group server)
-    (unless nnml-article-file-alist
-      (setq nnml-article-file-alist
-           (nnheader-article-to-file-alist nnml-current-directory)))
-    (and 
+    (nnml-update-file-alist)
+    (and
      (nnml-deletable-article-p group article)
      (nnml-request-article article group server)
      (save-excursion
@@ -311,32 +323,39 @@ all.  This may very well take some time.")
        (nnml-possibly-change-directory group server)
        (condition-case ()
           (funcall nnmail-delete-file-function
-                   (concat nnml-current-directory 
-                           (int-to-string article)))
+                   (nnml-article-to-file  article))
         (file-error nil))
        (nnml-nov-delete-article group article)
-       (and last (nnml-save-nov))))
+       (when last
+        (nnml-save-nov)
+        (nnmail-save-active nnml-group-alist nnml-active-file))))
     result))
 
 (deffoo nnml-request-accept-article (group &optional server last)
   (nnml-possibly-change-directory group server)
   (nnmail-check-syntax)
   (let (result)
+    (when nnmail-cache-accepted-message-ids
+      (nnmail-cache-insert (nnmail-fetch-field "message-id")))
     (if (stringp group)
-       (and 
+       (and
         (nnmail-activate 'nnml)
-        (setq result (car (nnml-save-mail 
+        (setq result (car (nnml-save-mail
                            (list (cons group (nnml-active-number group))))))
         (progn
           (nnmail-save-active nnml-group-alist nnml-active-file)
           (and last (nnml-save-nov))))
       (and
        (nnmail-activate 'nnml)
-       (setq result (car (nnml-save-mail
-                         (nnmail-article-group 'nnml-active-number))))
-       (progn
+       (if (and (not (setq result (nnmail-article-group 'nnml-active-number)))
+               (yes-or-no-p "Moved to `junk' group; delete article? "))
+          (setq result 'junk)
+        (setq result (car (nnml-save-mail result))))
+       (when last
         (nnmail-save-active nnml-group-alist nnml-active-file)
-        (and last (nnml-save-nov)))))
+        (when nnmail-cache-accepted-message-ids
+          (nnmail-cache-close))
+        (nnml-save-nov))))
     result))
 
 (deffoo nnml-request-replace-article (article group buffer)
@@ -347,18 +366,17 @@ all.  This may very well take some time.")
     (let ((chars (nnmail-insert-lines))
          (art (concat (int-to-string article) "\t"))
          headers)
-      (when (condition-case ()
-               (progn
-                 (nnmail-write-region 
-                  (point-min) (point-max)
+      (when (ignore-errors
+             (nnmail-write-region
+              (point-min) (point-max)
+              (or (nnml-article-to-file article)
                   (concat nnml-current-directory
-                          (int-to-string article))
-                  nil (if (nnheader-be-verbose 5) nil 'nomesg))
-                 t)
-             (error nil))
+                          (int-to-string article)))
+              nil (if (nnheader-be-verbose 5) nil 'nomesg))
+             t)
        (setq headers (nnml-parse-head chars article))
        ;; Replace the NOV line in the NOV file.
-       (save-excursion 
+       (save-excursion
          (set-buffer (nnml-open-nov group))
          (goto-char (point-min))
          (if (or (looking-at art)
@@ -370,8 +388,8 @@ all.  This may very well take some time.")
            ;; we should insert it.  (This situation should never
            ;; occur, but one likes to make sure...)
            (while (and (looking-at "[0-9]+\t")
-                       (< (string-to-int 
-                           (buffer-substring 
+                       (< (string-to-int
+                           (buffer-substring
                             (match-beginning 0) (match-end 0)))
                           article)
                        (zerop (forward-line 1)))))
@@ -384,23 +402,21 @@ all.  This may very well take some time.")
   (nnml-possibly-change-directory group server)
   (when force
     ;; Delete all articles in GROUP.
-    (let ((articles 
-          (directory-files 
+    (let ((articles
+          (directory-files
            nnml-current-directory t
            (concat nnheader-numerical-short-files
                    "\\|" (regexp-quote nnml-nov-file-name) "$")))
          article)
-      (while articles 
+      (while articles
        (setq article (pop articles))
        (when (file-writable-p article)
          (nnheader-message 5 "Deleting article %s in %s..." article group)
          (funcall nnmail-delete-file-function article))))
     ;; Try to delete the directory itself.
-    (condition-case ()
-       (delete-directory nnml-current-directory)
-      (error nil)))
+    (ignore-errors (delete-directory nnml-current-directory)))
   ;; Remove the group from all structures.
-  (setq nnml-group-alist 
+  (setq nnml-group-alist
        (delq (assoc group nnml-group-alist) nnml-group-alist)
        nnml-current-group nil
        nnml-current-directory nil)
@@ -412,17 +428,15 @@ all.  This may very well take some time.")
   (nnml-possibly-change-directory group server)
   (let ((new-dir (nnmail-group-pathname new-name nnml-directory))
        (old-dir (nnmail-group-pathname group nnml-directory)))
-    (when (condition-case ()
-             (progn
-               (make-directory new-dir t)
-               t)
-           (error nil))
+    (when (ignore-errors
+           (make-directory new-dir t)
+           t)
       ;; We move the articles file by file instead of renaming
       ;; the directory -- there may be subgroups in this group.
       ;; One might be more clever, I guess.
       (let ((files (nnheader-article-to-file-alist old-dir)))
        (while files
-         (rename-file 
+         (rename-file
           (concat old-dir (cdar files))
           (concat new-dir (cdar files)))
          (pop files)))
@@ -431,9 +445,7 @@ all.  This may very well take some time.")
        (when (file-exists-p overview)
          (rename-file overview (concat new-dir nnml-nov-file-name))))
       (when (<= (length (directory-files old-dir)) 2)
-       (condition-case ()
-           (delete-directory old-dir)
-         (error nil)))
+       (ignore-errors (delete-directory old-dir)))
       ;; That went ok, so we change the internal structures.
       (let ((entry (assoc group nnml-group-alist)))
        (when entry
@@ -451,7 +463,7 @@ all.  This may very well take some time.")
      ((not (file-exists-p file))
       (nnheader-report 'nnml "File %s does not exist" file))
      (t
-      (nnheader-temp-write file
+      (with-temp-file file
        (nnheader-insert-file-contents file)
        (nnmail-replace-status name value))
       t))))
@@ -460,33 +472,36 @@ all.  This may very well take some time.")
 ;;; Internal functions.
 
 (defun nnml-article-to-file (article)
-  (unless nnml-article-file-alist
-    (setq nnml-article-file-alist
-         (nnheader-article-to-file-alist nnml-current-directory)))
+  (nnml-update-file-alist)
   (let (file)
-    (when (setq file (cdr (assq article nnml-article-file-alist)))
-      (concat nnml-current-directory file))))
+    (if (setq file (cdr (assq article nnml-article-file-alist)))
+       (concat nnml-current-directory file)
+      ;; Just to make sure nothing went wrong when reading over NFS --
+      ;; check once more.
+      (when (file-exists-p
+            (setq file (expand-file-name (number-to-string article)
+                                         nnml-current-directory)))
+       (nnml-update-file-alist t)
+       file))))
 
 (defun nnml-deletable-article-p (group article)
   "Say whether ARTICLE in GROUP can be deleted."
-  (let (file path)
-    (when (setq file (cdr (assq article nnml-article-file-alist)))
-      (setq path (concat nnml-current-directory file))
+  (let (path)
+    (when (setq path (nnml-article-to-file article))
       (when (file-writable-p path)
        (or (not nnmail-keep-last-article)
            (not (eq (cdr (nth 1 (assoc group nnml-group-alist)))
                     article)))))))
 
-;; Find an article number in the current group given the Message-ID. 
+;; Find an article number in the current group given the Message-ID.
 (defun nnml-find-group-number (id)
   (save-excursion
     (set-buffer (get-buffer-create " *nnml id*"))
-    (buffer-disable-undo (current-buffer))
     (let ((alist nnml-group-alist)
          number)
       ;; We want to look through all .overview files, but we want to
       ;; start with the one in the current directory.  It seems most
-      ;; likely that the article we are looking for is in that group. 
+      ;; likely that the article we are looking for is in that group.
       (if (setq number (nnml-find-id nnml-current-group id))
          (cons nnml-current-group number)
        ;; It wasn't there, so we look through the other groups as well.
@@ -505,7 +520,7 @@ all.  This may very well take some time.")
                     nnml-nov-file-name))
        number found)
     (when (file-exists-p nov)
-      (insert-file-contents nov)
+      (nnheader-insert-file-contents nov)
       (while (and (not found)
                  (search-forward id nil t)) ; We find the ID.
        ;; And the id is in the fourth field.
@@ -516,64 +531,47 @@ all.  This may very well take some time.")
          (setq found t)
          ;; We return the article number.
          (setq number
-               (condition-case ()
-                   (read (current-buffer))
-                 (error nil)))))
+               (ignore-errors (read (current-buffer))))))
       number)))
 
 (defun nnml-retrieve-headers-with-nov (articles &optional fetch-old)
   (if (or gnus-nov-is-evil nnml-nov-is-evil)
       nil
-    (let ((first (car articles))
-         (last (progn (while (cdr articles) (setq articles (cdr articles)))
-                      (car articles)))
-         (nov (concat nnml-current-directory nnml-nov-file-name)))
+    (let ((nov (concat nnml-current-directory nnml-nov-file-name)))
       (when (file-exists-p nov)
        (save-excursion
          (set-buffer nntp-server-buffer)
          (erase-buffer)
-         (insert-file-contents nov)
+         (nnheader-insert-file-contents nov)
          (if (and fetch-old
                   (not (numberp fetch-old)))
              t                         ; Don't remove anything.
-           (when fetch-old
-             (setq first (max 1 (- first fetch-old))))
-           (goto-char (point-min))
-           (while (and (not (eobp)) (> first (read (current-buffer))))
-             (forward-line 1))
-           (beginning-of-line)
-           (when (not (eobp))
-             (delete-region 1 (point)))
-           (while (and (not (eobp)) (>= last (read (current-buffer))))
-             (forward-line 1))
-           (beginning-of-line)
-           (when (not (eobp))
-             (delete-region (point) (point-max)))
+           (nnheader-nov-delete-outside-range
+            (if fetch-old (max 1 (- (car articles) fetch-old))
+              (car articles))
+            (car (last articles)))
            t))))))
 
 (defun nnml-possibly-change-directory (group &optional server)
   (when (and server
             (not (nnml-server-opened server)))
     (nnml-open-server server))
-  (when group
-    (let ((pathname (nnmail-group-pathname group nnml-directory)))
+  (if (not group)
+      t
+    (let ((pathname (nnmail-group-pathname group nnml-directory))
+         (pathname-coding-system 'binary))
       (when (not (equal pathname nnml-current-directory))
        (setq nnml-current-directory pathname
              nnml-current-group group
-             nnml-article-file-alist nil))))
-  t)
+             nnml-article-file-alist nil))
+      (file-exists-p nnml-current-directory))))
 
 (defun nnml-possibly-create-directory (group)
-  (let (dir dirs)
-    (setq dir (nnmail-group-pathname group nnml-directory))
-    (while (not (file-directory-p dir))
-      (push dir dirs)
-      (setq dir (file-name-directory (directory-file-name dir))))
-    (while dirs
-      (make-directory (directory-file-name (car dirs)))
-      (nnheader-message 5 "Creating mail directory %s" (car dirs))
-      (setq dirs (cdr dirs)))))
-            
+  (let ((dir (nnmail-group-pathname group nnml-directory)))
+    (unless (file-exists-p dir)
+      (make-directory (directory-file-name dir) t)
+      (nnheader-message 5 "Creating mail directory %s" dir))))
+
 (defun nnml-save-mail (group-art)
   "Called narrowed to an article."
   (let (chars headers)
@@ -585,25 +583,25 @@ all.  This may very well take some time.")
     (while (looking-at "From ")
       (replace-match "X-From-Line: ")
       (forward-line 1))
-    ;; We save the article in all the newsgroups it belongs in.
+    ;; We save the article in all the groups it belongs in.
     (let ((ga group-art)
          first)
       (while ga
        (nnml-possibly-create-directory (caar ga))
-       (let ((file (concat (nnmail-group-pathname 
+       (let ((file (concat (nnmail-group-pathname
                             (caar ga) nnml-directory)
                            (int-to-string (cdar ga)))))
          (if first
              ;; It was already saved, so we just make a hard link.
              (funcall nnmail-crosspost-link-function first file t)
            ;; Save the article.
-           (nnmail-write-region (point-min) (point-max) file nil 
+           (nnmail-write-region (point-min) (point-max) file nil
                                 (if (nnheader-be-verbose 5) nil 'nomesg))
            (setq first file)))
        (setq ga (cdr ga))))
     ;; Generate a nov line for this article.  We generate the nov
     ;; line after saving, because nov generation destroys the
-    ;; header. 
+    ;; header.
     (setq headers (nnml-parse-head chars))
     ;; Output the nov line to all nov databases that should have it.
     (let ((ga group-art))
@@ -616,7 +614,7 @@ all.  This may very well take some time.")
   "Compute the next article number in GROUP."
   (let ((active (cadr (assoc group nnml-group-alist))))
     ;; The group wasn't known to nnml, so we just create an active
-    ;; entry for it.   
+    ;; entry for it.
     (unless active
       ;; Perhaps the active file was corrupt?  See whether
       ;; there are any articles in this group.
@@ -626,7 +624,7 @@ all.  This may very well take some time.")
        (setq nnml-article-file-alist
              (sort
               (nnheader-article-to-file-alist nnml-current-directory)
-              (lambda (a1 a2) (< (car a1) (car a2))))))
+              'car-less-than-car)))
       (setq active
            (if nnml-article-file-alist
                (cons (caar nnml-article-file-alist)
@@ -642,7 +640,7 @@ all.  This may very well take some time.")
 
 (defun nnml-add-nov (group article headers)
   "Add a nov line for the GROUP base."
-  (save-excursion 
+  (save-excursion
     (set-buffer (nnml-open-nov group))
     (goto-char (point-max))
     (mail-header-set-number headers article)
@@ -655,10 +653,10 @@ all.  This may very well take some time.")
   "Parse the head of the current buffer."
   (save-excursion
     (save-restriction
-      (goto-char (point-min))
-      (narrow-to-region 
-       (point)
-       (1- (or (search-forward "\n\n" nil t) (point-max))))
+      (unless (zerop (buffer-size))
+       (narrow-to-region
+        (goto-char (point-min))
+        (if (search-forward "\n\n" nil t) (1- (point)) (point-max))))
       ;; Fold continuation lines.
       (goto-char (point-min))
       (while (re-search-forward "\\(\r?\n[ \t]+\\)+" nil t)
@@ -672,12 +670,15 @@ all.  This may very well take some time.")
 
 (defun nnml-open-nov (group)
   (or (cdr (assoc group nnml-nov-buffer-alist))
-      (let ((buffer (find-file-noselect 
-                    (concat (nnmail-group-pathname group nnml-directory)
-                            nnml-nov-file-name))))
+      (let ((buffer (get-buffer-create (format " *nnml overview %s*" group))))
        (save-excursion
          (set-buffer buffer)
-         (buffer-disable-undo (current-buffer)))
+         (set (make-local-variable 'nnml-nov-buffer-file-name)
+              (concat (nnmail-group-pathname group nnml-directory)
+                      nnml-nov-file-name))
+         (erase-buffer)
+         (when (file-exists-p nnml-nov-buffer-file-name)
+           (nnheader-insert-file-contents nnml-nov-buffer-file-name)))
        (push (cons group buffer) nnml-nov-buffer-alist)
        buffer)))
 
@@ -687,58 +688,67 @@ all.  This may very well take some time.")
       (when (buffer-name (cdar nnml-nov-buffer-alist))
        (set-buffer (cdar nnml-nov-buffer-alist))
        (when (buffer-modified-p)
-         (nnmail-write-region 1 (point-max) (buffer-file-name) nil 'nomesg))
+         (nnmail-write-region 1 (point-max) nnml-nov-buffer-file-name
+                              nil 'nomesg))
        (set-buffer-modified-p nil)
        (kill-buffer (current-buffer)))
       (setq nnml-nov-buffer-alist (cdr nnml-nov-buffer-alist)))))
 
 ;;;###autoload
 (defun nnml-generate-nov-databases ()
-  "Generate nov databases in all nnml directories."
+  "Generate NOV databases in all nnml directories."
   (interactive)
-  ;; Read the active file to make sure we don't re-use articles 
+  ;; Read the active file to make sure we don't re-use articles
   ;; numbers in empty groups.
   (nnmail-activate 'nnml)
   (nnml-open-server (or (nnoo-current-server 'nnml) ""))
   (setq nnml-directory (expand-file-name nnml-directory))
   ;; Recurse down the directories.
-  (nnml-generate-nov-databases-1 nnml-directory)
+  (nnml-generate-nov-databases-1 nnml-directory nil t)
   ;; Save the active file.
   (nnmail-save-active nnml-group-alist nnml-active-file))
 
-(defun nnml-generate-nov-databases-1 (dir)
+(defun nnml-generate-nov-databases-1 (dir &optional seen no-active)
+  "Regenerate the NOV database in DIR."
+  (interactive "DRegenerate NOV in: ")
   (setq dir (file-name-as-directory dir))
-  ;; We descend recursively 
-  (let ((dirs (directory-files dir t nil t))
-       dir)
-    (while dirs 
-      (setq dir (pop dirs))
-      (when (and (not (member (file-name-nondirectory dir) '("." "..")))
-                (file-directory-p dir))
-       (nnml-generate-nov-databases-1 dir))))
-  ;; Do this directory.
-  (let ((files (sort
-               (mapcar
-                (lambda (name) (string-to-int name))
-                (directory-files dir nil "^[0-9]+$" t))
-               '<)))
-    (when files
-      (funcall nnml-generate-active-function dir)
-      ;; Generate the nov file.
-      (nnml-generate-nov-file dir files))))
+  ;; Only scan this sub-tree if we haven't been here yet.
+  (unless (member (file-truename dir) seen)
+    (push (file-truename dir) seen)
+    ;; We descend recursively
+    (let ((dirs (directory-files dir t nil t))
+         dir)
+      (while (setq dir (pop dirs))
+       (when (and (not (string-match "^\\." (file-name-nondirectory dir)))
+                  (file-directory-p dir))
+         (nnml-generate-nov-databases-1 dir seen))))
+    ;; Do this directory.
+    (let ((files (sort (nnheader-article-to-file-alist dir)
+                      'car-less-than-car)))
+      (if (not files)
+         (let* ((group (nnheader-file-to-group
+                        (directory-file-name dir) nnml-directory))
+                (info (cadr (assoc group nnml-group-alist))))
+           (when info
+             (setcar info (1+ (cdr info)))))
+       (funcall nnml-generate-active-function dir)
+       ;; Generate the nov file.
+       (nnml-generate-nov-file dir files)
+       (unless no-active
+         (nnmail-save-active nnml-group-alist nnml-active-file))))))
 
 (defvar files)
 (defun nnml-generate-active-info (dir)
   ;; Update the active info for this group.
-  (let ((group (nnheader-file-to-group 
+  (let ((group (nnheader-file-to-group
                (directory-file-name dir) nnml-directory)))
     (setq nnml-group-alist
          (delq (assoc group nnml-group-alist) nnml-group-alist))
     (push (list group
-               (cons (car files)
+               (cons (caar files)
                      (let ((f files))
                        (while (cdr f) (setq f (cdr f)))
-                       (car f))))
+                       (caar f))))
          nnml-group-alist)))
 
 (defun nnml-generate-nov-file (dir files)
@@ -749,27 +759,25 @@ all.  This may very well take some time.")
     (save-excursion
       ;; Init the nov buffer.
       (set-buffer nov-buffer)
-      (buffer-disable-undo (current-buffer))
+      (buffer-disable-undo)
       (erase-buffer)
       (set-buffer nntp-server-buffer)
       ;; Delete the old NOV file.
       (when (file-exists-p nov)
        (funcall nnmail-delete-file-function nov))
       (while files
-       (unless (file-directory-p 
-                (setq file (concat dir (int-to-string (car files)))))
+       (unless (file-directory-p (setq file (concat dir (cdar files))))
          (erase-buffer)
          (nnheader-insert-file-contents file)
-         (narrow-to-region 
+         (narrow-to-region
           (goto-char (point-min))
           (progn
             (search-forward "\n\n" nil t)
             (setq chars (- (point-max) (point)))
             (max 1 (1- (point)))))
-         (when (and (not (= 0 chars))  ; none of them empty files...
-                    (not (= (point-min) (point-max))))
+         (unless (zerop (buffer-size))
            (goto-char (point-min))
-           (setq headers (nnml-parse-head chars (car files)))
+           (setq headers (nnml-parse-head chars (caar files)))
            (save-excursion
              (set-buffer nov-buffer)
              (goto-char (point-max))
@@ -784,11 +792,25 @@ all.  This may very well take some time.")
 (defun nnml-nov-delete-article (group article)
   (save-excursion
     (set-buffer (nnml-open-nov group))
-    (goto-char (point-min))
-    (when (re-search-forward (concat "^" (int-to-string article) "\t") nil t)
-      (delete-region (match-beginning 0) (progn (forward-line 1) (point))))
+    (when (nnheader-find-nov-line article)
+      (delete-region (point) (progn (forward-line 1) (point)))
+      (when (bobp)
+       (let ((active (cadr (assoc group nnml-group-alist)))
+             num)
+         (when active
+           (if (eobp)
+               (setf (car active) (1+ (cdr active)))
+             (when (and (setq num (ignore-errors (read (current-buffer))))
+                        (numberp num))
+               (setf (car active) num)))))))
     t))
 
+(defun nnml-update-file-alist (&optional force)
+  (when (or (not nnml-article-file-alist)
+           force)
+    (setq nnml-article-file-alist
+         (nnheader-article-to-file-alist nnml-current-directory))))
+
 (provide 'nnml)
 
 ;;; nnml.el ends here