* nnagent.el (nnagent-retrieve-headers): Use gnus-sorted-difference.
[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-difference 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         (save-match-data
477           (mail-header-unfold-field))
478         (replace-match "From "))
479       (with-temp-buffer
480         (let ((nnmail-file-coding-system nnfolder-active-file-coding-system)
481               (nntp-server-buffer (current-buffer)))
482           (nnmail-find-file nnfolder-active-file)
483           (setq nnfolder-group-alist (nnmail-parse-active))))
484       (save-excursion
485         (goto-char (point-min))
486         (if (search-forward "\n\n" nil t)
487             (forward-line -1)
488           (goto-char (point-max)))
489         (while (re-search-backward (concat "^" nnfolder-article-marker) nil t)
490           (delete-region (point) (progn (forward-line 1) (point))))
491         (when nnmail-cache-accepted-message-ids
492           (nnmail-cache-insert (nnmail-fetch-field "message-id")))
493         (setq result (if (stringp group)
494                          (list (cons group (nnfolder-active-number group)))
495                        (setq art-group
496                              (nnmail-article-group 'nnfolder-active-number))))
497         (if (and (null result)
498                  (yes-or-no-p "Moved to `junk' group; delete article? "))
499             (setq result 'junk)
500           (setq result
501                 (car (nnfolder-save-mail result)))))
502       (when last
503         (save-excursion
504           (nnfolder-possibly-change-folder (or (caar art-group) group))
505           (nnfolder-save-buffer)
506           (when nnmail-cache-accepted-message-ids
507             (nnmail-cache-close))))
508       (nnfolder-save-active nnfolder-group-alist nnfolder-active-file)
509       (unless result
510         (nnheader-report 'nnfolder "Couldn't store article"))
511       result)))
512
513 (deffoo nnfolder-request-replace-article (article group buffer)
514   (nnfolder-possibly-change-group group)
515   (save-excursion
516     (set-buffer buffer)
517     (goto-char (point-min))
518     (if (not (looking-at "X-From-Line: "))
519         (insert "From nobody " (current-time-string) "\n")
520       (save-match-data
521         (mail-header-unfold-field))
522       (replace-match "From "))
523     (nnfolder-normalize-buffer)
524     (set-buffer nnfolder-current-buffer)
525     (goto-char (point-min))
526     (if (not (nnfolder-goto-article article))
527         nil
528       (nnfolder-delete-mail)
529       (insert-buffer-substring buffer)
530       (unless (or gnus-nov-is-evil nnfolder-nov-is-evil)
531         (save-excursion
532           (set-buffer buffer)
533           (let ((headers (nnfolder-parse-head article
534                                               (point-min) (point-max))))
535             (with-current-buffer (nnfolder-open-nov group)
536               (if (nnheader-find-nov-line article)
537                   (delete-region (point) (progn (forward-line 1) (point))))
538               (nnheader-insert-nov headers)))))
539       (nnfolder-save-buffer)
540       t)))
541
542 (deffoo nnfolder-request-delete-group (group &optional force server)
543   (nnfolder-close-group group server t)
544   ;; Delete all articles in GROUP.
545   (if (not force)
546       ()                                ; Don't delete the articles.
547     ;; Delete the file that holds the group.
548     (ignore-errors
549       (delete-file (nnfolder-group-pathname group))
550       (when (file-exists-p (nnfolder-group-nov-pathname group))
551         (delete-file (nnfolder-group-nov-pathname group)))
552       (when (file-exists-p (nnfolder-group-marks-pathname group))
553         (delete-file (nnfolder-group-marks-pathname group)))))
554   ;; Remove the group from all structures.
555   (setq nnfolder-group-alist
556         (delq (assoc group nnfolder-group-alist) nnfolder-group-alist)
557         nnfolder-current-group nil
558         nnfolder-current-buffer nil)
559   ;; Save the active file.
560   (nnfolder-save-active nnfolder-group-alist nnfolder-active-file)
561   t)
562
563 (deffoo nnfolder-request-rename-group (group new-name &optional server)
564   (nnfolder-possibly-change-group group server)
565   (save-excursion
566     (set-buffer nnfolder-current-buffer)
567     (and (file-writable-p buffer-file-name)
568          (ignore-errors
569            (let ((new-file (nnfolder-group-pathname new-name)))
570              (gnus-make-directory (file-name-directory new-file))
571              (rename-file buffer-file-name new-file)
572              (when (file-exists-p (nnfolder-group-nov-pathname group))
573                (setq new-file (nnfolder-group-nov-pathname new-name))
574                (gnus-make-directory (file-name-directory new-file))
575                (rename-file (nnfolder-group-nov-pathname group) new-file))
576              (when (file-exists-p (nnfolder-group-marks-pathname group))
577                (setq new-file (nnfolder-group-marks-pathname new-name))
578                (gnus-make-directory (file-name-directory new-file))
579                (rename-file (nnfolder-group-marks-pathname group) new-file)))
580            t)
581          ;; That went ok, so we change the internal structures.
582          (let ((entry (assoc group nnfolder-group-alist)))
583            (and entry (setcar entry new-name))
584            (setq nnfolder-current-buffer nil
585                  nnfolder-current-group nil)
586            ;; Save the new group alist.
587            (nnfolder-save-active nnfolder-group-alist nnfolder-active-file)
588            ;; We kill the buffer instead of renaming it and stuff.
589            (kill-buffer (current-buffer))
590            t))))
591
592 (deffoo nnfolder-request-regenerate (server)
593   (nnfolder-possibly-change-group nil server)
594   (nnfolder-generate-active-file)
595   t)
596
597 \f
598 ;;; Internal functions.
599
600 (defun nnfolder-adjust-min-active (group)
601   ;; Find the lowest active article in this group.
602   (let* ((active (cadr (assoc group nnfolder-group-alist)))
603          (marker (concat "\n" nnfolder-article-marker))
604          (number "[0-9]+")
605          (activemin (cdr active)))
606     (save-excursion
607       (set-buffer nnfolder-current-buffer)
608       (goto-char (point-min))
609       (while (and (search-forward marker nil t)
610                   (re-search-forward number nil t))
611         (let ((newnum (string-to-number (match-string 0))))
612           (if (nnmail-within-headers-p)
613               (setq activemin (min activemin newnum)))))
614       (setcar active activemin))))
615
616 (defun nnfolder-article-string (article)
617   (if (numberp article)
618       (concat "\n" nnfolder-article-marker (int-to-string article) " ")
619     (concat "\nMessage-ID: " article)))
620
621 (defun nnfolder-goto-article (article)
622   "Place point at the start of the headers of ARTICLE.
623 ARTICLE can be an article number or a Message-ID.
624 Returns t if successful, nil otherwise."
625   (let ((art-string (nnfolder-article-string article))
626         start found)
627     ;; It is likely that we are at or before the delimiter line.
628     ;; We therefore go to the end of the previous line, and start
629     ;; searching from there.
630     (beginning-of-line)
631     (unless (bobp)
632       (forward-char -1))
633     (setq start (point))
634     ;; First search forward.
635     (while (and (setq found (search-forward art-string nil t))
636                 (not (nnmail-within-headers-p))))
637     ;; If unsuccessful, search backward from where we started,
638     (unless found
639       (goto-char start)
640       (while (and (setq found (search-backward art-string nil t))
641                   (not (nnmail-within-headers-p)))))
642     (when found
643       (nnmail-search-unix-mail-delim-backward))))
644
645 (defun nnfolder-delete-mail (&optional leave-delim)
646   "Delete the message that point is in.
647 If optional argument LEAVE-DELIM is t, then mailbox delimiter is not
648 deleted.  Point is left where the deleted region was."
649   (save-restriction
650     (narrow-to-region
651      (save-excursion
652        ;; In case point is at the beginning of the message already.
653        (forward-line 1)
654        (nnmail-search-unix-mail-delim-backward)
655        (if leave-delim (progn (forward-line 1) (point))
656          (point)))
657      (progn
658        (forward-line 1)
659        (if (nnmail-search-unix-mail-delim)
660            (point)
661          (point-max))))
662     (run-hooks 'nnfolder-delete-mail-hook)
663     (delete-region (point-min) (point-max))))
664
665 (defun nnfolder-possibly-change-group (group &optional server dont-check)
666   ;; Change servers.
667   (when (and server
668              (not (nnfolder-server-opened server)))
669     (nnfolder-open-server server))
670   (unless (gnus-buffer-live-p nnfolder-current-buffer)
671     (setq nnfolder-current-buffer nil
672           nnfolder-current-group nil))
673   ;; Change group.
674   (let ((file-name-coding-system nnmail-pathname-coding-system))
675     (when (and group
676                (not (equal group nnfolder-current-group))
677                (progn
678                  (nnmail-activate 'nnfolder)
679                  (and (assoc group nnfolder-group-alist)
680                       (file-exists-p (nnfolder-group-pathname group)))))
681       (if dont-check
682           (setq nnfolder-current-group group
683                 nnfolder-current-buffer nil)
684         (let (inf file)
685           ;; If we have to change groups, see if we don't already have
686           ;; the folder in memory.  If we do, verify the modtime and
687           ;; destroy the folder if needed so we can rescan it.
688           (setq nnfolder-current-buffer
689                 (nth 1 (assoc group nnfolder-buffer-alist)))
690
691           ;; If the buffer is not live, make sure it isn't in the
692           ;; alist.  If it is live, verify that nobody else has
693           ;; touched the file since last time.
694           (when (and nnfolder-current-buffer
695                      (not (gnus-buffer-live-p nnfolder-current-buffer)))
696             (setq nnfolder-buffer-alist (delq inf nnfolder-buffer-alist)
697                   nnfolder-current-buffer nil))
698
699           (setq nnfolder-current-group group)
700
701           (when (or (not nnfolder-current-buffer)
702                     (not (verify-visited-file-modtime
703                           nnfolder-current-buffer)))
704             (save-excursion
705               (setq file (nnfolder-group-pathname group))
706               ;; See whether we need to create the new file.
707               (unless (file-exists-p file)
708                 (gnus-make-directory (file-name-directory file))
709                 (let ((nnmail-file-coding-system
710                        (or nnfolder-file-coding-system-for-write
711                            nnfolder-file-coding-system-for-write)))
712                   (nnmail-write-region 1 1 file t 'nomesg)))
713               (when (setq nnfolder-current-buffer (nnfolder-read-folder group))
714                 (set-buffer nnfolder-current-buffer)
715                 (push (list group nnfolder-current-buffer)
716                       nnfolder-buffer-alist)))))))))
717
718 (defun nnfolder-save-mail (group-art-list)
719   "Called narrowed to an article."
720   (let* (save-list group-art)
721     (goto-char (point-min))
722     ;; The From line may have been quoted by movemail.
723     (when (looking-at ">From")
724       (delete-char 1))
725     ;; This might come from somewhere else.
726     (unless (looking-at "From ")
727       (insert "From nobody " (current-time-string) "\n")
728       (goto-char (point-min)))
729     ;; Quote all "From " lines in the article.
730     (forward-line 1)
731     (let (case-fold-search)
732       (while (re-search-forward "^From " nil t)
733         (beginning-of-line)
734         (insert "> ")))
735     (setq save-list group-art-list)
736     (nnmail-insert-lines)
737     (nnmail-insert-xref group-art-list)
738     (run-hooks 'nnmail-prepare-save-mail-hook)
739     (run-hooks 'nnfolder-prepare-save-mail-hook)
740
741     ;; Insert the mail into each of the destination groups.
742     (while (setq group-art (pop group-art-list))
743       ;; Kill any previous newsgroup markers.
744       (goto-char (point-min))
745       (if (search-forward "\n\n" nil t)
746           (forward-line -1)
747         (goto-char (point-max)))
748       (while (search-backward (concat "\n" nnfolder-article-marker) nil t)
749         (delete-region (1+ (point)) (progn (forward-line 2) (point))))
750
751       ;; Insert the new newsgroup marker.
752       (nnfolder-insert-newsgroup-line group-art)
753
754       (save-excursion
755         (let ((beg (point-min))
756               (end (point-max))
757               (obuf (current-buffer)))
758           (nnfolder-possibly-change-folder (car group-art))
759           (let ((buffer-read-only nil))
760             (nnfolder-normalize-buffer)
761             (insert-buffer-substring obuf beg end))
762           (unless (or gnus-nov-is-evil nnfolder-nov-is-evil)
763             (set-buffer obuf)
764             (nnfolder-add-nov (car group-art) (cdr group-art)
765                               (nnfolder-parse-head nil beg end))))))
766
767     ;; Did we save it anywhere?
768     save-list))
769
770 (defun nnfolder-normalize-buffer ()
771   "Make sure there are two newlines at the end of the buffer."
772   (goto-char (point-max))
773   (skip-chars-backward "\n")
774   (delete-region (point) (point-max))
775   (unless (bobp)
776     (insert "\n\n")))
777
778 (defun nnfolder-insert-newsgroup-line (group-art)
779   (save-excursion
780     (goto-char (point-min))
781     (unless (search-forward "\n\n" nil t)
782       (goto-char (point-max))
783       (insert "\n"))
784     (forward-char -1)
785     (insert (format (concat nnfolder-article-marker "%d   %s\n")
786                     (cdr group-art) (current-time-string)))))
787
788 (defun nnfolder-active-number (group)
789   ;; Find the next article number in GROUP.
790   (let ((active (cadr (assoc group nnfolder-group-alist))))
791     (if active
792         (setcdr active (1+ (cdr active)))
793       ;; This group is new, so we create a new entry for it.
794       ;; This might be a bit naughty... creating groups on the drop of
795       ;; a hat, but I don't know...
796       (push (list group (setq active (cons 1 1)))
797             nnfolder-group-alist))
798     (cdr active)))
799
800 (defun nnfolder-possibly-change-folder (group)
801   (let ((inf (assoc group nnfolder-buffer-alist)))
802     (if (and inf
803              (gnus-buffer-live-p (cadr inf)))
804         (set-buffer (cadr inf))
805       (when inf
806         (setq nnfolder-buffer-alist (delq inf nnfolder-buffer-alist)))
807       (when nnfolder-group-alist
808         (nnfolder-save-active nnfolder-group-alist nnfolder-active-file))
809       (push (list group (nnfolder-read-folder group))
810             nnfolder-buffer-alist))))
811
812 ;; This method has a problem if you've accidentally let the active
813 ;; list get out of sync with the files.  This could happen, say, if
814 ;; you've accidentally gotten new mail with something other than Gnus
815 ;; (but why would _that_ ever happen? :-).  In that case, we will be
816 ;; in the middle of processing the file, ready to add new X-Gnus
817 ;; article number markers, and we'll run across a message with no ID
818 ;; yet - the active list _may_not_ be ready for us yet.
819
820 ;; To handle this, I'm modifying this routine to maintain the maximum
821 ;; ID seen so far, and when we hit a message with no ID, we will
822 ;; _manually_ scan the rest of the message looking for any more,
823 ;; possibly higher IDs.  We'll assume the maximum that we find is the
824 ;; highest active.  Note that this shouldn't cost us much extra time
825 ;; at all, but will be a lot less vulnerable to glitches between the
826 ;; mbox and the active file.
827
828 (defun nnfolder-read-folder (group)
829   (let* ((file (nnfolder-group-pathname group))
830          (nov  (nnfolder-group-nov-pathname group))
831          (buffer (set-buffer
832                   (let ((nnheader-file-coding-system
833                          nnfolder-file-coding-system))
834                     (nnheader-find-file-noselect file)))))
835     (mm-enable-multibyte) ;; Use multibyte buffer for future copying.
836     (if (equal (cadr (assoc group nnfolder-scantime-alist))
837                (nth 5 (file-attributes file)))
838         ;; This looks up-to-date, so we don't do any scanning.
839         (if (file-exists-p file)
840             buffer
841           (push (list group buffer) nnfolder-buffer-alist)
842           (set-buffer-modified-p t)
843           (nnfolder-save-buffer))
844       ;; Parse the damn thing.
845       (save-excursion
846         (goto-char (point-min))
847         ;; Remove any blank lines at the start.
848         (while (eq (following-char) ?\n)
849           (delete-char 1))
850         (nnmail-activate 'nnfolder)
851         ;; Read in the file.
852         (let ((delim "^From ")
853               (marker (concat "\n" nnfolder-article-marker))
854               (number "[0-9]+")
855               (active (or (cadr (assoc group nnfolder-group-alist))
856                           (cons 1 0)))
857               (scantime (assoc group nnfolder-scantime-alist))
858               (minid (lsh -1 -1))
859               maxid start end newscantime
860               novbuf articles newnum
861               buffer-read-only)
862           (buffer-disable-undo)
863           (setq maxid (cdr active))
864
865           (unless (or gnus-nov-is-evil nnfolder-nov-is-evil
866                       (and (file-exists-p nov)
867                            (file-newer-than-file-p nov file)))
868             (unless (file-exists-p nov)
869               (gnus-make-directory (file-name-directory nov)))
870             (with-current-buffer
871                 (setq novbuf (nnfolder-open-nov group))
872               (goto-char (point-min))
873               (while (not (eobp))
874                 (push (read novbuf) articles)
875                 (forward-line 1))
876               (setq articles (nreverse articles))))
877           (goto-char (point-min))
878
879           ;; Anytime the active number is 1 or 0, it is suspect.  In
880           ;; that case, search the file manually to find the active
881           ;; number.  Or, of course, if we're being paranoid.  (This
882           ;; would also be the place to build other lists from the
883           ;; header markers, such as expunge lists, etc., if we ever
884           ;; desired to abandon the active file entirely for mboxes.)
885           (when (or nnfolder-ignore-active-file
886                     novbuf
887                     (< maxid 2))
888             (while (and (search-forward marker nil t)
889                         (looking-at number))
890               (setq newnum (string-to-number (match-string 0)))
891               (when (nnmail-within-headers-p)
892                 (setq maxid (max maxid newnum)
893                       minid (min minid newnum))
894                 (when novbuf
895                   (if (memq newnum articles)
896                       (setq articles (delq newnum articles))
897                     (let ((headers (nnfolder-parse-head newnum)))
898                       (with-current-buffer novbuf
899                         (nnheader-find-nov-line newnum)
900                         (nnheader-insert-nov headers)))))))
901             (when (and novbuf articles)
902               (with-current-buffer novbuf
903                 (dolist (article articles)
904                   (when (nnheader-find-nov-line article)
905                     (delete-region (point)
906                                    (progn (forward-line 1) (point)))))))
907             (setcar active (max 1 (min minid maxid)))
908             (setcdr active (max maxid (cdr active)))
909             (goto-char (point-min)))
910
911           ;; As long as we trust that the user will only insert
912           ;; unmarked mail at the end, go to the end and search
913           ;; backwards for the last marker.  Find the start of that
914           ;; message, and begin to search for unmarked messages from
915           ;; there.
916           (when (not (or nnfolder-distrust-mbox
917                          (< maxid 2)))
918             (goto-char (point-max))
919             (unless (re-search-backward marker nil t)
920               (goto-char (point-min)))
921             ;;(when (nnmail-search-unix-mail-delim)
922             ;;  (goto-char (point-min)))
923             )
924
925           ;; Keep track of the active number on our own, and insert it
926           ;; back into the active list when we're done.  Also, prime
927           ;; the pump to cut down on the number of searches we do.
928           (unless (nnmail-search-unix-mail-delim)
929             (goto-char (point-max)))
930           (setq end (point-marker))
931           (while (not (= end (point-max)))
932             (setq start (marker-position end))
933             (goto-char end)
934            ;; There may be more than one "From " line, so we skip past
935             ;; them.
936             (while (looking-at delim)
937               (forward-line 1))
938             (set-marker end (if (nnmail-search-unix-mail-delim)
939                                 (point)
940                               (point-max)))
941             (goto-char start)
942             (when (not (search-forward marker end t))
943               (narrow-to-region start end)
944               (nnmail-insert-lines)
945               (nnfolder-insert-newsgroup-line
946                (cons nil
947                      (setq newnum
948                            (nnfolder-active-number group))))
949               (when novbuf
950                 (let ((headers (nnfolder-parse-head newnum (point-min)
951                                                     (point-max))))
952                   (with-current-buffer novbuf
953                     (goto-char (point-max))
954                     (nnheader-insert-nov headers))))
955               (widen)))
956
957           (set-marker end nil)
958           ;; Make absolutely sure that the active list reflects
959           ;; reality!
960           (nnfolder-save-active nnfolder-group-alist nnfolder-active-file)
961
962           ;; Set the scantime for this group.
963           (setq newscantime (visited-file-modtime))
964           (if scantime
965               (setcdr scantime (list newscantime))
966             (push (list group newscantime)
967                   nnfolder-scantime-alist))
968           ;; Save nov.
969           (when novbuf
970             (nnfolder-save-nov))
971           (current-buffer))))))
972
973 ;;;###autoload
974 (defun nnfolder-generate-active-file ()
975   "Look for mbox folders in the nnfolder directory and make them into groups.
976 This command does not work if you use short group names."
977   (interactive)
978   (nnmail-activate 'nnfolder)
979   (unless (or gnus-nov-is-evil nnfolder-nov-is-evil)
980     (dolist (file (directory-files (or nnfolder-nov-directory
981                                        nnfolder-directory)
982                                    t
983                                    (concat
984                                     (regexp-quote nnfolder-nov-file-suffix)
985                                     "$")))
986       (when (not (message-mail-file-mbox-p file))
987         (ignore-errors
988           (delete-file file)))))
989   (let ((files (directory-files nnfolder-directory))
990         file)
991     (while (setq file (pop files))
992       (when (and (not (backup-file-name-p file))
993                  (message-mail-file-mbox-p
994                   (nnheader-concat nnfolder-directory file)))
995         (let ((oldgroup (assoc file nnfolder-group-alist)))
996           (if oldgroup
997               (nnheader-message 5 "Refreshing group %s..." file)
998             (nnheader-message 5 "Adding group %s..." file))
999           (if oldgroup
1000               (setq nnfolder-group-alist
1001                     (delq oldgroup (copy-sequence nnfolder-group-alist))))
1002           (push (list file (cons 1 0)) nnfolder-group-alist)
1003           (nnfolder-possibly-change-folder file)
1004           (nnfolder-possibly-change-group file)
1005           (nnfolder-close-group file))))
1006     (nnheader-message 5 "")))
1007
1008 (defun nnfolder-group-pathname (group)
1009   "Make pathname for GROUP."
1010   (setq group
1011         (mm-encode-coding-string group nnmail-pathname-coding-system))
1012   (let ((dir (file-name-as-directory (expand-file-name nnfolder-directory))))
1013     ;; If this file exists, we use it directly.
1014     (if (or nnmail-use-long-file-names
1015             (file-exists-p (concat dir group)))
1016         (concat dir group)
1017       ;; If not, we translate dots into slashes.
1018       (concat dir (nnheader-replace-chars-in-string group ?. ?/)))))
1019
1020 (defun nnfolder-group-nov-pathname (group)
1021   "Make pathname for GROUP NOV."
1022   (let ((nnfolder-directory
1023          (or nnfolder-nov-directory nnfolder-directory)))
1024     (concat (nnfolder-group-pathname group) nnfolder-nov-file-suffix)))
1025
1026 (defun nnfolder-save-buffer ()
1027   "Save the buffer."
1028   (when (buffer-modified-p)
1029     (run-hooks 'nnfolder-save-buffer-hook)
1030     (gnus-make-directory (file-name-directory (buffer-file-name)))
1031     (let ((coding-system-for-write
1032            (or nnfolder-file-coding-system-for-write
1033                nnfolder-file-coding-system)))
1034       (save-buffer)))
1035   (unless (or gnus-nov-is-evil nnfolder-nov-is-evil)
1036     (nnfolder-save-nov)))
1037
1038 (defun nnfolder-save-active (group-alist active-file)
1039   (let ((nnmail-active-file-coding-system
1040          (or nnfolder-active-file-coding-system-for-write
1041              nnfolder-active-file-coding-system)))
1042     (nnmail-save-active group-alist active-file)))
1043
1044 (defun nnfolder-open-nov (group)
1045   (or (cdr (assoc group nnfolder-nov-buffer-alist))
1046       (let ((buffer (get-buffer-create (format " *nnfolder overview %s*" group))))
1047         (save-excursion
1048           (set-buffer buffer)
1049           (set (make-local-variable 'nnfolder-nov-buffer-file-name)
1050                (nnfolder-group-nov-pathname group))
1051           (erase-buffer)
1052           (when (file-exists-p nnfolder-nov-buffer-file-name)
1053             (nnheader-insert-file-contents nnfolder-nov-buffer-file-name)))
1054         (push (cons group buffer) nnfolder-nov-buffer-alist)
1055         buffer)))
1056
1057 (defun nnfolder-save-nov ()
1058   (save-excursion
1059     (while nnfolder-nov-buffer-alist
1060       (when (buffer-name (cdar nnfolder-nov-buffer-alist))
1061         (set-buffer (cdar nnfolder-nov-buffer-alist))
1062         (when (buffer-modified-p)
1063           (gnus-make-directory (file-name-directory
1064                                 nnfolder-nov-buffer-file-name))
1065           (nnmail-write-region 1 (point-max) nnfolder-nov-buffer-file-name
1066                                nil 'nomesg))
1067         (set-buffer-modified-p nil)
1068         (kill-buffer (current-buffer)))
1069       (setq nnfolder-nov-buffer-alist (cdr nnfolder-nov-buffer-alist)))))
1070
1071 (defun nnfolder-nov-delete-article (group article)
1072   (save-excursion
1073     (set-buffer (nnfolder-open-nov group))
1074     (when (nnheader-find-nov-line article)
1075       (delete-region (point) (progn (forward-line 1) (point))))
1076     t))
1077
1078 (defun nnfolder-retrieve-headers-with-nov (articles &optional fetch-old)
1079   (if (or gnus-nov-is-evil nnfolder-nov-is-evil)
1080       nil
1081     (let ((nov (nnfolder-group-nov-pathname nnfolder-current-group)))
1082       (when (file-exists-p nov)
1083         (save-excursion
1084           (set-buffer nntp-server-buffer)
1085           (erase-buffer)
1086           (nnheader-insert-file-contents nov)
1087           (if (and fetch-old
1088                    (not (numberp fetch-old)))
1089               t                         ; Don't remove anything.
1090             (nnheader-nov-delete-outside-range
1091              (if fetch-old (max 1 (- (car articles) fetch-old))
1092                (car articles))
1093              (car (last articles)))
1094             t))))))
1095
1096 (defun nnfolder-parse-head (&optional number b e)
1097   "Parse the head of the current buffer."
1098   (let ((buf (current-buffer))
1099         chars)
1100     (save-excursion
1101       (unless b
1102         (setq b (if (nnmail-search-unix-mail-delim-backward)
1103                     (point) (point-min)))
1104         (forward-line 1)
1105         (setq e (if (nnmail-search-unix-mail-delim)
1106                     (point) (point-max))))
1107       (setq chars (- e b))
1108       (unless (zerop chars)
1109         (goto-char b)
1110         (if (search-forward "\n\n" e t) (setq e (1- (point)))))
1111       (with-temp-buffer
1112         (insert-buffer-substring buf b e)
1113         ;; Fold continuation lines.
1114         (goto-char (point-min))
1115         (while (re-search-forward "\\(\r?\n[ \t]+\\)+" nil t)
1116           (replace-match " " t t))
1117         ;; Remove any tabs; they are too confusing.
1118         (subst-char-in-region (point-min) (point-max) ?\t ? )
1119         (let ((headers (nnheader-parse-head t)))
1120           (mail-header-set-chars headers chars)
1121           (mail-header-set-number headers number)
1122           headers)))))
1123
1124 (defun nnfolder-add-nov (group article headers)
1125   "Add a nov line for the GROUP base."
1126   (save-excursion
1127     (set-buffer (nnfolder-open-nov group))
1128     (goto-char (point-max))
1129     (mail-header-set-number headers article)
1130     (nnheader-insert-nov headers)))
1131
1132 (deffoo nnfolder-request-set-mark (group actions &optional server)
1133   (when (and server
1134              (not (nnfolder-server-opened server)))
1135     (nnfolder-open-server server))
1136   (unless nnfolder-marks-is-evil
1137     (nnfolder-open-marks group server)
1138     (dolist (action actions)
1139       (let ((range (nth 0 action))
1140             (what  (nth 1 action))
1141             (marks (nth 2 action)))
1142         (assert (or (eq what 'add) (eq what 'del)) t
1143                 "Unknown request-set-mark action: %s" what)
1144         (dolist (mark marks)
1145           (setq nnfolder-marks (gnus-update-alist-soft
1146                             mark
1147                             (funcall (if (eq what 'add) 'gnus-range-add
1148                                        'gnus-remove-from-range)
1149                                      (cdr (assoc mark nnfolder-marks)) range)
1150                             nnfolder-marks)))))
1151     (nnfolder-save-marks group server))
1152   nil)
1153
1154 (deffoo nnfolder-request-update-info (group info &optional server)
1155   ;; Change servers.
1156   (when (and server
1157              (not (nnfolder-server-opened server)))
1158     (nnfolder-open-server server))
1159   (when (and (not nnfolder-marks-is-evil) (nnfolder-marks-changed-p group))
1160     (nnheader-message 8 "Updating marks for %s..." group)
1161     (nnfolder-open-marks group server)
1162     ;; Update info using `nnfolder-marks'.
1163     (mapcar (lambda (pred)
1164               (unless (memq (cdr pred) gnus-article-unpropagated-mark-lists)
1165                 (gnus-info-set-marks
1166                  info
1167                  (gnus-update-alist-soft
1168                   (cdr pred)
1169                   (cdr (assq (cdr pred) nnfolder-marks))
1170                   (gnus-info-marks info))
1171                  t)))
1172             gnus-article-mark-lists)
1173     (let ((seen (cdr (assq 'read nnfolder-marks))))
1174       (gnus-info-set-read info
1175                           (if (and (integerp (car seen))
1176                                    (null (cdr seen)))
1177                               (list (cons (car seen) (car seen)))
1178                             seen)))
1179     (nnheader-message 8 "Updating marks for %s...done" group))
1180   info)
1181
1182 (defun nnfolder-group-marks-pathname (group)
1183   "Make pathname for GROUP NOV."
1184   (let ((nnfolder-directory (or nnfolder-marks-directory nnfolder-directory)))
1185     (concat (nnfolder-group-pathname group) nnfolder-marks-file-suffix)))
1186
1187 (defun nnfolder-marks-changed-p (group)
1188   (let ((file (nnfolder-group-marks-pathname group)))
1189     (if (null (gnus-gethash file nnfolder-marks-modtime))
1190         t ;; never looked at marks file, assume it has changed
1191       (not (equal (gnus-gethash file nnfolder-marks-modtime)
1192                   (nth 5 (file-attributes file)))))))
1193
1194 (defun nnfolder-save-marks (group server)
1195   (let ((file-name-coding-system nnmail-pathname-coding-system)
1196         (file (nnfolder-group-marks-pathname group)))
1197     (condition-case err
1198         (progn
1199           (with-temp-file file
1200             (erase-buffer)
1201             (gnus-prin1 nnfolder-marks)
1202             (insert "\n"))
1203           (gnus-sethash file
1204                         (nth 5 (file-attributes file))
1205                         nnfolder-marks-modtime))
1206       (error (or (gnus-yes-or-no-p
1207                   (format "Could not write to %s (%s).  Continue? " file err))
1208                  (error "Cannot write to %s (%s)" err))))))
1209
1210 (defun nnfolder-open-marks (group server)
1211   (let ((file (nnfolder-group-marks-pathname group)))
1212     (if (file-exists-p file)
1213         (condition-case err
1214             (with-temp-buffer
1215               (gnus-sethash file (nth 5 (file-attributes file)) 
1216                             nnfolder-marks-modtime)
1217               (nnheader-insert-file-contents file)
1218               (setq nnfolder-marks (read (current-buffer)))
1219               (dolist (el gnus-article-unpropagated-mark-lists)
1220                 (setq nnfolder-marks (gnus-remassoc el nnfolder-marks))))
1221           (error (or (gnus-yes-or-no-p
1222                       (format "Error reading nnfolder marks file %s (%s).  Continuing will use marks from .newsrc.eld.  Continue? " file err))
1223                      (error "Cannot read nnfolder marks file %s (%s)" file err))))
1224       ;; User didn't have a .marks file.  Probably first time
1225       ;; user of the .marks stuff.  Bootstrap it from .newsrc.eld.
1226       (let ((info (gnus-get-info
1227                    (gnus-group-prefixed-name
1228                     group
1229                     (gnus-server-to-method (format "nnfolder:%s" server))))))
1230         (nnheader-message 7 "Bootstrapping marks for %s..." group)
1231         (setq nnfolder-marks (gnus-info-marks info))
1232         (push (cons 'read (gnus-info-read info)) nnfolder-marks)
1233         (dolist (el gnus-article-unpropagated-mark-lists)
1234           (setq nnfolder-marks (gnus-remassoc el nnfolder-marks)))
1235         (nnfolder-save-marks group server)))))
1236
1237 (provide 'nnfolder)
1238
1239 ;;; nnfolder.el ends here