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)) "")))
66 ;;; Interface functions
68 (defun nnfolder-retrieve-headers (sequence &optional newsgroup server)
70 (set-buffer nntp-server-buffer)
73 (number (length sequence))
74 (delim-string (concat "^" rmail-unix-mail-delimiter))
75 beg article art-string start stop)
76 (nnfolder-possibly-change-group newsgroup)
78 (setq article (car sequence))
79 (setq art-string (nnfolder-article-string article))
80 (set-buffer nnfolder-current-buffer)
81 (if (or (search-forward art-string nil t)
82 ;; Don't search the whole file twice! Also, articles
83 ;; probably have some locality by number, so searching
84 ;; backwards will be faster. Especially if we're at the
85 ;; beginning of the buffer :-). -SLB
86 (search-backward art-string nil t))
88 (setq start (or (re-search-backward delim-string nil t)
90 (search-forward "\n\n" nil t)
91 (setq stop (1- (point)))
92 (set-buffer nntp-server-buffer)
93 (insert (format "221 %d Article retrieved.\n" article))
95 (insert-buffer-substring nnfolder-current-buffer start stop)
96 (goto-char (point-max))
98 (setq sequence (cdr sequence)))
100 ;; Fold continuation lines.
101 (set-buffer nntp-server-buffer)
103 (while (re-search-forward "\\(\r?\n[ \t]+\\)+" nil t)
104 (replace-match " " t t))
107 (defun nnfolder-open-server (host &optional service)
108 (setq nnfolder-status-string "")
109 (setq nnfolder-group-alist nil)
110 (nnheader-init-server-buffer))
112 (defun nnfolder-close-server (&optional server)
115 (defun nnfolder-request-close ()
116 (let ((alist nnfolder-buffer-alist))
118 (nnfolder-close-group (car (car alist)))
119 (setq alist (cdr alist))))
120 (setq nnfolder-buffer-alist nil
121 nnfolder-group-alist nil))
123 (defun nnfolder-server-opened (&optional server)
124 (and nntp-server-buffer
125 (buffer-name nntp-server-buffer)))
127 (defun nnfolder-status-message (&optional server)
128 nnfolder-status-string)
130 (defun nnfolder-request-article (article &optional newsgroup server buffer)
131 (nnfolder-possibly-change-group newsgroup)
132 (if (stringp article)
135 (set-buffer nnfolder-current-buffer)
137 (if (search-forward (nnfolder-article-string article) nil t)
139 (re-search-backward (concat "^" rmail-unix-mail-delimiter) nil t)
142 (or (and (re-search-forward
143 (concat "^" rmail-unix-mail-delimiter) nil t)
145 (goto-char (point-max)))
147 (let ((nntp-server-buffer (or buffer nntp-server-buffer)))
148 (set-buffer nntp-server-buffer)
150 (insert-buffer-substring nnfolder-current-buffer start stop)
151 (goto-char (point-min))
152 (while (looking-at "From ")
154 (insert "X-From-Line: ")
158 (defun nnfolder-request-group (group &optional server dont-check)
160 (nnfolder-possibly-change-group group)
161 (and (assoc group nnfolder-group-alist)
163 (set-buffer nntp-server-buffer)
167 (nnfolder-get-new-mail)
168 (let ((active (assoc group nnfolder-group-alist)))
169 ;; I've been getting stray 211 lines in my nnfolder active
170 ;; file. So, let's make sure that doesn't happen. -SLB
171 (set-buffer nntp-server-buffer)
172 (insert (format "211 %d %d %d %s\n"
173 (1+ (- (cdr (car (cdr active)))
174 (car (car (cdr active)))))
175 (car (car (cdr active)))
176 (cdr (car (cdr active)))
180 (defun nnfolder-close-group (group &optional server)
181 (nnfolder-possibly-change-group group)
183 (set-buffer nnfolder-current-buffer)
184 (or (buffer-modified-p)
185 (kill-buffer (current-buffer))))
186 (setq nnfolder-buffer-alist (delq (assoc group nnfolder-buffer-alist)
187 nnfolder-buffer-alist))
188 (setq nnfolder-current-group nil
189 nnfolder-current-buffer nil)
192 (defun nnfolder-request-list (&optional server)
193 (if server (nnfolder-get-new-mail))
194 (or nnfolder-group-alist
195 (nnmail-find-file nnfolder-active-file)
197 (setq nnfolder-group-alist (nnmail-get-active))
198 (nnmail-save-active nnfolder-group-alist nnfolder-active-file)
199 (nnmail-find-file nnfolder-active-file))))
201 (defun nnfolder-request-newgroups (date &optional server)
202 (nnfolder-request-list server))
204 (defun nnfolder-request-list-newsgroups (&optional server)
205 (nnmail-find-file nnfolder-newsgroups-file))
207 (defun nnfolder-request-post (&optional server)
208 (mail-send-and-exit nil))
210 (fset 'nnfolder-request-post-buffer 'nnmail-request-post-buffer)
212 (defun nnfolder-request-expire-articles (articles newsgroup &optional server force)
213 (nnfolder-possibly-change-group newsgroup)
214 (let* ((days (or (and nnmail-expiry-wait-function
215 (funcall nnmail-expiry-wait-function newsgroup))
219 (set-buffer nnfolder-current-buffer)
222 (if (search-forward (nnfolder-article-string (car articles)) nil t)
224 (> (nnmail-days-between
225 (current-time-string)
227 (point) (progn (end-of-line) (point))))
230 (and gnus-verbose-backends
231 (message "Deleting: %s" (car articles)))
232 (nnfolder-delete-mail))
233 (setq rest (cons (car articles) rest))))
234 (setq articles (cdr articles)))
236 ;; Find the lowest active article in this group.
237 (let ((active (nth 1 (assoc newsgroup nnfolder-group-alist))))
238 (goto-char (point-min))
239 (while (not (search-forward
240 (nnfolder-article-string (car active)) nil t))
241 (setcar active (1+ (car active)))
242 (goto-char (point-min))))
243 (nnmail-save-active nnfolder-group-alist nnfolder-active-file)
246 (defun nnfolder-request-move-article
247 (article group server accept-form &optional last)
248 (nnfolder-possibly-change-group group)
249 (let ((buf (get-buffer-create " *nnfolder move*"))
252 (nnfolder-request-article article group server)
255 (buffer-disable-undo (current-buffer))
257 (insert-buffer-substring nntp-server-buffer)
258 (goto-char (point-min))
259 (while (re-search-forward
261 (save-excursion (search-forward "\n\n" nil t) (point)) t)
262 (delete-region (progn (beginning-of-line) (point))
263 (progn (forward-line 1) (point))))
264 (setq result (eval accept-form))
268 (nnfolder-possibly-change-group group)
269 (set-buffer nnfolder-current-buffer)
271 (if (search-forward (nnfolder-article-string article) nil t)
272 (nnfolder-delete-mail))
273 (and last (save-buffer))))
276 (defun nnfolder-request-accept-article (group &optional last)
277 (nnfolder-possibly-change-group group)
278 (let ((buf (current-buffer))
280 (goto-char (point-min))
281 (if (looking-at "X-From-Line: ")
282 (replace-match "From ")
283 (insert "From nobody " (current-time-string) "\n"))
285 (nnfolder-request-list)
286 (setq nnfolder-group-alist (nnmail-get-active))
289 (goto-char (point-min))
290 (search-forward "\n\n" nil t)
292 (while (re-search-backward "^X-Gnus-Newsgroup: " nil t)
293 (delete-region (point) (progn (forward-line 1) (point))))
294 (setq result (nnfolder-save-mail (and (stringp group) group))))
296 (set-buffer nnfolder-current-buffer)
297 (insert-buffer-substring buf)
298 (and last (save-buffer))
300 (nnmail-save-active nnfolder-group-alist nnfolder-active-file))
303 (defun nnfolder-request-replace-article (article group buffer)
304 (nnfolder-possibly-change-group group)
306 (set-buffer nnfolder-current-buffer)
308 (if (not (search-forward (nnfolder-article-string article) nil t))
310 (nnfolder-delete-mail t t)
311 (insert-buffer-substring buffer)
316 ;;; Internal functions.
318 (defun nnfolder-delete-mail (&optional force leave-delim)
319 ;; Beginning of the article.
324 (re-search-backward (concat "^" rmail-unix-mail-delimiter) nil t)
325 (if leave-delim (progn (forward-line 1) (point))
326 (match-beginning 0)))
329 (or (and (re-search-forward (concat "^" rmail-unix-mail-delimiter)
331 (if (and (not (bobp)) leave-delim)
332 (progn (forward-line -2) (point))
333 (match-beginning 0)))
335 (delete-region (point-min) (point-max)))))
337 (defun nnfolder-possibly-change-group (group)
338 (or (file-exists-p nnfolder-directory)
339 (make-directory (directory-file-name nnfolder-directory)))
340 (if (not nnfolder-group-alist)
342 (nnfolder-request-list)
343 (setq nnfolder-group-alist (nnmail-get-active))))
344 (or (assoc group nnfolder-group-alist)
345 (not (file-exists-p (concat nnfolder-directory group)))
347 (setq nnfolder-group-alist
348 (cons (list group (cons 1 0)) nnfolder-group-alist))
349 (nnmail-save-active nnfolder-group-alist nnfolder-active-file)))
351 (if (and (equal group nnfolder-current-group)
352 (buffer-name nnfolder-current-buffer))
354 (if (setq inf (member group nnfolder-buffer-alist))
355 (setq nnfolder-current-buffer (nth 1 inf)))
356 (setq nnfolder-current-group group)
357 (if (not (buffer-name nnfolder-current-buffer))
359 (setq nnfolder-buffer-alist (delq inf nnfolder-buffer-alist))
364 (setq file (concat nnfolder-directory group))
365 (if (not (file-exists-p file))
366 (write-region 1 1 file t 'nomesg))
367 (set-buffer (nnfolder-read-folder file))
368 (setq nnfolder-buffer-alist (cons (list group (current-buffer))
369 nnfolder-buffer-alist))))))
370 (setq nnfolder-current-group group))
372 (defun nnfolder-save-mail (&optional group)
373 "Called narrowed to an article."
374 (let* ((nnmail-split-methods
375 (if group (list (list group "")) nnmail-split-methods))
377 (nreverse (nnmail-article-group 'nnfolder-active-number)))
379 (nnmail-insert-lines)
380 (nnmail-insert-xref group-art-list)
381 (while group-art-list
382 (setq group-art (car group-art-list)
383 group-art-list (cdr group-art-list))
384 (nnfolder-possibly-change-group (car group-art))
385 (nnfolder-insert-newsgroup-line group-art)
386 (let ((beg (point-min))
388 (obuf (current-buffer)))
390 (set-buffer nnfolder-current-buffer)
391 (goto-char (point-max))
392 (insert-buffer-substring obuf beg end)))
393 (goto-char (point-min))
394 (search-forward (concat "\n" nnfolder-article-marker))
395 (delete-region (progn (beginning-of-line) (point))
396 (progn (forward-line 1) (point))))))
398 (defun nnfolder-insert-newsgroup-line (group-art)
400 (goto-char (point-min))
401 (if (search-forward "\n\n" nil t)
404 (insert (format (concat nnfolder-article-marker "%d %s\n")
405 (cdr group-art) (current-time-string)))))))
407 (defun nnfolder-active-number (group)
408 (if (not nnfolder-group-alist)
410 (nnfolder-request-list)
411 (setq nnfolder-group-alist (nnmail-get-active))))
412 (let ((active (car (cdr (assoc group nnfolder-group-alist)))))
413 (setcdr active (1+ (cdr active)))
417 ;; This method has a problem if you've accidentally let the active list get
418 ;; out of sync with the files. This could happen, say, if you've
419 ;; accidentally gotten new mail with something other than (ding) (but why
420 ;; would _that_ ever happen? :-). In that case, we will be in the middle of
421 ;; processing the file, ready to add new X-Gnus article number markers, and
422 ;; we'll run accross a message with no ID yet - the active list _may_not_ be
425 ;; To handle this, I'm modifying this routine to maintain the maximum ID seen
426 ;; so far, and when we hit a message with no ID, we will _manually_ scan the
427 ;; rest of the message looking for any more, possibly higher IDs. We'll
428 ;; assume the maximum that we find is the highest active. Note that this
429 ;; shouldn't cost us much extra time at all, but will be a lot less
430 ;; vulnerable to glitches between the mbox and the active file.
432 (defun nnfolder-read-folder (file)
434 (if (not nnfolder-group-alist)
436 (nnfolder-request-list)
437 (setq nnfolder-group-alist (nnmail-get-active))))
438 ;; We should be paranoid here and make sure the group is in the alist,
439 ;; and add it if it isn't.
440 ;;(if (not (assoc nnfoler-current-group nnfolder-group-alist)
441 (set-buffer (setq nnfolder-current-buffer (find-file-noselect file)))
442 (buffer-disable-undo (current-buffer))
443 (let ((delim (concat "^" rmail-unix-mail-delimiter))
444 (marker (concat "\n" nnfolder-article-marker))
446 (active (car (cdr (assoc nnfolder-current-group
447 nnfolder-group-alist))))
448 activenumber start end)
449 (goto-char (point-min))
451 ;; Anytime the active number is 1 or 0, it is supect. In that case,
452 ;; search the file manually to find the active number.
453 (setq activenumber (cdr active))
454 (if (< activenumber 2)
456 (while (and (search-forward marker nil t)
457 (re-search-forward number nil t))
458 (setq activenumber (max activenumber
459 (string-to-number (buffer-substring
462 (goto-char (point-min))))
464 ;; Keep track of the active number on our own, and insert it back into
465 ;; the active list when we're done. Also, prime the pump to cut down on
466 ;; the number of searches we do.
467 (setq end (or (and (re-search-forward delim nil t)
470 (while (not (= end (point-max)))
474 (setq end (or (and (re-search-forward delim nil t)
478 (if (not (search-forward marker end t))
480 (narrow-to-region start end)
481 (nnmail-insert-lines)
482 (setq activenumber (1+ activenumber))
483 (nnfolder-insert-newsgroup-line (cons nil activenumber))
486 ;; Make absolutely sure that the active list reflects reality!
487 (setcdr active activenumber)
488 (nnmail-save-active nnfolder-group-alist nnfolder-active-file)
491 (defun nnfolder-get-new-mail ()
493 (if (and nnmail-spool-file
494 nnfolder-get-new-mail
495 (file-exists-p nnmail-spool-file)
496 (> (nth 7 (file-attributes nnmail-spool-file)) 0))
498 (and gnus-verbose-backends
499 (message "nnfolder: Reading incoming mail..."))
501 (nnmail-move-inbox nnmail-spool-file
502 (concat nnfolder-directory "Incoming")))
503 (nnmail-split-incoming incoming 'nnfolder-save-mail)
504 (run-hooks 'nnmail-read-incoming-hook)
505 (nnmail-save-active nnfolder-group-alist nnfolder-active-file)
506 (and gnus-verbose-backends
507 (message "nnfolder: Reading incoming mail...done"))))
508 (let ((bufs nnfolder-buffer-alist))
511 (if (not (buffer-name (nth 1 (car bufs))))
512 (setq nnfolder-buffer-alist
513 (delq (car bufs) nnfolder-buffer-alist))
514 (set-buffer (nth 1 (car bufs)))
515 (and (buffer-modified-p)
517 (setq bufs (cdr bufs)))))
518 ;; (if incoming (delete-file incoming))
523 ;;; nnfolder.el ends here