*** empty log message ***
[gnus] / lisp / nnbabyl.el
1 ;;; nnbabyl.el --- rmail mbox access for Gnus
2 ;; Copyright (C) 1995,96 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 the
22 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
23 ;; Boston, MA 02111-1307, USA.
24
25 ;;; Commentary:
26
27 ;; For an overview of what the interface functions do, please see the
28 ;; Gnus sources.  
29
30 ;;; Code:
31
32 (require 'nnheader)
33 (require 'rmail)
34 (require 'nnmail)
35 (eval-when-compile (require 'cl))
36
37 (defvar nnbabyl-mbox-file (expand-file-name "~/RMAIL")
38   "The name of the rmail box file in the users home directory.")
39
40 (defvar nnbabyl-active-file (expand-file-name "~/.rmail-active")
41   "The name of the active file for the rmail box.")
42
43 (defvar nnbabyl-get-new-mail t
44   "If non-nil, nnbabyl will check the incoming mail file and split the mail.")
45
46 (defvar nnbabyl-prepare-save-mail-hook nil
47   "Hook run narrowed to an article before saving.")
48
49 \f
50
51 (defvar nnbabyl-mail-delimiter "\^_")
52
53 (defconst nnbabyl-version "nnbabyl 1.0"
54   "nnbabyl version.")
55
56 (defvar nnbabyl-mbox-buffer nil)
57 (defvar nnbabyl-current-group nil)
58 (defvar nnbabyl-status-string "")
59 (defvar nnbabyl-group-alist nil)
60 (defvar nnbabyl-active-timestamp nil)
61
62 (defvar nnbabyl-previous-buffer-mode nil)
63
64 \f
65
66 (defvar nnbabyl-current-server nil)
67 (defvar nnbabyl-server-alist nil)
68 (defvar nnbabyl-server-variables 
69   (list
70    (list 'nnbabyl-mbox-file nnbabyl-mbox-file)
71    (list 'nnbabyl-active-file nnbabyl-active-file)
72    (list 'nnbabyl-get-new-mail nnbabyl-get-new-mail)
73    '(nnbabyl-current-group nil)
74    '(nnbabyl-status-string "")
75    '(nnbabyl-previous-buffer-mode nil)
76    '(nnbabyl-group-alist nil)))
77
78 \f
79
80 ;;; Interface functions
81
82 (defun nnbabyl-retrieve-headers (sequence &optional newsgroup server fetch-old)
83   (save-excursion
84     (set-buffer nntp-server-buffer)
85     (erase-buffer)
86     (let ((number (length sequence))
87           (count 0)
88           article art-string start stop)
89       (nnbabyl-possibly-change-newsgroup newsgroup)
90       (while sequence
91         (setq article (car sequence))
92         (setq art-string (nnbabyl-article-string article))
93         (set-buffer nnbabyl-mbox-buffer)
94         (if (or (search-forward art-string nil t)
95                 (search-backward art-string nil t))
96             (progn
97               (re-search-backward (concat "^" nnbabyl-mail-delimiter) nil t)
98               (while (and (not (looking-at ".+:"))
99                           (zerop (forward-line 1))))
100               (setq start (point))
101               (search-forward "\n\n" nil t)
102               (setq stop (1- (point)))
103               (set-buffer nntp-server-buffer)
104               (insert "221 " (int-to-string article) " Article retrieved.\n")
105               (insert-buffer-substring nnbabyl-mbox-buffer start stop)
106               (goto-char (point-max))
107               (insert ".\n")))
108         (setq sequence (cdr sequence))
109         (setq count (1+ count))
110         (and (numberp nnmail-large-newsgroup)
111              (> number nnmail-large-newsgroup)
112              (zerop (% count 20))
113              (nnheader-message 5 "nnbabyl: Receiving headers... %d%%"
114                                (/ (* count 100) number))))
115
116       (and (numberp nnmail-large-newsgroup)
117            (> number nnmail-large-newsgroup)
118            (nnheader-message 5 "nnbabyl: Receiving headers...done"))
119
120       (set-buffer nntp-server-buffer)
121       (nnheader-fold-continuation-lines)
122       'headers)))
123
124 (defun nnbabyl-open-server (server &optional defs)
125   (nnheader-init-server-buffer)
126   (if (equal server nnbabyl-current-server)
127       t
128     (if nnbabyl-current-server
129         (setq nnbabyl-server-alist 
130               (cons (list nnbabyl-current-server
131                           (nnheader-save-variables nnbabyl-server-variables))
132                     nnbabyl-server-alist)))
133     (let ((state (assoc server nnbabyl-server-alist)))
134       (if state 
135           (progn
136             (nnheader-restore-variables (nth 1 state))
137             (setq nnbabyl-server-alist (delq state nnbabyl-server-alist)))
138         (nnheader-set-init-variables nnbabyl-server-variables defs)))
139     (setq nnbabyl-current-server server)))
140
141 (defun nnbabyl-close-server (&optional server)
142   ;; Restore buffer mode.
143   (when (and (nnbabyl-server-opened)
144              nnbabyl-previous-buffer-mode)
145     (save-excursion
146       (set-buffer nnbabyl-mbox-buffer)
147       (narrow-to-region
148        (car (car nnbabyl-previous-buffer-mode))
149        (cdr (car nnbabyl-previous-buffer-mode)))
150       (funcall (cdr nnbabyl-previous-buffer-mode))))
151   (setq nnbabyl-current-server nil
152         nnbabyl-mbox-buffer nil)
153   t)
154
155 (defun nnbabyl-server-opened (&optional server)
156   (and (equal server nnbabyl-current-server)
157        nnbabyl-mbox-buffer
158        (buffer-name nnbabyl-mbox-buffer)
159        nntp-server-buffer
160        (buffer-name nntp-server-buffer)))
161
162 (defun nnbabyl-status-message (&optional server)
163   nnbabyl-status-string)
164
165 (defun nnbabyl-request-article (article &optional newsgroup server buffer)
166   (nnbabyl-possibly-change-newsgroup newsgroup)
167   (save-excursion
168     (set-buffer nnbabyl-mbox-buffer)
169     (goto-char (point-min))
170     (if (search-forward (nnbabyl-article-string article) nil t)
171         (let (start stop summary-line)
172           (re-search-backward (concat "^" nnbabyl-mail-delimiter) nil t)
173           (while (and (not (looking-at ".+:"))
174                       (zerop (forward-line 1))))
175           (setq start (point))
176           (or (and (re-search-forward 
177                     (concat "^" nnbabyl-mail-delimiter) nil t)
178                    (forward-line -1))
179               (goto-char (point-max)))
180           (setq stop (point))
181           (let ((nntp-server-buffer (or buffer nntp-server-buffer)))
182             (set-buffer nntp-server-buffer)
183             (erase-buffer)
184             (insert-buffer-substring nnbabyl-mbox-buffer start stop)
185             (goto-char (point-min))
186             ;; If there is an EOOH header, then we have to remove some
187             ;; duplicated headers. 
188             (setq summary-line (looking-at "Summary-line:"))
189             (when (search-forward "\n*** EOOH ***" nil t)
190               (if summary-line
191                   ;; The headers to be deleted are located before the
192                   ;; EOOH line...
193                   (delete-region (point-min) (progn (forward-line 1)
194                   (point)))
195                 ;; ...or after.
196                 (delete-region (progn (beginning-of-line) (point))
197                                (or (search-forward "\n\n" nil t)
198                                    (point)))))
199             (if (numberp article) 
200                 (cons nnbabyl-current-group article)
201               (nnbabyl-article-group-number)))))))
202
203 (defun nnbabyl-request-group (group &optional server dont-check)
204   (let ((active (cadr (assoc group nnbabyl-group-alist))))
205     (save-excursion
206       (cond 
207        ((null active)
208         (nnheader-report 'nnbabyl "No such group: %s" group))
209        ((null (nnbabyl-possibly-change-newsgroup group))
210         (nnheader-report 'nnbabyl "No such group: %s" group))
211        (dont-check
212         (nnheader-report 'nnbabyl "Selected group %s" group)
213         t)
214        (t
215         (nnheader-report 'nnbabyl "Selected group %s" group)
216         (nnheader-insert "211 %d %d %d %s\n" 
217                          (1+ (- (cdr active) (car active)))
218                          (car active) (cdr active) group)
219         t)))))
220
221 (defun nnbabyl-request-scan (&optional group server)
222   (nnbabyl-read-mbox)
223   (nnmail-get-new-mail 
224    'nnbabyl 
225    (lambda ()
226      (save-excursion
227        (set-buffer nnbabyl-mbox-buffer)
228        (save-buffer)))
229    nnbabyl-mbox-file group
230    (lambda ()
231      (save-excursion
232        (let ((in-buf (current-buffer)))
233          (goto-char (point-min))
234          (while (search-forward "\n\^_\n" nil t)
235            (delete-char -1))
236          (set-buffer nnbabyl-mbox-buffer)
237          (goto-char (point-max))
238          (search-backward "\n\^_" nil t)
239          (goto-char (match-end 0))
240          (insert-buffer-substring in-buf))))))
241
242 (defun nnbabyl-close-group (group &optional server)
243   t)
244
245 (defun nnbabyl-request-create-group (group &optional server) 
246   (nnmail-activate 'nnbabyl)
247   (or (assoc group nnbabyl-group-alist)
248       (let (active)
249         (setq nnbabyl-group-alist (cons (list group (setq active (cons 1 0)))
250                                         nnbabyl-group-alist))
251         (nnmail-save-active nnbabyl-group-alist nnbabyl-active-file)))
252   t)
253
254 (defun nnbabyl-request-list (&optional server)
255   (save-excursion
256     (or (nnmail-find-file nnbabyl-active-file)
257         (progn
258           (setq nnbabyl-group-alist (nnmail-get-active))
259           (nnmail-save-active nnbabyl-group-alist nnbabyl-active-file)
260           (nnmail-find-file nnbabyl-active-file)))))
261
262 (defun nnbabyl-request-newgroups (date &optional server)
263   (nnbabyl-request-list server))
264
265 (defun nnbabyl-request-list-newsgroups (&optional server)
266   (setq nnbabyl-status-string "nnbabyl: LIST NEWSGROUPS is not implemented.")
267   nil)
268
269 (defun nnbabyl-request-post (&optional server)
270   (mail-send-and-exit nil))
271
272 (defun nnbabyl-request-expire-articles
273   (articles newsgroup &optional server force)
274   (nnbabyl-possibly-change-newsgroup newsgroup)
275   (let* ((is-old t)
276          rest)
277     (nnmail-activate 'nnbabyl)
278
279     (save-excursion 
280       (set-buffer nnbabyl-mbox-buffer)
281       (set-text-properties (point-min) (point-max) nil)
282       (while (and articles is-old)
283         (goto-char (point-min))
284         (if (search-forward (nnbabyl-article-string (car articles)) nil t)
285             (if (setq is-old
286                       (nnmail-expired-article-p
287                        newsgroup
288                        (buffer-substring 
289                         (point) (progn (end-of-line) (point))) force))
290                 (progn
291                   (nnheader-message 5 "Deleting article %d in %s..." 
292                                     (car articles) newsgroup)
293                   (nnbabyl-delete-mail))
294               (setq rest (cons (car articles) rest))))
295         (setq articles (cdr articles)))
296       (save-buffer)
297       ;; Find the lowest active article in this group.
298       (let ((active (nth 1 (assoc newsgroup nnbabyl-group-alist))))
299         (goto-char (point-min))
300         (while (and (not (search-forward
301                           (nnbabyl-article-string (car active)) nil t))
302                     (<= (car active) (cdr active)))
303           (setcar active (1+ (car active)))
304           (goto-char (point-min))))
305       (nnmail-save-active nnbabyl-group-alist nnbabyl-active-file)
306       (nconc rest articles))))
307
308 (defun nnbabyl-request-move-article 
309   (article group server accept-form &optional last)
310   (nnbabyl-possibly-change-newsgroup group)
311   (let ((buf (get-buffer-create " *nnbabyl move*"))
312         result)
313     (and 
314      (nnbabyl-request-article article group server)
315      (save-excursion
316        (set-buffer buf)
317        (insert-buffer-substring nntp-server-buffer)
318        (goto-char (point-min))
319        (if (re-search-forward 
320             "^X-Gnus-Newsgroup:" 
321             (save-excursion (search-forward "\n\n" nil t) (point)) t)
322            (delete-region (progn (beginning-of-line) (point))
323                           (progn (forward-line 1) (point))))
324        (setq result (eval accept-form))
325        (kill-buffer (current-buffer))
326        result)
327      (save-excursion
328        (set-buffer nnbabyl-mbox-buffer)
329        (goto-char (point-min))
330        (if (search-forward (nnbabyl-article-string article) nil t)
331            (nnbabyl-delete-mail))
332        (and last (save-buffer))))
333     result))
334
335 (defun nnbabyl-request-accept-article (group &optional last)
336   (let ((buf (current-buffer))
337         result beg)
338     (and 
339      (nnmail-activate 'nnbabyl)
340      (save-excursion
341        (goto-char (point-min))
342        (search-forward "\n\n" nil t)
343        (forward-line -1)
344        (save-excursion
345          (while (re-search-backward "^X-Gnus-Newsgroup: " beg t)
346            (delete-region (point) (progn (forward-line 1) (point)))))
347        (let ((nnmail-split-methods
348               (if (stringp group) (list (list group "")) 
349                 nnmail-split-methods)))
350          (setq result (car (nnbabyl-save-mail))))
351        (set-buffer nnbabyl-mbox-buffer)
352        (goto-char (point-max))
353        (search-backward "\n\^_")
354        (goto-char (match-end 0))
355        (insert-buffer-substring buf)
356        (and last (progn 
357                    (save-buffer)
358                    (nnmail-save-active
359                     nnbabyl-group-alist nnbabyl-active-file)))
360        result))))
361
362 (defun nnbabyl-request-replace-article (article group buffer)
363   (nnbabyl-possibly-change-newsgroup group)
364   (save-excursion
365     (set-buffer nnbabyl-mbox-buffer)
366     (goto-char (point-min))
367     (if (not (search-forward (nnbabyl-article-string article) nil t))
368         nil
369       (nnbabyl-delete-mail t t)
370       (insert-buffer-substring buffer)
371       (save-buffer)
372       t)))
373
374 (defun nnbabyl-request-delete-group (group &optional force server)
375   (nnbabyl-possibly-change-newsgroup group)
376   ;; Delete all articles in GROUP.
377   (if (not force)
378       ()                                ; Don't delete the articles.
379     (save-excursion
380       (set-buffer nnbabyl-mbox-buffer)
381       (goto-char (point-min))
382       ;; Delete all articles in this group.
383       (let ((ident (concat "\nX-Gnus-Newsgroup: " nnbabyl-current-group ":"))
384             found)
385         (while (search-forward ident nil t)
386           (setq found t)
387           (nnbabyl-delete-mail))
388         (and found (save-buffer)))))
389   ;; Remove the group from all structures.
390   (setq nnbabyl-group-alist 
391         (delq (assoc group nnbabyl-group-alist) nnbabyl-group-alist)
392         nnbabyl-current-group nil)
393   ;; Save the active file.
394   (nnmail-save-active nnbabyl-group-alist nnbabyl-active-file)
395   t)
396
397 (defun nnbabyl-request-rename-group (group new-name &optional server)
398   (nnbabyl-possibly-change-newsgroup group)
399   (save-excursion
400     (set-buffer nnbabyl-mbox-buffer)
401     (goto-char (point-min))
402     (let ((ident (concat "\nX-Gnus-Newsgroup: " nnbabyl-current-group ":"))
403           (new-ident (concat "\nX-Gnus-Newsgroup: " new-name ":"))
404           found)
405       (while (search-forward ident nil t)
406         (replace-match new-ident t t)
407         (setq found t))
408       (and found (save-buffer))))
409   (let ((entry (assoc group nnbabyl-group-alist)))
410     (and entry (setcar entry new-name))
411     (setq nnbabyl-current-group nil)
412     ;; Save the new group alist.
413     (nnmail-save-active nnbabyl-group-alist nnbabyl-active-file)
414     t))
415
416 \f
417 ;;; Internal functions.
418
419 ;; If FORCE, delete article no matter how many X-Gnus-Newsgroup
420 ;; headers there are. If LEAVE-DELIM, don't delete the Unix mbox
421 ;; delimiter line.
422 (defun nnbabyl-delete-mail (&optional force leave-delim)
423   ;; Delete the current X-Gnus-Newsgroup line.
424   (or force
425       (delete-region
426        (progn (beginning-of-line) (point))
427        (progn (forward-line 1) (point))))
428   ;; Beginning of the article.
429   (save-excursion
430     (save-restriction
431       (widen)
432       (narrow-to-region
433        (save-excursion
434          (re-search-backward (concat "^" nnbabyl-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 "^" nnbabyl-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 groups owns it as well.
447       (if (or force (not (re-search-forward "^X-Gnus-Newsgroup: " nil t)))
448           (delete-region (point-min) (point-max))))))
449
450 (defun nnbabyl-possibly-change-newsgroup (newsgroup)
451   (if (or (not nnbabyl-mbox-buffer)
452           (not (buffer-name nnbabyl-mbox-buffer)))
453       (save-excursion (nnbabyl-read-mbox)))
454   (or nnbabyl-group-alist
455       (nnmail-activate 'nnbabyl))
456   (if newsgroup
457       (if (assoc newsgroup nnbabyl-group-alist)
458           (setq nnbabyl-current-group newsgroup)
459         (setq nnbabyl-status-string "No such group in file")
460         nil)
461     t))
462
463 (defun nnbabyl-article-string (article)
464   (if (numberp article)
465       (concat "\nX-Gnus-Newsgroup: " nnbabyl-current-group ":" 
466               (int-to-string article) " ")
467     (concat "\nMessage-ID: " article)))
468
469 (defun nnbabyl-article-group-number ()
470   (save-excursion
471     (goto-char (point-min))
472     (and (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 nnbabyl-insert-lines ()
479   "Insert how many lines and chars there are in the body of the mail."
480   (let (lines chars)
481     (save-excursion
482       (goto-char (point-min))
483       (if (search-forward "\n\n" nil t) 
484           (progn
485             ;; There may be an EOOH line here...
486             (if (looking-at "\\*\\*\\* EOOH \\*\\*\\*")
487                 (search-forward "\n\n" nil t))
488             (setq chars (- (point-max) (point)))
489             (setq lines (- (count-lines (point) (point-max)) 1))
490             ;; Move back to the end of the headers. 
491             (goto-char (point-min))
492             (search-forward "\n\n" nil t)
493             (forward-char -1)
494             (save-excursion
495               (if (re-search-backward "^Lines: " nil t)
496                   (delete-region (point) (progn (forward-line 1) (point)))))
497             (insert (format "Lines: %d\n" lines))
498             chars)))))
499
500 (defun nnbabyl-save-mail ()
501   ;; Called narrowed to an article.
502   (let ((group-art (nreverse (nnmail-article-group 'nnbabyl-active-number))))
503     (nnbabyl-insert-lines)
504     (nnmail-insert-xref group-art)
505     (nnbabyl-insert-newsgroup-line group-art)
506     (run-hooks 'nnbabyl-prepare-save-mail-hook)
507     group-art))
508
509 (defun nnbabyl-insert-newsgroup-line (group-art)
510   (save-excursion
511     (goto-char (point-min))
512     (while (looking-at "From ")
513       (replace-match "Mail-from: From " t t)
514       (forward-line 1))
515     ;; If there is a C-l at the beginning of the narrowed region, this
516     ;; isn't really a "save", but rather a "scan".
517     (goto-char (point-min))
518     (or (looking-at "\^L")
519         (save-excursion
520           (insert "\^L\n0, unseen,,\n*** EOOH ***\n")
521           (goto-char (point-max))
522           (insert "\^_\n")))
523     (if (search-forward "\n\n" nil t)
524         (progn
525           (forward-char -1)
526           (while group-art
527             (insert (format "X-Gnus-Newsgroup: %s:%d   %s\n" 
528                             (car (car group-art)) (cdr (car group-art))
529                             (current-time-string)))
530             (setq group-art (cdr group-art)))))
531     t))
532
533 (defun nnbabyl-active-number (group)
534   ;; Find the next article number in GROUP.
535   (let ((active (car (cdr (assoc group nnbabyl-group-alist)))))
536     (if active
537         (setcdr active (1+ (cdr active)))
538       ;; This group is new, so we create a new entry for it.
539       ;; This might be a bit naughty... creating groups on the drop of
540       ;; a hat, but I don't know...
541       (setq nnbabyl-group-alist (cons (list group (setq active (cons 1 1)))
542                                       nnbabyl-group-alist)))
543     (cdr active)))
544
545 (defun nnbabyl-read-mbox ()
546   (nnmail-activate 'nnbabyl)
547   (or (file-exists-p nnbabyl-mbox-file)
548       (save-excursion
549         (set-buffer (setq nnbabyl-mbox-buffer
550                           (create-file-buffer nnbabyl-mbox-file)))
551         (setq buffer-file-name nnbabyl-mbox-file)
552         (insert "BABYL OPTIONS:\n\n\^_")
553         (write-region (point-min) (point-max) nnbabyl-mbox-file t 'nomesg)))
554
555   (if (and nnbabyl-mbox-buffer
556            (buffer-name nnbabyl-mbox-buffer)
557            (save-excursion
558              (set-buffer nnbabyl-mbox-buffer)
559              (= (buffer-size) (nth 7 (file-attributes nnbabyl-mbox-file)))))
560       ()
561     (save-excursion
562       (let ((delim (concat "^" nnbabyl-mail-delimiter))
563             start end)
564         (set-buffer (setq nnbabyl-mbox-buffer 
565                           (nnheader-find-file-noselect 
566                            nnbabyl-mbox-file nil 'raw)))
567         ;; Save buffer mode.
568         (setq nnbabyl-previous-buffer-mode 
569               (cons (cons (point-min) (point-max))
570                     major-mode))
571
572         (buffer-disable-undo (current-buffer))
573         (widen)
574         (setq buffer-read-only nil)
575         (fundamental-mode)
576         
577         (goto-char (point-min))
578         (re-search-forward delim nil t)
579         (setq start (match-end 0))
580         (while (re-search-forward delim nil t)
581           (setq end (match-end 0))
582           (or (search-backward "\nX-Gnus-Newsgroup: " start t)
583               (progn
584                 (goto-char end)
585                 (save-excursion
586                   (save-restriction
587                     (goto-char start)
588                     (narrow-to-region start end)
589                     (nnbabyl-save-mail)
590                     (setq end (point-max))))))
591           (goto-char (setq start end)))
592         (and (buffer-modified-p (current-buffer)) (save-buffer))
593         (nnmail-save-active nnbabyl-group-alist nnbabyl-active-file)))))
594
595 (defun nnbabyl-remove-incoming-delims ()
596   (goto-char (point-min))
597   (while (search-forward "\^_" nil t)
598     (replace-match "?" t t)))
599
600 (provide 'nnbabyl)
601
602 ;;; nnbabyl.el ends here