2001-12-21 08:00:00 ShengHuo ZHU <zsh@cs.rochester.edu>
[gnus] / lisp / nnfolder.el
1 ;;; nnfolder.el --- mail folder access for Gnus
2 ;; Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000, 2001
3 ;;        Free Software Foundation, Inc.
4
5 ;; Author: Simon Josefsson <simon@josefsson.org> (adding MARKS)
6 ;;      ShengHuo Zhu <zsh@cs.rochester.edu> (adding NOV)
7 ;;      Scott Byer <byer@mv.us.adobe.com>
8 ;;      Lars Magne Ingebrigtsen <larsi@gnus.org>
9 ;;      Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
10 ;; Keywords: mail
11
12 ;; This file is part of GNU Emacs.
13
14 ;; GNU Emacs is free software; you can redistribute it and/or modify
15 ;; it under the terms of the GNU General Public License as published by
16 ;; the Free Software Foundation; either version 2, or (at your option)
17 ;; any later version.
18
19 ;; GNU Emacs is distributed in the hope that it will be useful,
20 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
21 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22 ;; GNU General Public License for more details.
23
24 ;; You should have received a copy of the GNU General Public License
25 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
26 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
27 ;; Boston, MA 02111-1307, USA.
28
29 ;;; Commentary:
30
31 ;;; Code:
32
33 (require 'nnheader)
34 (require 'message)
35 (require 'nnmail)
36 (require 'nnoo)
37 (eval-when-compile (require 'cl))
38 (require 'gnus)
39 (require 'gnus-util)
40 (require 'gnus-range)
41
42 (eval-and-compile
43   (autoload 'gnus-article-unpropagatable-p "gnus-sum")
44   (autoload 'gnus-intersection "gnus-range"))
45
46 (nnoo-declare nnfolder)
47
48 (defvoo nnfolder-directory (expand-file-name message-directory)
49   "The name of the nnfolder directory.")
50
51 (defvoo nnfolder-nov-directory nil
52   "The name of the nnfolder NOV directory.
53 If nil, `nnfolder-directory' is used.")
54
55 (defvoo nnfolder-marks-directory nil
56   "The name of the nnfolder MARKS directory.
57 If nil, `nnfolder-directory' is used.")
58
59 (defvoo nnfolder-active-file
60     (nnheader-concat nnfolder-directory "active")
61   "The name of the active file.")
62
63 ;; I renamed this variable to something more in keeping with the general GNU
64 ;; style. -SLB
65
66 (defvoo nnfolder-ignore-active-file nil
67   "If non-nil, the active file is ignored.
68 This causes nnfolder to do some extra work in order to determine the
69 true active ranges of an mbox file.  Note that the active file is
70 still saved, but its values are not used.  This costs some extra time
71 when scanning an mbox when opening it.")
72
73 (defvoo nnfolder-distrust-mbox nil
74   "If non-nil, the folder will be distrusted.
75 This means that nnfolder will not trust the user with respect to
76 inserting unaccounted for mail in the middle of an mbox file.  This
77 can greatly slow down scans, which now must scan the entire file for
78 unmarked messages.  When nil, scans occur forward from the last marked
79 message, a huge time saver for large mailboxes.")
80
81 (defvoo nnfolder-newsgroups-file
82     (concat (file-name-as-directory nnfolder-directory) "newsgroups")
83   "Mail newsgroups description file.")
84
85 (defvoo nnfolder-get-new-mail t
86   "If non-nil, nnfolder will check the incoming mail file and split the mail.")
87
88 (defvoo nnfolder-prepare-save-mail-hook nil
89   "Hook run narrowed to an article before saving.")
90
91 (defvoo nnfolder-save-buffer-hook nil
92   "Hook run before saving the nnfolder mbox buffer.")
93
94
95 (defvoo nnfolder-inhibit-expiry nil
96   "If non-nil, inhibit expiry.")
97
98 \f
99
100 (defconst nnfolder-version "nnfolder 2.0"
101   "nnfolder version.")
102
103 (defconst nnfolder-article-marker "X-Gnus-Article-Number: "
104   "String used to demarcate what the article number for a message is.")
105
106 (defvoo nnfolder-current-group nil)
107 (defvoo nnfolder-current-buffer nil)
108 (defvoo nnfolder-status-string "")
109 (defvoo nnfolder-group-alist nil)
110 (defvoo nnfolder-buffer-alist nil)
111 (defvoo nnfolder-scantime-alist nil)
112 (defvoo nnfolder-active-timestamp nil)
113 (defvoo nnfolder-active-file-coding-system mm-text-coding-system)
114 (defvoo nnfolder-active-file-coding-system-for-write
115     nnmail-active-file-coding-system)
116 (defvoo nnfolder-file-coding-system mm-text-coding-system)
117 (defvoo nnfolder-file-coding-system-for-write nnheader-file-coding-system
118   "Coding system for save nnfolder file.
119 if nil, `nnfolder-file-coding-system' is used.") ; FIXME: fill-in the doc-string of this variable
120
121 (defvoo nnfolder-nov-is-evil nil
122   "If non-nil, Gnus will never generate and use nov databases for mail groups.
123 Using nov databases will speed up header fetching considerably.
124 This variable shouldn't be flipped much.  If you have, for some reason,
125 set this to t, and want to set it to nil again, you should always run
126 the `nnfolder-generate-active-file' command.  The function will go
127 through all nnfolder directories and generate nov databases for them
128 all.  This may very well take some time.")
129
130 (defvoo nnfolder-nov-file-suffix ".nov")
131
132 (defvoo nnfolder-nov-buffer-alist nil)
133
134 (defvar nnfolder-nov-buffer-file-name nil)
135
136 (defvoo nnfolder-marks-is-evil nil
137   "If non-nil, Gnus will never generate and use marks file for mail groups.
138 Using marks files makes it possible to backup and restore mail groups
139 separately from `.newsrc.eld'.  If you have, for some reason, set
140 this to t, and want to set it to nil again, you should always remove
141 the corresponding marks file (usually base nnfolder file name
142 concatenated with `.mrk', but see `nnfolder-marks-file-suffix') for
143 the group.  Then the marks file will be regenerated properly by Gnus.")
144
145 (defvoo nnfolder-marks nil)
146
147 (defvoo nnfolder-marks-file-suffix ".mrk")
148
149 (defvar nnfolder-marks-modtime (gnus-make-hashtable))
150
151 \f
152
153 ;;; Interface functions
154
155 (nnoo-define-basics nnfolder)
156
157 (deffoo nnfolder-retrieve-headers (articles &optional group server fetch-old)
158   (save-excursion
159     (set-buffer nntp-server-buffer)
160     (erase-buffer)
161     (let (article start stop)
162       (nnfolder-possibly-change-group group server)
163       (when nnfolder-current-buffer
164         (set-buffer nnfolder-current-buffer)
165         (goto-char (point-min))
166         (if (stringp (car articles))
167             'headers
168           (if (nnfolder-retrieve-headers-with-nov articles fetch-old)
169               'nov
170             (setq articles (gnus-sorted-intersection
171                             ;; Is ARTICLES sorted?
172                             (sort articles '<)
173                             (nnfolder-existing-articles)))
174             (while (setq article (pop articles))
175               (set-buffer nnfolder-current-buffer)
176               (when (nnfolder-goto-article article)
177                 (setq start (point))
178                 (setq stop (if (search-forward "\n\n" nil t)
179                                (1- (point))
180                              (point-max)))
181                 (set-buffer nntp-server-buffer)
182                 (insert (format "221 %d Article retrieved.\n" article))
183                 (insert-buffer-substring nnfolder-current-buffer start stop)
184                 (goto-char (point-max))
185                 (insert ".\n")))
186             (set-buffer nntp-server-buffer)
187             (nnheader-fold-continuation-lines)
188             'headers))))))
189
190 (deffoo nnfolder-open-server (server &optional defs)
191   (nnoo-change-server 'nnfolder server defs)
192   (nnmail-activate 'nnfolder t)
193   (gnus-make-directory nnfolder-directory)
194   (unless (or gnus-nov-is-evil nnfolder-nov-is-evil)
195     (and nnfolder-nov-directory
196          (gnus-make-directory nnfolder-nov-directory)))
197   (unless nnfolder-marks-is-evil
198     (and nnfolder-marks-directory
199          (gnus-make-directory nnfolder-marks-directory)))
200   (cond
201    ((not (file-exists-p nnfolder-directory))
202     (nnfolder-close-server)
203     (nnheader-report 'nnfolder "Couldn't create directory: %s"
204                      nnfolder-directory))
205    ((not (file-directory-p (file-truename nnfolder-directory)))
206     (nnfolder-close-server)
207     (nnheader-report 'nnfolder "Not a directory: %s" nnfolder-directory))
208    (t
209     (nnmail-activate 'nnfolder)
210     (nnheader-report 'nnfolder "Opened server %s using directory %s"
211                      server nnfolder-directory)
212     t)))
213
214 (deffoo nnfolder-request-close ()
215   (let ((alist nnfolder-buffer-alist))
216     (while alist
217       (nnfolder-close-group (caar alist) nil t)
218       (setq alist (cdr alist))))
219   (nnoo-close-server 'nnfolder)
220   (setq nnfolder-buffer-alist nil
221         nnfolder-group-alist nil))
222
223 (deffoo nnfolder-request-article (article &optional group server buffer)
224   (nnfolder-possibly-change-group group server)
225   (save-excursion
226     (set-buffer nnfolder-current-buffer)
227     (goto-char (point-min))
228     (when (nnfolder-goto-article article)
229       (let (start stop)
230         (setq start (point))
231         (forward-line 1)
232         (unless (and (nnmail-search-unix-mail-delim)
233                      (forward-line -1))
234           (goto-char (point-max)))
235         (setq stop (point))
236         (let ((nntp-server-buffer (or buffer nntp-server-buffer)))
237           (set-buffer nntp-server-buffer)
238           (erase-buffer)
239           (insert-buffer-substring nnfolder-current-buffer start stop)
240           (goto-char (point-min))
241           (while (looking-at "From ")
242             (delete-char 5)
243             (insert "X-From-Line: ")
244             (forward-line 1))
245           (if (numberp article)
246               (cons nnfolder-current-group article)
247             (goto-char (point-min))
248             (cons nnfolder-current-group
249                   (if (search-forward (concat "\n" nnfolder-article-marker)
250                                       nil t)
251                       (string-to-int
252                        (buffer-substring
253                         (point) (progn (end-of-line) (point))))
254                     -1))))))))
255
256 (deffoo nnfolder-request-group (group &optional server dont-check)
257   (nnfolder-possibly-change-group group server t)
258   (save-excursion
259     (if (not (assoc group nnfolder-group-alist))
260         (nnheader-report 'nnfolder "No such group: %s" group)
261       (if dont-check
262           (progn
263             (nnheader-report 'nnfolder "Selected group %s" group)
264             t)
265         (let* ((active (assoc group nnfolder-group-alist))
266                (group (car active))
267                (range (cadr active)))
268           (cond
269            ((null active)
270             (nnheader-report 'nnfolder "No such group: %s" group))
271            ((null nnfolder-current-group)
272             (nnheader-report 'nnfolder "Empty group: %s" group))
273            (t
274             (nnheader-report 'nnfolder "Selected group %s" group)
275             (nnheader-insert "211 %d %d %d %s\n"
276                              (1+ (- (cdr range) (car range)))
277                              (car range) (cdr range) group))))))))
278
279 (deffoo nnfolder-request-scan (&optional group server)
280   (nnfolder-possibly-change-group nil server)
281   (when nnfolder-get-new-mail
282     (nnfolder-possibly-change-group group server)
283     (nnmail-get-new-mail
284      'nnfolder
285      (lambda ()
286        (let ((bufs nnfolder-buffer-alist))
287          (save-excursion
288            (while bufs
289              (if (not (gnus-buffer-live-p (nth 1 (car bufs))))
290                  (setq nnfolder-buffer-alist
291                        (delq (car bufs) nnfolder-buffer-alist))
292                (set-buffer (nth 1 (car bufs)))
293                (nnfolder-save-buffer)
294                (kill-buffer (current-buffer)))
295              (setq bufs (cdr bufs))))))
296      nnfolder-directory
297      group)))
298
299 ;; Don't close the buffer if we're not shutting down the server.  This way,
300 ;; we can keep the buffer in the group buffer cache, and not have to grovel
301 ;; over the buffer again unless we add new mail to it or modify it in some
302 ;; way.
303
304 (deffoo nnfolder-close-group (group &optional server force)
305   ;; Make sure we _had_ the group open.
306   (when (or (assoc group nnfolder-buffer-alist)
307             (equal group nnfolder-current-group))
308     (let ((inf (assoc group nnfolder-buffer-alist)))
309       (when inf
310         (when (and nnfolder-current-group
311                    nnfolder-current-buffer)
312           (push (list nnfolder-current-group nnfolder-current-buffer)
313                 nnfolder-buffer-alist))
314         (setq nnfolder-buffer-alist
315               (delq inf nnfolder-buffer-alist))
316         (setq nnfolder-current-buffer (cadr inf)
317               nnfolder-current-group (car inf))))
318     (when (and nnfolder-current-buffer
319                (buffer-name nnfolder-current-buffer))
320       (save-excursion
321         (set-buffer nnfolder-current-buffer)
322         ;; If the buffer was modified, write the file out now.
323         (nnfolder-save-buffer)
324         ;; If we're shutting the server down, we need to kill the
325         ;; buffer and remove it from the open buffer list.  Or, of
326         ;; course, if we're trying to minimize our space impact.
327         (kill-buffer (current-buffer))
328         (setq nnfolder-buffer-alist (delq (assoc group nnfolder-buffer-alist)
329                                           nnfolder-buffer-alist)))))
330   (setq nnfolder-current-group nil
331         nnfolder-current-buffer nil)
332   t)
333
334 (deffoo nnfolder-request-create-group (group &optional server args)
335   (nnfolder-possibly-change-group nil server)
336   (nnmail-activate 'nnfolder)
337   (when group
338     (unless (assoc group nnfolder-group-alist)
339       (push (list group (cons 1 0)) nnfolder-group-alist)
340       (nnfolder-save-active nnfolder-group-alist nnfolder-active-file)
341       (nnfolder-read-folder group)))
342   t)
343
344 (deffoo nnfolder-request-list (&optional server)
345   (nnfolder-possibly-change-group nil server)
346   (save-excursion
347     (let ((nnmail-file-coding-system nnfolder-active-file-coding-system))
348       (nnmail-find-file nnfolder-active-file)
349       (setq nnfolder-group-alist (nnmail-get-active)))
350     t))
351
352 (deffoo nnfolder-request-newgroups (date &optional server)
353   (nnfolder-possibly-change-group nil server)
354   (nnfolder-request-list server))
355
356 (deffoo nnfolder-request-list-newsgroups (&optional server)
357   (nnfolder-possibly-change-group nil server)
358   (save-excursion
359     (let ((nnmail-file-coding-system nnfolder-file-coding-system))
360       (nnmail-find-file nnfolder-newsgroups-file))))
361
362 ;; Return a list consisting of all article numbers existing in the
363 ;; current folder.
364
365 (defun nnfolder-existing-articles ()
366   (save-excursion
367     (when nnfolder-current-buffer
368       (set-buffer nnfolder-current-buffer)
369       (goto-char (point-min))
370       (let ((marker (concat "\n" nnfolder-article-marker))
371             (number "[0-9]+")
372             numbers)
373         (while (and (search-forward marker nil t)
374                     (re-search-forward number nil t))
375           (let ((newnum (string-to-number (match-string 0))))
376             (if (nnmail-within-headers-p)
377                 (push newnum numbers))))
378       ;; The article numbers are increasing, so this result is sorted.
379         (nreverse numbers)))))
380
381 (deffoo nnfolder-request-expire-articles
382     (articles newsgroup &optional server force)
383   (nnfolder-possibly-change-group newsgroup server)
384   (let* ((is-old t)
385          ;; The articles we have deleted so far.
386          (deleted-articles nil)
387          ;; The articles that really exist and will
388          ;; be expired if they are old enough.
389          (maybe-expirable
390           (gnus-sorted-intersection articles (nnfolder-existing-articles))))
391     (nnmail-activate 'nnfolder)
392
393     (save-excursion
394       (set-buffer nnfolder-current-buffer)
395       ;; Since messages are sorted in arrival order and expired in the
396       ;; same order, we can stop as soon as we find a message that is
397       ;; too old.
398       (while (and maybe-expirable is-old)
399         (goto-char (point-min))
400         (when (and (nnfolder-goto-article (car maybe-expirable))
401                    (search-forward (concat "\n" nnfolder-article-marker)
402                                    nil t))
403           (forward-sexp)
404           (when (setq is-old
405                       (nnmail-expired-article-p
406                        newsgroup
407                        (buffer-substring
408                         (point) (progn (end-of-line) (point)))
409                        force nnfolder-inhibit-expiry))
410             (unless (eq nnmail-expiry-target 'delete)
411               (with-temp-buffer
412                 (nnfolder-request-article (car maybe-expirable)
413                                           newsgroup server (current-buffer))
414                 (let ((nnfolder-current-directory nil))
415                   (nnmail-expiry-target-group
416                    nnmail-expiry-target newsgroup)))
417               (nnfolder-possibly-change-group newsgroup server))
418             (nnheader-message 5 "Deleting article %d in %s..."
419                               (car maybe-expirable) newsgroup)
420             (nnfolder-delete-mail)
421             (unless (or gnus-nov-is-evil nnfolder-nov-is-evil)
422               (nnfolder-nov-delete-article newsgroup (car maybe-expirable)))
423             ;; Must remember which articles were actually deleted
424             (push (car maybe-expirable) deleted-articles)))
425         (setq maybe-expirable (cdr maybe-expirable)))
426       (unless nnfolder-inhibit-expiry
427         (nnheader-message 5 "Deleting articles...done"))
428       (nnfolder-save-buffer)
429       (nnfolder-adjust-min-active newsgroup)
430       (nnfolder-save-active nnfolder-group-alist nnfolder-active-file)
431       (gnus-sorted-complement articles (nreverse deleted-articles)))))
432
433 (deffoo nnfolder-request-move-article (article group server
434                                                accept-form &optional last)
435   (save-excursion
436     (let ((buf (get-buffer-create " *nnfolder move*"))
437           result)
438       (and
439        (nnfolder-request-article article group server)
440        (save-excursion
441          (set-buffer buf)
442          (erase-buffer)
443          (insert-buffer-substring nntp-server-buffer)
444          (goto-char (point-min))
445          (while (re-search-forward
446                  (concat "^" nnfolder-article-marker)
447                  (save-excursion (and (search-forward "\n\n" nil t) (point)))
448                  t)
449            (delete-region (progn (beginning-of-line) (point))
450                           (progn (forward-line 1) (point))))
451          (setq result (eval accept-form))
452          (kill-buffer buf)
453          result)
454        (save-excursion
455          (nnfolder-possibly-change-group group server)
456          (set-buffer nnfolder-current-buffer)
457          (goto-char (point-min))
458          (when (nnfolder-goto-article article)
459            (nnfolder-delete-mail))
460          (unless (or gnus-nov-is-evil nnfolder-nov-is-evil)
461            (nnfolder-nov-delete-article group article))
462          (when last
463            (nnfolder-save-buffer)
464            (nnfolder-adjust-min-active group)
465            (nnfolder-save-active nnfolder-group-alist nnfolder-active-file))))
466       result)))
467
468 (deffoo nnfolder-request-accept-article (group &optional server last)
469   (save-excursion
470     (nnfolder-possibly-change-group group server)
471     (nnmail-check-syntax)
472     (let ((buf (current-buffer))
473           result art-group)
474       (goto-char (point-min))
475       (when (looking-at "X-From-Line: ")
476         (replace-match "From "))
477       (with-temp-buffer
478         (let ((nnmail-file-coding-system nnfolder-active-file-coding-system)
479               (nntp-server-buffer (current-buffer)))
480           (nnmail-find-file nnfolder-active-file)
481           (setq nnfolder-group-alist (nnmail-parse-active))))
482       (save-excursion
483         (goto-char (point-min))
484         (if (search-forward "\n\n" nil t)
485             (forward-line -1)
486           (goto-char (point-max)))
487         (while (re-search-backward (concat "^" nnfolder-article-marker) nil t)
488           (delete-region (point) (progn (forward-line 1) (point))))
489         (when nnmail-cache-accepted-message-ids
490           (nnmail-cache-insert (nnmail-fetch-field "message-id")))
491         (setq result (if (stringp group)
492                          (list (cons group (nnfolder-active-number group)))
493                        (setq art-group
494                              (nnmail-article-group 'nnfolder-active-number))))
495         (if (and (null result)
496                  (yes-or-no-p "Moved to `junk' group; delete article? "))
497             (setq result 'junk)
498           (setq result
499                 (car (nnfolder-save-mail result)))))
500       (when last
501         (save-excursion
502           (nnfolder-possibly-change-folder (or (caar art-group) group))
503           (nnfolder-save-buffer)
504           (when nnmail-cache-accepted-message-ids
505             (nnmail-cache-close))))
506       (nnfolder-save-active nnfolder-group-alist nnfolder-active-file)
507       (unless result
508         (nnheader-report 'nnfolder "Couldn't store article"))
509       result)))
510
511 (deffoo nnfolder-request-replace-article (article group buffer)
512   (nnfolder-possibly-change-group group)
513   (save-excursion
514     (set-buffer buffer)
515     (goto-char (point-min))
516     (let (xfrom)
517       (while (re-search-forward "^X-From-Line: \\(.*\\)$" nil t)
518         (setq xfrom (match-string 1))
519         (gnus-delete-line))
520       (goto-char (point-min))
521       (if xfrom
522           (insert "From " xfrom "\n")
523         (unless (looking-at "From ")
524           (insert "From nobody " (current-time-string) "\n"))))
525     (nnfolder-normalize-buffer)
526     (set-buffer nnfolder-current-buffer)
527     (goto-char (point-min))
528     (if (not (nnfolder-goto-article article))
529         nil
530       (nnfolder-delete-mail)
531       (insert-buffer-substring buffer)
532       (unless (or gnus-nov-is-evil nnfolder-nov-is-evil)
533         (save-excursion
534           (set-buffer buffer)
535           (let ((headers (nnfolder-parse-head article
536                                               (point-min) (point-max))))
537             (with-current-buffer (nnfolder-open-nov group)
538               (if (nnheader-find-nov-line article)
539                   (delete-region (point) (progn (forward-line 1) (point))))
540               (nnheader-insert-nov headers)))))
541       (nnfolder-save-buffer)
542       t)))
543
544 (deffoo nnfolder-request-delete-group (group &optional force server)
545   (nnfolder-close-group group server t)
546   ;; Delete all articles in GROUP.
547   (if (not force)
548       ()                                ; Don't delete the articles.
549     ;; Delete the file that holds the group.
550     (ignore-errors
551       (delete-file (nnfolder-group-pathname group))
552       (when (file-exists-p (nnfolder-group-nov-pathname group))
553         (delete-file (nnfolder-group-nov-pathname group)))
554       (when (file-exists-p (nnfolder-group-marks-pathname group))
555         (delete-file (nnfolder-group-marks-pathname group)))))
556   ;; Remove the group from all structures.
557   (setq nnfolder-group-alist
558         (delq (assoc group nnfolder-group-alist) nnfolder-group-alist)
559         nnfolder-current-group nil
560         nnfolder-current-buffer nil)
561   ;; Save the active file.
562   (nnfolder-save-active nnfolder-group-alist nnfolder-active-file)
563   t)
564
565 (deffoo nnfolder-request-rename-group (group new-name &optional server)
566   (nnfolder-possibly-change-group group server)
567   (save-excursion
568     (set-buffer nnfolder-current-buffer)
569     (and (file-writable-p buffer-file-name)
570          (ignore-errors
571            (let ((new-file (nnfolder-group-pathname new-name)))
572              (gnus-make-directory (file-name-directory new-file))
573              (rename-file buffer-file-name new-file)
574              (when (file-exists-p (nnfolder-group-nov-pathname group))
575                (setq new-file (nnfolder-group-nov-pathname new-name))
576                (gnus-make-directory (file-name-directory new-file))
577                (rename-file (nnfolder-group-nov-pathname group) new-file))
578              (when (file-exists-p (nnfolder-group-marks-pathname group))
579                (setq new-file (nnfolder-group-marks-pathname new-name))
580                (gnus-make-directory (file-name-directory new-file))
581                (rename-file (nnfolder-group-marks-pathname group) new-file)))
582            t)
583          ;; That went ok, so we change the internal structures.
584          (let ((entry (assoc group nnfolder-group-alist)))
585            (and entry (setcar entry new-name))
586            (setq nnfolder-current-buffer nil
587                  nnfolder-current-group nil)
588            ;; Save the new group alist.
589            (nnfolder-save-active nnfolder-group-alist nnfolder-active-file)
590            ;; We kill the buffer instead of renaming it and stuff.
591            (kill-buffer (current-buffer))
592            t))))
593
594 (deffoo nnfolder-request-regenerate (server)
595   (nnfolder-possibly-change-group nil server)
596   (nnfolder-generate-active-file)
597   t)
598
599 \f
600 ;;; Internal functions.
601
602 (defun nnfolder-adjust-min-active (group)
603   ;; Find the lowest active article in this group.
604   (let* ((active (cadr (assoc group nnfolder-group-alist)))
605          (marker (concat "\n" nnfolder-article-marker))
606          (number "[0-9]+")
607          (activemin (cdr active)))
608     (save-excursion
609       (set-buffer nnfolder-current-buffer)
610       (goto-char (point-min))
611       (while (and (search-forward marker nil t)
612                   (re-search-forward number nil t))
613         (let ((newnum (string-to-number (match-string 0))))
614           (if (nnmail-within-headers-p)
615               (setq activemin (min activemin newnum)))))
616       (setcar active activemin))))
617
618 (defun nnfolder-article-string (article)
619   (if (numberp article)
620       (concat "\n" nnfolder-article-marker (int-to-string article) " ")
621     (concat "\nMessage-ID: " article)))
622
623 (defun nnfolder-goto-article (article)
624   "Place point at the start of the headers of ARTICLE.
625 ARTICLE can be an article number or a Message-ID.
626 Returns t if successful, nil otherwise."
627   (let ((art-string (nnfolder-article-string article))
628         start found)
629     ;; It is likely that we are at or before the delimiter line.
630     ;; We therefore go to the end of the previous line, and start
631     ;; searching from there.
632     (beginning-of-line)
633     (unless (bobp)
634       (forward-char -1))
635     (setq start (point))
636     ;; First search forward.
637     (while (and (setq found (search-forward art-string nil t))
638                 (not (nnmail-within-headers-p))))
639     ;; If unsuccessful, search backward from where we started,
640     (unless found
641       (goto-char start)
642       (while (and (setq found (search-backward art-string nil t))
643                   (not (nnmail-within-headers-p)))))
644     (when found
645       (nnmail-search-unix-mail-delim-backward))))
646
647 (defun nnfolder-delete-mail (&optional leave-delim)
648   "Delete the message that point is in.
649 If optional argument LEAVE-DELIM is t, then mailbox delimiter is not
650 deleted.  Point is left where the deleted region was."
651   (save-restriction
652     (narrow-to-region
653      (save-excursion
654        ;; In case point is at the beginning of the message already.
655        (forward-line 1)
656        (nnmail-search-unix-mail-delim-backward)
657        (if leave-delim (progn (forward-line 1) (point))
658          (point)))
659      (progn
660        (forward-line 1)
661        (if (nnmail-search-unix-mail-delim)
662            (point)
663          (point-max))))
664     (run-hooks 'nnfolder-delete-mail-hook)
665     (delete-region (point-min) (point-max))))
666
667 (defun nnfolder-possibly-change-group (group &optional server dont-check)
668   ;; Change servers.
669   (when (and server
670              (not (nnfolder-server-opened server)))
671     (nnfolder-open-server server))
672   (unless (gnus-buffer-live-p nnfolder-current-buffer)
673     (setq nnfolder-current-buffer nil
674           nnfolder-current-group nil))
675   ;; Change group.
676   (let ((file-name-coding-system nnmail-pathname-coding-system))
677     (when (and group
678                (not (equal group nnfolder-current-group))
679                (progn
680                  (nnmail-activate 'nnfolder)
681                  (and (assoc group nnfolder-group-alist)
682                       (file-exists-p (nnfolder-group-pathname group)))))
683       (if dont-check
684           (setq nnfolder-current-group group
685                 nnfolder-current-buffer nil)
686         (let (inf file)
687           ;; If we have to change groups, see if we don't already have
688           ;; the folder in memory.  If we do, verify the modtime and
689           ;; destroy the folder if needed so we can rescan it.
690           (setq nnfolder-current-buffer
691                 (nth 1 (assoc group nnfolder-buffer-alist)))
692
693           ;; If the buffer is not live, make sure it isn't in the
694           ;; alist.  If it is live, verify that nobody else has
695           ;; touched the file since last time.
696           (when (and nnfolder-current-buffer
697                      (not (gnus-buffer-live-p nnfolder-current-buffer)))
698             (setq nnfolder-buffer-alist (delq inf nnfolder-buffer-alist)
699                   nnfolder-current-buffer nil))
700
701           (setq nnfolder-current-group group)
702
703           (when (or (not nnfolder-current-buffer)
704                     (not (verify-visited-file-modtime
705                           nnfolder-current-buffer)))
706             (save-excursion
707               (setq file (nnfolder-group-pathname group))
708               ;; See whether we need to create the new file.
709               (unless (file-exists-p file)
710                 (gnus-make-directory (file-name-directory file))
711                 (let ((nnmail-file-coding-system
712                        (or nnfolder-file-coding-system-for-write
713                            nnfolder-file-coding-system-for-write)))
714                   (nnmail-write-region 1 1 file t 'nomesg)))
715               (when (setq nnfolder-current-buffer (nnfolder-read-folder group))
716                 (set-buffer nnfolder-current-buffer)
717                 (push (list group nnfolder-current-buffer)
718                       nnfolder-buffer-alist)))))))))
719
720 (defun nnfolder-save-mail (group-art-list)
721   "Called narrowed to an article."
722   (let* (save-list group-art)
723     (goto-char (point-min))
724     ;; The From line may have been quoted by movemail.
725     (when (looking-at ">From")
726       (delete-char 1))
727     ;; This might come from somewhere else.
728     (unless (looking-at "From ")
729       (insert "From nobody " (current-time-string) "\n")
730       (goto-char (point-min)))
731     ;; Quote all "From " lines in the article.
732     (forward-line 1)
733     (let (case-fold-search)
734       (while (re-search-forward "^From " nil t)
735         (beginning-of-line)
736         (insert "> ")))
737     (setq save-list group-art-list)
738     (nnmail-insert-lines)
739     (nnmail-insert-xref group-art-list)
740     (run-hooks 'nnmail-prepare-save-mail-hook)
741     (run-hooks 'nnfolder-prepare-save-mail-hook)
742
743     ;; Insert the mail into each of the destination groups.
744     (while (setq group-art (pop group-art-list))
745       ;; Kill any previous newsgroup markers.
746       (goto-char (point-min))
747       (if (search-forward "\n\n" nil t)
748           (forward-line -1)
749         (goto-char (point-max)))
750       (while (search-backward (concat "\n" nnfolder-article-marker) nil t)
751         (delete-region (1+ (point)) (progn (forward-line 2) (point))))
752
753       ;; Insert the new newsgroup marker.
754       (nnfolder-insert-newsgroup-line group-art)
755
756       (save-excursion
757         (let ((beg (point-min))
758               (end (point-max))
759               (obuf (current-buffer)))
760           (nnfolder-possibly-change-folder (car group-art))
761           (let ((buffer-read-only nil))
762             (nnfolder-normalize-buffer)
763             (insert-buffer-substring obuf beg end))
764           (unless (or gnus-nov-is-evil nnfolder-nov-is-evil)
765             (set-buffer obuf)
766             (nnfolder-add-nov (car group-art) (cdr group-art)
767                               (nnfolder-parse-head nil beg end))))))
768
769     ;; Did we save it anywhere?
770     save-list))
771
772 (defun nnfolder-normalize-buffer ()
773   "Make sure there are two newlines at the end of the buffer."
774   (goto-char (point-max))
775   (skip-chars-backward "\n")
776   (delete-region (point) (point-max))
777   (unless (bobp)
778     (insert "\n\n")))
779
780 (defun nnfolder-insert-newsgroup-line (group-art)
781   (save-excursion
782     (goto-char (point-min))
783     (unless (search-forward "\n\n" nil t)
784       (goto-char (point-max))
785       (insert "\n"))
786     (forward-char -1)
787     (insert (format (concat nnfolder-article-marker "%d   %s\n")
788                     (cdr group-art) (current-time-string)))))
789
790 (defun nnfolder-active-number (group)
791   ;; Find the next article number in GROUP.
792   (let ((active (cadr (assoc group nnfolder-group-alist))))
793     (if active
794         (setcdr active (1+ (cdr active)))
795       ;; This group is new, so we create a new entry for it.
796       ;; This might be a bit naughty... creating groups on the drop of
797       ;; a hat, but I don't know...
798       (push (list group (setq active (cons 1 1)))
799             nnfolder-group-alist))
800     (cdr active)))
801
802 (defun nnfolder-possibly-change-folder (group)
803   (let ((inf (assoc group nnfolder-buffer-alist)))
804     (if (and inf
805              (gnus-buffer-live-p (cadr inf)))
806         (set-buffer (cadr inf))
807       (when inf
808         (setq nnfolder-buffer-alist (delq inf nnfolder-buffer-alist)))
809       (when nnfolder-group-alist
810         (nnfolder-save-active nnfolder-group-alist nnfolder-active-file))
811       (push (list group (nnfolder-read-folder group))
812             nnfolder-buffer-alist))))
813
814 ;; This method has a problem if you've accidentally let the active
815 ;; list get out of sync with the files.  This could happen, say, if
816 ;; you've accidentally gotten new mail with something other than Gnus
817 ;; (but why would _that_ ever happen? :-).  In that case, we will be
818 ;; in the middle of processing the file, ready to add new X-Gnus
819 ;; article number markers, and we'll run across a message with no ID
820 ;; yet - the active list _may_not_ be ready for us yet.
821
822 ;; To handle this, I'm modifying this routine to maintain the maximum
823 ;; ID seen so far, and when we hit a message with no ID, we will
824 ;; _manually_ scan the rest of the message looking for any more,
825 ;; possibly higher IDs.  We'll assume the maximum that we find is the
826 ;; highest active.  Note that this shouldn't cost us much extra time
827 ;; at all, but will be a lot less vulnerable to glitches between the
828 ;; mbox and the active file.
829
830 (defun nnfolder-read-folder (group)
831   (let* ((file (nnfolder-group-pathname group))
832          (nov  (nnfolder-group-nov-pathname group))
833          (buffer (set-buffer
834                   (let ((nnheader-file-coding-system
835                          nnfolder-file-coding-system))
836                     (nnheader-find-file-noselect file)))))
837     (mm-enable-multibyte) ;; Use multibyte buffer for future copying.
838     (if (equal (cadr (assoc group nnfolder-scantime-alist))
839                (nth 5 (file-attributes file)))
840         ;; This looks up-to-date, so we don't do any scanning.
841         (if (file-exists-p file)
842             buffer
843           (push (list group buffer) nnfolder-buffer-alist)
844           (set-buffer-modified-p t)
845           (nnfolder-save-buffer))
846       ;; Parse the damn thing.
847       (save-excursion
848         (goto-char (point-min))
849         ;; Remove any blank lines at the start.
850         (while (eq (following-char) ?\n)
851           (delete-char 1))
852         (nnmail-activate 'nnfolder)
853         ;; Read in the file.
854         (let ((delim "^From ")
855               (marker (concat "\n" nnfolder-article-marker))
856               (number "[0-9]+")
857               (active (or (cadr (assoc group nnfolder-group-alist))
858                           (cons 1 0)))
859               (scantime (assoc group nnfolder-scantime-alist))
860               (minid (lsh -1 -1))
861               maxid start end newscantime
862               novbuf articles newnum
863               buffer-read-only)
864           (buffer-disable-undo)
865           (setq maxid (cdr active))
866
867           (unless (or gnus-nov-is-evil nnfolder-nov-is-evil
868                       (and (file-exists-p nov)
869                            (file-newer-than-file-p nov file)))
870             (unless (file-exists-p nov)
871               (gnus-make-directory (file-name-directory nov)))
872             (with-current-buffer
873                 (setq novbuf (nnfolder-open-nov group))
874               (goto-char (point-min))
875               (while (not (eobp))
876                 (push (read novbuf) articles)
877                 (forward-line 1))
878               (setq articles (nreverse articles))))
879           (goto-char (point-min))
880
881           ;; Anytime the active number is 1 or 0, it is suspect.  In
882           ;; that case, search the file manually to find the active
883           ;; number.  Or, of course, if we're being paranoid.  (This
884           ;; would also be the place to build other lists from the
885           ;; header markers, such as expunge lists, etc., if we ever
886           ;; desired to abandon the active file entirely for mboxes.)
887           (when (or nnfolder-ignore-active-file
888                     novbuf
889                     (< maxid 2))
890             (while (and (search-forward marker nil t)
891                         (looking-at number))
892               (setq newnum (string-to-number (match-string 0)))
893               (when (nnmail-within-headers-p)
894                 (setq maxid (max maxid newnum)
895                       minid (min minid newnum))
896                 (when novbuf
897                   (if (memq newnum articles)
898                       (setq articles (delq newnum articles))
899                     (let ((headers (nnfolder-parse-head newnum)))
900                       (with-current-buffer novbuf
901                         (nnheader-find-nov-line newnum)
902                         (nnheader-insert-nov headers)))))))
903             (when (and novbuf articles)
904               (with-current-buffer novbuf
905                 (dolist (article articles)
906                   (when (nnheader-find-nov-line article)
907                     (delete-region (point)
908                                    (progn (forward-line 1) (point)))))))
909             (setcar active (max 1 (min minid maxid)))
910             (setcdr active (max maxid (cdr active)))
911             (goto-char (point-min)))
912
913           ;; As long as we trust that the user will only insert
914           ;; unmarked mail at the end, go to the end and search
915           ;; backwards for the last marker.  Find the start of that
916           ;; message, and begin to search for unmarked messages from
917           ;; there.
918           (when (not (or nnfolder-distrust-mbox
919                          (< maxid 2)))
920             (goto-char (point-max))
921             (unless (re-search-backward marker nil t)
922               (goto-char (point-min)))
923             ;;(when (nnmail-search-unix-mail-delim)
924             ;;  (goto-char (point-min)))
925             )
926
927           ;; Keep track of the active number on our own, and insert it
928           ;; back into the active list when we're done.  Also, prime
929           ;; the pump to cut down on the number of searches we do.
930           (unless (nnmail-search-unix-mail-delim)
931             (goto-char (point-max)))
932           (setq end (point-marker))
933           (while (not (= end (point-max)))
934             (setq start (marker-position end))
935             (goto-char end)
936            ;; There may be more than one "From " line, so we skip past
937             ;; them.
938             (while (looking-at delim)
939               (forward-line 1))
940             (set-marker end (if (nnmail-search-unix-mail-delim)
941                                 (point)
942                               (point-max)))
943             (goto-char start)
944             (when (not (search-forward marker end t))
945               (narrow-to-region start end)
946               (nnmail-insert-lines)
947               (nnfolder-insert-newsgroup-line
948                (cons nil
949                      (setq newnum
950                            (nnfolder-active-number group))))
951               (when novbuf
952                 (let ((headers (nnfolder-parse-head newnum (point-min)
953                                                     (point-max))))
954                   (with-current-buffer novbuf
955                     (goto-char (point-max))
956                     (nnheader-insert-nov headers))))
957               (widen)))
958
959           (set-marker end nil)
960           ;; Make absolutely sure that the active list reflects
961           ;; reality!
962           (nnfolder-save-active nnfolder-group-alist nnfolder-active-file)
963
964           ;; Set the scantime for this group.
965           (setq newscantime (visited-file-modtime))
966           (if scantime
967               (setcdr scantime (list newscantime))
968             (push (list group newscantime)
969                   nnfolder-scantime-alist))
970           ;; Save nov.
971           (when novbuf
972             (nnfolder-save-nov))
973           (current-buffer))))))
974
975 ;;;###autoload
976 (defun nnfolder-generate-active-file ()
977   "Look for mbox folders in the nnfolder directory and make them into groups.
978 This command does not work if you use short group names."
979   (interactive)
980   (nnmail-activate 'nnfolder)
981   (unless (or gnus-nov-is-evil nnfolder-nov-is-evil)
982     (dolist (file (directory-files (or nnfolder-nov-directory
983                                        nnfolder-directory)
984                                    t
985                                    (concat
986                                     (regexp-quote nnfolder-nov-file-suffix)
987                                     "$")))
988       (when (not (message-mail-file-mbox-p file))
989         (ignore-errors
990           (delete-file file)))))
991   (let ((files (directory-files nnfolder-directory))
992         file)
993     (while (setq file (pop files))
994       (when (and (not (backup-file-name-p file))
995                  (message-mail-file-mbox-p
996                   (nnheader-concat nnfolder-directory file)))
997         (let ((oldgroup (assoc file nnfolder-group-alist)))
998           (if oldgroup
999               (nnheader-message 5 "Refreshing group %s..." file)
1000             (nnheader-message 5 "Adding group %s..." file))
1001           (if oldgroup
1002               (setq nnfolder-group-alist
1003                     (delq oldgroup (copy-sequence nnfolder-group-alist))))
1004           (push (list file (cons 1 0)) nnfolder-group-alist)
1005           (nnfolder-possibly-change-folder file)
1006           (nnfolder-possibly-change-group file)
1007           (nnfolder-close-group file))))
1008     (nnheader-message 5 "")))
1009
1010 (defun nnfolder-group-pathname (group)
1011   "Make pathname for GROUP."
1012   (setq group
1013         (mm-encode-coding-string group nnmail-pathname-coding-system))
1014   (let ((dir (file-name-as-directory (expand-file-name nnfolder-directory))))
1015     ;; If this file exists, we use it directly.
1016     (if (or nnmail-use-long-file-names
1017             (file-exists-p (concat dir group)))
1018         (concat dir group)
1019       ;; If not, we translate dots into slashes.
1020       (concat dir (nnheader-replace-chars-in-string group ?. ?/)))))
1021
1022 (defun nnfolder-group-nov-pathname (group)
1023   "Make pathname for GROUP NOV."
1024   (let ((nnfolder-directory
1025          (or nnfolder-nov-directory nnfolder-directory)))
1026     (concat (nnfolder-group-pathname group) nnfolder-nov-file-suffix)))
1027
1028 (defun nnfolder-save-buffer ()
1029   "Save the buffer."
1030   (when (buffer-modified-p)
1031     (run-hooks 'nnfolder-save-buffer-hook)
1032     (gnus-make-directory (file-name-directory (buffer-file-name)))
1033     (let ((coding-system-for-write
1034            (or nnfolder-file-coding-system-for-write
1035                nnfolder-file-coding-system)))
1036       (save-buffer)))
1037   (unless (or gnus-nov-is-evil nnfolder-nov-is-evil)
1038     (nnfolder-save-nov)))
1039
1040 (defun nnfolder-save-active (group-alist active-file)
1041   (let ((nnmail-active-file-coding-system
1042          (or nnfolder-active-file-coding-system-for-write
1043              nnfolder-active-file-coding-system)))
1044     (nnmail-save-active group-alist active-file)))
1045
1046 (defun nnfolder-open-nov (group)
1047   (or (cdr (assoc group nnfolder-nov-buffer-alist))
1048       (let ((buffer (get-buffer-create (format " *nnfolder overview %s*" group))))
1049         (save-excursion
1050           (set-buffer buffer)
1051           (set (make-local-variable 'nnfolder-nov-buffer-file-name)
1052                (nnfolder-group-nov-pathname group))
1053           (erase-buffer)
1054           (when (file-exists-p nnfolder-nov-buffer-file-name)
1055             (nnheader-insert-file-contents nnfolder-nov-buffer-file-name)))
1056         (push (cons group buffer) nnfolder-nov-buffer-alist)
1057         buffer)))
1058
1059 (defun nnfolder-save-nov ()
1060   (save-excursion
1061     (while nnfolder-nov-buffer-alist
1062       (when (buffer-name (cdar nnfolder-nov-buffer-alist))
1063         (set-buffer (cdar nnfolder-nov-buffer-alist))
1064         (when (buffer-modified-p)
1065           (gnus-make-directory (file-name-directory
1066                                 nnfolder-nov-buffer-file-name))
1067           (nnmail-write-region 1 (point-max) nnfolder-nov-buffer-file-name
1068                                nil 'nomesg))
1069         (set-buffer-modified-p nil)
1070         (kill-buffer (current-buffer)))
1071       (setq nnfolder-nov-buffer-alist (cdr nnfolder-nov-buffer-alist)))))
1072
1073 (defun nnfolder-nov-delete-article (group article)
1074   (save-excursion
1075     (set-buffer (nnfolder-open-nov group))
1076     (when (nnheader-find-nov-line article)
1077       (delete-region (point) (progn (forward-line 1) (point))))
1078     t))
1079
1080 (defun nnfolder-retrieve-headers-with-nov (articles &optional fetch-old)
1081   (if (or gnus-nov-is-evil nnfolder-nov-is-evil)
1082       nil
1083     (let ((nov (nnfolder-group-nov-pathname nnfolder-current-group)))
1084       (when (file-exists-p nov)
1085         (save-excursion
1086           (set-buffer nntp-server-buffer)
1087           (erase-buffer)
1088           (nnheader-insert-file-contents nov)
1089           (if (and fetch-old
1090                    (not (numberp fetch-old)))
1091               t                         ; Don't remove anything.
1092             (nnheader-nov-delete-outside-range
1093              (if fetch-old (max 1 (- (car articles) fetch-old))
1094                (car articles))
1095              (car (last articles)))
1096             t))))))
1097
1098 (defun nnfolder-parse-head (&optional number b e)
1099   "Parse the head of the current buffer."
1100   (let ((buf (current-buffer))
1101         chars)
1102     (save-excursion
1103       (unless b
1104         (setq b (if (nnmail-search-unix-mail-delim-backward)
1105                     (point) (point-min)))
1106         (forward-line 1)
1107         (setq e (if (nnmail-search-unix-mail-delim)
1108                     (point) (point-max))))
1109       (setq chars (- e b))
1110       (unless (zerop chars)
1111         (goto-char b)
1112         (if (search-forward "\n\n" e t) (setq e (1- (point)))))
1113       (with-temp-buffer
1114         (insert-buffer-substring buf b e)
1115         ;; Fold continuation lines.
1116         (goto-char (point-min))
1117         (while (re-search-forward "\\(\r?\n[ \t]+\\)+" nil t)
1118           (replace-match " " t t))
1119         ;; Remove any tabs; they are too confusing.
1120         (subst-char-in-region (point-min) (point-max) ?\t ? )
1121         (let ((headers (nnheader-parse-head t)))
1122           (mail-header-set-chars headers chars)
1123           (mail-header-set-number headers number)
1124           headers)))))
1125
1126 (defun nnfolder-add-nov (group article headers)
1127   "Add a nov line for the GROUP base."
1128   (save-excursion
1129     (set-buffer (nnfolder-open-nov group))
1130     (goto-char (point-max))
1131     (mail-header-set-number headers article)
1132     (nnheader-insert-nov headers)))
1133
1134 (deffoo nnfolder-request-set-mark (group actions &optional server)
1135   (when (and server
1136              (not (nnfolder-server-opened server)))
1137     (nnfolder-open-server server))
1138   (unless nnfolder-marks-is-evil
1139     (nnfolder-open-marks group server)
1140     (dolist (action actions)
1141       (let ((range (nth 0 action))
1142             (what  (nth 1 action))
1143             (marks (nth 2 action)))
1144         (assert (or (eq what 'add) (eq what 'del)) t
1145                 "Unknown request-set-mark action: %s" what)
1146         (dolist (mark marks)
1147           (setq nnfolder-marks (gnus-update-alist-soft
1148                             mark
1149                             (funcall (if (eq what 'add) 'gnus-range-add
1150                                        'gnus-remove-from-range)
1151                                      (cdr (assoc mark nnfolder-marks)) range)
1152                             nnfolder-marks)))))
1153     (nnfolder-save-marks group server))
1154   nil)
1155
1156 (deffoo nnfolder-request-update-info (group info &optional server)
1157   ;; Change servers.
1158   (when (and server
1159              (not (nnfolder-server-opened server)))
1160     (nnfolder-open-server server))
1161   (when (and (not nnfolder-marks-is-evil) (nnfolder-marks-changed-p group))
1162     (nnheader-message 8 "Updating marks for %s..." group)
1163     (nnfolder-open-marks group server)
1164     ;; Update info using `nnfolder-marks'.
1165     (mapcar (lambda (pred)
1166               (gnus-info-set-marks
1167                info
1168                (gnus-update-alist-soft
1169                 (cdr pred)
1170                 (cdr (assq (cdr pred) nnfolder-marks))
1171                 (gnus-info-marks info))
1172                t))
1173             gnus-article-mark-lists)
1174     (let ((seen (cdr (assq 'read nnfolder-marks))))
1175       (gnus-info-set-read info
1176                           (if (and (integerp (car seen))
1177                                    (null (cdr seen)))
1178                               (list (cons (car seen) (car seen)))
1179                             seen)))
1180     (nnheader-message 8 "Updating marks for %s...done" group))
1181   info)
1182
1183 (defun nnfolder-group-marks-pathname (group)
1184   "Make pathname for GROUP NOV."
1185   (let ((nnfolder-directory (or nnfolder-marks-directory nnfolder-directory)))
1186     (concat (nnfolder-group-pathname group) nnfolder-marks-file-suffix)))
1187
1188 (defun nnfolder-marks-changed-p (group)
1189   (let ((file (nnfolder-group-marks-pathname group)))
1190     (if (null (gnus-gethash file nnfolder-marks-modtime))
1191         t ;; never looked at marks file, assume it has changed
1192       (not (equal (gnus-gethash file nnfolder-marks-modtime)
1193                   (nth 5 (file-attributes file)))))))
1194
1195 (defun nnfolder-save-marks (group server)
1196   (let ((file-name-coding-system nnmail-pathname-coding-system)
1197         (file (nnfolder-group-marks-pathname group)))
1198     (condition-case err
1199         (progn
1200           (with-temp-file file
1201             (erase-buffer)
1202             (gnus-prin1 nnfolder-marks)
1203             (insert "\n"))
1204           (gnus-sethash file
1205                         (nth 5 (file-attributes file))
1206                         nnfolder-marks-modtime))
1207       (error (or (gnus-yes-or-no-p
1208                   (format "Could not write to %s (%s).  Continue? " file err))
1209                  (error "Cannot write to %s (%s)" err))))))
1210
1211 (defun nnfolder-open-marks (group server)
1212   (let ((file (nnfolder-group-marks-pathname group)))
1213     (if (file-exists-p file)
1214         (condition-case err
1215             (with-temp-buffer
1216               (gnus-sethash file (nth 5 (file-attributes file)) 
1217                             nnfolder-marks-modtime)
1218               (nnheader-insert-file-contents file)
1219               (setq nnfolder-marks (read (current-buffer)))
1220               (dolist (el gnus-article-unpropagated-mark-lists)
1221                 (setq nnfolder-marks (gnus-remassoc el nnfolder-marks))))
1222           (error (or (gnus-yes-or-no-p
1223                       (format "Error reading nnfolder marks file %s (%s).  Continuing will use marks from .newsrc.eld.  Continue? " file err))
1224                      (error "Cannot read nnfolder marks file %s (%s)" file err))))
1225       ;; User didn't have a .marks file.  Probably first time
1226       ;; user of the .marks stuff.  Bootstrap it from .newsrc.eld.
1227       (let ((info (gnus-get-info
1228                    (gnus-group-prefixed-name
1229                     group
1230                     (gnus-server-to-method (format "nnfolder:%s" server))))))
1231         (nnheader-message 7 "Bootstrapping marks for %s..." group)
1232         (setq nnfolder-marks (gnus-info-marks info))
1233         (push (cons 'read (gnus-info-read info)) nnfolder-marks)
1234         (dolist (el gnus-article-unpropagated-mark-lists)
1235           (setq nnfolder-marks (gnus-remassoc el nnfolder-marks)))
1236         (nnfolder-save-marks group server)))))
1237
1238 (provide 'nnfolder)
1239
1240 ;;; nnfolder.el ends here