*** empty log message ***
[gnus] / lisp / nnmbox.el
1 ;;; nnmbox.el --- mail mbox access for Gnus
2 ;; Copyright (C) 1995 Free Software Foundation, Inc.
3
4 ;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
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 ;; GNU Emacs is distributed in the hope that it will be useful,
16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 ;; GNU General Public License for more details.
19
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with GNU Emacs; see the file COPYING.  If not, write to
22 ;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
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 'rmail)
33 (require 'nnmail)
34
35 (defvar nnmbox-mbox-file (expand-file-name "~/mbox")
36   "The name of the mail box file in the user's home directory.")
37
38 (defvar nnmbox-active-file (expand-file-name "~/.mbox-active")
39   "The name of the active file for the mail box.")
40
41 (defvar nnmbox-get-new-mail t
42   "If non-nil, nnmbox will check the incoming mail file and split the mail.")
43
44 \f
45
46 (defconst nnmbox-version "nnmbox 0.1"
47   "nnmbox version.")
48
49 (defvar nnmbox-current-group nil
50   "Current nnmbox news group directory.")
51
52 (defconst nnmbox-mbox-buffer " *nnmbox mbox buffer*")
53
54 (defvar nnmbox-status-string "")
55
56 (defvar nnmbox-group-alist nil)
57
58 \f
59
60 (defvar nnmbox-current-server nil)
61 (defvar nnmbox-server-alist nil)
62 (defvar nnmbox-server-variables 
63   (list
64    (list 'nnmbox-mbox-file nnmbox-mbox-file)
65    (list 'nnmbox-active-file nnmbox-active-file)
66    (list 'nnmbox-get-new-mail nnmbox-get-new-mail)
67    '(nnmbox-current-group nil)
68    '(nnmbox-status-string "")
69    '(nnmbox-group-alist nil)))
70
71 \f
72
73 ;;; Interface functions
74
75 (defun nnmbox-retrieve-headers (sequence &optional newsgroup server)
76   (save-excursion
77     (set-buffer nntp-server-buffer)
78     (erase-buffer)
79     (let ((file nil)
80           (number (length sequence))
81           (count 0)
82           beg article art-string start stop)
83       (nnmbox-possibly-change-newsgroup newsgroup)
84       (while sequence
85         (setq article (car sequence))
86         (setq art-string (nnmbox-article-string article))
87         (set-buffer nnmbox-mbox-buffer)
88         (if (or (search-forward art-string nil t)
89                 (progn (goto-char 1)
90                        (search-forward art-string nil t)))
91             (progn
92               (setq start 
93                     (save-excursion
94                       (re-search-backward 
95                        (concat "^" rmail-unix-mail-delimiter) nil t)
96                       (point)))
97               (search-forward "\n\n" nil t)
98               (setq stop (1- (point)))
99               (set-buffer nntp-server-buffer)
100               (insert (format "221 %d Article retrieved.\n" article))
101               (setq beg (point))
102               (insert-buffer-substring nnmbox-mbox-buffer start stop)
103               (goto-char (point-max))
104               (insert ".\n")))
105         (setq sequence (cdr sequence))
106         (setq count (1+ count))
107         (and (numberp nnmail-large-newsgroup)
108              (> number nnmail-large-newsgroup)
109              (zerop (% count 20))
110              gnus-verbose-backends
111              (message "nnmbox: Receiving headers... %d%%"
112                       (/ (* count 100) number))))
113
114       (and (numberp nnmail-large-newsgroup)
115            (> number nnmail-large-newsgroup)
116            gnus-verbose-backends
117            (message "nnmbox: Receiving headers... done"))
118
119       ;; Fold continuation lines.
120       (goto-char 1)
121       (while (re-search-forward "\\(\r?\n[ \t]+\\)+" nil t)
122         (replace-match " " t t))
123       'headers)))
124
125 (defun nnmbox-open-server (server &optional defs)
126   (nnheader-init-server-buffer)
127   (if (equal server nnmbox-current-server)
128       t
129     (if nnmbox-current-server
130         (setq nnmbox-server-alist 
131               (cons (list nnmbox-current-server
132                           (nnheader-save-variables nnmbox-server-variables))
133                     nnmbox-server-alist)))
134     (let ((state (assoc server nnmbox-server-alist)))
135       (if state 
136           (progn
137             (nnheader-restore-variables (nth 1 state))
138             (setq nnmbox-server-alist (delq state nnmbox-server-alist)))
139         (nnheader-set-init-variables nnmbox-server-variables defs)))
140     (setq nnmbox-current-server server)))
141
142 (defun nnmbox-close-server (&optional server)
143   t)
144
145 (defun nnmbox-server-opened (&optional server)
146   (and (equal server nnmbox-current-server)
147        nntp-server-buffer
148        (buffer-name nntp-server-buffer)))
149
150 (defun nnmbox-status-message (&optional server)
151   nnmbox-status-string)
152
153 (defun nnmbox-request-article (article &optional newsgroup server buffer)
154   (nnmbox-possibly-change-newsgroup newsgroup)
155   (if (stringp article)
156       nil
157     (save-excursion
158       (set-buffer nnmbox-mbox-buffer)
159       (goto-char 1)
160       (if (search-forward (nnmbox-article-string article) nil t)
161           (let (start stop)
162             (re-search-backward (concat "^" rmail-unix-mail-delimiter) nil t)
163             (setq start (point))
164             (forward-line 1)
165             (or (and (re-search-forward 
166                       (concat "^" rmail-unix-mail-delimiter) nil t)
167                      (forward-line -1))
168                 (goto-char (point-max)))
169             (setq stop (point))
170             (let ((nntp-server-buffer (or buffer nntp-server-buffer)))
171               (set-buffer nntp-server-buffer)
172               (erase-buffer)
173               (insert-buffer-substring nnmbox-mbox-buffer start stop)
174               (goto-char (point-min))
175               (while (looking-at "From ")
176                 (delete-char 5)
177                 (insert "X-From-Line: ")
178                 (forward-line 1))
179               t))))))
180
181 (defun nnmbox-request-group (group &optional server dont-check)
182   (save-excursion
183     (if (nnmbox-possibly-change-newsgroup group)
184         (if dont-check
185             t
186           (nnmbox-get-new-mail)
187           (save-excursion
188             (set-buffer nntp-server-buffer)
189             (erase-buffer)
190             (let ((active (assoc group nnmbox-group-alist)))
191               (insert (format "211 %d %d %d %s\n" 
192                               (1+ (- (cdr (car (cdr active)))
193                                      (car (car (cdr active)))))
194                               (car (car (cdr active)))
195                               (cdr (car (cdr active)))
196                               (car active))))
197             t)))))
198
199 (defun nnmbox-close-group (group &optional server)
200   t)
201
202 (defun nnmbox-request-list (&optional server)
203   (if server (nnmbox-get-new-mail))
204   (save-excursion
205     (or (nnmail-find-file nnmbox-active-file)
206         (progn
207           (setq nnmbox-group-alist (nnmail-get-active))
208           (nnmail-save-active nnmbox-group-alist nnmbox-active-file)
209           (nnmail-find-file nnmbox-active-file)))))
210
211 (defun nnmbox-request-newgroups (date &optional server)
212   (nnmbox-request-list server))
213
214 (defun nnmbox-request-list-newsgroups (&optional server)
215   (setq nnmbox-status-string "nnmbox: LIST NEWSGROUPS is not implemented.")
216   nil)
217
218 (defun nnmbox-request-post (&optional server)
219   (mail-send-and-exit nil))
220
221 (fset 'nnmbox-request-post-buffer 'nnmail-request-post-buffer)
222
223 (defun nnmbox-request-expire-articles 
224   (articles newsgroup &optional server force)
225   (nnmbox-possibly-change-newsgroup newsgroup)
226   (let* ((days (or (and nnmail-expiry-wait-function
227                         (funcall nnmail-expiry-wait-function newsgroup))
228                    nnmail-expiry-wait))
229          article rest)
230     (save-excursion 
231       (set-buffer nnmbox-mbox-buffer)
232       (while articles
233         (goto-char 1)
234         (if (search-forward (nnmbox-article-string (car articles)) nil t)
235             (if (or force
236                     (> (nnmail-days-between 
237                         (current-time-string)
238                         (buffer-substring 
239                          (point) (progn (end-of-line) (point))))
240                        days))
241                 (progn
242                   (and gnus-verbose-backends
243                        (message "Deleting: %s" (car articles)))
244                   (nnmbox-delete-mail))
245               (setq rest (cons (car articles) rest))))
246         (setq articles (cdr articles)))
247       (save-buffer)
248       ;; Find the lowest active article in this group.
249       (let ((active (nth 1 (assoc newsgroup nnmbox-group-alist))))
250         (goto-char (point-min))
251         (while (not (search-forward
252                      (nnmbox-article-string (car active)) nil t))
253           (setcar active (1+ (car active)))
254           (goto-char (point-min))))
255       (nnmail-save-active nnmbox-group-alist nnmbox-active-file)
256       rest)))
257
258 (defun nnmbox-request-move-article
259   (article group server accept-form &optional last)
260   (nnmbox-possibly-change-newsgroup group)
261   (let ((buf (get-buffer-create " *nnmbox move*"))
262         result)
263     (and 
264      (nnmbox-request-article article group server)
265      (save-excursion
266        (set-buffer buf)
267        (buffer-disable-undo (current-buffer))
268        (erase-buffer)
269        (insert-buffer-substring nntp-server-buffer)
270        (goto-char (point-min))
271        (while (re-search-forward 
272                "^X-Gnus-Newsgroup:" 
273                (save-excursion (search-forward "\n\n" nil t) (point)) t)
274          (delete-region (progn (beginning-of-line) (point))
275                         (progn (forward-line 1) (point))))
276        (setq result (eval accept-form))
277        (kill-buffer buf)
278        result)
279      (save-excursion
280        (set-buffer nnmbox-mbox-buffer)
281        (goto-char 1)
282        (if (search-forward (nnmbox-article-string article) nil t)
283            (nnmbox-delete-mail))
284        (and last (save-buffer))))
285     result))
286
287 (defun nnmbox-request-accept-article (group &optional last)
288   (let ((buf (current-buffer))
289         result beg)
290     (goto-char (point-min))
291     (if (looking-at "X-From-Line: ")
292         (replace-match "From ")
293       (insert "From nobody " (current-time-string) "\n"))
294     (and 
295      (nnmbox-request-list)
296      (setq nnmbox-group-alist (nnmail-get-active))
297      (progn
298        (set-buffer buf)
299        (goto-char (point-min))
300        (search-forward "\n\n" nil t)
301        (forward-line -1)
302        (while (re-search-backward "^X-Gnus-Newsgroup: " nil t)
303          (delete-region (point) (progn (forward-line 1) (point))))
304        (setq result (nnmbox-save-mail (and (stringp group) group))))
305      (save-excursion
306        (set-buffer nnmbox-mbox-buffer)
307        (insert-buffer-substring buf)
308        (and last (save-buffer))
309        result)
310      (nnmail-save-active nnmbox-group-alist nnmbox-active-file))
311     (car result)))
312
313 (defun nnmbox-request-replace-article (article group buffer)
314   (nnmbox-possibly-change-newsgroup group)
315   (save-excursion
316     (set-buffer nnmbox-mbox-buffer)
317     (goto-char 1)
318     (if (not (search-forward (nnmbox-article-string article) nil t))
319         nil
320       (nnmbox-delete-mail t t)
321       (insert-buffer-substring buffer)
322       (save-buffer)
323       t)))
324
325 \f
326 ;;; Internal functions.
327
328 ;; If FORCE, delete article no matter how many X-Gnus-Newsgroup
329 ;; headers there are. If LEAVE-DELIM, don't delete the Unix mbox
330 ;; delimeter line.
331 (defun nnmbox-delete-mail (&optional force leave-delim)
332   ;; Delete the current X-Gnus-Newsgroup line.
333   (or force
334       (delete-region
335        (progn (beginning-of-line) (point))
336        (progn (forward-line 1) (point))))
337   ;; Beginning of the article.
338   (save-excursion
339     (save-restriction
340       (narrow-to-region
341        (save-excursion
342          (re-search-backward (concat "^" rmail-unix-mail-delimiter) nil t)
343          (if leave-delim (progn (forward-line 1) (point))
344            (match-beginning 0)))
345        (progn
346          (forward-line 1)
347          (or (and (re-search-forward (concat "^" rmail-unix-mail-delimiter) 
348                                      nil t)
349                   (if (and (not (bobp)) leave-delim)
350                       (progn (forward-line -2) (point))
351                     (match-beginning 0)))
352              (point-max))))
353       (goto-char (point-min))
354       ;; Only delete the article if no other groups owns it as well.
355       (if (or force (not (re-search-forward "^X-Gnus-Newsgroup: " nil t)))
356           (delete-region (point-min) (point-max))))))
357
358 (defun nnmbox-possibly-change-newsgroup (newsgroup)
359   (if (not (get-buffer nnmbox-mbox-buffer))
360       (save-excursion
361         (set-buffer (setq nnmbox-mbox-buffer 
362                           (find-file-noselect nnmbox-mbox-file)))
363         (buffer-disable-undo (current-buffer))))
364   (if (not nnmbox-group-alist)
365       (progn
366         (nnmbox-request-list)
367         (setq nnmbox-group-alist (nnmail-get-active))))
368   (if newsgroup
369       (if (assoc newsgroup nnmbox-group-alist)
370           (setq nnmbox-current-group newsgroup))))
371
372 (defun nnmbox-article-string (article)
373   (concat "\nX-Gnus-Newsgroup: " nnmbox-current-group ":" 
374           (int-to-string article) " "))
375
376 (defun nnmbox-save-mail (&optional group)
377   "Called narrowed to an article."
378   (let* ((nnmail-split-methods 
379           (if group (list (list group "")) nnmail-split-methods))
380          (group-art (nreverse (nnmail-article-group 'nnmbox-active-number))))
381     (nnmail-insert-lines)
382     (nnmail-insert-xref group-art)
383     (nnmbox-insert-newsgroup-line group-art)
384     group-art))
385
386 (defun nnmbox-insert-newsgroup-line (group-art)
387   (save-excursion
388     (goto-char (point-min))
389     (if (search-forward "\n\n" nil t)
390         (progn
391           (forward-char -1)
392           (while group-art
393             (insert (format "X-Gnus-Newsgroup: %s:%d   %s\n" 
394                             (car (car group-art)) (cdr (car group-art))
395                             (current-time-string)))
396             (setq group-art (cdr group-art)))))
397     t))
398
399 (defun nnmbox-active-number (group)
400   "Find the next article number in GROUP."
401   (let ((active (car (cdr (assoc group nnmbox-group-alist)))))
402     (setcdr active (1+ (cdr active)))
403     (cdr active)))
404
405 (defun nnmbox-read-mbox ()
406   (nnmbox-request-list)
407   (setq nnmbox-group-alist (nnmail-get-active))
408   (if (not (file-exists-p nnmbox-mbox-file))
409       (write-region 1 1 nnmbox-mbox-file t 'nomesg))
410   (if (and nnmbox-mbox-buffer
411            (get-buffer nnmbox-mbox-buffer)
412            (buffer-name nnmbox-mbox-buffer)
413            (save-excursion
414              (set-buffer nnmbox-mbox-buffer)
415              (= (buffer-size) (nth 7 (file-attributes nnmbox-mbox-file)))))
416       ()
417     (save-excursion
418       (let ((delim (concat "^" rmail-unix-mail-delimiter))
419             start end)
420         (set-buffer (setq nnmbox-mbox-buffer 
421                           (find-file-noselect nnmbox-mbox-file)))
422         (buffer-disable-undo (current-buffer))
423         (goto-char (point-min))
424         (while (re-search-forward delim nil t)
425           (setq start (match-beginning 0))
426           (if (not (search-forward "\nX-Gnus-Newsgroup: " 
427                                    (save-excursion 
428                                      (setq end
429                                            (or
430                                             (and
431                                              (re-search-forward delim nil t)
432                                              (match-beginning 0))
433                                             (point-max))))
434                                    t))
435               (save-excursion
436                 (save-restriction
437                   (narrow-to-region start end)
438                   (nnmbox-save-mail))))
439           (goto-char end))))))
440
441 (defun nnmbox-get-new-mail ()
442   (let (incoming)
443     (nnmbox-read-mbox)
444     (if (and nnmail-spool-file nnmbox-get-new-mail
445              (file-exists-p nnmail-spool-file)
446              (> (nth 7 (file-attributes nnmail-spool-file)) 0))
447         (progn
448           (and gnus-verbose-backends
449                (message "nnmbox: Reading incoming mail..."))
450           (setq incoming 
451                 (nnmail-move-inbox nnmail-spool-file
452                                    (concat nnmbox-mbox-file "-Incoming")))
453           (save-excursion
454             (let ((in-buf (nnmail-split-incoming 
455                            incoming 'nnmbox-save-mail t)))
456               (set-buffer nnmbox-mbox-buffer)
457               (goto-char (point-max))
458               (insert-buffer-substring in-buf)
459               (kill-buffer in-buf)))
460           (run-hooks 'nnmail-read-incoming-hook)
461           (and gnus-verbose-backends
462                (message "nnmbox: Reading incoming mail...done"))))
463     (and (buffer-modified-p nnmbox-mbox-buffer) 
464          (save-excursion
465            (nnmail-save-active nnmbox-group-alist nnmbox-active-file)
466            (set-buffer nnmbox-mbox-buffer)
467            (save-buffer)))
468 ;    (if incoming
469 ;       (delete-file incoming))
470     ))
471
472 (provide 'nnmbox)
473
474 ;;; nnmbox.el ends here