;;; 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: Simon Josefsson <simon@josefsson.org> (adding MARKS)
;;; Code:
+(require 'gnus)
(require 'nnheader)
(require 'nnmail)
(require 'nnoo)
(eval-when-compile (require 'cl))
+(eval-and-compile
+ (autoload 'gnus-article-unpropagatable-p "gnus-sum"))
+
(nnoo-declare nnml)
(defvoo nnml-directory message-directory
This variable is a virtual server slot. See the Gnus manual for details.")
+(defvoo nnml-filenames-are-evil t
+ "If non-nil, Gnus will not assume that the articles file name
+is the same as the article number listed in the nov database. This
+variable should be set if any of the files are compressed.
+
+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.
(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
+(defvar nnml-marks-modtime (gnus-make-hashtable))
+\f
;;; Interface functions.
(nnoo-define-basics nnml)
(number (length sequence))
(count 0)
(file-name-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)
(setq beg (point))
(nnheader-insert-head file)
(goto-char beg)
- (if (search-forward "\n\n" nil t)
+ (if (re-search-forward "\n\r?\n" nil t)
(forward-char -1)
(goto-char (point-max))
(insert "\n\n"))
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))))
(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)
(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)))
+ ;; Maybe directory is changed during nnmail-expiry-target-group.
+ (nnml-possibly-change-directory group server))
+ (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
(let (file)
(if (setq file (cdr (assq article nnml-article-file-alist)))
(expand-file-name file nnml-current-directory)
- (if nnml-check-directory-twice
- ;; Just to make sure nothing went wrong when reading over NFS --
+ (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
(setq file (expand-file-name (number-to-string article)
(unless nnml-article-file-alist
(setq nnml-article-file-alist
(sort
- (nnheader-article-to-file-alist nnml-current-directory)
+ (nnml-current-group-article-to-file-alist)
'car-less-than-car)))
(setq active
(if nnml-article-file-alist
(unless (zerop (buffer-size))
(narrow-to-region
(goto-char (point-min))
- (if (search-forward "\n\n" nil t) (1- (point)) (point-max))))
+ (if (re-search-forward "\n\r?\n" nil t) (1- (point)) (point-max))))
;; Fold continuation lines.
(goto-char (point-min))
(while (re-search-forward "\\(\r?\n[ \t]+\\)+" nil t)
(replace-match " " t t))
;; Remove any tabs; they are too confusing.
(subst-char-in-region (point-min) (point-max) ?\t ? )
+ ;; Remove any ^M's; they are too confusing.
+ (subst-char-in-region (point-min) (point-max) ?\r ? )
(let ((headers (nnheader-parse-head t)))
(mail-header-set-chars headers chars)
(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)))
(narrow-to-region
(goto-char (point-min))
(progn
- (search-forward "\n\n" nil t)
+ (re-search-forward "\n\r?\n" nil t)
(setq chars (- (point-max) (point)))
(max 1 (1- (point)))))
(unless (zerop (buffer-size))
(when (or (not nnml-article-file-alist)
force)
(setq nnml-article-file-alist
- (nnheader-article-to-file-alist nnml-current-directory))))
+ (nnml-current-group-article-to-file-alist))))
+
+(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-current-group-article-to-file-alist ()
+ "Return an alist of article/file pairs in the current group.
+Use the nov database for the current group if available."
+ (if (or gnus-nov-is-evil
+ nnml-nov-is-evil
+ nnml-filenames-are-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)
(deffoo nnml-request-update-info (group info &optional server)
(nnml-possibly-change-directory group server)
- (unless nnml-marks-is-evil
+ (when (and (not nnml-marks-is-evil) (nnml-marks-changed-p group))
+ (nnheader-message 8 "Updating marks for %s..." group)
(nnml-open-marks group server)
;; Update info using `nnml-marks'.
(mapcar (lambda (pred)
(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-marks-changed-p (group)
+ (let ((file (expand-file-name nnml-marks-file-name
+ (nnmail-group-pathname group nnml-directory))))
+ (if (null (gnus-gethash file nnml-marks-modtime))
+ t ;; never looked at marks file, assume it has changed
+ (not (equal (gnus-gethash file nnml-marks-modtime)
+ (nth 5 (file-attributes file)))))))
+
(defun nnml-save-marks (group server)
(let ((file-name-coding-system nnmail-pathname-coding-system)
(file (expand-file-name nnml-marks-file-name
(nnml-possibly-create-directory group)
(with-temp-file file
(erase-buffer)
- (princ nnml-marks (current-buffer))
- (insert "\n")))
+ (gnus-prin1 nnml-marks (current-buffer))
+ (insert "\n"))
+ (gnus-sethash file
+ (nth 5 (file-attributes file))
+ nnml-marks-modtime))
(error (or (gnus-yes-or-no-p
(format "Could not write to %s (%s). Continue? " file err))
(error "Cannot write to %s (%s)" err))))))
nnml-marks-file-name
(nnmail-group-pathname group nnml-directory))))
(if (file-exists-p file)
- (setq nnml-marks (condition-case err
- (with-temp-buffer
- (nnheader-insert-file-contents file)
- (read (current-buffer)))
- (error (or (gnus-yes-or-no-p
- (format "Error reading nnml marks file %s (%s). Continuing will use marks from .newsrc.eld. Continue? " file err))
- (error "Cannot read nnml marks file %s (%s)" file err)))))
+ (condition-case err
+ (with-temp-buffer
+ (gnus-sethash file (nth 5 (file-attributes file))
+ nnml-marks-modtime)
+ (nnheader-insert-file-contents file)
+ (setq nnml-marks (read (current-buffer)))
+ (dolist (el gnus-article-unpropagated-mark-lists)
+ (setq nnml-marks (gnus-remassoc el nnml-marks))))
+ (error (or (gnus-yes-or-no-p
+ (format "Error reading nnml marks file %s (%s). Continuing will use marks from .newsrc.eld. Continue? " file err))
+ (error "Cannot read nnml marks file %s (%s)" file err))))
;; User didn't have a .marks file. Probably first time
;; user of the .marks stuff. Bootstrap it from .newsrc.eld.
(let ((info (gnus-get-info
(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)
+ (dolist (el gnus-article-unpropagated-mark-lists)
+ (setq nnml-marks (gnus-remassoc el nnml-marks)))
(nnml-save-marks group server)))))
(provide 'nnml)