1 ;;; nnfolder.el --- mail folder access for Gnus
2 ;; Copyright (C) 1995 Free Software Foundation, Inc.
4 ;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
5 ;; Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
6 ;; Keywords: news, mail
8 ;; This file is part of GNU Emacs.
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)
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.
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.
32 (defvar nnfolder-directory (expand-file-name "~/Mail/")
33 "The name of the mail box file in the users home directory.")
35 (defvar nnfolder-active-file (concat nnfolder-directory "active")
36 "The name of the active file.")
38 (defvar nnfolder-newsgroups-file (concat nnfolder-directory "newsgroups")
39 "Mail newsgroups description file.")
41 (defvar nnfolder-get-new-mail t
42 "If non-nil, nnml will check the incoming mail file and split the mail.")
46 (defconst nnfolder-version "nnfolder 0.1"
49 (defvar nnfolder-current-group nil)
50 (defvar nnfolder-current-buffer nil)
51 (defvar nnfolder-status-string "")
52 (defvar nnfolder-group-alist nil)
53 (defvar nnfolder-buffer-alist nil)
55 ;;; Interface functions
57 (defun nnfolder-retrieve-headers (sequence &optional newsgroup server)
58 "Retrieve the headers for the articles in SEQUENCE.
59 Newsgroup must be selected before calling this function."
61 (set-buffer nntp-server-buffer)
64 (number (length sequence))
65 beg article art-string start stop)
66 (nnfolder-possibly-change-group newsgroup)
68 (setq article (car sequence))
69 (setq art-string (nnfolder-article-string article))
70 (set-buffer nnfolder-current-buffer)
71 (if (or (search-forward art-string nil t)
73 (search-forward art-string nil t)))
78 (concat "^" rmail-unix-mail-delimiter) nil t)
80 (search-forward "\n\n" nil t)
81 (setq stop (1- (point)))
82 (set-buffer nntp-server-buffer)
83 (insert (format "221 %d Article retrieved.\n" article))
85 (insert-buffer-substring nnfolder-current-buffer start stop)
86 (goto-char (point-max))
88 (setq sequence (cdr sequence)))
90 ;; Fold continuation lines.
92 (while (re-search-forward "\\(\r?\n[ \t]+\\)+" nil t)
93 (replace-match " " t t))
96 (defun nnfolder-open-server (host &optional service)
98 (setq nnfolder-status-string "")
99 (setq nnfolder-group-alist nil)
100 (nnheader-init-server-buffer))
102 (defun nnfolder-close-server (&optional server)
106 (defun nnfolder-server-opened (&optional server)
107 "Return server process status, T or NIL.
108 If the stream is opened, return T, otherwise return NIL."
109 (and nntp-server-buffer
110 (buffer-name nntp-server-buffer)))
112 (defun nnfolder-status-message (&optional server)
113 "Return server status response as string."
114 nnfolder-status-string)
116 (defun nnfolder-request-article (article &optional newsgroup server buffer)
117 "Select ARTICLE by number."
118 (nnfolder-possibly-change-group newsgroup)
119 (if (stringp article)
122 (set-buffer nnfolder-current-buffer)
124 (if (search-forward (nnfolder-article-string article) nil t)
126 (re-search-backward (concat "^" rmail-unix-mail-delimiter) nil t)
129 (or (and (re-search-forward
130 (concat "^" rmail-unix-mail-delimiter) nil t)
132 (goto-char (point-max)))
134 (let ((nntp-server-buffer (or buffer nntp-server-buffer)))
135 (set-buffer nntp-server-buffer)
137 (insert-buffer-substring nnfolder-current-buffer start stop)
138 (goto-char (point-min))
139 (while (looking-at "From ")
141 (insert "X-From-Line: ")
145 (defun nnfolder-request-group (group &optional server dont-check)
148 (nnfolder-possibly-change-group group)
149 (and (assoc group nnfolder-group-alist)
151 (set-buffer nntp-server-buffer)
155 (nnfolder-get-new-mail)
156 (let ((active (assoc group nnfolder-group-alist)))
157 (insert (format "211 %d %d %d %s\n"
158 (1+ (- (cdr (car (cdr active)))
159 (car (car (cdr active)))))
160 (car (car (cdr active)))
161 (cdr (car (cdr active)))
165 (defun nnfolder-close-group (group &optional server)
168 (defun nnfolder-request-list (&optional server)
169 "List active newsgoups."
170 (if server (nnfolder-get-new-mail))
171 (or (nnmail-find-file nnfolder-active-file)
173 (setq nnfolder-group-alist (nnmail-get-active))
174 (nnmail-save-active nnfolder-group-alist nnfolder-active-file)
175 (nnmail-find-file nnfolder-active-file))))
177 (defun nnfolder-request-newgroups (date &optional server)
178 "List groups created after DATE."
179 (nnfolder-request-list server))
181 (defun nnfolder-request-list-newsgroups (&optional server)
182 "List newsgroups (defined in NNTP2)."
183 (nnmail-find-file nnfolder-newsgroups-file))
185 (defun nnfolder-request-post (&optional server)
186 "Post a new news in current buffer."
187 (mail-send-and-exit nil))
189 (fset 'nnfolder-request-post-buffer 'nnmail-request-post-buffer)
191 (defun nnfolder-request-expire-articles (articles newsgroup &optional server force)
192 "Expire all articles in the ARTICLES list in group GROUP.
193 The list of unexpired articles will be returned (ie. all articles that
194 were too fresh to be expired).
195 If FORCE is non-nil, the ARTICLES will be deleted without looking at
197 (nnfolder-possibly-change-group newsgroup)
198 (let* ((days (or (and nnmail-expiry-wait-function
199 (funcall nnmail-expiry-wait-function newsgroup))
203 (set-buffer nnfolder-current-buffer)
206 (if (search-forward (nnfolder-article-string (car articles)) nil t)
208 (> (nnmail-days-between
209 (current-time-string)
211 (point) (progn (end-of-line) (point))))
214 (and gnus-verbose-backends
215 (message "Deleting: %s" (car articles)))
216 (nnfolder-delete-mail))
217 (setq rest (cons (car articles) rest))))
218 (setq articles (cdr articles)))
222 (defun nnfolder-request-move-article
223 (article group server accept-form &optional last)
224 (nnfolder-possibly-change-group group)
225 (let ((buf (get-buffer-create " *nnfolder move*"))
228 (nnfolder-request-article article group server)
231 (buffer-disable-undo (current-buffer))
233 (insert-buffer-substring nntp-server-buffer)
234 (goto-char (point-min))
235 (while (re-search-forward
237 (save-excursion (search-forward "\n\n" nil t) (point)) t)
238 (delete-region (progn (beginning-of-line) (point))
239 (progn (forward-line 1) (point))))
240 (setq result (eval accept-form))
244 (nnfolder-possibly-change-group group)
245 (set-buffer nnfolder-current-buffer)
247 (if (search-forward (nnfolder-article-string article) nil t)
248 (nnfolder-delete-mail))
249 (and last (save-buffer))))
252 (defun nnfolder-request-accept-article (group &optional last)
253 (nnfolder-possibly-change-group group)
254 (let ((buf (current-buffer))
256 (debug (current-buffer))
257 (goto-char (point-min))
258 (if (looking-at "X-From-Line: ")
259 (replace-match "From ")
260 (insert "From nobody " (current-time-string) "\n"))
262 (nnfolder-request-list)
263 (setq nnfolder-group-alist (nnmail-get-active))
266 (goto-char (point-min))
267 (search-forward "\n\n" nil t)
269 (while (re-search-backward "^X-Gnus-Newsgroup: " nil t)
270 (delete-region (point) (progn (forward-line 1) (point))))
271 (setq result (nnfolder-save-mail (and (stringp group) group))))
273 (set-buffer nnfolder-current-buffer)
274 (insert-buffer-substring buf)
275 (and last (save-buffer))
277 (nnmail-save-active nnfolder-group-alist nnfolder-active-file))
280 (defun nnfolder-request-replace-article (article group buffer)
281 (nnfolder-possibly-change-group group)
283 (set-buffer nnfolder-current-buffer)
285 (if (not (search-forward (nnfolder-article-string article) nil t))
287 (nnfolder-delete-mail t t)
288 (insert-buffer-substring buffer)
293 ;;; Internal functions.
295 (defun nnfolder-delete-mail (&optional force leave-delim)
296 "If FORCE, delete article no matter how many X-Gnus-Newsgroup
297 headers there are. If LEAVE-DELIM, don't delete the Unix mbox
299 ;; Beginning of the article.
304 (re-search-backward (concat "^" rmail-unix-mail-delimiter) nil t)
305 (if leave-delim (progn (forward-line 1) (point))
306 (match-beginning 0)))
309 (or (and (re-search-forward (concat "^" rmail-unix-mail-delimiter)
311 (if (and (not (bobp)) leave-delim)
312 (progn (forward-line -2) (point))
313 (match-beginning 0)))
315 (delete-region (point-min) (point-max)))))
317 (defun nnfolder-possibly-change-group (group)
318 (or (file-exists-p nnfolder-directory)
319 (make-directory (directory-file-name nnfolder-directory)))
320 (if (not nnfolder-group-alist)
322 (nnfolder-request-list)
323 (setq nnfolder-group-alist (nnmail-get-active))))
324 (or (assoc group nnfolder-group-alist)
325 (not (file-exists-p (concat nnfolder-directory group)))
327 (setq nnfolder-group-alist
328 (cons (list group (cons 1 0)) nnfolder-group-alist))
329 (nnmail-save-active nnfolder-group-alist nnfolder-active-file)))
331 (if (and (equal group nnfolder-current-group)
332 (buffer-name nnfolder-current-buffer))
334 (if (setq inf (member group nnfolder-buffer-alist))
335 (setq nnfolder-current-buffer (nth 1 inf)))
336 (setq nnfolder-current-group group)
337 (if (not (buffer-name nnfolder-current-buffer))
339 (setq nnfolder-buffer-alist (delq inf nnfolder-buffer-alist))
344 (setq file (concat nnfolder-directory group))
345 (if (not (file-exists-p file))
346 (write-region 1 1 file t 'nomesg))
347 (set-buffer (nnfolder-read-folder file))
348 (setq nnfolder-buffer-alist (cons (list group (current-buffer))
349 nnfolder-buffer-alist))))))
350 (setq nnfolder-current-group group))
352 (defun nnfolder-article-string (article)
353 (concat "\nX-Gnus-Article-Number: " (int-to-string article) " "))
355 (defun nnfolder-save-mail (&optional group)
356 "Called narrowed to an article."
357 (let* ((nnmail-split-methods
358 (if group (list (list group "")) nnmail-split-methods))
360 (nreverse (nnmail-article-group 'nnfolder-active-number)))
362 (nnmail-insert-lines)
363 (nnmail-insert-xref group-art-list)
364 (while group-art-list
365 (setq group-art (car group-art-list)
366 group-art-list (cdr group-art-list))
367 (nnfolder-possibly-change-group (car group-art))
368 (nnfolder-insert-newsgroup-line group-art)
369 (let ((beg (point-min))
371 (obuf (current-buffer)))
373 (set-buffer nnfolder-current-buffer)
374 (goto-char (point-max))
375 (insert-buffer-substring obuf beg end)))
376 (goto-char (point-min))
377 (search-forward "\nX-Gnus-Article-Number: ")
378 (delete-region (progn (beginning-of-line) (point))
379 (progn (forward-line 1) (point))))))
381 (defun nnfolder-insert-newsgroup-line (group-art)
383 (goto-char (point-min))
384 (if (search-forward "\n\n" nil t)
387 (insert (format "X-Gnus-Article-Number: %d %s\n"
388 (cdr group-art) (current-time-string)))))))
390 (defun nnfolder-active-number (group)
391 "Find the next article number in GROUP."
392 (let ((active (car (cdr (assoc group nnfolder-group-alist)))))
393 (setcdr active (1+ (cdr active)))
396 (defun nnfolder-read-folder (file)
397 (nnfolder-request-list)
398 (setq nnfolder-group-alist (nnmail-get-active))
401 (setq nnfolder-current-buffer
402 (find-file-noselect file)))
403 (buffer-disable-undo (current-buffer))
404 (let ((delim (concat "^" rmail-unix-mail-delimiter))
406 (goto-char (point-min))
407 (while (re-search-forward delim nil t)
408 (setq start (match-beginning 0))
409 (if (not (search-forward "\nX-Gnus-Article-Number: "
414 (re-search-forward delim nil t)
420 (narrow-to-region start end)
421 (nnfolder-insert-newsgroup-line
422 (cons nil (nnfolder-active-number nnfolder-current-group))))))
424 (nnmail-save-active nnfolder-group-alist nnfolder-active-file)
427 (defun nnfolder-get-new-mail ()
429 (if (and nnmail-spool-file
430 nnfolder-get-new-mail
431 (file-exists-p nnmail-spool-file)
432 (> (nth 7 (file-attributes nnmail-spool-file)) 0))
434 (and gnus-verbose-backends
435 (message "nnfolder: Reading incoming mail..."))
437 (nnmail-move-inbox nnmail-spool-file
438 (concat nnfolder-directory "Incoming")))
439 (nnmail-split-incoming incoming 'nnfolder-save-mail)
440 (run-hooks 'nnmail-read-incoming-hook)
441 (nnmail-save-active nnfolder-group-alist nnfolder-active-file)
442 (and gnus-verbose-backends
443 (message "nnfolder: Reading incoming mail...done"))))
444 (let ((bufs nnfolder-buffer-alist))
447 (if (not (buffer-name (nth 1 (car bufs))))
448 (setq nnfolder-buffer-alist
449 (delq (car bufs) nnfolder-buffer-alist))
450 (set-buffer (nth 1 (car bufs)))
452 (setq bufs (cdr bufs)))))
453 ;; (if incoming (delete-file incoming))
458 ;;; nnfolder.el ends here