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