2000-02-15 Andrew Innes <andrewi@gnu.org>
[gnus] / lisp / nnmbox.el
1 ;;; nnmbox.el --- mail mbox access for Gnus
2 ;; Copyright (C) 1995,96,97,98,99 Free Software Foundation, Inc.
3
4 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
5 ;;      Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
6 ;; Keywords: news, mail
7
8 ;; This file is part of GNU Emacs.
9
10 ;; GNU Emacs is free software; you can redistribute it and/or modify
11 ;; it under the terms of the GNU General Public License as published by
12 ;; the Free Software Foundation; either version 2, or (at your option)
13 ;; any later version.
14
15 ;; You should have received a copy of the GNU General Public License
16 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
17 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 ;; Boston, MA 02111-1307, USA.
19
20 ;;; Commentary:
21
22 ;; For an overview of what the interface functions do, please see the
23 ;; Gnus sources.
24
25 ;;; Code:
26
27 (require 'nnheader)
28 (require 'message)
29 (require 'nnmail)
30 (require 'nnoo)
31 (require 'gnus-range)
32 (eval-when-compile (require 'cl))
33
34 (nnoo-declare nnmbox)
35
36 (defvoo nnmbox-mbox-file (expand-file-name "~/mbox")
37   "The name of the mail box file in the user's home directory.")
38
39 (defvoo nnmbox-active-file (expand-file-name "~/.mbox-active")
40   "The name of the active file for the mail box.")
41
42 (defvoo nnmbox-get-new-mail t
43   "If non-nil, nnmbox will check the incoming mail file and split the mail.")
44
45 (defvoo nnmbox-prepare-save-mail-hook nil
46   "Hook run narrowed to an article before saving.")
47
48 \f
49
50 (defconst nnmbox-version "nnmbox 1.0"
51   "nnmbox version.")
52
53 (defvoo nnmbox-current-group nil
54   "Current nnmbox news group directory.")
55
56 (defconst nnmbox-mbox-buffer nil)
57
58 (defvoo nnmbox-status-string "")
59
60 (defvoo nnmbox-group-alist nil)
61 (defvoo nnmbox-active-timestamp nil)
62
63 (defvoo nnmbox-file-coding-system mm-text-coding-system)
64 (defvoo nnmbox-file-coding-system-for-write nil)
65 (defvoo nnmbox-active-file-coding-system mm-text-coding-system)
66 (defvoo nnmbox-active-file-coding-system-for-write nil)
67
68 (defvar nnmbox-group-building-active-articles nil)
69 (defvar nnmbox-group-active-articles nil)
70 \f
71
72 ;;; Interface functions
73
74 (nnoo-define-basics nnmbox)
75
76 (deffoo nnmbox-retrieve-headers (sequence &optional newsgroup server fetch-old)
77   (save-excursion
78     (set-buffer nntp-server-buffer)
79     (erase-buffer)
80     (let ((number (length sequence))
81           (count 0)
82           article start stop)
83       (nnmbox-possibly-change-newsgroup newsgroup server)
84       (while sequence
85         (setq article (car sequence))
86         (set-buffer nnmbox-mbox-buffer)
87         (when (nnmbox-find-article article)
88           (setq start
89                 (save-excursion
90                   (re-search-backward
91                    (concat "^" message-unix-mail-delimiter) nil t)
92                   (point)))
93           (search-forward "\n\n" nil t)
94           (setq stop (1- (point)))
95           (set-buffer nntp-server-buffer)
96           (insert (format "221 %d Article retrieved.\n" article))
97           (insert-buffer-substring nnmbox-mbox-buffer start stop)
98           (goto-char (point-max))
99           (insert ".\n"))
100         (setq sequence (cdr sequence))
101         (setq count (1+ count))
102         (and (numberp nnmail-large-newsgroup)
103              (> number nnmail-large-newsgroup)
104              (zerop (% count 20))
105              (nnheader-message 5 "nnmbox: Receiving headers... %d%%"
106                                (/ (* count 100) number))))
107
108       (and (numberp nnmail-large-newsgroup)
109            (> number nnmail-large-newsgroup)
110            (nnheader-message 5 "nnmbox: Receiving headers...done"))
111
112       (set-buffer nntp-server-buffer)
113       (nnheader-fold-continuation-lines)
114       'headers)))
115
116 (deffoo nnmbox-open-server (server &optional defs)
117   (nnoo-change-server 'nnmbox server defs)
118   (nnmbox-create-mbox)
119   (cond
120    ((not (file-exists-p nnmbox-mbox-file))
121     (nnmbox-close-server)
122     (nnheader-report 'nnmbox "No such file: %s" nnmbox-mbox-file))
123    ((file-directory-p nnmbox-mbox-file)
124     (nnmbox-close-server)
125     (nnheader-report 'nnmbox "Not a regular file: %s" nnmbox-mbox-file))
126    (t
127     (nnheader-report 'nnmbox "Opened server %s using mbox %s" server
128                      nnmbox-mbox-file)
129     t)))
130
131 (deffoo nnmbox-close-server (&optional server)
132   (when (and nnmbox-mbox-buffer
133              (buffer-name nnmbox-mbox-buffer))
134     (kill-buffer nnmbox-mbox-buffer))
135   (nnoo-close-server 'nnmbox server)
136   t)
137
138 (deffoo nnmbox-server-opened (&optional server)
139   (and (nnoo-current-server-p 'nnmbox server)
140        nnmbox-mbox-buffer
141        (buffer-name nnmbox-mbox-buffer)
142        nntp-server-buffer
143        (buffer-name nntp-server-buffer)))
144
145 (deffoo nnmbox-request-article (article &optional newsgroup server buffer)
146   (nnmbox-possibly-change-newsgroup newsgroup server)
147   (save-excursion
148     (set-buffer nnmbox-mbox-buffer)
149     (when (nnmbox-find-article article)
150       (let (start stop)
151         (re-search-backward (concat "^" message-unix-mail-delimiter) nil t)
152         (setq start (point))
153         (forward-line 1)
154         (or (and (re-search-forward
155                   (concat "^" message-unix-mail-delimiter) nil t)
156                  (forward-line -1))
157             (goto-char (point-max)))
158         (setq stop (point))
159         (let ((nntp-server-buffer (or buffer nntp-server-buffer)))
160           (set-buffer nntp-server-buffer)
161           (erase-buffer)
162           (insert-buffer-substring nnmbox-mbox-buffer start stop)
163           (goto-char (point-min))
164           (while (looking-at "From ")
165             (delete-char 5)
166             (insert "X-From-Line: ")
167             (forward-line 1))
168           (if (numberp article)
169               (cons nnmbox-current-group article)
170             (nnmbox-article-group-number nil)))))))
171
172 (deffoo nnmbox-request-group (group &optional server dont-check)
173   (nnmbox-possibly-change-newsgroup nil server)
174   (let ((active (cadr (assoc group nnmbox-group-alist))))
175     (cond
176      ((or (null active)
177           (null (nnmbox-possibly-change-newsgroup group server)))
178       (nnheader-report 'nnmbox "No such group: %s" group))
179      (dont-check
180       (nnheader-report 'nnmbox "Selected group %s" group)
181       (nnheader-insert ""))
182      (t
183       (nnheader-report 'nnmbox "Selected group %s" group)
184       (nnheader-insert "211 %d %d %d %s\n"
185                        (1+ (- (cdr active) (car active)))
186                        (car active) (cdr active) group)))))
187
188 (defun nnmbox-save-buffer ()
189   (let ((coding-system-for-write 
190          (or nnmbox-file-coding-system-for-write
191              nnmbox-file-coding-system)))
192     (save-buffer)))
193
194 (defun nnmbox-save-active (group-alist active-file)
195   (let ((nnmail-active-file-coding-system
196          (or nnmbox-active-file-coding-system-for-write
197              nnmbox-active-file-coding-system)))
198     (nnmail-save-active group-alist active-file)))
199
200 (deffoo nnmbox-request-scan (&optional group server)
201   (nnmbox-possibly-change-newsgroup group server)
202   (nnmbox-read-mbox)
203   (nnmail-get-new-mail
204    'nnmbox
205    (lambda ()
206      (save-excursion
207        (set-buffer nnmbox-mbox-buffer)
208        (nnmbox-save-buffer)))
209    (file-name-directory nnmbox-mbox-file)
210    group
211    (lambda ()
212      (save-excursion
213        (let ((in-buf (current-buffer)))
214          (set-buffer nnmbox-mbox-buffer)
215          (goto-char (point-max))
216          (insert-buffer-substring in-buf)))
217      (nnmbox-save-active nnmbox-group-alist nnmbox-active-file))))
218
219 (deffoo nnmbox-close-group (group &optional server)
220   t)
221
222 (deffoo nnmbox-request-create-group (group &optional server args)
223   (nnmail-activate 'nnmbox)
224   (unless (assoc group nnmbox-group-alist)
225     (push (list group (cons 1 0))
226           nnmbox-group-alist)
227     (nnmbox-save-active nnmbox-group-alist nnmbox-active-file))
228   t)
229
230 (deffoo nnmbox-request-list (&optional server)
231   (save-excursion
232     (let ((nnmail-file-coding-system
233            nnmbox-active-file-coding-system))
234       (nnmail-find-file nnmbox-active-file))
235     (setq nnmbox-group-alist (nnmail-get-active))
236     t))
237
238 (deffoo nnmbox-request-newgroups (date &optional server)
239   (nnmbox-request-list server))
240
241 (deffoo nnmbox-request-list-newsgroups (&optional server)
242   (nnheader-report 'nnmbox "LIST NEWSGROUPS is not implemented."))
243
244 (deffoo nnmbox-request-expire-articles
245     (articles newsgroup &optional server force)
246   (nnmbox-possibly-change-newsgroup newsgroup server)
247   (let* ((is-old t)
248          rest)
249     (nnmail-activate 'nnmbox)
250
251     (save-excursion
252       (set-buffer nnmbox-mbox-buffer)
253       (while (and articles is-old)
254         (when (nnmbox-find-article (car articles))
255           (if (setq is-old
256                     (nnmail-expired-article-p
257                      newsgroup
258                      (buffer-substring
259                       (point) (progn (end-of-line) (point))) force))
260               (progn
261                 (unless (eq nnmail-expiry-target 'delete)
262                   (with-temp-buffer
263                     (nnmbox-request-article (car articles) 
264                                              newsgroup server 
265                                              (current-buffer))
266                     (let ((nnml-current-directory nil))
267                       (nnmail-expiry-target-group
268                        nnmail-expiry-target newsgroup))))
269                 (nnheader-message 5 "Deleting article %d in %s..."
270                                   (car articles) newsgroup)
271                 (nnmbox-delete-mail))
272             (push (car articles) rest)))
273         (setq articles (cdr articles)))
274       (nnmbox-save-buffer)
275       ;; Find the lowest active article in this group.
276       (let ((active (nth 1 (assoc newsgroup nnmbox-group-alist))))
277         (while (and (not (nnmbox-find-article (car active)))
278                     (<= (car active) (cdr active)))
279           (setcar active (1+ (car active)))))
280       (nnmbox-save-active nnmbox-group-alist nnmbox-active-file)
281       (nconc rest articles))))
282
283 (deffoo nnmbox-request-move-article
284     (article group server accept-form &optional last)
285   (let ((buf (get-buffer-create " *nnmbox move*"))
286         result)
287     (and
288      (nnmbox-request-article article group server)
289      (save-excursion
290        (set-buffer buf)
291        (erase-buffer)
292        (insert-buffer-substring nntp-server-buffer)
293        (goto-char (point-min))
294        (while (re-search-forward
295                "^X-Gnus-Newsgroup:"
296                (save-excursion (search-forward "\n\n" nil t) (point)) t)
297          (delete-region (progn (beginning-of-line) (point))
298                         (progn (forward-line 1) (point))))
299        (setq result (eval accept-form))
300        (kill-buffer buf)
301        result)
302      (save-excursion
303        (nnmbox-possibly-change-newsgroup group server)
304        (set-buffer nnmbox-mbox-buffer)
305        (when (nnmbox-find-article article)
306          (nnmbox-delete-mail))
307        (and last (nnmbox-save-buffer))))
308     result))
309
310 (deffoo nnmbox-request-accept-article (group &optional server last)
311   (nnmbox-possibly-change-newsgroup group server)
312   (nnmail-check-syntax)
313   (let ((buf (current-buffer))
314         result)
315     (goto-char (point-min))
316     ;; The From line may have been quoted by movemail.
317     (when (looking-at (concat ">" message-unix-mail-delimiter))
318       (delete-char 1))
319     (if (looking-at "X-From-Line: ")
320         (replace-match "From ")
321       (insert "From nobody " (current-time-string) "\n"))
322     (and
323      (nnmail-activate 'nnmbox)
324      (progn
325        (set-buffer buf)
326        (goto-char (point-min))
327        (search-forward "\n\n" nil t)
328        (forward-line -1)
329        (while (re-search-backward "^X-Gnus-Newsgroup: " nil t)
330          (delete-region (point) (progn (forward-line 1) (point))))
331        (when nnmail-cache-accepted-message-ids
332          (nnmail-cache-insert (nnmail-fetch-field "message-id")))
333        (setq result (if (stringp group)
334                         (list (cons group (nnmbox-active-number group)))
335                       (nnmail-article-group 'nnmbox-active-number)))
336        (if (and (null result)
337                 (yes-or-no-p "Moved to `junk' group; delete article? "))
338            (setq result 'junk)
339          (setq result (car (nnmbox-save-mail result)))))
340      (save-excursion
341        (set-buffer nnmbox-mbox-buffer)
342        (goto-char (point-max))
343        (insert-buffer-substring buf)
344        (when last
345          (when nnmail-cache-accepted-message-ids
346            (nnmail-cache-close))
347          (nnmbox-save-active nnmbox-group-alist nnmbox-active-file)
348          (nnmbox-save-buffer))))
349     result))
350
351 (deffoo nnmbox-request-replace-article (article group buffer)
352   (nnmbox-possibly-change-newsgroup group)
353   (save-excursion
354     (set-buffer nnmbox-mbox-buffer)
355     (if (not (nnmbox-find-article article))
356         nil
357       (nnmbox-delete-mail t t)
358       (insert-buffer-substring buffer)
359       (nnmbox-save-buffer)
360       t)))
361
362 (deffoo nnmbox-request-delete-group (group &optional force server)
363   (nnmbox-possibly-change-newsgroup group server)
364   ;; Delete all articles in GROUP.
365   (if (not force)
366       ()                                ; Don't delete the articles.
367     (save-excursion
368       (set-buffer nnmbox-mbox-buffer)
369       (goto-char (point-min))
370       ;; Delete all articles in this group.
371       (let ((ident (concat "\nX-Gnus-Newsgroup: " nnmbox-current-group ":"))
372             found)
373         (while (search-forward ident nil t)
374           (setq found t)
375           (nnmbox-delete-mail))
376         (when found
377           (nnmbox-save-buffer)))))
378   ;; Remove the group from all structures.
379   (setq nnmbox-group-alist
380         (delq (assoc group nnmbox-group-alist) nnmbox-group-alist)
381         nnmbox-current-group nil)
382   ;; Save the active file.
383   (nnmbox-save-active nnmbox-group-alist nnmbox-active-file)
384   t)
385
386 (deffoo nnmbox-request-rename-group (group new-name &optional server)
387   (nnmbox-possibly-change-newsgroup group server)
388   (save-excursion
389     (set-buffer nnmbox-mbox-buffer)
390     (goto-char (point-min))
391     (let ((ident (concat "\nX-Gnus-Newsgroup: " nnmbox-current-group ":"))
392           (new-ident (concat "\nX-Gnus-Newsgroup: " new-name ":"))
393           found)
394       (while (search-forward ident nil t)
395         (replace-match new-ident t t)
396         (setq found t))
397       (when found
398         (nnmbox-save-buffer))))
399   (let ((entry (assoc group nnmbox-group-active-articles)))
400     (when entry
401       (setcar entry new-name)))
402   (let ((entry (assoc group nnmbox-group-alist)))
403     (when entry
404       (setcar entry new-name))
405     (setq nnmbox-current-group nil)
406     ;; Save the new group alist.
407     (nnmbox-save-active nnmbox-group-alist nnmbox-active-file)
408     t))
409
410 \f
411 ;;; Internal functions.
412
413 ;; If FORCE, delete article no matter how many X-Gnus-Newsgroup
414 ;; headers there are.  If LEAVE-DELIM, don't delete the Unix mbox
415 ;; delimiter line.
416 (defun nnmbox-delete-mail (&optional force leave-delim)
417   ;; Delete the current X-Gnus-Newsgroup line.
418   ;; First delete record of active article, unless the article is being
419   ;; replaced, indicated by FORCE being non-nil.
420   (if (not force)
421       (nnmbox-record-deleted-article (nnmbox-article-group-number t)))
422   (or force
423       (delete-region
424        (progn (beginning-of-line) (point))
425        (progn (forward-line 1) (point))))
426   ;; Beginning of the article.
427   (save-excursion
428     (save-restriction
429       (narrow-to-region
430        (save-excursion
431          (re-search-backward (concat "^" message-unix-mail-delimiter) nil t)
432          (if leave-delim (progn (forward-line 1) (point))
433            (match-beginning 0)))
434        (progn
435          (forward-line 1)
436          (or (and (re-search-forward (concat "^" message-unix-mail-delimiter)
437                                      nil t)
438                   (if (and (not (bobp)) leave-delim)
439                       (progn (forward-line -2) (point))
440                     (match-beginning 0)))
441              (point-max))))
442       (goto-char (point-min))
443       ;; Only delete the article if no other group owns it as well.
444       (when (or force (not (re-search-forward "^X-Gnus-Newsgroup: " nil t)))
445         (delete-region (point-min) (point-max))))))
446
447 (defun nnmbox-possibly-change-newsgroup (newsgroup &optional server)
448   (when (and server
449              (not (nnmbox-server-opened server)))
450     (nnmbox-open-server server))
451   (when (or (not nnmbox-mbox-buffer)
452             (not (buffer-name nnmbox-mbox-buffer)))
453     (nnmbox-read-mbox))
454   (when (not nnmbox-group-alist)
455     (nnmail-activate 'nnmbox))
456   (if newsgroup
457       (when (assoc newsgroup nnmbox-group-alist)
458         (setq nnmbox-current-group newsgroup))
459     t))
460
461 (defun nnmbox-article-string (article)
462   (if (numberp article)
463       (concat "\nX-Gnus-Newsgroup: " nnmbox-current-group ":"
464               (int-to-string article) " ")
465     (concat "\nMessage-ID: " article)))
466
467 (defun nnmbox-article-group-number (this-line)
468   (save-excursion
469     (if this-line
470         (beginning-of-line)
471       (goto-char (point-min)))
472     (when (re-search-forward "^X-Gnus-Newsgroup: +\\([^:]+\\):\\([0-9]+\\) "
473                              nil t)
474       (cons (buffer-substring (match-beginning 1) (match-end 1))
475             (string-to-int
476              (buffer-substring (match-beginning 2) (match-end 2)))))))
477
478 (defun nnmbox-in-header-p (pos)
479   "Return non-nil if POS is in the header of an article."
480   (save-excursion
481     (goto-char pos)
482     (re-search-backward (concat "^" message-unix-mail-delimiter) nil t)
483     (search-forward "\n\n" nil t)
484     (< pos (point))))
485
486 (defun nnmbox-find-article (article)
487   "Leaves point on the relevant X-Gnus-Newsgroup line if found."
488   ;; Check that article is in the active range first, to avoid an
489   ;; expensive exhaustive search if it isn't.
490   (if (and (numberp article)
491            (not (nnmbox-is-article-active-p article)))
492       nil
493     (let ((art-string (nnmbox-article-string article))
494           (found nil))
495       ;; There is the possibility that the X-Gnus-Newsgroup line appears
496       ;; in the body of an article (for instance, if an article has been
497       ;; forwarded from someone using Gnus as their mailer), so check
498       ;; that the line is actually part of the article header.
499       (or (and (search-forward art-string nil t)
500                (nnmbox-in-header-p (point)))
501           (progn
502             (goto-char (point-min))
503             (while (not found)
504               (setq found (and (search-forward art-string nil t)
505                                (nnmbox-in-header-p (point)))))
506             found)))))
507
508 (defun nnmbox-record-active-article (group-art)
509   (let* ((group (car group-art))
510          (article (cdr group-art))
511          (entry
512           (or (assoc group nnmbox-group-active-articles)
513               (progn
514                 (push (list group)
515                       nnmbox-group-active-articles)
516                 (car nnmbox-group-active-articles)))))
517     ;; add article to index, either by building complete list
518     ;; in reverse order, or as a list of ranges.
519     (if (not nnmbox-group-building-active-articles)
520         (setcdr entry (gnus-add-to-range (cdr entry) (list article)))
521       (when (memq article (cdr entry))
522         (switch-to-buffer nnmbox-mbox-buffer)
523         (error "Article %s:%d already exists!" group article))
524       (when (and (cadr entry) (< article (cadr entry)))
525         (switch-to-buffer nnmbox-mbox-buffer)
526         (error "Article %s:%d out of order" group article))
527       (setcdr entry (cons article (cdr entry))))))
528
529 (defun nnmbox-record-deleted-article (group-art)
530   (let* ((group (car group-art))
531          (article (cdr group-art))
532          (entry
533           (or (assoc group nnmbox-group-active-articles)
534               (progn
535                 (push (list group)
536                       nnmbox-group-active-articles)
537                 (car nnmbox-group-active-articles)))))
538     ;; remove article from index
539     (setcdr entry (gnus-remove-from-range (cdr entry) (list article)))))
540
541 (defun nnmbox-is-article-active-p (article)
542   (gnus-member-of-range
543    article
544    (cdr (assoc nnmbox-current-group
545                nnmbox-group-active-articles))))
546
547 (defun nnmbox-save-mail (group-art)
548   "Called narrowed to an article."
549   (let ((delim (concat "^" message-unix-mail-delimiter)))
550     (goto-char (point-min))
551     ;; This might come from somewhere else.
552     (unless (looking-at delim)
553       (insert "From nobody " (current-time-string) "\n")
554       (goto-char (point-min)))
555     ;; Quote all "From " lines in the article.
556     (forward-line 1)
557     (while (re-search-forward delim nil t)
558       (beginning-of-line)
559       (insert "> "))
560     (nnmail-insert-lines)
561     (nnmail-insert-xref group-art)
562     (nnmbox-insert-newsgroup-line group-art)
563     (let ((alist group-art))
564       (while alist
565         (nnmbox-record-active-article (car alist))
566         (setq alist (cdr alist))))
567     (run-hooks 'nnmail-prepare-save-mail-hook)
568     (run-hooks 'nnmbox-prepare-save-mail-hook)
569     group-art))
570
571 (defun nnmbox-insert-newsgroup-line (group-art)
572   (save-excursion
573     (goto-char (point-min))
574     (when (search-forward "\n\n" nil t)
575       (forward-char -1)
576       (while group-art
577         (insert (format "X-Gnus-Newsgroup: %s:%d   %s\n"
578                         (caar group-art) (cdar group-art)
579                         (current-time-string)))
580         (setq group-art (cdr group-art))))
581     t))
582
583 (defun nnmbox-active-number (group)
584   ;; Find the next article number in GROUP.
585   (let ((active (cadr (assoc group nnmbox-group-alist))))
586     (if active
587         (setcdr active (1+ (cdr active)))
588       ;; This group is new, so we create a new entry for it.
589       ;; This might be a bit naughty... creating groups on the drop of
590       ;; a hat, but I don't know...
591       (push (list group (setq active (cons 1 1)))
592             nnmbox-group-alist))
593     (cdr active)))
594
595 (defun nnmbox-create-mbox ()
596   (when (not (file-exists-p nnmbox-mbox-file))
597     (let ((nnmail-file-coding-system
598            (or nnmbox-file-coding-system-for-write
599                nnmbox-file-coding-system)))
600       (nnmail-write-region 1 1 nnmbox-mbox-file t 'nomesg))))
601
602 (defun nnmbox-read-mbox ()
603   (nnmail-activate 'nnmbox)
604   (nnmbox-create-mbox)
605   (if (and nnmbox-mbox-buffer
606            (buffer-name nnmbox-mbox-buffer)
607            (save-excursion
608              (set-buffer nnmbox-mbox-buffer)
609              (= (buffer-size) (nnheader-file-size nnmbox-mbox-file))))
610       ()
611     (save-excursion
612       (let ((delim (concat "^" message-unix-mail-delimiter))
613             (alist nnmbox-group-alist)
614             (nnmbox-group-building-active-articles t)
615             start end end-header number)
616         (set-buffer (setq nnmbox-mbox-buffer
617                           (let ((nnheader-file-coding-system
618                                  nnmbox-file-coding-system))
619                             (nnheader-find-file-noselect
620                              nnmbox-mbox-file nil t))))
621         (mm-enable-multibyte)
622         (buffer-disable-undo)
623
624         ;; Go through the group alist and compare against the mbox file.
625         (while alist
626           (goto-char (point-max))
627           (when (and (re-search-backward
628                       (format "^X-Gnus-Newsgroup: %s:\\([0-9]+\\) "
629                               (caar alist)) nil t)
630                      (> (setq number
631                               (string-to-number
632                                (buffer-substring
633                                 (match-beginning 1) (match-end 1))))
634                         (cdadar alist)))
635             (setcdr (cadar alist) number))
636           (setq alist (cdr alist)))
637
638         ;; Examine all articles for our private X-Gnus-Newsgroup
639         ;; headers.  This is done primarily as a consistency check, but
640         ;; it is convenient for building an index of the articles
641         ;; present, to avoid costly searches for missing articles
642         ;; (eg. when expiring articles).
643         (goto-char (point-min))
644         (setq nnmbox-group-active-articles nil)
645         (while (re-search-forward delim nil t)
646           (setq start (match-beginning 0))
647           (save-excursion
648             (search-forward "\n\n" nil t)
649             (setq end-header (point))
650             (setq end (or (and
651                            (re-search-forward delim nil t)
652                            (match-beginning 0))
653                           (point-max))))
654           (if (search-forward "\nX-Gnus-Newsgroup: " end-header t)
655               ;; Build a list of articles in each group, remembering
656               ;; that each article may be in more than one group.
657               (progn
658                 (nnmbox-record-active-article (nnmbox-article-group-number t))
659                 (while (search-forward "\nX-Gnus-Newsgroup: " end-header t)
660                   (nnmbox-record-active-article (nnmbox-article-group-number t))))
661             ;; The article is either new, or for some other reason
662             ;; hasn't got our private headers, so add them now.  The
663             ;; only situation I've encountered when the X-Gnus-Newsgroup
664             ;; header is missing is if the article contains a forwarded
665             ;; message which does contain that header line (earlier
666             ;; versions of Gnus didn't restrict their search to the
667             ;; headers).  In this case, there is an Xref line which
668             ;; provides the relevant information to construct the
669             ;; missing header(s).
670             (save-excursion
671               (save-restriction
672                 (narrow-to-region start end)
673                 (if (re-search-forward "\nXref: [^ ]+" end-header t)
674                     ;; generate headers from Xref:
675                     (let (alist)
676                       (while (re-search-forward " \\([^:]+\\):\\([0-9]+\\)" end-header t)
677                         (push (cons (match-string 1)
678                                     (string-to-int (match-string 2))) alist))
679                       (nnmbox-insert-newsgroup-line alist))
680                   ;; this is really a new article
681                   (nnmbox-save-mail
682                    (nnmail-article-group 'nnmbox-active-number))))))
683           (goto-char end))
684         ;; put article lists in order
685         (setq alist nnmbox-group-active-articles)
686         (while alist
687           (setcdr (car alist) (gnus-compress-sequence (nreverse (cdar alist))))
688           (setq alist (cdr alist)))))))
689
690 (provide 'nnmbox)
691
692 ;;; nnmbox.el ends here