*** empty log message ***
[gnus] / lisp / nnheader.el
1 ;;; nnheader.el --- header access macros for Gnus and its backends
2 ;; Copyright (C) 1987,88,89,90,93,94,95,96 Free Software Foundation, Inc.
3
4 ;; Author: Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
5 ;;      Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
6 ;; Keywords: news
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 ;; These macros may look very much like the ones in GNUS 4.1.  They
28 ;; are, in a way, but you should note that the indices they use have
29 ;; been changed from the internal GNUS format to the NOV format.  The
30 ;; makes it possible to read headers from XOVER much faster.
31 ;;
32 ;; The format of a header is now:
33 ;; [number subject from date id references chars lines xref]
34 ;;
35 ;; (That last entry is defined as "misc" in the NOV format, but Gnus
36 ;; uses it for xrefs.)
37
38 ;;; Code:
39
40 (require 'mail-utils)
41 (require 'sendmail)
42 (require 'message)
43 (require 'rmail)
44 (eval-when-compile (require 'cl))
45
46 (defvar nnheader-max-head-length 4096
47   "*Max length of the head of articles.")
48
49 (defvar nnheader-file-name-translation-alist nil
50   "*Alist that says how to translate characters in file names.
51 For instance, if \":\" is illegal as a file character in file names
52 on your system, you could say something like:
53
54 \(setq nnheader-file-name-translation-alist '((?: . ?_)))")
55
56 ;;; Header access macros.
57
58 (defmacro mail-header-number (header)
59   "Return article number in HEADER."
60   `(aref ,header 0))
61
62 (defmacro mail-header-set-number (header number)
63   "Set article number of HEADER to NUMBER."
64   `(aset ,header 0 ,number))
65
66 (defmacro mail-header-subject (header)
67   "Return subject string in HEADER."
68   `(aref ,header 1))
69
70 (defmacro mail-header-set-subject (header subject)
71   "Set article subject of HEADER to SUBJECT."
72   `(aset ,header 1 ,subject))
73
74 (defmacro mail-header-from (header)
75   "Return author string in HEADER."
76   `(aref ,header 2))
77
78 (defmacro mail-header-set-from (header from)
79   "Set article author of HEADER to FROM."
80   `(aset ,header 2 ,from))
81
82 (defmacro mail-header-date (header)
83   "Return date in HEADER."
84   `(aref ,header 3))
85
86 (defmacro mail-header-set-date (header date)
87   "Set article date of HEADER to DATE."
88   `(aset ,header 3 ,date))
89
90 (defalias 'mail-header-message-id 'mail-header-id)
91 (defmacro mail-header-id (header)
92   "Return Id in HEADER."
93   `(aref ,header 4))
94
95 (defalias 'mail-header-set-message-id 'mail-header-set-id)
96 (defmacro mail-header-set-id (header id)
97   "Set article Id of HEADER to ID."
98   `(aset ,header 4 ,id))
99
100 (defmacro mail-header-references (header)
101   "Return references in HEADER."
102   `(aref ,header 5))
103
104 (defmacro mail-header-set-references (header ref)
105   "Set article references of HEADER to REF."
106   `(aset ,header 5 ,ref))
107
108 (defmacro mail-header-chars (header)
109   "Return number of chars of article in HEADER."
110   `(aref ,header 6))
111
112 (defmacro mail-header-set-chars (header chars)
113   "Set number of chars in article of HEADER to CHARS."
114   `(aset ,header 6 ,chars))
115
116 (defmacro mail-header-lines (header)
117   "Return lines in HEADER."
118   `(aref ,header 7))
119
120 (defmacro mail-header-set-lines (header lines)
121   "Set article lines of HEADER to LINES."
122   `(aset ,header 7 ,lines))
123
124 (defmacro mail-header-xref (header)
125   "Return xref string in HEADER."
126   `(aref ,header 8))
127
128 (defmacro mail-header-set-xref (header xref)
129   "Set article xref of HEADER to xref."
130   `(aset ,header 8 ,xref))
131
132 (defun make-mail-header (&optional init)
133   "Create a new mail header structure initialized with INIT."
134   (make-vector 9 init))
135
136 ;; Various cruft the backends and Gnus need to communicate.
137
138 (defvar nntp-server-buffer nil)
139 (defvar gnus-verbose-backends 7
140   "*A number that says how talkative the Gnus backends should be.")
141 (defvar gnus-nov-is-evil nil
142   "If non-nil, Gnus backends will never output headers in the NOV format.")
143 (defvar news-reply-yank-from nil)
144 (defvar news-reply-yank-message-id nil)
145
146 (defvar nnheader-callback-function nil)
147
148 (defun nnheader-init-server-buffer ()
149   "Initialize the Gnus-backend communication buffer."
150   (save-excursion
151     (setq nntp-server-buffer (get-buffer-create " *nntpd*"))
152     (set-buffer nntp-server-buffer)
153     (buffer-disable-undo (current-buffer))
154     (erase-buffer)
155     (kill-all-local-variables)
156     (setq case-fold-search t)           ;Should ignore case.
157     t))
158
159 ;;; Virtual server functions.
160
161 (defun nnheader-set-init-variables (server defs)
162   (let ((s server)
163         val)
164     ;; First we set the server variables in the sequence required.  We
165     ;; use the definitions from the `defs' list where that is
166     ;; possible. 
167     (while s
168       (set (caar s) 
169            (if (setq val (assq (caar s) defs))
170                (nth 1 val)
171              (nth 1 (car s))))
172       (setq s (cdr s)))
173     ;; The we go through the defs list and set any variables that were
174     ;; not set in the first sweep.
175     (while defs
176       (if (not (assq (caar defs) server))
177           (set (caar defs) 
178                (if (and (symbolp (nth 1 (car defs)))
179                         (not (boundp (nth 1 (car defs)))))
180                    (nth 1 (car defs))
181                  (eval (nth 1 (car defs))))))
182       (setq defs (cdr defs)))))
183
184 (defun nnheader-save-variables (server)
185   (let (out)
186     (while server
187       (push (list (caar server) (symbol-value (caar server))) out)
188       (setq server (cdr server)))
189     (nreverse out)))
190
191 (defun nnheader-restore-variables (state)
192   (while state
193     (set (caar state) (nth 1 (car state)))
194     (setq state (cdr state))))
195
196 (defun nnheader-change-server (backend server defs)
197   (nnheader-init-server-buffer)
198   (let ((current-server (intern (format "%s-current-server" backend)))
199         (alist (intern (format "%s-server-alist" backend)))
200         (variables (intern (format "%s-server-variables" backend))))
201
202     (when (and (symbol-value current-server)
203                (not (equal server (symbol-value current-server))))
204       (set alist
205            (cons (list (symbol-value current-server)
206                        (nnheader-save-variables (symbol-value variables)))
207                  (symbol-value alist))))
208     (let ((state (assoc server (symbol-value alist))))
209       (if (not state)
210           (nnheader-set-init-variables (symbol-value variables) defs)
211         (nnheader-restore-variables (nth 1 state))
212         (set alist (delq state (symbol-value alist)))))
213     (set current-server server)
214     t))
215
216 ;;; Various functions the backends use.
217
218 (defun nnheader-insert-head (file)
219   "Insert the head of the article."
220   (if (eq nnheader-max-head-length t)
221       ;; Just read the entire file.
222       (insert-file-contents-literally file)
223     ;; Read 1K blocks until we find a separator.
224     (let ((beg 0)
225           format-alist 
226           (chop 1024))
227       (while (and (eq chop (nth 1 (insert-file-contents
228                                    file nil beg (incf beg chop))))
229                   (prog1 (not (search-forward "\n\n" nil t)) 
230                     (goto-char (point-max)))
231                   (or (null nnheader-max-head-length)
232                       (< beg nnheader-max-head-length)))))))
233
234 (defun nnheader-article-p ()
235   "Say whether the current buffer looks like an article."
236   (goto-char (point-min))
237   (if (not (search-forward "\n\n" nil t))
238       nil
239     (narrow-to-region (point-min) (1- (point)))
240     (goto-char (point-min))
241     (while (looking-at "[A-Z][^ \t]+:.*\n\\([ \t].*\n\\)*\\|From .*\n")
242       (goto-char (match-end 0)))
243     (prog1
244         (eobp)
245       (widen))))    
246
247 (defun nnheader-insert-references (references message-id)
248   "Insert a References header based on REFERENCES and MESSAGE-ID."
249   (if (and (not references) (not message-id)) 
250       ()        ; This is illegal, but not all articles have Message-IDs.
251     (mail-position-on-field "References")
252     (let ((begin (save-excursion (beginning-of-line) (point)))
253           (fill-column 78)
254           (fill-prefix "\t"))
255       (if references (insert references))
256       (if (and references message-id) (insert " "))
257       (if message-id (insert message-id))
258       ;; Fold long References lines to conform to RFC1036 (sort of).
259       ;; The region must end with a newline to fill the region
260       ;; without inserting extra newline.
261       (fill-region-as-paragraph begin (1+ (point))))))
262
263 (defun nnheader-replace-header (header new-value)
264   "Remove HEADER and insert the NEW-VALUE."
265   (save-excursion
266     (save-restriction
267       (nnheader-narrow-to-headers)
268       (prog1
269           (message-remove-header header)
270         (goto-char (point-max))
271         (insert header ": " new-value "\n")))))
272
273 (defun nnheader-narrow-to-headers ()
274   "Narrow to the head of an article."
275   (widen)
276   (narrow-to-region
277    (goto-char (point-min))
278    (if (search-forward "\n\n" nil t)
279        (1- (point))
280      (point-max)))
281   (goto-char (point-min)))
282
283 (defun nnheader-set-temp-buffer (name)
284   "Set-buffer to an empty (possibly new) buffer called NAME with undo disabled."
285   (set-buffer (get-buffer-create name))
286   (buffer-disable-undo (current-buffer))
287   (erase-buffer)
288   (current-buffer))
289
290 (defmacro nnheader-temp-write (file &rest forms)
291   "Create a new buffer, evaluate FORM there, and write the buffer to FILE."
292   `(save-excursion
293      (let ((nnheader-temp-file ,file)
294            (nnheader-temp-cur-buffer
295             (nnheader-set-temp-buffer
296              (generate-new-buffer-name " *nnheader temp*"))))
297        (when (and nnheader-temp-file 
298                   (not (file-directory-p (file-name-directory 
299                                           nnheader-temp-file))))
300          (make-directory (file-name-directory nnheader-temp-file) t))
301        (unwind-protect
302            (prog1
303                (progn
304                  ,@forms)
305              (when nnheader-temp-file
306                (set-buffer nnheader-temp-cur-buffer)
307                (write-region (point-min) (point-max) 
308                              nnheader-temp-file nil 'nomesg)))
309          (when (buffer-name nnheader-temp-cur-buffer)
310            (kill-buffer nnheader-temp-cur-buffer))))))
311
312 (put 'nnheader-temp-write 'lisp-indent-function 1)
313 (put 'nnheader-temp-write 'lisp-indent-hook 1)
314 (put 'nnheader-temp-write 'edebug-form-spec '(file &rest form))
315
316 (defvar jka-compr-compression-info-list)
317 (defvar nnheader-numerical-files
318   (if (boundp 'jka-compr-compression-info-list)
319       (concat "\\([0-9]+\\)\\(" 
320               (mapconcat (lambda (i) (aref i 0))
321                          jka-compr-compression-info-list "\\|")
322               "\\)?")
323     "[0-9]+$")
324   "Regexp that match numerical files.")
325
326 (defvar nnheader-numerical-short-files (concat "^" nnheader-numerical-files)
327   "Regexp that matches numerical file names.")
328
329 (defvar nnheader-numerical-full-files (concat "/" nnheader-numerical-files)
330   "Regexp that matches numerical full file paths.")
331
332 (defun nnheader-file-to-number (file)
333   "Take a file name and return the article number."
334   (if (not (boundp 'jka-compr-compression-info-list))
335       (string-to-int file)
336     (string-match nnheader-numerical-short-files file)
337     (string-to-int (match-string 0 file))))
338
339 (defun nnheader-directory-articles (dir)
340   "Return a list of all article files in a directory."
341   (mapcar 'nnheader-file-to-number
342           (directory-files dir nil nnheader-numerical-short-files t)))
343
344 (defun nnheader-article-to-file-alist (dir)
345   "Return an alist of article/file pairs in DIR."
346   (mapcar (lambda (file) (cons (nnheader-file-to-number file) file))
347           (directory-files dir nil nnheader-numerical-short-files t)))
348
349 (defun nnheader-fold-continuation-lines ()
350   "Fold continuation lines in the current buffer."
351   (goto-char (point-min))
352   (while (re-search-forward "\\(\r?\n[ \t]+\\)+" nil t)
353     (replace-match " " t t)))
354
355 (defun nnheader-translate-file-chars (file)
356   (if (null nnheader-file-name-translation-alist)
357       ;; No translation is necessary.
358       file 
359     ;; We translate -- but only the file name.  We leave the directory
360     ;; alone.
361     (let* ((new (file-name-nondirectory file))
362            (len (length new))
363            (i 0)
364           trans)
365       (while (< i len)
366         (when (setq trans (cdr (assq (aref new i)
367                                      nnheader-file-name-translation-alist)))
368           (aset new i trans))
369         (incf i))
370       (concat (file-name-directory file) new))))
371
372 (defun nnheader-report (backend &rest args)
373   "Report an error from the BACKEND.
374 The first string in ARGS can be a format string."
375   (set (intern (format "%s-status-string" backend))
376        (if (< (length args) 2)
377            (car args)
378          (apply 'format args)))
379   nil)
380
381 (defun nnheader-get-report (backend)
382   (message "%s" (symbol-value (intern (format "%s-status-string" backend)))))
383
384 (defun nnheader-insert (format &rest args)
385   "Clear the communicaton buffer and insert FORMAT and ARGS into the buffer.
386 If FORMAT isn't a format string, it and all ARGS will be inserted
387 without formatting."
388   (save-excursion
389     (set-buffer nntp-server-buffer)
390     (erase-buffer)
391     (if (string-match "%" format)
392         (insert (apply 'format format args))
393       (apply 'insert format args))
394     t))
395
396 (defun nnheader-mail-file-mbox-p (file)
397   "Say whether FILE looks like an Unix mbox file."
398   (when (and (file-exists-p file)
399              (file-readable-p file)
400              (file-regular-p file))
401     (save-excursion
402       (nnheader-set-temp-buffer " *mail-file-mbox-p*")
403       (insert-file-contents-literally file)
404       (goto-char (point-min))
405       (prog1
406           (looking-at rmail-unix-mail-delimiter)
407         (kill-buffer (current-buffer))))))
408
409 (defun nnheader-replace-chars-in-string (string from to)
410   "Replace characters in STRING from FROM to TO."
411   (let ((string (substring string 0))   ;Copy string.
412         (len (length string))
413         (idx 0))
414     ;; Replace all occurrences of FROM with TO.
415     (while (< idx len)
416       (if (= (aref string idx) from)
417           (aset string idx to))
418       (setq idx (1+ idx)))
419     string))
420
421 (defun nnheader-file-to-group (file &optional top)
422   "Return a group name based on FILE and TOP."
423   (nnheader-replace-chars-in-string 
424    (if (not top)
425        file
426      (condition-case ()
427          (substring (expand-file-name file)
428                     (length 
429                      (expand-file-name
430                       (file-name-as-directory top))))
431        (error "")))
432    ?/ ?.))
433
434 (defun nnheader-message (level &rest args)
435   "Message if the Gnus backends are talkative."
436   (if (or (not (numberp gnus-verbose-backends))
437           (<= level gnus-verbose-backends))
438       (apply 'message args)
439     (apply 'format args)))
440
441 (defun nnheader-be-verbose (level)
442   "Return whether the backends should be verbose on LEVEL."
443   (or (not (numberp gnus-verbose-backends))
444       (<= level gnus-verbose-backends)))
445
446 (defun nnheader-group-pathname (group dir &optional file)
447   "Make pathname for GROUP."
448   (concat
449    (let ((dir (file-name-as-directory (expand-file-name dir))))
450      ;; If this directory exists, we use it directly.
451      (if (file-directory-p (concat dir group))
452          (concat dir group "/")
453        ;; If not, we translate dots into slashes.
454        (concat dir (nnheader-replace-chars-in-string group ?. ?/) "/")))
455    (cond ((null file) "")
456          ((numberp file) (int-to-string file))
457          (t file))))
458
459 (defun nnheader-functionp (form)
460   "Return non-nil if FORM is funcallable."
461   (or (and (symbolp form) (fboundp form))
462       (and (listp form) (eq (car form) 'lambda))))
463
464 (fset 'nnheader-find-file-noselect 'find-file-noselect)
465   
466 (provide 'nnheader)
467
468 ;;; nnheader.el ends here