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