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.
26 ;; For an overview of what the interface functions do, please see the
29 ;; Various enhancements by byer@mv.us.adobe.com (Scott Byer).
37 (defvar nnfolder-directory (expand-file-name "~/Mail/")
38 "The name of the mail box file in the users home directory.")
40 (defvar nnfolder-active-file (concat nnfolder-directory "active")
41 "The name of the active file.")
43 (defvar nnfolder-newsgroups-file (concat nnfolder-directory "newsgroups")
44 "Mail newsgroups description file.")
46 (defvar nnfolder-get-new-mail t
47 "If non-nil, nnml will check the incoming mail file and split the mail.")
51 (defconst nnfolder-version "nnfolder 0.1"
54 (defconst nnfolder-article-marker "X-Gnus-Article-Number: "
55 "String used to demarcate what the article number for a message is.")
57 (defvar nnfolder-current-group nil)
58 (defvar nnfolder-current-buffer nil)
59 (defvar nnfolder-status-string "")
60 (defvar nnfolder-group-alist nil)
61 (defvar nnfolder-buffer-alist nil)
63 (defmacro nnfolder-article-string (article)
64 (` (concat "\n" nnfolder-article-marker (int-to-string (, article)) "")))
68 (defvar nnfolder-current-server nil)
69 (defvar nnfolder-server-alist nil)
70 (defvar nnfolder-server-variables
72 (list 'nnfolder-directory nnfolder-directory)
73 (list 'nnfolder-active-file nnfolder-active-file)
74 (list 'nnfolder-newsgroups-file nnfolder-newsgroups-file)
75 (list 'nnfolder-get-new-mail nnfolder-get-new-mail)
76 '(nnfolder-current-group nil)
77 '(nnfolder-current-buffer nil)
78 '(nnfolder-status-string "")
79 '(nnfolder-group-alist nil)
80 '(nnfolder-buffer-alist nil)))
84 ;;; Interface functions
86 (defun nnfolder-retrieve-headers (sequence &optional newsgroup server)
88 (set-buffer nntp-server-buffer)
91 (number (length sequence))
92 (delim-string (concat "^" rmail-unix-mail-delimiter))
93 beg article art-string start stop)
94 (nnfolder-possibly-change-group newsgroup)
96 (setq article (car sequence))
97 (setq art-string (nnfolder-article-string article))
98 (set-buffer nnfolder-current-buffer)
99 (if (or (search-forward art-string nil t)
100 ;; Don't search the whole file twice! Also, articles
101 ;; probably have some locality by number, so searching
102 ;; backwards will be faster. Especially if we're at the
103 ;; beginning of the buffer :-). -SLB
104 (search-backward art-string nil t))
106 (setq start (or (re-search-backward delim-string nil t)
108 (search-forward "\n\n" nil t)
109 (setq stop (1- (point)))
110 (set-buffer nntp-server-buffer)
111 (insert (format "221 %d Article retrieved.\n" article))
113 (insert-buffer-substring nnfolder-current-buffer start stop)
114 (goto-char (point-max))
116 (setq sequence (cdr sequence)))
118 ;; Fold continuation lines.
119 (set-buffer nntp-server-buffer)
121 (while (re-search-forward "\\(\r?\n[ \t]+\\)+" nil t)
122 (replace-match " " t t))
125 (defun nnfolder-open-server (server &optional defs)
126 (nnheader-init-server-buffer)
127 (if (equal server nnfolder-current-server)
129 (if nnfolder-current-server
130 (setq nnfolder-server-alist
131 (cons (list nnfolder-current-server
132 (nnheader-save-variables nnfolder-server-variables))
133 nnfolder-server-alist)))
134 (let ((state (assoc server nnfolder-server-alist)))
137 (nnheader-restore-variables (nth 1 state))
138 (setq nnfolder-server-alist (delq state nnfolder-server-alist)))
139 (nnheader-set-init-variables nnfolder-server-variables defs)))
140 (setq nnfolder-current-server server)))
142 (defun nnfolder-close-server (&optional server)
145 (defun nnfolder-server-opened (&optional server)
146 (equal server nnfolder-current-server))
148 (defun nnfolder-request-close ()
149 (let ((alist nnfolder-buffer-alist))
151 (nnfolder-close-group (car (car alist)))
152 (setq alist (cdr alist))))
153 (setq nnfolder-buffer-alist nil
154 nnfolder-group-alist nil))
156 (defun nnfolder-status-message (&optional server)
157 nnfolder-status-string)
159 (defun nnfolder-request-article (article &optional newsgroup server buffer)
160 (nnfolder-possibly-change-group newsgroup)
161 (if (stringp article)
164 (set-buffer nnfolder-current-buffer)
166 (if (search-forward (nnfolder-article-string article) nil t)
168 (re-search-backward (concat "^" rmail-unix-mail-delimiter) nil t)
171 (or (and (re-search-forward
172 (concat "^" rmail-unix-mail-delimiter) nil t)
174 (goto-char (point-max)))
176 (let ((nntp-server-buffer (or buffer nntp-server-buffer)))
177 (set-buffer nntp-server-buffer)
179 (insert-buffer-substring nnfolder-current-buffer start stop)
180 (goto-char (point-min))
181 (while (looking-at "From ")
183 (insert "X-From-Line: ")
187 (defun nnfolder-request-group (group &optional server dont-check)
189 (nnfolder-possibly-change-group group)
190 (and (assoc group nnfolder-group-alist)
192 (set-buffer nntp-server-buffer)
196 (nnfolder-get-new-mail)
197 (let ((active (assoc group nnfolder-group-alist)))
198 ;; I've been getting stray 211 lines in my nnfolder active
199 ;; file. So, let's make sure that doesn't happen. -SLB
200 (set-buffer nntp-server-buffer)
201 (insert (format "211 %d %d %d %s\n"
202 (1+ (- (cdr (car (cdr active)))
203 (car (car (cdr active)))))
204 (car (car (cdr active)))
205 (cdr (car (cdr active)))
209 (defun nnfolder-close-group (group &optional server)
210 (nnfolder-possibly-change-group group)
212 (set-buffer nnfolder-current-buffer)
213 (or (buffer-modified-p)
214 (kill-buffer (current-buffer))))
215 (setq nnfolder-buffer-alist (delq (assoc group nnfolder-buffer-alist)
216 nnfolder-buffer-alist))
217 (setq nnfolder-current-group nil
218 nnfolder-current-buffer nil)
221 (defun nnfolder-request-list (&optional server)
222 (if server (nnfolder-get-new-mail))
223 (or nnfolder-group-alist
224 (nnmail-find-file nnfolder-active-file)
226 (setq nnfolder-group-alist (nnmail-get-active))
227 (nnmail-save-active nnfolder-group-alist nnfolder-active-file)
228 (nnmail-find-file nnfolder-active-file))))
230 (defun nnfolder-request-newgroups (date &optional server)
231 (nnfolder-request-list server))
233 (defun nnfolder-request-list-newsgroups (&optional server)
234 (nnmail-find-file nnfolder-newsgroups-file))
236 (defun nnfolder-request-post (&optional server)
237 (mail-send-and-exit nil))
239 (fset 'nnfolder-request-post-buffer 'nnmail-request-post-buffer)
241 (defun nnfolder-request-expire-articles (articles newsgroup &optional server force)
242 (nnfolder-possibly-change-group newsgroup)
243 (let* ((days (or (and nnmail-expiry-wait-function
244 (funcall nnmail-expiry-wait-function newsgroup))
248 (set-buffer nnfolder-current-buffer)
251 (if (search-forward (nnfolder-article-string (car articles)) nil t)
253 (> (nnmail-days-between
254 (current-time-string)
256 (point) (progn (end-of-line) (point))))
259 (and gnus-verbose-backends
260 (message "Deleting: %s" (car articles)))
261 (nnfolder-delete-mail))
262 (setq rest (cons (car articles) rest))))
263 (setq articles (cdr articles)))
265 ;; Find the lowest active article in this group.
266 (let ((active (nth 1 (assoc newsgroup nnfolder-group-alist))))
267 (goto-char (point-min))
268 (while (not (search-forward
269 (nnfolder-article-string (car active)) nil t))
270 (setcar active (1+ (car active)))
271 (goto-char (point-min))))
272 (nnmail-save-active nnfolder-group-alist nnfolder-active-file)
275 (defun nnfolder-request-move-article
276 (article group server accept-form &optional last)
277 (nnfolder-possibly-change-group group)
278 (let ((buf (get-buffer-create " *nnfolder move*"))
281 (nnfolder-request-article article group server)
284 (buffer-disable-undo (current-buffer))
286 (insert-buffer-substring nntp-server-buffer)
287 (goto-char (point-min))
288 (while (re-search-forward
290 (save-excursion (search-forward "\n\n" nil t) (point)) t)
291 (delete-region (progn (beginning-of-line) (point))
292 (progn (forward-line 1) (point))))
293 (setq result (eval accept-form))
297 (nnfolder-possibly-change-group group)
298 (set-buffer nnfolder-current-buffer)
300 (if (search-forward (nnfolder-article-string article) nil t)
301 (nnfolder-delete-mail))
302 (and last (save-buffer))))
305 (defun nnfolder-request-accept-article (group &optional last)
306 (nnfolder-possibly-change-group group)
307 (let ((buf (current-buffer))
309 (goto-char (point-min))
310 (if (looking-at "X-From-Line: ")
311 (replace-match "From ")
312 (insert "From nobody " (current-time-string) "\n"))
314 (nnfolder-request-list)
315 (setq nnfolder-group-alist (nnmail-get-active))
318 (goto-char (point-min))
319 (search-forward "\n\n" nil t)
321 (while (re-search-backward "^X-Gnus-Newsgroup: " nil t)
322 (delete-region (point) (progn (forward-line 1) (point))))
323 (setq result (nnfolder-save-mail (and (stringp group) group))))
325 (set-buffer nnfolder-current-buffer)
326 (insert-buffer-substring buf)
327 (and last (save-buffer))
329 (nnmail-save-active nnfolder-group-alist nnfolder-active-file))
332 (defun nnfolder-request-replace-article (article group buffer)
333 (nnfolder-possibly-change-group group)
335 (set-buffer nnfolder-current-buffer)
337 (if (not (search-forward (nnfolder-article-string article) nil t))
339 (nnfolder-delete-mail t t)
340 (insert-buffer-substring buffer)
345 ;;; Internal functions.
347 (defun nnfolder-delete-mail (&optional force leave-delim)
348 ;; Beginning of the article.
353 (re-search-backward (concat "^" rmail-unix-mail-delimiter) nil t)
354 (if leave-delim (progn (forward-line 1) (point))
355 (match-beginning 0)))
358 (or (and (re-search-forward (concat "^" rmail-unix-mail-delimiter)
360 (if (and (not (bobp)) leave-delim)
361 (progn (forward-line -2) (point))
362 (match-beginning 0)))
364 (delete-region (point-min) (point-max)))))
366 (defun nnfolder-possibly-change-group (group)
367 (or (file-exists-p nnfolder-directory)
368 (make-directory (directory-file-name nnfolder-directory)))
369 (if (not nnfolder-group-alist)
371 (nnfolder-request-list)
372 (setq nnfolder-group-alist (nnmail-get-active))))
373 (or (assoc group nnfolder-group-alist)
374 (not (file-exists-p (concat nnfolder-directory group)))
376 (setq nnfolder-group-alist
377 (cons (list group (cons 1 0)) nnfolder-group-alist))
378 (nnmail-save-active nnfolder-group-alist nnfolder-active-file)))
380 (if (and (equal group nnfolder-current-group)
381 (buffer-name nnfolder-current-buffer))
383 (if (setq inf (member group nnfolder-buffer-alist))
384 (setq nnfolder-current-buffer (nth 1 inf)))
385 (setq nnfolder-current-group group)
386 (if (not (buffer-name nnfolder-current-buffer))
388 (setq nnfolder-buffer-alist (delq inf nnfolder-buffer-alist))
393 (setq file (concat nnfolder-directory group))
394 (if (not (file-exists-p file))
395 (write-region 1 1 file t 'nomesg))
396 (set-buffer (nnfolder-read-folder file))
397 (setq nnfolder-buffer-alist (cons (list group (current-buffer))
398 nnfolder-buffer-alist))))))
399 (setq nnfolder-current-group group))
401 (defun nnfolder-save-mail (&optional group)
402 "Called narrowed to an article."
403 (let* ((nnmail-split-methods
404 (if group (list (list group "")) nnmail-split-methods))
406 (nreverse (nnmail-article-group 'nnfolder-active-number)))
408 (nnmail-insert-lines)
409 (nnmail-insert-xref group-art-list)
410 (while group-art-list
411 (setq group-art (car group-art-list)
412 group-art-list (cdr group-art-list))
413 (nnfolder-possibly-change-group (car group-art))
414 (nnfolder-insert-newsgroup-line group-art)
415 (let ((beg (point-min))
417 (obuf (current-buffer)))
419 (set-buffer nnfolder-current-buffer)
420 (goto-char (point-max))
421 (insert-buffer-substring obuf beg end)))
422 (goto-char (point-min))
423 (search-forward (concat "\n" nnfolder-article-marker))
424 (delete-region (progn (beginning-of-line) (point))
425 (progn (forward-line 1) (point))))))
427 (defun nnfolder-insert-newsgroup-line (group-art)
429 (goto-char (point-min))
430 (if (search-forward "\n\n" nil t)
433 (insert (format (concat nnfolder-article-marker "%d %s\n")
434 (cdr group-art) (current-time-string)))))))
436 (defun nnfolder-active-number (group)
437 (if (not nnfolder-group-alist)
439 (nnfolder-request-list)
440 (setq nnfolder-group-alist (nnmail-get-active))))
441 (let ((active (car (cdr (assoc group nnfolder-group-alist)))))
442 (setcdr active (1+ (cdr active)))
446 ;; This method has a problem if you've accidentally let the active list get
447 ;; out of sync with the files. This could happen, say, if you've
448 ;; accidentally gotten new mail with something other than (ding) (but why
449 ;; would _that_ ever happen? :-). In that case, we will be in the middle of
450 ;; processing the file, ready to add new X-Gnus article number markers, and
451 ;; we'll run accross a message with no ID yet - the active list _may_not_ be
454 ;; To handle this, I'm modifying this routine to maintain the maximum ID seen
455 ;; so far, and when we hit a message with no ID, we will _manually_ scan the
456 ;; rest of the message looking for any more, possibly higher IDs. We'll
457 ;; assume the maximum that we find is the highest active. Note that this
458 ;; shouldn't cost us much extra time at all, but will be a lot less
459 ;; vulnerable to glitches between the mbox and the active file.
461 (defun nnfolder-read-folder (file)
463 (if (not nnfolder-group-alist)
465 (nnfolder-request-list)
466 (setq nnfolder-group-alist (nnmail-get-active))))
467 ;; We should be paranoid here and make sure the group is in the alist,
468 ;; and add it if it isn't.
469 ;;(if (not (assoc nnfoler-current-group nnfolder-group-alist)
470 (set-buffer (setq nnfolder-current-buffer (find-file-noselect file)))
471 (buffer-disable-undo (current-buffer))
472 (let ((delim (concat "^" rmail-unix-mail-delimiter))
473 (marker (concat "\n" nnfolder-article-marker))
475 (active (car (cdr (assoc nnfolder-current-group
476 nnfolder-group-alist))))
477 activenumber start end)
478 (goto-char (point-min))
480 ;; Anytime the active number is 1 or 0, it is supect. In that case,
481 ;; search the file manually to find the active number.
482 (setq activenumber (cdr active))
483 (if (< activenumber 2)
485 (while (and (search-forward marker nil t)
486 (re-search-forward number nil t))
487 (setq activenumber (max activenumber
488 (string-to-number (buffer-substring
491 (goto-char (point-min))))
493 ;; Keep track of the active number on our own, and insert it back into
494 ;; the active list when we're done. Also, prime the pump to cut down on
495 ;; the number of searches we do.
496 (setq end (or (and (re-search-forward delim nil t)
499 (while (not (= end (point-max)))
503 (setq end (or (and (re-search-forward delim nil t)
507 (if (not (search-forward marker end t))
509 (narrow-to-region start end)
510 (nnmail-insert-lines)
511 (setq activenumber (1+ activenumber))
512 (nnfolder-insert-newsgroup-line (cons nil activenumber))
515 ;; Make absolutely sure that the active list reflects reality!
516 (setcdr active activenumber)
517 (nnmail-save-active nnfolder-group-alist nnfolder-active-file)
520 (defun nnfolder-get-new-mail ()
522 (if (and nnmail-spool-file
523 nnfolder-get-new-mail
524 (file-exists-p nnmail-spool-file)
525 (> (nth 7 (file-attributes nnmail-spool-file)) 0))
527 (and gnus-verbose-backends
528 (message "nnfolder: Reading incoming mail..."))
530 (nnmail-move-inbox nnmail-spool-file
531 (concat nnfolder-directory "Incoming")))
532 (nnmail-split-incoming incoming 'nnfolder-save-mail)
533 (run-hooks 'nnmail-read-incoming-hook)
534 (nnmail-save-active nnfolder-group-alist nnfolder-active-file)
535 (and gnus-verbose-backends
536 (message "nnfolder: Reading incoming mail...done"))))
537 (let ((bufs nnfolder-buffer-alist))
540 (if (not (buffer-name (nth 1 (car bufs))))
541 (setq nnfolder-buffer-alist
542 (delq (car bufs) nnfolder-buffer-alist))
543 (set-buffer (nth 1 (car bufs)))
544 (and (buffer-modified-p)
546 (setq bufs (cdr bufs)))))
547 ;; (if incoming (delete-file incoming))
552 ;;; nnfolder.el ends here