;;; nnfolder.el --- mail folder access for Gnus
-;; Copyright (C) 1995,96,97,98,99 Free Software Foundation, Inc.
+;; Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000
+;; Free Software Foundation, Inc.
-;; Author: Scott Byer <byer@mv.us.adobe.com>
+;; Author: ShengHuo Zhu <zsh@cs.rochester.edu> (adding NOV)
+;; Scott Byer <byer@mv.us.adobe.com>
;; Lars Magne Ingebrigtsen <larsi@gnus.org>
;; Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
;; Keywords: mail
(defvoo nnfolder-directory (expand-file-name message-directory)
"The name of the nnfolder directory.")
+(defvoo nnfolder-nov-directory nil
+ "The name of the nnfolder NOV directory.
+If nil, `nnfolder-directory' is used.")
+
(defvoo nnfolder-active-file
- (nnheader-concat nnfolder-directory "active")
+ (nnheader-concat nnfolder-directory "active")
"The name of the active file.")
;; I renamed this variable to something more in keeping with the general GNU
;; style. -SLB
(defvoo nnfolder-ignore-active-file nil
- "If non-nil, causes nnfolder to do some extra work in order to determine
-the true active ranges of an mbox file. Note that the active file is still
-saved, but it's values are not used. This costs some extra time when
-scanning an mbox when opening it.")
+ "If non-nil, the active file is ignored.
+This causes nnfolder to do some extra work in order to determine the
+true active ranges of an mbox file. Note that the active file is
+still saved, but its values are not used. This costs some extra time
+when scanning an mbox when opening it.")
(defvoo nnfolder-distrust-mbox nil
- "If non-nil, causes nnfolder to not trust the user with respect to
-inserting unaccounted for mail in the middle of an mbox file. This can greatly
-slow down scans, which now must scan the entire file for unmarked messages.
-When nil, scans occur forward from the last marked message, a huge
-time saver for large mailboxes.")
+ "If non-nil, the folder will be distrusted.
+This means that nnfolder will not trust the user with respect to
+inserting unaccounted for mail in the middle of an mbox file. This
+can greatly slow down scans, which now must scan the entire file for
+unmarked messages. When nil, scans occur forward from the last marked
+message, a huge time saver for large mailboxes.")
(defvoo nnfolder-newsgroups-file
- (concat (file-name-as-directory nnfolder-directory) "newsgroups")
+ (concat (file-name-as-directory nnfolder-directory) "newsgroups")
"Mail newsgroups description file.")
(defvoo nnfolder-get-new-mail t
\f
-(defconst nnfolder-version "nnfolder 1.0"
+(defconst nnfolder-version "nnfolder 2.0"
"nnfolder version.")
(defconst nnfolder-article-marker "X-Gnus-Article-Number: "
"Coding system for save nnfolder file.
If NIL, NNFOLDER-FILE-CODING-SYSTEM is used.")
+(defvoo nnfolder-nov-is-evil nil
+ "If non-nil, Gnus will never generate and use nov databases for mail groups.
+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
+the `nnfolder-generate-active-file' command. The function will go
+through all nnfolder directories and generate nov databases for them
+all. This may very well take some time.")
+
+(defvoo nnfolder-nov-file-suffix ".nov")
+
+(defvoo nnfolder-nov-buffer-alist nil)
+
+(defvar nnfolder-nov-buffer-file-name nil)
+
\f
;;; Interface functions
(goto-char (point-min))
(if (stringp (car articles))
'headers
- (while (setq article (pop articles))
- (set-buffer nnfolder-current-buffer)
- (when (nnfolder-goto-article article)
- (setq start (point))
- (search-forward "\n\n" nil t)
- (setq stop (1- (point)))
- (set-buffer nntp-server-buffer)
- (insert (format "221 %d Article retrieved.\n" article))
- (insert-buffer-substring nnfolder-current-buffer start stop)
- (goto-char (point-max))
- (insert ".\n")))
-
- (set-buffer nntp-server-buffer)
- (nnheader-fold-continuation-lines)
- 'headers)))))
+ (if (nnfolder-retrieve-headers-with-nov articles fetch-old)
+ 'nov
+ (while (setq article (pop articles))
+ (set-buffer nnfolder-current-buffer)
+ (when (nnfolder-goto-article article)
+ (setq start (point))
+ (setq stop (if (search-forward "\n\n" nil t)
+ (1- (point))
+ (point-max)))
+ (set-buffer nntp-server-buffer)
+ (insert (format "221 %d Article retrieved.\n" article))
+ (insert-buffer-substring nnfolder-current-buffer start stop)
+ (goto-char (point-max))
+ (insert ".\n")))
+ (set-buffer nntp-server-buffer)
+ (nnheader-fold-continuation-lines)
+ 'headers))))))
(deffoo nnfolder-open-server (server &optional defs)
(nnoo-change-server 'nnfolder server defs)
(nnmail-activate 'nnfolder t)
(gnus-make-directory nnfolder-directory)
+ (unless (or gnus-nov-is-evil nnfolder-nov-is-evil)
+ (and nnfolder-nov-directory
+ (gnus-make-directory nnfolder-nov-directory)))
(cond
((not (file-exists-p nnfolder-directory))
(nnfolder-close-server)
(if (numberp article)
(cons nnfolder-current-group article)
(goto-char (point-min))
- (search-forward (concat "\n" nnfolder-article-marker))
(cons nnfolder-current-group
- (string-to-int
- (buffer-substring
- (point) (progn (end-of-line) (point)))))))))))
+ (if (search-forward (concat "\n" nnfolder-article-marker)
+ nil t)
+ (string-to-int
+ (buffer-substring
+ (point) (progn (end-of-line) (point))))
+ -1))))))))
(deffoo nnfolder-request-group (group &optional server dont-check)
(nnfolder-possibly-change-group group server t)
numbers))))
(deffoo nnfolder-request-expire-articles
- (articles newsgroup &optional server force)
+ (articles newsgroup &optional server force)
(nnfolder-possibly-change-group newsgroup server)
(let* ((is-old t)
;; The articles we have deleted so far.
(deleted-articles nil)
- ;; The articles that really exist and will be expired if they are old enough.
- (maybe-expirable (gnus-intersection articles (nnfolder-existing-articles))))
+ ;; The articles that really exist and will
+ ;; be expired if they are old enough.
+ (maybe-expirable
+ (gnus-intersection articles (nnfolder-existing-articles))))
(nnmail-activate 'nnfolder)
(save-excursion
nil t))
(forward-sexp)
(when (setq is-old
- (nnmail-expired-article-p
- newsgroup
- (buffer-substring
- (point) (progn (end-of-line) (point)))
- force nnfolder-inhibit-expiry))
- (nnheader-message 5 "Deleting article %d..."
+ (nnmail-expired-article-p
+ newsgroup
+ (buffer-substring
+ (point) (progn (end-of-line) (point)))
+ force nnfolder-inhibit-expiry))
+ (nnheader-message 5 "Deleting article %d..."
(car maybe-expirable) newsgroup)
(nnfolder-delete-mail)
+ (unless (or gnus-nov-is-evil nnfolder-nov-is-evil)
+ (nnfolder-nov-delete-article newsgroup (car maybe-expirable)))
;; Must remember which articles were actually deleted
(push (car maybe-expirable) deleted-articles)))
(setq maybe-expirable (cdr maybe-expirable)))
(goto-char (point-min))
(while (re-search-forward
(concat "^" nnfolder-article-marker)
- (save-excursion (search-forward "\n\n" nil t) (point)) t)
+ (save-excursion (and (search-forward "\n\n" nil t) (point)))
+ t)
(delete-region (progn (beginning-of-line) (point))
(progn (forward-line 1) (point))))
(setq result (eval accept-form))
(goto-char (point-min))
(when (nnfolder-goto-article article)
(nnfolder-delete-mail))
+ (unless (or gnus-nov-is-evil nnfolder-nov-is-evil)
+ (nnfolder-nov-delete-article group article))
(when last
(nnfolder-save-buffer)
(nnfolder-adjust-min-active group)
(save-excursion
(set-buffer buf)
(goto-char (point-min))
- (search-forward "\n\n" nil t)
- (forward-line -1)
+ (if (search-forward "\n\n" nil t)
+ (forward-line -1)
+ (goto-char (point-max)))
(while (re-search-backward (concat "^" nnfolder-article-marker) nil t)
(delete-region (point) (progn (forward-line 1) (point))))
(when nnmail-cache-accepted-message-ids
nil
(nnfolder-delete-mail)
(insert-buffer-substring buffer)
+ (unless (or gnus-nov-is-evil nnfolder-nov-is-evil)
+ (save-excursion
+ (set-buffer buffer)
+ (let ((headers (nnfolder-parse-head article
+ (point-min) (point-max))))
+ (with-current-buffer (nnfolder-open-nov group)
+ (if (nnheader-find-nov-line article)
+ (delete-region (point) (progn (forward-line 1) (point))))
+ (nnheader-insert-nov headers)))))
(nnfolder-save-buffer)
t)))
() ; Don't delete the articles.
;; Delete the file that holds the group.
(ignore-errors
- (delete-file (nnfolder-group-pathname group))))
+ (delete-file (nnfolder-group-pathname group))
+ (delete-file (nnfolder-group-nov-pathname group))))
;; Remove the group from all structures.
(setq nnfolder-group-alist
(delq (assoc group nnfolder-group-alist) nnfolder-group-alist)
(set-buffer nnfolder-current-buffer)
(and (file-writable-p buffer-file-name)
(ignore-errors
- (rename-file
- buffer-file-name
- (let ((new-file (nnfolder-group-pathname new-name)))
- (gnus-make-directory (file-name-directory new-file))
- new-file))
+ (let ((new-file (nnfolder-group-pathname new-name)))
+ (gnus-make-directory (file-name-directory new-file))
+ (rename-file buffer-file-name new-file)
+ (setq new-file (nnfolder-group-nov-pathname new-name))
+ (rename-file (nnfolder-group-nov-pathname group)
+ new-file))
t)
;; That went ok, so we change the internal structures.
(let ((entry (assoc group nnfolder-group-alist)))
;; Change group.
(when (and group
(not (equal group nnfolder-current-group)))
- (let ((pathname-coding-system nnmail-pathname-coding-system))
+ (let ((file-name-coding-system nnmail-pathname-coding-system))
(nnmail-activate 'nnfolder)
(when (and (not (assoc group nnfolder-group-alist))
(not (file-exists-p
(while (setq group-art (pop group-art-list))
;; Kill any previous newsgroup markers.
(goto-char (point-min))
- (search-forward "\n\n" nil t)
- (forward-line -1)
+ (if (search-forward "\n\n" nil t)
+ (forward-line -1)
+ (goto-char (point-max)))
(while (search-backward (concat "\n" nnfolder-article-marker) nil t)
(delete-region (1+ (point)) (progn (forward-line 2) (point))))
(nnfolder-possibly-change-folder (car group-art))
(let ((buffer-read-only nil))
(nnfolder-normalize-buffer)
- (insert-buffer-substring obuf beg end)))))
+ (insert-buffer-substring obuf beg end))
+ (unless (or gnus-nov-is-evil nnfolder-nov-is-evil)
+ (set-buffer obuf)
+ (nnfolder-add-nov (car group-art) (cdr group-art)
+ (nnfolder-parse-head nil beg end))))))
;; Did we save it anywhere?
save-list))
(defun nnfolder-insert-newsgroup-line (group-art)
(save-excursion
(goto-char (point-min))
- (when (search-forward "\n\n" nil t)
- (forward-char -1)
- (insert (format (concat nnfolder-article-marker "%d %s\n")
- (cdr group-art) (current-time-string))))))
+ (unless (search-forward "\n\n" nil t)
+ (goto-char (point-max))
+ (insert "\n"))
+ (forward-char -1)
+ (insert (format (concat nnfolder-article-marker "%d %s\n")
+ (cdr group-art) (current-time-string)))))
(defun nnfolder-active-number (group)
;; Find the next article number in GROUP.
(defun nnfolder-read-folder (group)
(let* ((file (nnfolder-group-pathname group))
+ (nov (nnfolder-group-nov-pathname group))
(buffer (set-buffer
(let ((nnheader-file-coding-system
nnfolder-file-coding-system))
buffer
(push (list group buffer) nnfolder-buffer-alist)
(set-buffer-modified-p t)
- (save-buffer))
+ (nnfolder-save-buffer))
;; Parse the damn thing.
(save-excursion
(goto-char (point-min))
(scantime (assoc group nnfolder-scantime-alist))
(minid (lsh -1 -1))
maxid start end newscantime
+ novbuf articles newnum
buffer-read-only)
(buffer-disable-undo)
(setq maxid (cdr active))
+
+ (unless (or gnus-nov-is-evil nnfolder-nov-is-evil
+ (and (file-exists-p nov)
+ (file-newer-than-file-p nov file)))
+ (unless (file-exists-p nov)
+ (gnus-make-directory (file-name-directory nov)))
+ (with-current-buffer
+ (setq novbuf (nnfolder-open-nov group))
+ (goto-char (point-min))
+ (while (not (eobp))
+ (push (read novbuf) articles)
+ (forward-line 1))
+ (setq articles (nreverse articles))))
(goto-char (point-min))
;; Anytime the active number is 1 or 0, it is suspect. In that
;; expunge lists, etc., if we ever desired to abandon the active
;; file entirely for mboxes.)
(when (or nnfolder-ignore-active-file
+ novbuf
(< maxid 2))
(while (and (search-forward marker nil t)
- (re-search-forward number nil t))
- (let ((newnum (string-to-number (match-string 0))))
- (if (nnmail-within-headers-p)
- (setq maxid (max maxid newnum)
- minid (min minid newnum)))))
+ (looking-at number))
+ (setq newnum (string-to-number (match-string 0)))
+ (when (nnmail-within-headers-p)
+ (setq maxid (max maxid newnum)
+ minid (min minid newnum))
+ (when novbuf
+ (if (memq newnum articles)
+ (setq articles (delq newnum articles))
+ (let ((headers (nnfolder-parse-head newnum)))
+ (with-current-buffer novbuf
+ (nnheader-find-nov-line newnum)
+ (nnheader-insert-nov headers)))))))
+ (when (and novbuf articles)
+ (with-current-buffer novbuf
+ (dolist (article articles)
+ (when (nnheader-find-nov-line article)
+ (delete-region (point)
+ (progn (forward-line 1) (point)))))))
(setcar active (max 1 (min minid maxid)))
(setcdr active (max maxid (cdr active)))
(goto-char (point-min)))
(goto-char (point-max))
(unless (re-search-backward marker nil t)
(goto-char (point-min)))
- (when (nnmail-search-unix-mail-delim)
- (goto-char (point-min))))
+ ;;(when (nnmail-search-unix-mail-delim)
+ ;; (goto-char (point-min)))
+ )
;; Keep track of the active number on our own, and insert it back
;; into the active list when we're done. Also, prime the pump to
(narrow-to-region start end)
(nnmail-insert-lines)
(nnfolder-insert-newsgroup-line
- (cons nil (nnfolder-active-number nnfolder-current-group)))
+ (cons nil
+ (setq newnum
+ (nnfolder-active-number nnfolder-current-group))))
+ (when novbuf
+ (let ((headers (nnfolder-parse-head newnum (point-min)
+ (point-max))))
+ (with-current-buffer novbuf
+ (goto-char (point-max))
+ (nnheader-insert-nov headers))))
(widen)))
(set-marker end nil)
;; Make absolutely sure that the active list reflects reality!
(nnfolder-save-active nnfolder-group-alist nnfolder-active-file)
+
;; Set the scantime for this group.
(setq newscantime (visited-file-modtime))
(if scantime
(setcdr scantime (list newscantime))
(push (list nnfolder-current-group newscantime)
nnfolder-scantime-alist))
+ ;; Save nov.
+ (when novbuf
+ (nnfolder-save-nov))
(current-buffer))))))
;;;###autoload
This command does not work if you use short group names."
(interactive)
(nnmail-activate 'nnfolder)
+ (unless (or gnus-nov-is-evil nnfolder-nov-is-evil)
+ (dolist (file (directory-files (or nnfolder-nov-directory
+ nnfolder-directory)
+ t
+ (concat
+ (regexp-quote nnfolder-nov-file-suffix)
+ "$")))
+ (when (not (message-mail-file-mbox-p file))
+ (ignore-errors
+ (delete-file file)))))
(let ((files (directory-files nnfolder-directory))
file)
(while (setq file (pop files))
;; If not, we translate dots into slashes.
(concat dir (nnheader-replace-chars-in-string group ?. ?/)))))
+(defun nnfolder-group-nov-pathname (group)
+ "Make pathname for GROUP NOV."
+ (let ((nnfolder-directory
+ (or nnfolder-nov-directory nnfolder-directory)))
+ (concat (nnfolder-group-pathname group) nnfolder-nov-file-suffix)))
+
(defun nnfolder-save-buffer ()
"Save the buffer."
(when (buffer-modified-p)
(let ((coding-system-for-write
(or nnfolder-file-coding-system-for-write
nnfolder-file-coding-system)))
- (save-buffer))))
+ (save-buffer)))
+ (unless (or gnus-nov-is-evil nnfolder-nov-is-evil)
+ (nnfolder-save-nov)))
(defun nnfolder-save-active (group-alist active-file)
(let ((nnmail-active-file-coding-system
nnfolder-active-file-coding-system)))
(nnmail-save-active group-alist active-file)))
+(defun nnfolder-open-nov (group)
+ (or (cdr (assoc group nnfolder-nov-buffer-alist))
+ (let ((buffer (get-buffer-create (format " *nnfolder overview %s*" group))))
+ (save-excursion
+ (set-buffer buffer)
+ (set (make-local-variable 'nnfolder-nov-buffer-file-name)
+ (nnfolder-group-nov-pathname nnfolder-current-group))
+ (erase-buffer)
+ (when (file-exists-p nnfolder-nov-buffer-file-name)
+ (nnheader-insert-file-contents nnfolder-nov-buffer-file-name)))
+ (push (cons group buffer) nnfolder-nov-buffer-alist)
+ buffer)))
+
+(defun nnfolder-save-nov ()
+ (save-excursion
+ (while nnfolder-nov-buffer-alist
+ (when (buffer-name (cdar nnfolder-nov-buffer-alist))
+ (set-buffer (cdar nnfolder-nov-buffer-alist))
+ (when (buffer-modified-p)
+ (gnus-make-directory (file-name-directory
+ nnfolder-nov-buffer-file-name))
+ (nnmail-write-region 1 (point-max) nnfolder-nov-buffer-file-name
+ nil 'nomesg))
+ (set-buffer-modified-p nil)
+ (kill-buffer (current-buffer)))
+ (setq nnfolder-nov-buffer-alist (cdr nnfolder-nov-buffer-alist)))))
+
+(defun nnfolder-nov-delete-article (group article)
+ (save-excursion
+ (set-buffer (nnfolder-open-nov group))
+ (when (nnheader-find-nov-line article)
+ (delete-region (point) (progn (forward-line 1) (point))))
+ t))
+
+(defun nnfolder-retrieve-headers-with-nov (articles &optional fetch-old)
+ (if (or gnus-nov-is-evil nnfolder-nov-is-evil)
+ nil
+ (let ((nov (nnfolder-group-nov-pathname nnfolder-current-group)))
+ (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))
+ (car (last articles)))
+ t))))))
+
+(defun nnfolder-parse-head (&optional number b e)
+ "Parse the head of the current buffer."
+ (let ((buf (current-buffer))
+ chars)
+ (save-excursion
+ (unless b
+ (setq b (if (nnmail-search-unix-mail-delim-backward)
+ (point) (point-min)))
+ (forward-line 1)
+ (setq e (if (nnmail-search-unix-mail-delim)
+ (point) (point-max))))
+ (setq chars (- e b))
+ (unless (zerop chars)
+ (goto-char b)
+ (if (search-forward "\n\n" e t) (setq e (1- (point)))))
+ (with-temp-buffer
+ (insert-buffer-substring buf b e)
+ ;; 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 ? )
+ (let ((headers (nnheader-parse-head t)))
+ (mail-header-set-chars headers chars)
+ (mail-header-set-number headers number)
+ headers)))))
+
+(defun nnfolder-add-nov (group article headers)
+ "Add a nov line for the GROUP base."
+ (save-excursion
+ (set-buffer (nnfolder-open-nov group))
+ (goto-char (point-max))
+ (mail-header-set-number headers article)
+ (nnheader-insert-nov headers)))
+
(provide 'nnfolder)
;;; nnfolder.el ends here