1 ;;; nnmail.el --- mail mbox access for Gnus
3 ;; Copyright (C) 1994 Free Software Foundation, Inc.
5 ;; Author: Lars Ingebrigtsen <larsi@ifi.uio.no>
6 ;; Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
9 ;; This file is part of GNU Emacs.
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 2, or (at your option)
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.
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with GNU Emacs; see the file COPYING. If not, write to
23 ;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
32 (defvar nnmail-split-methods
34 "nnmail will split incoming mail into the groups detailed in this variable.")
36 (defvar nnmail-mbox-file (expand-file-name "~/mbox")
37 "The name of the mail box file in the users home directory.")
39 (defvar nnmail-active-file (expand-file-name "~/.mbox-active")
40 "The name of the active file for the mail box.")
42 (defvar nnmail-expiry-wait 7
43 "Articles that are older than `nnmail-expiry-wait' days will be expired.")
45 ;; Quote fix by Sudish Joseph <joseph@cis.ohio-state.edu>.
46 (defvar nnmail-expiry-wait-function nil
47 "Variable that holds funtion to specify how old articles should be before they are expired.
48 The function will be called with the name of the group that the
49 expiry is to be performed in, and it should return an integer that
50 says how many days an article can be stored before it is considered
55 (setq nnmail-expiry-wait-function
58 (cond ((string-match \"private\" newsgroup) 31)
59 ((string-match \"junk\" newsgroup) 1)
62 (defvar nnmail-spool-file
64 (concat "/usr/spool/mail/" (user-login-name))))
66 (defvar nnmail-read-incoming-hook nil
67 "Hook that will be run after the incoming mail has been transferred.
68 The incoming mail is moved from `nnmail-spool-file' (which normally is
69 something like \"/usr/spool/mail/$user\") to the user's home
70 directory. This hook is called after the incoming mail box has been
71 emptied, and can be used to call any mail box programs you have
72 running (\"xwatch\", etc.)
76 (add-hook 'nnmail-read-incoming-hook
79 (start-process \"mailsend\" nil
80 \"/local/bin/mailsend\" \"read\" \"mbox\"))))")
82 (defvar nnmail-large-newsgroup 50
83 "*The number of the articles which indicates a large newsgroup.
84 If the number of the articles is greater than the value, verbose
85 messages will be shown to indicate the current status.")
89 (defconst nnmail-version "nnmail 0.1"
92 (defvar nnmail-current-group nil
93 "Current nnmail news group directory.")
95 (defconst nnmail-mbox-buffer "*nnmail mbox buffer*")
97 (defvar nnmail-active-alist nil)
99 (defvar nnmail-status-string "")
101 ;;; Interface functions
103 (defun nnmail-retrieve-headers (sequence &optional newsgroup server)
104 "Retrieve the headers for the articles in SEQUENCE.
105 Newsgroup must be selected before calling this function."
107 (set-buffer nntp-server-buffer)
110 (number (length sequence))
112 beg article art-string start stop)
113 (nnmail-possibly-change-newsgroup newsgroup)
115 (setq article (car sequence))
116 (setq art-string (nnmail-article-string article))
117 (set-buffer nnmail-mbox-buffer)
118 (if (or (search-forward art-string nil t)
120 (search-forward art-string nil t)))
125 (concat "^" rmail-unix-mail-delimiter) nil t)
127 (search-forward "\n\n" nil t)
128 (setq stop (1- (point)))
129 (set-buffer nntp-server-buffer)
130 (insert (format "221 %d Article retrieved.\n" article))
132 (insert-buffer-substring nnmail-mbox-buffer start stop)
133 (goto-char (point-max))
135 (setq sequence (cdr sequence))
136 (setq count (1+ count))
137 (and (numberp nnmail-large-newsgroup)
138 (> number nnmail-large-newsgroup)
140 (message "NNMAIL: Receiving headers... %d%%"
141 (/ (* count 100) number))))
143 (and (numberp nnmail-large-newsgroup)
144 (> number nnmail-large-newsgroup)
145 (message "NNMAIL: Receiving headers... done"))
147 ;; Fold continuation lines.
149 (while (re-search-forward "\\(\r?\n[ \t]+\\)+" nil t)
150 (replace-match " " t t))
153 (defun nnmail-open-server (host &optional service)
155 (setq nnmail-status-string "")
156 (nnmail-open-server-internal host service))
158 (defun nnmail-close-server (&optional server)
160 (nnmail-close-server-internal))
162 (fset 'nnmail-request-quit (symbol-function 'nnmail-close-server))
164 (defun nnmail-server-opened (&optional server)
165 "Return server process status, T or NIL.
166 If the stream is opened, return T, otherwise return NIL."
167 (and nntp-server-buffer
168 (get-buffer nntp-server-buffer)))
170 (defun nnmail-status-message ()
171 "Return server status response as string."
172 nnmail-status-string)
174 (defun nnmail-request-article (article &optional newsgroup server buffer)
175 "Select ARTICLE by number."
176 (nnmail-possibly-change-newsgroup newsgroup)
177 (if (stringp article)
180 (set-buffer nnmail-mbox-buffer)
182 (if (search-forward (nnmail-article-string article) nil t)
184 (re-search-backward (concat "^" rmail-unix-mail-delimiter) nil t)
187 (or (and (re-search-forward
188 (concat "^" rmail-unix-mail-delimiter) nil t)
190 (goto-char (point-max)))
192 (let ((nntp-server-buffer (or buffer nntp-server-buffer)))
193 (set-buffer nntp-server-buffer)
195 (insert-buffer-substring nnmail-mbox-buffer start stop)
198 (defun nnmail-request-group (group &optional server dont-check)
200 (if (nnmail-possibly-change-newsgroup group)
203 (nnmail-get-new-mail)
205 (set-buffer nntp-server-buffer)
207 (let ((active (assoc group nnmail-active-alist)))
208 (insert (format "211 %d %d %d %s\n"
209 (1+ (- (cdr (car (cdr active)))
210 (car (car (cdr active)))))
211 (car (car (cdr active)))
212 (cdr (car (cdr active)))
216 (defun nnmail-request-list (&optional server)
217 "List active newsgoups."
218 (nnmail-find-file nnmail-active-file))
220 (defun nnmail-request-list-newsgroups (&optional server)
221 "List newsgroups (defined in NNTP2)."
222 (setq nntp-status-string "NNMAIL: LIST NEWSGROUPS is not implemented.")
225 (defun nnmail-request-post (&optional server)
226 "Post a new news in current buffer."
227 (mail-send-and-exit nil))
229 (defun nnmail-request-post-buffer (method header article-buffer group info)
230 (let ((method-address (nth 1 (nth 4 info)))
231 from subject date to reply-to message-of
232 references message-id sender follow-to)
234 (if (and (stringp method-address)
235 (string= method-address ""))
238 (set-buffer (get-buffer-create "*mail*"))
240 (local-set-key "\C-c\C-c" 'gnus-mail-send-and-exit)
241 (local-set-key "\C-c\C-y" 'gnus-mail-yank-original)
242 (if (and (buffer-modified-p)
244 (not (y-or-n-p "Unsent mail being composed; erase it? ")))
247 (if (eq method 'post)
248 (mail-setup method-address nil nil nil nil nil)
250 (set-buffer article-buffer)
251 (goto-char (point-min))
252 (narrow-to-region (point-min)
253 (progn (search-forward "\n\n") (point)))
254 (set-text-properties (point-min) (point-max) nil)
255 (if (and (boundp 'gnus-followup-to-function)
256 gnus-followup-to-function)
257 (setq follow-to (funcall gnus-followup-to-function group)))
258 (setq from (header-from header))
259 (setq date (header-date header))
262 (string-match " *at \\| *@ \\| *(\\| *<" from)))
264 (concat (if stop-pos (substring from 0 stop-pos) from)
265 "'s message of " date))))
266 (setq sender (mail-fetch-field "sender"))
267 (setq subject (header-subject header))
268 (or (string-match "^[Rr][Ee]:" subject)
269 (setq subject (concat "Re: " subject)))
270 (setq reply-to (mail-fetch-field "reply-to"))
271 (setq references (header-references header))
272 (setq message-id (header-id header))
274 (setq news-reply-yank-from from)
275 (setq news-reply-yank-message-id message-id)
276 (mail-setup (or follow-to method-address sender reply-to from)
277 subject message-of nil article-buffer nil)
278 ;; Fold long references line to follow RFC1036.
279 (mail-position-on-field "References")
280 (let ((begin (- (point) (length "References: ")))
283 (if references (insert references))
284 (if (and references message-id) (insert " "))
285 (if message-id (insert message-id))
286 ;; The region must end with a newline to fill the region
287 ;; without inserting extra newline.
288 (fill-region-as-paragraph begin (1+ (point))))
292 (defun nnmail-request-expire-articles (articles newsgroup &optional server)
293 "Expire all articles in the ARTICLES list in group GROUP.
294 The list of unexpired articles will be returned (ie. all articles that
295 were too fresh to be expired)."
296 (nnmail-possibly-change-newsgroup newsgroup)
297 (let* ((days (or (and nnmail-expiry-wait-function
298 (funcall nnmail-expiry-wait-function newsgroup))
300 (cur-time (current-time))
301 (day-sec (* 24 60 60 days))
302 (day-time (list nil nil))
303 mod-time article rest)
304 (setcar day-time (/ day-sec 65536))
305 (setcar (cdr day-time) (- day-sec (* (car day-time) 65536)))
306 (if (< (car (cdr cur-time)) (car (cdr day-time)))
308 (setcar day-time (+ 1 (- (car cur-time) (car day-time))))
309 (setcar (cdr day-time) (- (+ 65536 (car (cdr cur-time)))
310 (car (cdr day-time)))))
311 (setcar day-time (- (car cur-time) (car day-time)))
312 (setcar (cdr day-time) (- (car (cdr cur-time)) (car (cdr day-time)))))
314 (set-buffer nnmail-mbox-buffer)
317 (if (and (search-forward (nnmail-article-string (car articles)) nil t)
318 (setq mod-time (read (current-buffer)))
319 (or (< (car mod-time) (car day-time))
320 (and (= (car mod-time) (car day-time))
321 (< (car (cdr mod-time)) (car (cdr day-time))))))
323 (message "Deleting: %s" article)
324 (nnmail-delete-mail))
325 (setq rest (cons (car articles) rest)))
326 (setq articles (cdr articles)))
330 (defun nnmail-request-move-article (article group server accept-form)
331 (let ((buf (get-buffer-create " *nnmail move*"))
334 (nnmail-request-article article group server)
337 (insert-buffer-substring nntp-server-buffer)
338 (goto-char (point-min))
339 (if (re-search-forward
341 (save-excursion (search-forward "\n\n" nil t) (point)) t)
342 (delete-region (progn (beginning-of-line) (point))
343 (progn (forward-line 1) (point))))
344 (setq result (eval accept-form))
345 (kill-buffer (current-buffer))
348 (set-buffer nnmail-mbox-buffer)
350 (if (search-forward (nnmail-article-string article) nil t)
351 (nnmail-delete-mail))
355 (defun nnmail-request-accept-article (group)
356 (let ((buf (current-buffer))
361 (set-buffer nnmail-mbox-buffer)
362 (setq beg (goto-char (point-max)))
363 (insert-buffer-substring buf)
367 (search-forward "\n\n" nil t)
369 (setq result (nnmail-insert-newsgroup-line group beg (point))))
370 (setq result (nnmail-choose-mail beg (point-max))))
373 (nnmail-save-active))
378 ;;; Low-Level Interface
380 (defun nnmail-delete-mail ()
381 (re-search-backward (concat "^" rmail-unix-mail-delimiter) nil t)
386 (or (and (re-search-forward
387 (concat "^" rmail-unix-mail-delimiter) nil t)
392 (defun nnmail-open-server-internal (host &optional service)
393 "Open connection to news server on HOST by SERVICE (default is nntp)."
395 (if (not (string-equal host (system-name)))
396 (error "NNMAIL: cannot talk to %s." host))
397 ;; Initialize communication buffer.
398 (setq nntp-server-buffer (get-buffer-create " *nntpd*"))
399 (set-buffer nntp-server-buffer)
400 (buffer-disable-undo (current-buffer))
402 (kill-all-local-variables)
403 (setq case-fold-search t) ;Should ignore case.
406 (defun nnmail-close-server-internal ()
407 "Close connection to news server."
410 (defun nnmail-find-file (file)
411 "Insert FILE in server buffer safely."
413 (set-buffer nntp-server-buffer)
416 (progn (insert-file-contents file) t)
419 (defun nnmail-possibly-change-newsgroup (newsgroup)
420 (if (not (get-buffer nnmail-mbox-buffer))
422 (set-buffer (setq nnmail-mbox-buffer
423 (find-file-noselect nnmail-mbox-file)))
424 (buffer-disable-undo (current-buffer))))
425 (if (not nnmail-active-alist)
428 (if (assoc newsgroup nnmail-active-alist)
429 (setq nnmail-current-group newsgroup))))
431 ;; Most of this function was taken from rmail.el
432 (defun nnmail-move-inbox ()
433 (let ((inbox (expand-file-name nnmail-spool-file))
435 (setq tofile (make-temp-name
436 (expand-file-name (concat nnmail-mbox-file "-Incoming"))))
439 (setq errors (generate-new-buffer " *nnmail loss*"))
440 (buffer-disable-undo errors)
442 (expand-file-name "movemail" exec-directory)
443 nil errors nil inbox tofile)
444 (if (not (buffer-modified-p errors))
445 ;; No output => movemail won
448 (subst-char-in-region (point-min) (point-max) ?\n ?\ )
449 (goto-char (point-max))
450 (skip-chars-backward " \t")
451 (delete-region (point) (point-max))
452 (goto-char (point-min))
453 (if (looking-at "movemail: ")
454 (delete-region (point-min) (match-end 0)))
455 (error (concat "movemail: "
456 (buffer-substring (point-min)
460 (defun nnmail-article-string (article)
461 (concat "\nX-Gnus-Newsgroup: " nnmail-current-group ":"
462 (int-to-string article) " ("))
464 (defun nnmail-choose-mail (beg end)
468 (let ((methods nnmail-split-methods)
470 (while (and (not found) methods)
471 (if (re-search-backward (car (cdr (car methods))) beg t)
473 (setq result (nnmail-insert-newsgroup-line
474 (car (car methods)) beg end))
476 (setq methods (cdr methods))))
478 (setq result (nnmail-insert-newsgroup-line
479 (car (car nnmail-split-methods)) beg end)))))
482 (defun nnmail-insert-newsgroup-line (group beg end)
483 (let ((active (car (cdr (assoc group nnmail-active-alist))))
484 (time (current-time)))
487 (setq nnmail-active-alist
488 (cons (list group (cons 1 0)) nnmail-active-alist))
489 (setq active (car (cdr (car nnmail-active-alist))))))
490 (setcdr active (1+ (cdr active)))
491 (insert (format "X-Gnus-Newsgroup: %s:%d (%d %d)\n" group (cdr active)
492 (car time) (car (cdr time))))
493 (cons group (cdr active))))
495 (defun nnmail-split-region (beg end)
497 (let ((delim (concat "^" rmail-unix-mail-delimiter))
499 (while (re-search-forward delim nil t)
501 (search-forward "\n\n" nil t)
504 (if (not (save-excursion (re-search-backward "^Lines:" start t)))
506 (format "Lines: %d\n"
509 (or (re-search-forward rmail-unix-mail-delimiter nil t)
511 (setq stop (1- (point)))
512 (if (not (search-backward "X-Gnus-Newsgroup: " start t))
513 (nnmail-choose-mail start stop)))))
515 (defun nnmail-read-mbox ()
516 (if (and nnmail-mbox-buffer
517 (get-buffer nnmail-mbox-buffer)
518 (buffer-name nnmail-mbox-buffer)
520 (set-buffer nnmail-mbox-buffer)
521 (= (buffer-size) (nth 7 (file-attributes nnmail-mbox-file)))))
524 (set-buffer (setq nnmail-mbox-buffer
525 (find-file-noselect nnmail-mbox-file)))
526 (buffer-disable-undo (current-buffer))
527 (nnmail-split-region (point-min) (point-max)))))
529 (defun nnmail-split-incoming (incoming)
531 (set-buffer nnmail-mbox-buffer)
532 (goto-char (point-max))
533 (let ((start (point)))
534 (insert-file-contents incoming)
535 (nnmail-split-region start (point-max)))))
537 (defun nnmail-get-active ()
538 (let ((methods nnmail-split-methods))
539 (setq nnmail-active-alist nil)
540 (if (nnmail-request-list)
542 (set-buffer (get-buffer-create " *nntpd*"))
544 (while (re-search-forward
545 "^\\([^ \t]+\\)[ \t]+\\([0-9]+\\)[ \t]+\\([0-9]+\\)" nil t)
546 (setq nnmail-active-alist
547 (cons (list (buffer-substring (match-beginning 1)
550 (buffer-substring (match-beginning 3)
553 (buffer-substring (match-beginning 2)
555 nnmail-active-alist)))))
557 (if (not (assoc (car (car methods)) nnmail-active-alist))
558 (setq nnmail-active-alist
559 (cons (list (car (car methods)) (cons 1 0))
560 nnmail-active-alist)))
561 (setq methods (cdr methods)))
564 (defun nnmail-save-active ()
565 (let ((groups nnmail-active-alist)
568 (set-buffer (get-buffer-create " *nnmail*"))
569 (buffer-disable-undo (current-buffer))
572 (setq group (car groups))
573 (insert (format "%s %d %d y\n" (car group) (cdr (car (cdr group)) )
574 (car (car (cdr group)))))
575 (setq groups (cdr groups)))
576 (write-region 1 (point-max) (expand-file-name nnmail-active-file) nil
578 (kill-buffer (current-buffer)))))
580 (defun nnmail-get-new-mail ()
584 (if (and (file-exists-p nnmail-spool-file)
585 (> (nth 7 (file-attributes nnmail-spool-file)) 0))
587 (setq incoming (nnmail-move-inbox))
588 (nnmail-split-incoming incoming)
589 (run-hooks 'nnmail-read-incoming-hook)))
590 (and (buffer-modified-p nnmail-mbox-buffer)
593 (set-buffer nnmail-mbox-buffer)
596 ; (delete-file incoming))
601 ;;; nnmail.el ends here