Synch with Oort Gnus.
[elisp/gnus.git-] / lisp / nnml.el
index 18b651d..817ea15 100644 (file)
@@ -1,8 +1,9 @@
 ;;; nnml.el --- mail spool access for Gnus
-;; Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000
+;; Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000, 2001
 ;;        Free Software Foundation, Inc.
 
-;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
+;; Author: Simon Josefsson <simon@josefsson.org> (adding MARKS)
+;;      Lars Magne Ingebrigtsen <larsi@gnus.org>
 ;;     Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
 ;; Keywords: news, mail
 
@@ -34,6 +35,7 @@
 (eval-when-compile (require 'cl))
 (eval-when-compile (require 'gnus-clfns))
 
+(require 'gnus)
 (require 'nnheader)
 (require 'nnmail)
 (require 'nnoo)
@@ -63,7 +65,7 @@ This variable is a virtual server slot.  See the Gnus manual for details.")
 This variable is a virtual server slot.  See the Gnus manual for details.")
 
 (defvoo nnml-nov-is-evil nil
-  "If non-nil, Gnus will never generate and use nov databases for mail groups.
+  "If non-nil, Gnus will never generate and use nov databases for mail spools.
 Using nov databases will speed up header fetching considerably.
 This variable shouldn't be flipped much.  If you have, for some reason,
 set this to t, and want to set it to nil again, you should always run
@@ -73,6 +75,17 @@ all.  This may very well take some time.
 
 This variable is a virtual server slot.  See the Gnus manual for details.")
 
+(defvoo nnml-marks-is-evil nil
+  "If non-nil, Gnus will never generate and use marks file for mail spools.
+Using marks files makes it possible to backup and restore mail groups
+separately from `.newsrc.eld'.  If you have, for some reason, set this
+to t, and want to set it to nil again, you should always remove the
+corresponding marks file (usually named `.marks' in the nnml group
+directory, but see `nnml-marks-file-name') for the group.  Then the
+marks file will be regenerated properly by Gnus.
+
+This variable is a virtual server slot.  See the Gnus manual for details.")
+
 (defvoo nnml-prepare-save-mail-hook nil
   "Hook run narrowed to an article before saving.
 
@@ -90,6 +103,7 @@ This variable is a virtual server slot.  See the Gnus manual for details.")
   "nnml version.")
 
 (defvoo nnml-nov-file-name ".overview")
+(defvoo nnml-marks-file-name ".marks")
 
 (defvoo nnml-current-directory nil)
 (defvoo nnml-current-group nil)
@@ -102,12 +116,11 @@ This variable is a virtual server slot.  See the Gnus manual for details.")
 (defvoo nnml-generate-active-function 'nnml-generate-active-info)
 
 (defvar nnml-nov-buffer-file-name nil)
-(defvar nnml-check-directory-twice t
-  "If t, to make sure nothing went wrong when reading over NFS --
-check twice.")
 
 (defvoo nnml-file-coding-system nnmail-file-coding-system)
 
+(defvoo nnml-marks nil)
+
 \f
 
 ;;; Interface functions.
@@ -124,12 +137,7 @@ check twice.")
             (count 0)
             (file-name-coding-system nnmail-pathname-coding-system)
             (pathname-coding-system nnmail-pathname-coding-system)
-            beg article
-            (nnml-check-directory-twice
-             (and nnml-check-directory-twice
-                  ;; To speed up, disable it in some case.
-                  (or (not (numberp nnmail-large-newsgroup))
-                      (<= number nnmail-large-newsgroup)))))
+            beg article)
        (if (stringp (car sequence))
            'headers
          (if (nnml-retrieve-headers-with-nov sequence fetch-old)
@@ -196,7 +204,7 @@ check twice.")
        (when (and (setq group-num (nnml-find-group-number id))
                   (cdr
                    (assq (cdr group-num)
-                         (nnheader-article-to-file-alist
+                         (nnml-article-to-file-alist
                           (setq gpath
                                 (nnmail-group-pathname
                                  (car group-num)
@@ -270,7 +278,7 @@ check twice.")
            nnml-group-alist)
       (nnml-possibly-create-directory group)
       (nnml-possibly-change-directory group server)
-      (let ((articles (nnheader-directory-articles nnml-current-directory)))
+      (let ((articles (nnml-directory-articles nnml-current-directory)))
        (when articles
          (setcar active (apply 'min articles))
          (setcdr active (apply 'max articles))))
@@ -296,7 +304,7 @@ check twice.")
 (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))
+        (nnml-directory-articles nnml-current-directory))
        (is-old t)
        article rest mod-time number)
     (nnmail-activate 'nnml)
@@ -307,30 +315,29 @@ check twice.")
     (setq articles (gnus-sorted-intersection articles active-articles))
 
     (while (and articles is-old)
-      (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
-               ;; Allow a special target group.
-               (unless (eq nnmail-expiry-target 'delete)
-                 (with-temp-buffer
-                   (nnml-request-article number group server
-                                         (current-buffer))
-                   (let ((nnml-current-directory nil))
-                     (nnmail-expiry-target-group
-                      nnmail-expiry-target group))))
-               (nnheader-message 5 "Deleting article %s in %s"
-                                 number 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)))))
+      (if (and (setq article (nnml-article-to-file (setq number (pop articles))))
+              (setq mod-time (nth 5 (file-attributes article)))
+              (nnml-deletable-article-p group number)
+              (setq is-old (nnmail-expired-article-p group mod-time force
+                                                     nnml-inhibit-expiry)))
+         (progn
+           ;; Allow a special target group.
+           (unless (eq nnmail-expiry-target 'delete)
+             (with-temp-buffer
+               (nnml-request-article number group server (current-buffer))
+               (let (nnml-current-directory
+                     nnml-current-group
+                     nnml-article-file-alist)
+                 (nnmail-expiry-target-group nnmail-expiry-target group))))
+           (nnheader-message 5 "Deleting article %s in %s"
+                             number 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
@@ -477,7 +484,7 @@ check twice.")
       ;; 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)))
+      (let ((files (nnml-article-to-file-alist old-dir)))
        (while files
          (rename-file
           (concat old-dir (cdar files))
@@ -523,7 +530,7 @@ check twice.")
   (let (file)
     (if (setq file (cdr (assq article nnml-article-file-alist)))
        (expand-file-name file nnml-current-directory)
-      (if nnml-check-directory-twice
+      (if (not nnheader-directory-files-is-safe)
          ;; Just to make sure nothing went wrong when reading over NFS --
          ;; check once more.
          (when (file-exists-p
@@ -672,7 +679,7 @@ check twice.")
       (unless nnml-article-file-alist
        (setq nnml-article-file-alist
              (sort
-              (nnheader-article-to-file-alist nnml-current-directory)
+              (nnml-article-to-file-alist nnml-current-directory)
               'car-less-than-car)))
       (setq active
            (if nnml-article-file-alist
@@ -717,18 +724,22 @@ check twice.")
        (mail-header-set-number headers number)
        headers))))
 
+(defun nnml-get-nov-buffer (group)
+  (let ((buffer (get-buffer-create (format " *nnml overview %s*" group))))
+    (save-excursion
+      (set-buffer buffer)
+      (set (make-local-variable 'nnml-nov-buffer-file-name)
+          (expand-file-name
+           nnml-nov-file-name
+           (nnmail-group-pathname group nnml-directory)))
+      (erase-buffer)
+      (when (file-exists-p nnml-nov-buffer-file-name)
+       (nnheader-insert-file-contents nnml-nov-buffer-file-name)))
+    buffer))
+
 (defun nnml-open-nov (group)
   (or (cdr (assoc group nnml-nov-buffer-alist))
-      (let ((buffer (get-buffer-create (format " *nnml overview %s*" group))))
-       (save-excursion
-         (set-buffer buffer)
-         (set (make-local-variable 'nnml-nov-buffer-file-name)
-              (expand-file-name
-               nnml-nov-file-name
-               (nnmail-group-pathname group nnml-directory)))
-         (erase-buffer)
-         (when (file-exists-p nnml-nov-buffer-file-name)
-           (nnheader-insert-file-contents nnml-nov-buffer-file-name)))
+      (let ((buffer (nnml-get-nov-buffer group)))
        (push (cons group buffer) nnml-nov-buffer-alist)
        buffer)))
 
@@ -863,11 +874,51 @@ check twice.")
   (when (or (not nnml-article-file-alist)
            force)
     (setq nnml-article-file-alist
-         (nnheader-article-to-file-alist nnml-current-directory))))
-
-(defvoo nnml-marks-file-name ".marks")
-(defvoo nnml-marks-is-evil nil)
-(defvoo nnml-marks nil)
+         (nnml-article-to-file-alist nnml-current-directory))))
+
+(defun nnml-directory-articles (dir)
+  "Return a list of all article files in a directory.
+Use the nov database for that directory if available."
+  (if (or gnus-nov-is-evil nnml-nov-is-evil
+         (not (file-exists-p
+               (expand-file-name nnml-nov-file-name dir))))
+      (nnheader-directory-articles dir)
+    ;; build list from .overview if available
+    ;; We would use nnml-open-nov, except that nnml-nov-buffer-alist is
+    ;; defvoo'd, and we might get called when it hasn't been swapped in.
+    (save-excursion
+      (let ((list nil)
+           art
+           (buffer (nnml-get-nov-buffer nnml-current-group)))
+       (set-buffer buffer)
+       (goto-char (point-min))
+       (while (not (eobp))
+         (setq art (read (current-buffer)))
+         (push art list)
+         (forward-line 1))
+       list))))
+
+(defun nnml-article-to-file-alist (dir)
+  "Return an alist of article/file pairs in DIR.
+Use the nov database for that directory if available."
+  (if (or gnus-nov-is-evil nnml-nov-is-evil
+         (not (file-exists-p
+               (expand-file-name nnml-nov-file-name
+                                 nnml-current-directory))))
+      (nnheader-article-to-file-alist nnml-current-directory)
+    ;; build list from .overview if available
+    (save-excursion
+      (let ((alist nil)
+           art
+           (buffer (nnml-get-nov-buffer nnml-current-group)))
+       (set-buffer buffer)
+       (goto-char (point-min))
+       (while (not (eobp))
+         (setq art (read (current-buffer)))
+         ;; assume file name is unadorned (ie. not compressed etc)
+         (push (cons art (int-to-string art)) alist)
+         (forward-line 1))
+       alist))))
 
 (deffoo nnml-request-set-mark (group actions &optional server)
   (nnml-possibly-change-directory group server)
@@ -892,6 +943,7 @@ check twice.")
 (deffoo nnml-request-update-info (group info &optional server)
   (nnml-possibly-change-directory group server)
   (unless nnml-marks-is-evil
+    (nnheader-message 8 "Updating marks for %s..." group)
     (nnml-open-marks group server)
     ;; Update info using `nnml-marks'.
     (mapcar (lambda (pred)
@@ -908,22 +960,28 @@ check twice.")
                          (if (and (integerp (car seen))
                                   (null (cdr seen)))
                              (list (cons (car seen) (car seen)))
-                           seen))))
+                           seen)))
+    (nnheader-message 8 "Updating marks for %s...done" group))
   info)
 
 (defun nnml-save-marks (group server)
   (let ((file-name-coding-system nnmail-pathname-coding-system)
        (file (expand-file-name nnml-marks-file-name
                                (nnmail-group-pathname group nnml-directory))))
-    (nnml-possibly-create-directory group)
-    (with-temp-file file
-      (erase-buffer)
-      (princ nnml-marks (current-buffer))
-      (insert "\n"))))
+    (condition-case err
+       (progn
+         (nnml-possibly-create-directory group)
+         (with-temp-file file
+           (erase-buffer)
+           (princ nnml-marks (current-buffer))
+           (insert "\n")))
+      (error (or (gnus-yes-or-no-p
+                 (format "Could not write to %s (%s).  Continue? " file err))
+                (error "Cannot write to %s (%s)" err))))))
 
 (defun nnml-open-marks (group server)
-  (let ((file (expand-file-name 
-              nnml-marks-file-name 
+  (let ((file (expand-file-name
+              nnml-marks-file-name
               (nnmail-group-pathname group nnml-directory))))
     (if (file-exists-p file)
        (setq nnml-marks (condition-case err
@@ -939,7 +997,7 @@ check twice.")
                   (gnus-group-prefixed-name
                    group
                    (gnus-server-to-method (format "nnml:%s" server))))))
-       (nnheader-message 6 "Boostrapping nnml marks...")
+       (nnheader-message 7 "Bootstrapping marks for %s..." group)
        (setq nnml-marks (gnus-info-marks info))
        (push (cons 'read (gnus-info-read info)) nnml-marks)
        (nnml-save-marks group server)))))