mirror of
git://git.sv.gnu.org/emacs.git
synced 2026-02-16 17:24:23 +00:00
Initial revision
This commit is contained in:
parent
efac8cf189
commit
eec82323c2
64 changed files with 55945 additions and 0 deletions
245
lisp/gnus/earcon.el
Normal file
245
lisp/gnus/earcon.el
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
;;; earcon.el --- Sound effects for messages
|
||||
;; Copyright (C) 1996 Free Software Foundation
|
||||
|
||||
;; Author: Steven L. Baur <steve@miranova.com>
|
||||
;; Keywords: news fun sound
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
;; This file provides access to sound effects in Gnus.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(if (null (boundp 'running-xemacs))
|
||||
(defvar running-xemacs (string-match "XEmacs\\|Lucid" emacs-version)))
|
||||
|
||||
(require 'gnus)
|
||||
(require 'gnus-audio)
|
||||
(require 'gnus-art)
|
||||
(eval-when-compile (require 'cl))
|
||||
|
||||
(defgroup earcon nil
|
||||
"Turn ** sounds ** into noise."
|
||||
:group 'gnus-visual)
|
||||
|
||||
(defcustom earcon-auto-play nil
|
||||
"When True, automatically play sounds as well as buttonize them."
|
||||
:type 'boolean
|
||||
:group 'earcon)
|
||||
|
||||
(defcustom earcon-prefix "**"
|
||||
"String denoting the start of an earcon."
|
||||
:type 'string
|
||||
:group 'earcon)
|
||||
|
||||
(defcustom earcon-suffix "**"
|
||||
"String denoting the end of an earcon."
|
||||
:type 'string
|
||||
:group 'earcon)
|
||||
|
||||
(defcustom earcon-regexp-alist
|
||||
'(("boring" 1 "Boring.au")
|
||||
("evil[ \t]+laugh" 1 "Evil_Laugh.au")
|
||||
("gag\\|puke" 1 "Puke.au")
|
||||
("snicker" 1 "Snicker.au")
|
||||
("meow" 1 "catmeow.au")
|
||||
("sob\\|boohoo" 1 "cry.wav")
|
||||
("drum[ \t]*roll" 1 "drumroll.au")
|
||||
("blast" 1 "explosion.au")
|
||||
("flush\\|plonk!*" 1 "flush.au")
|
||||
("kiss" 1 "kiss.wav")
|
||||
("tee[ \t]*hee" 1 "laugh.au")
|
||||
("shoot" 1 "shotgun.wav")
|
||||
("yawn" 1 "snore.wav")
|
||||
("cackle" 1 "witch.au")
|
||||
("yell\\|roar" 1 "yell2.au")
|
||||
("whoop-de-doo" 1 "whistle.au"))
|
||||
"A list of regexps to map earcons to real sounds."
|
||||
:type '(repeat (list regexp
|
||||
(integer :tag "Match")
|
||||
(string :tag "Sound")))
|
||||
:group 'earcon)
|
||||
|
||||
(defvar earcon-button-marker-list nil)
|
||||
(make-variable-buffer-local 'earcon-button-marker-list)
|
||||
|
||||
|
||||
|
||||
;;; FIXME!! clone of code from gnus-vis.el FIXME!!
|
||||
(defun earcon-article-push-button (event)
|
||||
"Check text under the mouse pointer for a callback function.
|
||||
If the text under the mouse pointer has a `earcon-callback' property,
|
||||
call it with the value of the `earcon-data' text property."
|
||||
(interactive "e")
|
||||
(set-buffer (window-buffer (posn-window (event-start event))))
|
||||
(let* ((pos (posn-point (event-start event)))
|
||||
(data (get-text-property pos 'earcon-data))
|
||||
(fun (get-text-property pos 'earcon-callback)))
|
||||
(if fun (funcall fun data))))
|
||||
|
||||
(defun earcon-article-press-button ()
|
||||
"Check text at point for a callback function.
|
||||
If the text at point has a `earcon-callback' property,
|
||||
call it with the value of the `earcon-data' text property."
|
||||
(interactive)
|
||||
(let* ((data (get-text-property (point) 'earcon-data))
|
||||
(fun (get-text-property (point) 'earcon-callback)))
|
||||
(if fun (funcall fun data))))
|
||||
|
||||
(defun earcon-article-prev-button (n)
|
||||
"Move point to N buttons backward.
|
||||
If N is negative, move forward instead."
|
||||
(interactive "p")
|
||||
(earcon-article-next-button (- n)))
|
||||
|
||||
(defun earcon-article-next-button (n)
|
||||
"Move point to N buttons forward.
|
||||
If N is negative, move backward instead."
|
||||
(interactive "p")
|
||||
(let ((function (if (< n 0) 'previous-single-property-change
|
||||
'next-single-property-change))
|
||||
(inhibit-point-motion-hooks t)
|
||||
(backward (< n 0))
|
||||
(limit (if (< n 0) (point-min) (point-max))))
|
||||
(setq n (abs n))
|
||||
(while (and (not (= limit (point)))
|
||||
(> n 0))
|
||||
;; Skip past the current button.
|
||||
(when (get-text-property (point) 'earcon-callback)
|
||||
(goto-char (funcall function (point) 'earcon-callback nil limit)))
|
||||
;; Go to the next (or previous) button.
|
||||
(gnus-goto-char (funcall function (point) 'earcon-callback nil limit))
|
||||
;; Put point at the start of the button.
|
||||
(when (and backward (not (get-text-property (point) 'earcon-callback)))
|
||||
(goto-char (funcall function (point) 'earcon-callback nil limit)))
|
||||
;; Skip past intangible buttons.
|
||||
(when (get-text-property (point) 'intangible)
|
||||
(incf n))
|
||||
(decf n))
|
||||
(unless (zerop n)
|
||||
(gnus-message 5 "No more buttons"))
|
||||
n))
|
||||
|
||||
(defun earcon-article-add-button (from to fun &optional data)
|
||||
"Create a button between FROM and TO with callback FUN and data DATA."
|
||||
(and (boundp gnus-article-button-face)
|
||||
gnus-article-button-face
|
||||
(gnus-overlay-put (gnus-make-overlay from to)
|
||||
'face gnus-article-button-face))
|
||||
(gnus-add-text-properties
|
||||
from to
|
||||
(nconc (and gnus-article-mouse-face
|
||||
(list gnus-mouse-face-prop gnus-article-mouse-face))
|
||||
(list 'gnus-callback fun)
|
||||
(and data (list 'gnus-data data)))))
|
||||
|
||||
(defun earcon-button-entry ()
|
||||
;; Return the first entry in `gnus-button-alist' matching this place.
|
||||
(let ((alist earcon-regexp-alist)
|
||||
(case-fold-search t)
|
||||
(entry nil))
|
||||
(while alist
|
||||
(setq entry (pop alist))
|
||||
(if (looking-at (car entry))
|
||||
(setq alist nil)
|
||||
(setq entry nil)))
|
||||
entry))
|
||||
|
||||
|
||||
(defun earcon-button-push (marker)
|
||||
;; Push button starting at MARKER.
|
||||
(save-excursion
|
||||
(set-buffer gnus-article-buffer)
|
||||
(goto-char marker)
|
||||
(let* ((entry (earcon-button-entry))
|
||||
(inhibit-point-motion-hooks t)
|
||||
(fun 'gnus-audio-play)
|
||||
(args (list (nth 2 entry))))
|
||||
(cond
|
||||
((fboundp fun)
|
||||
(apply fun args))
|
||||
((and (boundp fun)
|
||||
(fboundp (symbol-value fun)))
|
||||
(apply (symbol-value fun) args))
|
||||
(t
|
||||
(gnus-message 1 "You must define `%S' to use this button"
|
||||
(cons fun args)))))))
|
||||
|
||||
;;; FIXME!! clone of code from gnus-vis.el FIXME!!
|
||||
|
||||
;;;###interactive
|
||||
(defun earcon-region (beg end)
|
||||
"Play Sounds in the region between point and mark."
|
||||
(interactive "r")
|
||||
(earcon-buffer (current-buffer) beg end))
|
||||
|
||||
;;;###interactive
|
||||
(defun earcon-buffer (&optional buffer st nd)
|
||||
(interactive)
|
||||
(save-excursion
|
||||
;; clear old markers.
|
||||
(if (boundp 'earcon-button-marker-list)
|
||||
(while earcon-button-marker-list
|
||||
(set-marker (pop earcon-button-marker-list) nil))
|
||||
(setq earcon-button-marker-list nil))
|
||||
(and buffer (set-buffer buffer))
|
||||
(let ((buffer-read-only nil)
|
||||
(inhibit-point-motion-hooks t)
|
||||
(case-fold-search t)
|
||||
(alist earcon-regexp-alist)
|
||||
beg entry regexp)
|
||||
(goto-char (point-min))
|
||||
(setq beg (point))
|
||||
(while (setq entry (pop alist))
|
||||
(setq regexp (concat (regexp-quote earcon-prefix)
|
||||
".*\\("
|
||||
(car entry)
|
||||
"\\).*"
|
||||
(regexp-quote earcon-suffix)))
|
||||
(goto-char beg)
|
||||
(while (re-search-forward regexp nil t)
|
||||
(let* ((start (and entry (match-beginning 1)))
|
||||
(end (and entry (match-end 1)))
|
||||
(from (match-beginning 1)))
|
||||
(earcon-article-add-button
|
||||
start end 'earcon-button-push
|
||||
(car (push (set-marker (make-marker) from)
|
||||
earcon-button-marker-list)))
|
||||
(gnus-audio-play (caddr entry))))))))
|
||||
|
||||
;;;###autoload
|
||||
(defun gnus-earcon-display ()
|
||||
"Play sounds in message buffers."
|
||||
(interactive)
|
||||
(save-excursion
|
||||
(set-buffer gnus-article-buffer)
|
||||
(goto-char (point-min))
|
||||
;; Skip headers
|
||||
(unless (search-forward "\n\n" nil t)
|
||||
(goto-char (point-max)))
|
||||
(sit-for 0)
|
||||
(earcon-buffer (current-buffer) (point))))
|
||||
|
||||
;;;***
|
||||
|
||||
(provide 'earcon)
|
||||
|
||||
(run-hooks 'earcon-load-hook)
|
||||
|
||||
;;; earcon.el ends here
|
||||
3082
lisp/gnus/gnus-art.el
Normal file
3082
lisp/gnus/gnus-art.el
Normal file
File diff suppressed because it is too large
Load diff
315
lisp/gnus/gnus-async.el
Normal file
315
lisp/gnus/gnus-async.el
Normal file
|
|
@ -0,0 +1,315 @@
|
|||
;;; gnus-async.el --- asynchronous support for Gnus
|
||||
;; Copyright (C) 1996,97 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
|
||||
;; Keywords: news
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'gnus)
|
||||
(require 'gnus-sum)
|
||||
(require 'nntp)
|
||||
|
||||
(defgroup gnus-asynchronous nil
|
||||
"Support for asynchronous operations."
|
||||
:group 'gnus)
|
||||
|
||||
(defcustom gnus-asynchronous t
|
||||
"*If nil, inhibit all Gnus asynchronicity.
|
||||
If non-nil, let the other asynch variables be heeded."
|
||||
:group 'gnus-asynchronous
|
||||
:type 'boolean)
|
||||
|
||||
(defcustom gnus-use-article-prefetch 30
|
||||
"*If non-nil, prefetch articles in groups that allow this.
|
||||
If a number, prefetch only that many articles forward;
|
||||
if t, prefetch as many articles as possible."
|
||||
:group 'gnus-asynchronous
|
||||
:type '(choice (const :tag "off" nil)
|
||||
(const :tag "all" t)
|
||||
(integer :tag "some" 0)))
|
||||
|
||||
(defcustom gnus-prefetched-article-deletion-strategy '(read exit)
|
||||
"List of symbols that say when to remove articles from the prefetch buffer.
|
||||
Possible values in this list are `read', which means that
|
||||
articles are removed as they are read, and `exit', which means
|
||||
that all articles belonging to a group are removed on exit
|
||||
from that group."
|
||||
:group 'gnus-asynchronous
|
||||
:type '(set (const read) (const exit)))
|
||||
|
||||
(defcustom gnus-use-header-prefetch nil
|
||||
"*If non-nil, prefetch the headers to the next group."
|
||||
:group 'gnus-asynchronous
|
||||
:type 'boolean)
|
||||
|
||||
(defcustom gnus-async-prefetch-article-p 'gnus-async-unread-p
|
||||
"Function called to say whether an article should be prefetched or not.
|
||||
The function is called with one parameter -- the article data.
|
||||
It should return non-nil if the article is to be prefetched."
|
||||
:group 'gnus-asynchronous
|
||||
:type 'function)
|
||||
|
||||
;;; Internal variables.
|
||||
|
||||
(defvar gnus-async-prefetch-article-buffer " *Async Prefetch Article*")
|
||||
(defvar gnus-async-article-alist nil)
|
||||
(defvar gnus-async-article-semaphore '(nil))
|
||||
(defvar gnus-async-fetch-list nil)
|
||||
|
||||
(defvar gnus-async-prefetch-headers-buffer " *Async Prefetch Headers*")
|
||||
(defvar gnus-async-header-prefetched nil)
|
||||
|
||||
;;; Utility functions.
|
||||
|
||||
(defun gnus-group-asynchronous-p (group)
|
||||
"Say whether GROUP is fetched from a server that supports asynchronicity."
|
||||
(gnus-asynchronous-p (gnus-find-method-for-group group)))
|
||||
|
||||
;;; Somewhat bogus semaphores.
|
||||
|
||||
(defun gnus-async-get-semaphore (semaphore)
|
||||
"Wait until SEMAPHORE is released."
|
||||
(while (/= (length (nconc (symbol-value semaphore) (list nil))) 2)
|
||||
(sleep-for 1)))
|
||||
|
||||
(defun gnus-async-release-semaphore (semaphore)
|
||||
"Release SEMAPHORE."
|
||||
(setcdr (symbol-value semaphore) nil))
|
||||
|
||||
(defmacro gnus-async-with-semaphore (&rest forms)
|
||||
`(unwind-protect
|
||||
(progn
|
||||
(gnus-async-get-semaphore 'gnus-async-article-semaphore)
|
||||
,@forms)
|
||||
(gnus-async-release-semaphore 'gnus-async-article-semaphore)))
|
||||
|
||||
(put 'gnus-asynch-with-semaphore 'lisp-indent-function 0)
|
||||
(put 'gnus-asynch-with-semaphore 'edebug-form-spec '(body))
|
||||
|
||||
;;;
|
||||
;;; Article prefetch
|
||||
;;;
|
||||
|
||||
(gnus-add-shutdown 'gnus-async-close 'gnus)
|
||||
(defun gnus-async-close ()
|
||||
(gnus-kill-buffer gnus-async-prefetch-article-buffer)
|
||||
(gnus-kill-buffer gnus-async-prefetch-headers-buffer)
|
||||
(setq gnus-async-article-alist nil
|
||||
gnus-async-header-prefetched nil))
|
||||
|
||||
(defun gnus-async-set-buffer ()
|
||||
(nnheader-set-temp-buffer gnus-async-prefetch-article-buffer t))
|
||||
|
||||
(defun gnus-async-halt-prefetch ()
|
||||
"Stop prefetching."
|
||||
(setq gnus-async-fetch-list nil))
|
||||
|
||||
(defun gnus-async-prefetch-next (group article summary)
|
||||
"Possibly prefetch several articles starting with the article after ARTICLE."
|
||||
(when (and (gnus-buffer-live-p summary)
|
||||
gnus-asynchronous
|
||||
(gnus-group-asynchronous-p group))
|
||||
(save-excursion
|
||||
(set-buffer gnus-summary-buffer)
|
||||
(let ((next (caadr (gnus-data-find-list article))))
|
||||
(when next
|
||||
(if (not (fboundp 'run-with-idle-timer))
|
||||
;; This is either an older Emacs or XEmacs, so we
|
||||
;; do this, which leads to slightly slower article
|
||||
;; buffer display.
|
||||
(gnus-async-prefetch-article group next summary)
|
||||
(run-with-idle-timer
|
||||
0.1 nil 'gnus-async-prefetch-article group next summary)))))))
|
||||
|
||||
(defun gnus-async-prefetch-article (group article summary &optional next)
|
||||
"Possibly prefetch several articles starting with ARTICLE."
|
||||
(if (not (gnus-buffer-live-p summary))
|
||||
(gnus-async-with-semaphore
|
||||
(setq gnus-async-fetch-list nil))
|
||||
(when (and gnus-asynchronous
|
||||
(gnus-alive-p))
|
||||
(when next
|
||||
(gnus-async-with-semaphore
|
||||
(pop gnus-async-fetch-list)))
|
||||
(let ((do-fetch next)
|
||||
(do-message t)) ;(eq major-mode 'gnus-summary-mode)))
|
||||
(when (and (gnus-group-asynchronous-p group)
|
||||
(gnus-buffer-live-p summary)
|
||||
(or (not next)
|
||||
gnus-async-fetch-list))
|
||||
(gnus-async-with-semaphore
|
||||
(unless next
|
||||
(setq do-fetch (not gnus-async-fetch-list))
|
||||
;; Nix out any outstanding requests.
|
||||
(setq gnus-async-fetch-list nil)
|
||||
;; Fill in the new list.
|
||||
(let ((n gnus-use-article-prefetch)
|
||||
(data (gnus-data-find-list article))
|
||||
d)
|
||||
(while (and (setq d (pop data))
|
||||
(if (numberp n)
|
||||
(natnump (decf n))
|
||||
n))
|
||||
(unless (or (gnus-async-prefetched-article-entry
|
||||
group (setq article (gnus-data-number d)))
|
||||
(not (natnump article))
|
||||
(not (funcall gnus-async-prefetch-article-p d)))
|
||||
;; Not already fetched -- so we add it to the list.
|
||||
(push article gnus-async-fetch-list)))
|
||||
(setq gnus-async-fetch-list
|
||||
(nreverse gnus-async-fetch-list))))
|
||||
|
||||
(when do-fetch
|
||||
(setq article (car gnus-async-fetch-list))))
|
||||
|
||||
(when (and do-fetch article)
|
||||
;; We want to fetch some more articles.
|
||||
(save-excursion
|
||||
(set-buffer summary)
|
||||
(let (mark)
|
||||
(gnus-async-set-buffer)
|
||||
(goto-char (point-max))
|
||||
(setq mark (point-marker))
|
||||
(let ((nnheader-callback-function
|
||||
(gnus-make-async-article-function
|
||||
group article mark summary next))
|
||||
(nntp-server-buffer
|
||||
(get-buffer gnus-async-prefetch-article-buffer)))
|
||||
(when do-message
|
||||
(gnus-message 9 "Prefetching article %d in group %s"
|
||||
article group))
|
||||
(gnus-request-article article group))))))))))
|
||||
|
||||
(defun gnus-make-async-article-function (group article mark summary next)
|
||||
"Return a callback function."
|
||||
`(lambda (arg)
|
||||
(save-excursion
|
||||
(when arg
|
||||
(gnus-async-set-buffer)
|
||||
(gnus-async-with-semaphore
|
||||
(push (list ',(intern (format "%s-%d" group article))
|
||||
,mark (set-marker (make-marker) (point-max))
|
||||
,group ,article)
|
||||
gnus-async-article-alist)))
|
||||
(if (not (gnus-buffer-live-p ,summary))
|
||||
(gnus-async-with-semaphore
|
||||
(setq gnus-async-fetch-list nil))
|
||||
(gnus-async-prefetch-article ,group ,next ,summary t)))))
|
||||
|
||||
(defun gnus-async-unread-p (data)
|
||||
"Return non-nil if DATA represents an unread article."
|
||||
(gnus-data-unread-p data))
|
||||
|
||||
(defun gnus-async-request-fetched-article (group article buffer)
|
||||
"See whether we have ARTICLE from GROUP and put it in BUFFER."
|
||||
(when (numberp article)
|
||||
(let ((entry (gnus-async-prefetched-article-entry group article)))
|
||||
(when entry
|
||||
(save-excursion
|
||||
(gnus-async-set-buffer)
|
||||
(copy-to-buffer buffer (cadr entry) (caddr entry))
|
||||
;; Remove the read article from the prefetch buffer.
|
||||
(when (memq 'read gnus-prefetched-article-deletion-strategy)
|
||||
(gnus-async-delete-prefected-entry entry))
|
||||
t)))))
|
||||
|
||||
(defun gnus-async-delete-prefected-entry (entry)
|
||||
"Delete ENTRY from buffer and alist."
|
||||
(ignore-errors
|
||||
(delete-region (cadr entry) (caddr entry))
|
||||
(set-marker (cadr entry) nil)
|
||||
(set-marker (caddr entry) nil))
|
||||
(gnus-async-with-semaphore
|
||||
(setq gnus-async-article-alist
|
||||
(delq entry gnus-async-article-alist))))
|
||||
|
||||
(defun gnus-async-prefetch-remove-group (group)
|
||||
"Remove all articles belonging to GROUP from the prefetch buffer."
|
||||
(when (and (gnus-group-asynchronous-p group)
|
||||
(memq 'exit gnus-prefetched-article-deletion-strategy))
|
||||
(let ((alist gnus-async-article-alist))
|
||||
(save-excursion
|
||||
(gnus-async-set-buffer)
|
||||
(while alist
|
||||
(when (equal group (nth 3 (car alist)))
|
||||
(gnus-async-delete-prefected-entry (car alist)))
|
||||
(pop alist))))))
|
||||
|
||||
(defun gnus-async-prefetched-article-entry (group article)
|
||||
"Return the entry for ARTICLE in GROUP iff it has been prefetched."
|
||||
(let ((entry (assq (intern (format "%s-%d" group article))
|
||||
gnus-async-article-alist)))
|
||||
;; Perhaps something has emptied the buffer?
|
||||
(if (and entry
|
||||
(= (cadr entry) (caddr entry)))
|
||||
(progn
|
||||
(ignore-errors
|
||||
(set-marker (cadr entry) nil)
|
||||
(set-marker (caddr entry) nil))
|
||||
(setq gnus-async-article-alist
|
||||
(delq entry gnus-async-article-alist))
|
||||
nil)
|
||||
entry)))
|
||||
|
||||
;;;
|
||||
;;; Header prefetch
|
||||
;;;
|
||||
|
||||
(defun gnus-async-prefetch-headers (group)
|
||||
"Prefetch the headers for group GROUP."
|
||||
(save-excursion
|
||||
(let (unread)
|
||||
(when (and gnus-use-header-prefetch
|
||||
gnus-asynchronous
|
||||
(gnus-group-asynchronous-p group)
|
||||
(listp gnus-async-header-prefetched)
|
||||
(setq unread (gnus-list-of-unread-articles group)))
|
||||
;; Mark that a fetch is in progress.
|
||||
(setq gnus-async-header-prefetched t)
|
||||
(nnheader-set-temp-buffer gnus-async-prefetch-headers-buffer t)
|
||||
(erase-buffer)
|
||||
(let ((nntp-server-buffer (current-buffer))
|
||||
(nnheader-callback-function
|
||||
`(lambda (arg)
|
||||
(setq gnus-async-header-prefetched
|
||||
,(cons group unread)))))
|
||||
(gnus-retrieve-headers unread group gnus-fetch-old-headers))))))
|
||||
|
||||
(defun gnus-async-retrieve-fetched-headers (articles group)
|
||||
"See whether we have prefetched headers."
|
||||
(when (and gnus-use-header-prefetch
|
||||
(gnus-group-asynchronous-p group)
|
||||
(listp gnus-async-header-prefetched)
|
||||
(equal group (car gnus-async-header-prefetched))
|
||||
(equal articles (cdr gnus-async-header-prefetched)))
|
||||
(save-excursion
|
||||
(nnheader-set-temp-buffer gnus-async-prefetch-headers-buffer t)
|
||||
(nntp-decode-text)
|
||||
(copy-to-buffer nntp-server-buffer (point-min) (point-max))
|
||||
(erase-buffer)
|
||||
(setq gnus-async-header-prefetched nil)
|
||||
t)))
|
||||
|
||||
(provide 'gnus-async)
|
||||
|
||||
;;; gnus-async.el ends here
|
||||
132
lisp/gnus/gnus-audio.el
Normal file
132
lisp/gnus/gnus-audio.el
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
;;; gnus-audio.el --- Sound effects for Gnus
|
||||
;; Copyright (C) 1996 Free Software Foundation
|
||||
|
||||
;; Author: Steven L. Baur <steve@miranova.com>
|
||||
;; Keywords: news
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
;; This file provides access to sound effects in Gnus.
|
||||
;; Prerelease: This file is partially stripped to support earcons.el
|
||||
;; You can safely ignore most of it until Red Gnus. **Evil Laugh**
|
||||
;;; Code:
|
||||
|
||||
(when (null (boundp 'running-xemacs))
|
||||
(defvar running-xemacs (string-match "XEmacs\\|Lucid" emacs-version)))
|
||||
|
||||
(require 'nnheader)
|
||||
(eval-when-compile (require 'cl))
|
||||
|
||||
(defvar gnus-audio-inline-sound
|
||||
(and (fboundp 'device-sound-enabled-p)
|
||||
(device-sound-enabled-p))
|
||||
"When t, we will not spawn a subprocess to play sounds.")
|
||||
|
||||
(defvar gnus-audio-directory (nnheader-find-etc-directory "sounds")
|
||||
"The directory containing the Sound Files.")
|
||||
|
||||
(defvar gnus-audio-au-player "/usr/bin/showaudio"
|
||||
"Executable program for playing sun AU format sound files")
|
||||
(defvar gnus-audio-wav-player "/usr/local/bin/play"
|
||||
"Executable program for playing WAV files")
|
||||
|
||||
|
||||
;;; The following isn't implemented yet. Wait for Red Gnus.
|
||||
;(defvar gnus-audio-effects-enabled t
|
||||
; "When t, Gnus will use sound effects.")
|
||||
;(defvar gnus-audio-enable-hooks nil
|
||||
; "Functions run when enabling sound effects.")
|
||||
;(defvar gnus-audio-disable-hooks nil
|
||||
; "Functions run when disabling sound effects.")
|
||||
;(defvar gnus-audio-theme-song nil
|
||||
; "Theme song for Gnus.")
|
||||
;(defvar gnus-audio-enter-group nil
|
||||
; "Sound effect played when selecting a group.")
|
||||
;(defvar gnus-audio-exit-group nil
|
||||
; "Sound effect played when exiting a group.")
|
||||
;(defvar gnus-audio-score-group nil
|
||||
; "Sound effect played when scoring a group.")
|
||||
;(defvar gnus-audio-busy-sound nil
|
||||
; "Sound effect played when going into a ... sequence.")
|
||||
|
||||
|
||||
;;;###autoload
|
||||
;(defun gnus-audio-enable-sound ()
|
||||
; "Enable Sound Effects for Gnus."
|
||||
; (interactive)
|
||||
; (setq gnus-audio-effects-enabled t)
|
||||
; (run-hooks gnus-audio-enable-hooks))
|
||||
|
||||
;;;###autoload
|
||||
;(defun gnus-audio-disable-sound ()
|
||||
; "Disable Sound Effects for Gnus."
|
||||
; (interactive)
|
||||
; (setq gnus-audio-effects-enabled nil)
|
||||
; (run-hooks gnus-audio-disable-hooks))
|
||||
|
||||
;;;###autoload
|
||||
(defun gnus-audio-play (file)
|
||||
"Play a sound through the speaker."
|
||||
(interactive)
|
||||
(let ((sound-file (if (file-exists-p file)
|
||||
file
|
||||
(concat gnus-audio-directory file))))
|
||||
(when (file-exists-p sound-file)
|
||||
(if gnus-audio-inline-sound
|
||||
(play-sound-file sound-file)
|
||||
(cond ((string-match "\\.wav$" sound-file)
|
||||
(call-process gnus-audio-wav-player
|
||||
sound-file
|
||||
0
|
||||
nil
|
||||
sound-file))
|
||||
((string-match "\\.au$" sound-file)
|
||||
(call-process gnus-audio-au-player
|
||||
sound-file
|
||||
0
|
||||
nil
|
||||
sound-file)))))))
|
||||
|
||||
|
||||
;;; The following isn't implemented yet, wait for Red Gnus
|
||||
;(defun gnus-audio-startrek-sounds ()
|
||||
; "Enable sounds from Star Trek the original series."
|
||||
; (interactive)
|
||||
; (setq gnus-audio-busy-sound "working.au")
|
||||
; (setq gnus-audio-enter-group "bulkhead_door.au")
|
||||
; (setq gnus-audio-exit-group "bulkhead_door.au")
|
||||
; (setq gnus-audio-score-group "ST_laser.au")
|
||||
; (setq gnus-audio-theme-song "startrek.au")
|
||||
; (add-hook 'gnus-select-group-hook 'gnus-audio-startrek-select-group)
|
||||
; (add-hook 'gnus-exit-group-hook 'gnus-audio-startrek-exit-group))
|
||||
;;;***
|
||||
|
||||
(defvar gnus-startup-jingle "Tuxedomoon.Jingle4.au"
|
||||
"Name of the Gnus startup jingle file.")
|
||||
|
||||
(defun gnus-play-jingle ()
|
||||
"Play the Gnus startup jingle, unless that's inhibited."
|
||||
(interactive)
|
||||
(gnus-audio-play gnus-startup-jingle))
|
||||
|
||||
(provide 'gnus-audio)
|
||||
|
||||
(run-hooks 'gnus-audio-load-hook)
|
||||
|
||||
;;; gnus-audio.el ends here
|
||||
152
lisp/gnus/gnus-bcklg.el
Normal file
152
lisp/gnus/gnus-bcklg.el
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
;;; gnus-bcklg.el --- backlog functions for Gnus
|
||||
;; Copyright (C) 1996,97 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
|
||||
;; Keywords: news
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'gnus)
|
||||
|
||||
;;;
|
||||
;;; Buffering of read articles.
|
||||
;;;
|
||||
|
||||
(defvar gnus-backlog-buffer " *Gnus Backlog*")
|
||||
(defvar gnus-backlog-articles nil)
|
||||
(defvar gnus-backlog-hashtb nil)
|
||||
|
||||
(defun gnus-backlog-buffer ()
|
||||
"Return the backlog buffer."
|
||||
(or (get-buffer gnus-backlog-buffer)
|
||||
(save-excursion
|
||||
(set-buffer (get-buffer-create gnus-backlog-buffer))
|
||||
(buffer-disable-undo (current-buffer))
|
||||
(setq buffer-read-only t)
|
||||
(gnus-add-current-to-buffer-list)
|
||||
(get-buffer gnus-backlog-buffer))))
|
||||
|
||||
(defun gnus-backlog-setup ()
|
||||
"Initialize backlog variables."
|
||||
(unless gnus-backlog-hashtb
|
||||
(setq gnus-backlog-hashtb (gnus-make-hashtable 1024))))
|
||||
|
||||
(gnus-add-shutdown 'gnus-backlog-shutdown 'gnus)
|
||||
|
||||
(defun gnus-backlog-shutdown ()
|
||||
"Clear all backlog variables and buffers."
|
||||
(when (get-buffer gnus-backlog-buffer)
|
||||
(kill-buffer gnus-backlog-buffer))
|
||||
(setq gnus-backlog-hashtb nil
|
||||
gnus-backlog-articles nil))
|
||||
|
||||
(defun gnus-backlog-enter-article (group number buffer)
|
||||
(gnus-backlog-setup)
|
||||
(let ((ident (intern (concat group ":" (int-to-string number))
|
||||
gnus-backlog-hashtb))
|
||||
b)
|
||||
(if (memq ident gnus-backlog-articles)
|
||||
() ; It's already kept.
|
||||
;; Remove the oldest article, if necessary.
|
||||
(and (numberp gnus-keep-backlog)
|
||||
(>= (length gnus-backlog-articles) gnus-keep-backlog)
|
||||
(gnus-backlog-remove-oldest-article))
|
||||
(push ident gnus-backlog-articles)
|
||||
;; Insert the new article.
|
||||
(save-excursion
|
||||
(set-buffer (gnus-backlog-buffer))
|
||||
(let (buffer-read-only)
|
||||
(goto-char (point-max))
|
||||
(unless (bolp)
|
||||
(insert "\n"))
|
||||
(setq b (point))
|
||||
(insert-buffer-substring buffer)
|
||||
;; Tag the beginning of the article with the ident.
|
||||
(gnus-put-text-property b (1+ b) 'gnus-backlog ident))))))
|
||||
|
||||
(defun gnus-backlog-remove-oldest-article ()
|
||||
(save-excursion
|
||||
(set-buffer (gnus-backlog-buffer))
|
||||
(goto-char (point-min))
|
||||
(if (zerop (buffer-size))
|
||||
() ; The buffer is empty.
|
||||
(let ((ident (get-text-property (point) 'gnus-backlog))
|
||||
buffer-read-only)
|
||||
;; Remove the ident from the list of articles.
|
||||
(when ident
|
||||
(setq gnus-backlog-articles (delq ident gnus-backlog-articles)))
|
||||
;; Delete the article itself.
|
||||
(delete-region
|
||||
(point) (next-single-property-change
|
||||
(1+ (point)) 'gnus-backlog nil (point-max)))))))
|
||||
|
||||
(defun gnus-backlog-remove-article (group number)
|
||||
"Remove article NUMBER in GROUP from the backlog."
|
||||
(when (numberp number)
|
||||
(gnus-backlog-setup)
|
||||
(let ((ident (intern (concat group ":" (int-to-string number))
|
||||
gnus-backlog-hashtb))
|
||||
beg end)
|
||||
(when (memq ident gnus-backlog-articles)
|
||||
;; It was in the backlog.
|
||||
(save-excursion
|
||||
(set-buffer (gnus-backlog-buffer))
|
||||
(let (buffer-read-only)
|
||||
(when (setq beg (text-property-any
|
||||
(point-min) (point-max) 'gnus-backlog
|
||||
ident))
|
||||
;; Find the end (i. e., the beginning of the next article).
|
||||
(setq end
|
||||
(next-single-property-change
|
||||
(1+ beg) 'gnus-backlog (current-buffer) (point-max)))
|
||||
(delete-region beg end)
|
||||
;; Return success.
|
||||
t)))))))
|
||||
|
||||
(defun gnus-backlog-request-article (group number buffer)
|
||||
(when (numberp number)
|
||||
(gnus-backlog-setup)
|
||||
(let ((ident (intern (concat group ":" (int-to-string number))
|
||||
gnus-backlog-hashtb))
|
||||
beg end)
|
||||
(when (memq ident gnus-backlog-articles)
|
||||
;; It was in the backlog.
|
||||
(save-excursion
|
||||
(set-buffer (gnus-backlog-buffer))
|
||||
(if (not (setq beg (text-property-any
|
||||
(point-min) (point-max) 'gnus-backlog
|
||||
ident)))
|
||||
;; It wasn't in the backlog after all.
|
||||
(ignore
|
||||
(setq gnus-backlog-articles (delq ident gnus-backlog-articles)))
|
||||
;; Find the end (i. e., the beginning of the next article).
|
||||
(setq end
|
||||
(next-single-property-change
|
||||
(1+ beg) 'gnus-backlog (current-buffer) (point-max)))))
|
||||
(let ((buffer-read-only nil))
|
||||
(erase-buffer)
|
||||
(insert-buffer-substring gnus-backlog-buffer beg end)
|
||||
t)))))
|
||||
|
||||
(provide 'gnus-bcklg)
|
||||
|
||||
;;; gnus-bcklg.el ends here
|
||||
657
lisp/gnus/gnus-cache.el
Normal file
657
lisp/gnus/gnus-cache.el
Normal file
|
|
@ -0,0 +1,657 @@
|
|||
;;; gnus-cache.el --- cache interface for Gnus
|
||||
;; Copyright (C) 1995,96,97 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
|
||||
;; Keywords: news
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'gnus)
|
||||
(require 'gnus-int)
|
||||
(require 'gnus-range)
|
||||
(require 'gnus-start)
|
||||
(eval-when-compile
|
||||
(require 'gnus-sum))
|
||||
|
||||
(defgroup gnus-cache nil
|
||||
"Cache interface."
|
||||
:group 'gnus)
|
||||
|
||||
(defcustom gnus-cache-directory
|
||||
(nnheader-concat gnus-directory "cache/")
|
||||
"*The directory where cached articles will be stored."
|
||||
:group 'gnus-cache
|
||||
:type 'directory)
|
||||
|
||||
(defcustom gnus-cache-active-file
|
||||
(concat (file-name-as-directory gnus-cache-directory) "active")
|
||||
"*The cache active file."
|
||||
:group 'gnus-cache
|
||||
:type 'file)
|
||||
|
||||
(defcustom gnus-cache-enter-articles '(ticked dormant)
|
||||
"Classes of articles to enter into the cache."
|
||||
:group 'gnus-cache
|
||||
:type '(set (const ticked) (const dormant) (const unread) (const read)))
|
||||
|
||||
(defcustom gnus-cache-remove-articles '(read)
|
||||
"Classes of articles to remove from the cache."
|
||||
:group 'gnus-cache
|
||||
:type '(set (const ticked) (const dormant) (const unread) (const read)))
|
||||
|
||||
(defcustom gnus-uncacheable-groups nil
|
||||
"*Groups that match this regexp will not be cached.
|
||||
|
||||
If you want to avoid caching your nnml groups, you could set this
|
||||
variable to \"^nnml\"."
|
||||
:group 'gnus-cache
|
||||
:type '(choice (const :tag "off" nil)
|
||||
regexp))
|
||||
|
||||
|
||||
|
||||
;;; Internal variables.
|
||||
|
||||
(defvar gnus-cache-removable-articles nil)
|
||||
(defvar gnus-cache-buffer nil)
|
||||
(defvar gnus-cache-active-hashtb nil)
|
||||
(defvar gnus-cache-active-altered nil)
|
||||
|
||||
(eval-and-compile
|
||||
(autoload 'nnml-generate-nov-databases-1 "nnml")
|
||||
(autoload 'nnvirtual-find-group-art "nnvirtual"))
|
||||
|
||||
|
||||
|
||||
;;; Functions called from Gnus.
|
||||
|
||||
(defun gnus-cache-open ()
|
||||
"Initialize the cache."
|
||||
(when (or (file-exists-p gnus-cache-directory)
|
||||
(and gnus-use-cache
|
||||
(not (eq gnus-use-cache 'passive))))
|
||||
(gnus-cache-read-active)))
|
||||
|
||||
;; Complexities of byte-compiling make this kludge necessary. Eeek.
|
||||
(ignore-errors
|
||||
(gnus-add-shutdown 'gnus-cache-close 'gnus))
|
||||
|
||||
(defun gnus-cache-close ()
|
||||
"Shut down the cache."
|
||||
(gnus-cache-write-active)
|
||||
(gnus-cache-save-buffers)
|
||||
(setq gnus-cache-active-hashtb nil))
|
||||
|
||||
(defun gnus-cache-save-buffers ()
|
||||
;; save the overview buffer if it exists and has been modified
|
||||
;; delete empty cache subdirectories
|
||||
(when gnus-cache-buffer
|
||||
(let ((buffer (cdr gnus-cache-buffer))
|
||||
(overview-file (gnus-cache-file-name
|
||||
(car gnus-cache-buffer) ".overview")))
|
||||
;; write the overview only if it was modified
|
||||
(when (buffer-modified-p buffer)
|
||||
(save-excursion
|
||||
(set-buffer buffer)
|
||||
(if (> (buffer-size) 0)
|
||||
;; Non-empty overview, write it to a file.
|
||||
(gnus-write-buffer overview-file)
|
||||
;; Empty overview file, remove it
|
||||
(when (file-exists-p overview-file)
|
||||
(delete-file overview-file))
|
||||
;; If possible, remove group's cache subdirectory.
|
||||
(condition-case nil
|
||||
;; FIXME: we can detect the error type and warn the user
|
||||
;; of any inconsistencies (articles w/o nov entries?).
|
||||
;; for now, just be conservative...delete only if safe -- sj
|
||||
(delete-directory (file-name-directory overview-file))
|
||||
(error nil)))))
|
||||
;; Kill the buffer -- it's either unmodified or saved.
|
||||
(gnus-kill-buffer buffer)
|
||||
(setq gnus-cache-buffer nil))))
|
||||
|
||||
(defun gnus-cache-possibly-enter-article
|
||||
(group article headers ticked dormant unread &optional force)
|
||||
(when (and (or force (not (eq gnus-use-cache 'passive)))
|
||||
(numberp article)
|
||||
(> article 0)
|
||||
(vectorp headers)) ; This might be a dummy article.
|
||||
;; If this is a virtual group, we find the real group.
|
||||
(when (gnus-virtual-group-p group)
|
||||
(let ((result (nnvirtual-find-group-art
|
||||
(gnus-group-real-name group) article)))
|
||||
(setq group (car result)
|
||||
headers (copy-sequence headers))
|
||||
(mail-header-set-number headers (cdr result))))
|
||||
(let ((number (mail-header-number headers))
|
||||
file dir)
|
||||
(when (and (> number 0) ; Reffed article.
|
||||
(or force
|
||||
(and (or (not gnus-uncacheable-groups)
|
||||
(not (string-match
|
||||
gnus-uncacheable-groups group)))
|
||||
(gnus-cache-member-of-class
|
||||
gnus-cache-enter-articles ticked dormant unread)))
|
||||
(not (file-exists-p (setq file (gnus-cache-file-name
|
||||
group number)))))
|
||||
;; Possibly create the cache directory.
|
||||
(gnus-make-directory (setq dir (file-name-directory file)))
|
||||
;; Save the article in the cache.
|
||||
(if (file-exists-p file)
|
||||
t ; The article already is saved.
|
||||
(save-excursion
|
||||
(set-buffer nntp-server-buffer)
|
||||
(let ((gnus-use-cache nil))
|
||||
(gnus-request-article-this-buffer number group))
|
||||
(when (> (buffer-size) 0)
|
||||
(gnus-write-buffer file)
|
||||
(gnus-cache-change-buffer group)
|
||||
(set-buffer (cdr gnus-cache-buffer))
|
||||
(goto-char (point-max))
|
||||
(forward-line -1)
|
||||
(while (condition-case ()
|
||||
(when (not (bobp))
|
||||
(> (read (current-buffer)) number))
|
||||
(error
|
||||
;; The line was malformed, so we just remove it!!
|
||||
(gnus-delete-line)
|
||||
t))
|
||||
(forward-line -1))
|
||||
(if (bobp)
|
||||
(if (not (eobp))
|
||||
(progn
|
||||
(beginning-of-line)
|
||||
(when (< (read (current-buffer)) number)
|
||||
(forward-line 1)))
|
||||
(beginning-of-line))
|
||||
(forward-line 1))
|
||||
(beginning-of-line)
|
||||
;; [number subject from date id references chars lines xref]
|
||||
(insert (format "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t\n"
|
||||
(mail-header-number headers)
|
||||
(mail-header-subject headers)
|
||||
(mail-header-from headers)
|
||||
(mail-header-date headers)
|
||||
(mail-header-id headers)
|
||||
(or (mail-header-references headers) "")
|
||||
(or (mail-header-chars headers) "")
|
||||
(or (mail-header-lines headers) "")
|
||||
(or (mail-header-xref headers) "")))
|
||||
;; Update the active info.
|
||||
(set-buffer gnus-summary-buffer)
|
||||
(gnus-cache-update-active group number)
|
||||
(push article gnus-newsgroup-cached)
|
||||
(gnus-summary-update-secondary-mark article))
|
||||
t))))))
|
||||
|
||||
(defun gnus-cache-enter-remove-article (article)
|
||||
"Mark ARTICLE for later possible removal."
|
||||
(when article
|
||||
(push article gnus-cache-removable-articles)))
|
||||
|
||||
(defun gnus-cache-possibly-remove-articles ()
|
||||
"Possibly remove some of the removable articles."
|
||||
(if (not (gnus-virtual-group-p gnus-newsgroup-name))
|
||||
(gnus-cache-possibly-remove-articles-1)
|
||||
(let ((arts gnus-cache-removable-articles)
|
||||
ga)
|
||||
(while arts
|
||||
(when (setq ga (nnvirtual-find-group-art
|
||||
(gnus-group-real-name gnus-newsgroup-name) (pop arts)))
|
||||
(let ((gnus-cache-removable-articles (list (cdr ga)))
|
||||
(gnus-newsgroup-name (car ga)))
|
||||
(gnus-cache-possibly-remove-articles-1)))))
|
||||
(setq gnus-cache-removable-articles nil)))
|
||||
|
||||
(defun gnus-cache-possibly-remove-articles-1 ()
|
||||
"Possibly remove some of the removable articles."
|
||||
(unless (eq gnus-use-cache 'passive)
|
||||
(let ((articles gnus-cache-removable-articles)
|
||||
(cache-articles gnus-newsgroup-cached)
|
||||
article)
|
||||
(gnus-cache-change-buffer gnus-newsgroup-name)
|
||||
(while articles
|
||||
(when (memq (setq article (pop articles)) cache-articles)
|
||||
;; The article was in the cache, so we see whether we are
|
||||
;; supposed to remove it from the cache.
|
||||
(gnus-cache-possibly-remove-article
|
||||
article (memq article gnus-newsgroup-marked)
|
||||
(memq article gnus-newsgroup-dormant)
|
||||
(or (memq article gnus-newsgroup-unreads)
|
||||
(memq article gnus-newsgroup-unselected))))))
|
||||
;; The overview file might have been modified, save it
|
||||
;; safe because we're only called at group exit anyway.
|
||||
(gnus-cache-save-buffers)))
|
||||
|
||||
(defun gnus-cache-request-article (article group)
|
||||
"Retrieve ARTICLE in GROUP from the cache."
|
||||
(let ((file (gnus-cache-file-name group article))
|
||||
(buffer-read-only nil))
|
||||
(when (file-exists-p file)
|
||||
(erase-buffer)
|
||||
(gnus-kill-all-overlays)
|
||||
(insert-file-contents file)
|
||||
t)))
|
||||
|
||||
(defun gnus-cache-possibly-alter-active (group active)
|
||||
"Alter the ACTIVE info for GROUP to reflect the articles in the cache."
|
||||
(when (equal group "no.norsk") (error "hie"))
|
||||
(when gnus-cache-active-hashtb
|
||||
(let ((cache-active (gnus-gethash group gnus-cache-active-hashtb)))
|
||||
(and cache-active
|
||||
(< (car cache-active) (car active))
|
||||
(setcar active (car cache-active)))
|
||||
(and cache-active
|
||||
(> (cdr cache-active) (cdr active))
|
||||
(setcdr active (cdr cache-active))))))
|
||||
|
||||
(defun gnus-cache-retrieve-headers (articles group &optional fetch-old)
|
||||
"Retrieve the headers for ARTICLES in GROUP."
|
||||
(let ((cached
|
||||
(setq gnus-newsgroup-cached (gnus-cache-articles-in-group group))))
|
||||
(if (not cached)
|
||||
;; No cached articles here, so we just retrieve them
|
||||
;; the normal way.
|
||||
(let ((gnus-use-cache nil))
|
||||
(gnus-retrieve-headers articles group fetch-old))
|
||||
(let ((uncached-articles (gnus-sorted-intersection
|
||||
(gnus-sorted-complement articles cached)
|
||||
articles))
|
||||
(cache-file (gnus-cache-file-name group ".overview"))
|
||||
type)
|
||||
;; We first retrieve all the headers that we don't have in
|
||||
;; the cache.
|
||||
(let ((gnus-use-cache nil))
|
||||
(when uncached-articles
|
||||
(setq type (and articles
|
||||
(gnus-retrieve-headers
|
||||
uncached-articles group fetch-old)))))
|
||||
(gnus-cache-save-buffers)
|
||||
;; Then we insert the cached headers.
|
||||
(save-excursion
|
||||
(cond
|
||||
((not (file-exists-p cache-file))
|
||||
;; There are no cached headers.
|
||||
type)
|
||||
((null type)
|
||||
;; There were no uncached headers (or retrieval was
|
||||
;; unsuccessful), so we use the cached headers exclusively.
|
||||
(set-buffer nntp-server-buffer)
|
||||
(erase-buffer)
|
||||
(insert-file-contents cache-file)
|
||||
'nov)
|
||||
((eq type 'nov)
|
||||
;; We have both cached and uncached NOV headers, so we
|
||||
;; braid them.
|
||||
(gnus-cache-braid-nov group cached)
|
||||
type)
|
||||
(t
|
||||
;; We braid HEADs.
|
||||
(gnus-cache-braid-heads group (gnus-sorted-intersection
|
||||
cached articles))
|
||||
type)))))))
|
||||
|
||||
(defun gnus-cache-enter-article (&optional n)
|
||||
"Enter the next N articles into the cache.
|
||||
If not given a prefix, use the process marked articles instead.
|
||||
Returns the list of articles entered."
|
||||
(interactive "P")
|
||||
(gnus-set-global-variables)
|
||||
(let ((articles (gnus-summary-work-articles n))
|
||||
article out)
|
||||
(while (setq article (pop articles))
|
||||
(if (natnump article)
|
||||
(when (gnus-cache-possibly-enter-article
|
||||
gnus-newsgroup-name article
|
||||
(gnus-summary-article-header article)
|
||||
nil nil nil t)
|
||||
(push article out))
|
||||
(gnus-message 2 "Can't cache article %d" article))
|
||||
(gnus-summary-remove-process-mark article)
|
||||
(gnus-summary-update-secondary-mark article))
|
||||
(gnus-summary-next-subject 1)
|
||||
(gnus-summary-position-point)
|
||||
(nreverse out)))
|
||||
|
||||
(defun gnus-cache-remove-article (n)
|
||||
"Remove the next N articles from the cache.
|
||||
If not given a prefix, use the process marked articles instead.
|
||||
Returns the list of articles removed."
|
||||
(interactive "P")
|
||||
(gnus-set-global-variables)
|
||||
(gnus-cache-change-buffer gnus-newsgroup-name)
|
||||
(let ((articles (gnus-summary-work-articles n))
|
||||
article out)
|
||||
(while articles
|
||||
(setq article (pop articles))
|
||||
(when (gnus-cache-possibly-remove-article article nil nil nil t)
|
||||
(push article out))
|
||||
(gnus-summary-remove-process-mark article)
|
||||
(gnus-summary-update-secondary-mark article))
|
||||
(gnus-summary-next-subject 1)
|
||||
(gnus-summary-position-point)
|
||||
(nreverse out)))
|
||||
|
||||
(defun gnus-cached-article-p (article)
|
||||
"Say whether ARTICLE is cached in the current group."
|
||||
(memq article gnus-newsgroup-cached))
|
||||
|
||||
(defun gnus-summary-insert-cached-articles ()
|
||||
"Insert all the articles cached for this group into the current buffer."
|
||||
(interactive)
|
||||
(let ((cached gnus-newsgroup-cached)
|
||||
(gnus-verbose (max 6 gnus-verbose)))
|
||||
(unless cached
|
||||
(error "No cached articles for this group"))
|
||||
(while cached
|
||||
(gnus-summary-goto-subject (pop cached) t))))
|
||||
|
||||
;;; Internal functions.
|
||||
|
||||
(defun gnus-cache-change-buffer (group)
|
||||
(and gnus-cache-buffer
|
||||
;; See if the current group's overview cache has been loaded.
|
||||
(or (string= group (car gnus-cache-buffer))
|
||||
;; Another overview cache is current, save it.
|
||||
(gnus-cache-save-buffers)))
|
||||
;; if gnus-cache buffer is nil, create it
|
||||
(unless gnus-cache-buffer
|
||||
;; Create cache buffer
|
||||
(save-excursion
|
||||
(setq gnus-cache-buffer
|
||||
(cons group
|
||||
(set-buffer (get-buffer-create " *gnus-cache-overview*"))))
|
||||
(buffer-disable-undo (current-buffer))
|
||||
;; Insert the contents of this group's cache overview.
|
||||
(erase-buffer)
|
||||
(let ((file (gnus-cache-file-name group ".overview")))
|
||||
(when (file-exists-p file)
|
||||
(nnheader-insert-file-contents file)))
|
||||
;; We have a fresh (empty/just loaded) buffer,
|
||||
;; mark it as unmodified to save a redundant write later.
|
||||
(set-buffer-modified-p nil))))
|
||||
|
||||
;; Return whether an article is a member of a class.
|
||||
(defun gnus-cache-member-of-class (class ticked dormant unread)
|
||||
(or (and ticked (memq 'ticked class))
|
||||
(and dormant (memq 'dormant class))
|
||||
(and unread (memq 'unread class))
|
||||
(and (not unread) (not ticked) (not dormant) (memq 'read class))))
|
||||
|
||||
(defun gnus-cache-file-name (group article)
|
||||
(concat (file-name-as-directory gnus-cache-directory)
|
||||
(file-name-as-directory
|
||||
(nnheader-translate-file-chars
|
||||
(if (gnus-use-long-file-name 'not-cache)
|
||||
group
|
||||
(let ((group (nnheader-replace-chars-in-string group ?/ ?_)))
|
||||
;; Translate the first colon into a slash.
|
||||
(when (string-match ":" group)
|
||||
(aset group (match-beginning 0) ?/))
|
||||
(nnheader-replace-chars-in-string group ?. ?/)))))
|
||||
(if (stringp article) article (int-to-string article))))
|
||||
|
||||
(defun gnus-cache-update-article (group article)
|
||||
"If ARTICLE is in the cache, remove it and re-enter it."
|
||||
(when (gnus-cache-possibly-remove-article article nil nil nil t)
|
||||
(let ((gnus-use-cache nil))
|
||||
(gnus-cache-possibly-enter-article
|
||||
gnus-newsgroup-name article (gnus-summary-article-header article)
|
||||
nil nil nil t))))
|
||||
|
||||
(defun gnus-cache-possibly-remove-article (article ticked dormant unread
|
||||
&optional force)
|
||||
"Possibly remove ARTICLE from the cache."
|
||||
(let ((group gnus-newsgroup-name)
|
||||
(number article)
|
||||
file)
|
||||
;; If this is a virtual group, we find the real group.
|
||||
(when (gnus-virtual-group-p group)
|
||||
(let ((result (nnvirtual-find-group-art
|
||||
(gnus-group-real-name group) article)))
|
||||
(setq group (car result)
|
||||
number (cdr result))))
|
||||
(setq file (gnus-cache-file-name group number))
|
||||
(when (and (file-exists-p file)
|
||||
(or force
|
||||
(gnus-cache-member-of-class
|
||||
gnus-cache-remove-articles ticked dormant unread)))
|
||||
(save-excursion
|
||||
(delete-file file)
|
||||
(set-buffer (cdr gnus-cache-buffer))
|
||||
(goto-char (point-min))
|
||||
(when (or (looking-at (concat (int-to-string number) "\t"))
|
||||
(search-forward (concat "\n" (int-to-string number) "\t")
|
||||
(point-max) t))
|
||||
(delete-region (progn (beginning-of-line) (point))
|
||||
(progn (forward-line 1) (point)))))
|
||||
(setq gnus-newsgroup-cached
|
||||
(delq article gnus-newsgroup-cached))
|
||||
(gnus-summary-update-secondary-mark article)
|
||||
t)))
|
||||
|
||||
(defun gnus-cache-articles-in-group (group)
|
||||
"Return a sorted list of cached articles in GROUP."
|
||||
(let ((dir (file-name-directory (gnus-cache-file-name group 1))))
|
||||
(when (file-exists-p dir)
|
||||
(sort (mapcar (lambda (name) (string-to-int name))
|
||||
(directory-files dir nil "^[0-9]+$" t))
|
||||
'<))))
|
||||
|
||||
(defun gnus-cache-braid-nov (group cached)
|
||||
(let ((cache-buf (get-buffer-create " *gnus-cache*"))
|
||||
beg end)
|
||||
(gnus-cache-save-buffers)
|
||||
(save-excursion
|
||||
(set-buffer cache-buf)
|
||||
(buffer-disable-undo (current-buffer))
|
||||
(erase-buffer)
|
||||
(insert-file-contents (gnus-cache-file-name group ".overview"))
|
||||
(goto-char (point-min))
|
||||
(insert "\n")
|
||||
(goto-char (point-min)))
|
||||
(set-buffer nntp-server-buffer)
|
||||
(goto-char (point-min))
|
||||
(while cached
|
||||
(while (and (not (eobp))
|
||||
(< (read (current-buffer)) (car cached)))
|
||||
(forward-line 1))
|
||||
(beginning-of-line)
|
||||
(save-excursion
|
||||
(set-buffer cache-buf)
|
||||
(if (search-forward (concat "\n" (int-to-string (car cached)) "\t")
|
||||
nil t)
|
||||
(setq beg (progn (beginning-of-line) (point))
|
||||
end (progn (end-of-line) (point)))
|
||||
(setq beg nil)))
|
||||
(when beg
|
||||
(insert-buffer-substring cache-buf beg end)
|
||||
(insert "\n"))
|
||||
(setq cached (cdr cached)))
|
||||
(kill-buffer cache-buf)))
|
||||
|
||||
(defun gnus-cache-braid-heads (group cached)
|
||||
(let ((cache-buf (get-buffer-create " *gnus-cache*")))
|
||||
(save-excursion
|
||||
(set-buffer cache-buf)
|
||||
(buffer-disable-undo (current-buffer))
|
||||
(erase-buffer))
|
||||
(set-buffer nntp-server-buffer)
|
||||
(goto-char (point-min))
|
||||
(while cached
|
||||
(while (and (not (eobp))
|
||||
(looking-at "2.. +\\([0-9]+\\) ")
|
||||
(< (progn (goto-char (match-beginning 1))
|
||||
(read (current-buffer)))
|
||||
(car cached)))
|
||||
(search-forward "\n.\n" nil 'move))
|
||||
(beginning-of-line)
|
||||
(save-excursion
|
||||
(set-buffer cache-buf)
|
||||
(erase-buffer)
|
||||
(insert-file-contents (gnus-cache-file-name group (car cached)))
|
||||
(goto-char (point-min))
|
||||
(insert "220 ")
|
||||
(princ (car cached) (current-buffer))
|
||||
(insert " Article retrieved.\n")
|
||||
(search-forward "\n\n" nil 'move)
|
||||
(delete-region (point) (point-max))
|
||||
(forward-char -1)
|
||||
(insert "."))
|
||||
(insert-buffer-substring cache-buf)
|
||||
(setq cached (cdr cached)))
|
||||
(kill-buffer cache-buf)))
|
||||
|
||||
;;;###autoload
|
||||
(defun gnus-jog-cache ()
|
||||
"Go through all groups and put the articles into the cache.
|
||||
|
||||
Usage:
|
||||
$ emacs -batch -l ~/.emacs -l gnus -f gnus-jog-cache"
|
||||
(interactive)
|
||||
(let ((gnus-mark-article-hook nil)
|
||||
(gnus-expert-user t)
|
||||
(nnmail-spool-file nil)
|
||||
(gnus-use-dribble-file nil)
|
||||
(gnus-novice-user nil)
|
||||
(gnus-large-newsgroup nil))
|
||||
;; Start Gnus.
|
||||
(gnus)
|
||||
;; Go through all groups...
|
||||
(gnus-group-mark-buffer)
|
||||
(gnus-group-universal-argument
|
||||
nil nil
|
||||
(lambda ()
|
||||
(interactive)
|
||||
(gnus-summary-read-group (gnus-group-group-name) nil t)
|
||||
;; ... and enter the articles into the cache.
|
||||
(when (eq major-mode 'gnus-summary-mode)
|
||||
(gnus-uu-mark-buffer)
|
||||
(gnus-cache-enter-article)
|
||||
(kill-buffer (current-buffer)))))))
|
||||
|
||||
(defun gnus-cache-read-active (&optional force)
|
||||
"Read the cache active file."
|
||||
(gnus-make-directory gnus-cache-directory)
|
||||
(if (not (and (file-exists-p gnus-cache-active-file)
|
||||
(or force (not gnus-cache-active-hashtb))))
|
||||
;; There is no active file, so we generate one.
|
||||
(gnus-cache-generate-active)
|
||||
;; We simply read the active file.
|
||||
(save-excursion
|
||||
(gnus-set-work-buffer)
|
||||
(insert-file-contents gnus-cache-active-file)
|
||||
(gnus-active-to-gnus-format
|
||||
nil (setq gnus-cache-active-hashtb
|
||||
(gnus-make-hashtable
|
||||
(count-lines (point-min) (point-max)))))
|
||||
(setq gnus-cache-active-altered nil))))
|
||||
|
||||
(defun gnus-cache-write-active (&optional force)
|
||||
"Write the active hashtb to the active file."
|
||||
(when (or force
|
||||
(and gnus-cache-active-hashtb
|
||||
gnus-cache-active-altered))
|
||||
(nnheader-temp-write gnus-cache-active-file
|
||||
(mapatoms
|
||||
(lambda (sym)
|
||||
(when (and sym (boundp sym))
|
||||
(insert (format "%s %d %d y\n"
|
||||
(symbol-name sym) (cdr (symbol-value sym))
|
||||
(car (symbol-value sym))))))
|
||||
gnus-cache-active-hashtb))
|
||||
;; Mark the active hashtb as unaltered.
|
||||
(setq gnus-cache-active-altered nil)))
|
||||
|
||||
(defun gnus-cache-update-active (group number &optional low)
|
||||
"Update the upper bound of the active info of GROUP to NUMBER.
|
||||
If LOW, update the lower bound instead."
|
||||
(let ((active (gnus-gethash group gnus-cache-active-hashtb)))
|
||||
(if (null active)
|
||||
;; We just create a new active entry for this group.
|
||||
(gnus-sethash group (cons number number) gnus-cache-active-hashtb)
|
||||
;; Update the lower or upper bound.
|
||||
(if low
|
||||
(setcar active number)
|
||||
(setcdr active number)))
|
||||
;; Mark the active hashtb as altered.
|
||||
(setq gnus-cache-active-altered t)))
|
||||
|
||||
;;;###autoload
|
||||
(defun gnus-cache-generate-active (&optional directory)
|
||||
"Generate the cache active file."
|
||||
(interactive)
|
||||
(let* ((top (null directory))
|
||||
(directory (expand-file-name (or directory gnus-cache-directory)))
|
||||
(files (directory-files directory 'full))
|
||||
(group
|
||||
(if top
|
||||
""
|
||||
(string-match
|
||||
(concat "^" (file-name-as-directory
|
||||
(expand-file-name gnus-cache-directory)))
|
||||
(directory-file-name directory))
|
||||
(nnheader-replace-chars-in-string
|
||||
(substring (directory-file-name directory) (match-end 0))
|
||||
?/ ?.)))
|
||||
nums alphs)
|
||||
(when top
|
||||
(gnus-message 5 "Generating the cache active file...")
|
||||
(setq gnus-cache-active-hashtb (gnus-make-hashtable 123)))
|
||||
;; Separate articles from all other files and directories.
|
||||
(while files
|
||||
(if (string-match "^[0-9]+$" (file-name-nondirectory (car files)))
|
||||
(push (string-to-int (file-name-nondirectory (pop files))) nums)
|
||||
(push (pop files) alphs)))
|
||||
;; If we have nums, then this is probably a valid group.
|
||||
(when (setq nums (sort nums '<))
|
||||
(gnus-sethash group (cons (car nums) (gnus-last-element nums))
|
||||
gnus-cache-active-hashtb))
|
||||
;; Go through all the other files.
|
||||
(while alphs
|
||||
(when (and (file-directory-p (car alphs))
|
||||
(not (string-match "^\\.\\.?$"
|
||||
(file-name-nondirectory (car alphs)))))
|
||||
;; We descend directories.
|
||||
(gnus-cache-generate-active (car alphs)))
|
||||
(setq alphs (cdr alphs)))
|
||||
;; Write the new active file.
|
||||
(when top
|
||||
(gnus-cache-write-active t)
|
||||
(gnus-message 5 "Generating the cache active file...done"))))
|
||||
|
||||
;;;###autoload
|
||||
(defun gnus-cache-generate-nov-databases (dir)
|
||||
"Generate NOV files recursively starting in DIR."
|
||||
(interactive (list gnus-cache-directory))
|
||||
(gnus-cache-close)
|
||||
(let ((nnml-generate-active-function 'identity))
|
||||
(nnml-generate-nov-databases-1 dir)))
|
||||
|
||||
(defun gnus-cache-move-cache (dir)
|
||||
"Move the cache tree to somewhere else."
|
||||
(interactive "DMove the cache tree to: ")
|
||||
(rename-file gnus-cache-directory dir))
|
||||
|
||||
(provide 'gnus-cache)
|
||||
|
||||
;;; gnus-cache.el ends here
|
||||
911
lisp/gnus/gnus-cite.el
Normal file
911
lisp/gnus/gnus-cite.el
Normal file
|
|
@ -0,0 +1,911 @@
|
|||
;;; gnus-cite.el --- parse citations in articles for Gnus
|
||||
;; Copyright (C) 1995,96,97 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Per Abrahamsen <abraham@iesd.auc.dk>
|
||||
;; Keywords: news, mail
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'gnus)
|
||||
(require 'gnus-art)
|
||||
(require 'gnus-range)
|
||||
|
||||
;;; Customization:
|
||||
|
||||
(defgroup gnus-cite nil
|
||||
"Citation."
|
||||
:prefix "gnus-cite-"
|
||||
:link '(custom-manual "(gnus)Article Highlighting")
|
||||
:group 'gnus-article)
|
||||
|
||||
(defcustom gnus-cite-reply-regexp
|
||||
"^\\(Subject: Re\\|In-Reply-To\\|References\\):"
|
||||
"If headers match this regexp it is reasonable to believe that
|
||||
article has citations."
|
||||
:group 'gnus-cite
|
||||
:type 'string)
|
||||
|
||||
(defcustom gnus-cite-always-check nil
|
||||
"Check article always for citations. Set it t to check all articles."
|
||||
:group 'gnus-cite
|
||||
:type '(choice (const :tag "no" nil)
|
||||
(const :tag "yes" t)))
|
||||
|
||||
(defcustom gnus-cited-text-button-line-format "%(%{[...]%}%)\n"
|
||||
"Format of cited text buttons."
|
||||
:group 'gnus-cite
|
||||
:type 'string)
|
||||
|
||||
(defcustom gnus-cited-lines-visible nil
|
||||
"The number of lines of hidden cited text to remain visible."
|
||||
:group 'gnus-cite
|
||||
:type '(choice (const :tag "none" nil)
|
||||
integer))
|
||||
|
||||
(defcustom gnus-cite-parse-max-size 25000
|
||||
"Maximum article size (in bytes) where parsing citations is allowed.
|
||||
Set it to nil to parse all articles."
|
||||
:group 'gnus-cite
|
||||
:type '(choice (const :tag "all" nil)
|
||||
integer))
|
||||
|
||||
(defcustom gnus-cite-prefix-regexp
|
||||
"^[]>|:}+ ]*[]>|:}+]\\(.*>\\)?\\|^.*>"
|
||||
"Regexp matching the longest possible citation prefix on a line."
|
||||
:group 'gnus-cite
|
||||
:type 'regexp)
|
||||
|
||||
(defcustom gnus-cite-max-prefix 20
|
||||
"Maximum possible length for a citation prefix."
|
||||
:group 'gnus-cite
|
||||
:type 'integer)
|
||||
|
||||
(defcustom gnus-supercite-regexp
|
||||
(concat "^\\(" gnus-cite-prefix-regexp "\\)? *"
|
||||
">>>>> +\"\\([^\"\n]+\\)\" +==")
|
||||
"Regexp matching normal Supercite attribution lines.
|
||||
The first grouping must match prefixes added by other packages."
|
||||
:group 'gnus-cite
|
||||
:type 'regexp)
|
||||
|
||||
(defcustom gnus-supercite-secondary-regexp "^.*\"\\([^\"\n]+\\)\" +=="
|
||||
"Regexp matching mangled Supercite attribution lines.
|
||||
The first regexp group should match the Supercite attribution."
|
||||
:group 'gnus-cite
|
||||
:type 'regexp)
|
||||
|
||||
(defcustom gnus-cite-minimum-match-count 2
|
||||
"Minimum number of identical prefixes before we believe it's a citation."
|
||||
:group 'gnus-cite
|
||||
:type 'integer)
|
||||
|
||||
(defcustom gnus-cite-attribution-prefix "in article\\|in <"
|
||||
"Regexp matching the beginning of an attribution line."
|
||||
:group 'gnus-cite
|
||||
:type 'regexp)
|
||||
|
||||
(defcustom gnus-cite-attribution-suffix
|
||||
"\\(wrote\\|writes\\|said\\|says\\):[ \t]*$"
|
||||
"Regexp matching the end of an attribution line.
|
||||
The text matching the first grouping will be used as a button."
|
||||
:group 'gnus-cite
|
||||
:type 'regexp)
|
||||
|
||||
(defface gnus-cite-attribution-face '((t
|
||||
(:underline t)))
|
||||
"Face used for attribution lines.")
|
||||
|
||||
(defcustom gnus-cite-attribution-face 'gnus-cite-attribution-face
|
||||
"Face used for attribution lines.
|
||||
It is merged with the face for the cited text belonging to the attribution."
|
||||
:group 'gnus-cite
|
||||
:type 'face)
|
||||
|
||||
(defface gnus-cite-face-1 '((((class color)
|
||||
(background dark))
|
||||
(:foreground "light blue"))
|
||||
(((class color)
|
||||
(background light))
|
||||
(:foreground "MidnightBlue"))
|
||||
(t
|
||||
(:italic t)))
|
||||
"Citation face.")
|
||||
|
||||
(defface gnus-cite-face-2 '((((class color)
|
||||
(background dark))
|
||||
(:foreground "light cyan"))
|
||||
(((class color)
|
||||
(background light))
|
||||
(:foreground "firebrick"))
|
||||
(t
|
||||
(:italic t)))
|
||||
"Citation face.")
|
||||
|
||||
(defface gnus-cite-face-3 '((((class color)
|
||||
(background dark))
|
||||
(:foreground "light yellow"))
|
||||
(((class color)
|
||||
(background light))
|
||||
(:foreground "dark green"))
|
||||
(t
|
||||
(:italic t)))
|
||||
"Citation face.")
|
||||
|
||||
(defface gnus-cite-face-4 '((((class color)
|
||||
(background dark))
|
||||
(:foreground "light pink"))
|
||||
(((class color)
|
||||
(background light))
|
||||
(:foreground "OrangeRed"))
|
||||
(t
|
||||
(:italic t)))
|
||||
"Citation face.")
|
||||
|
||||
(defface gnus-cite-face-5 '((((class color)
|
||||
(background dark))
|
||||
(:foreground "pale green"))
|
||||
(((class color)
|
||||
(background light))
|
||||
(:foreground "dark khaki"))
|
||||
(t
|
||||
(:italic t)))
|
||||
"Citation face.")
|
||||
|
||||
(defface gnus-cite-face-6 '((((class color)
|
||||
(background dark))
|
||||
(:foreground "beige"))
|
||||
(((class color)
|
||||
(background light))
|
||||
(:foreground "dark violet"))
|
||||
(t
|
||||
(:italic t)))
|
||||
"Citation face.")
|
||||
|
||||
(defface gnus-cite-face-7 '((((class color)
|
||||
(background dark))
|
||||
(:foreground "orange"))
|
||||
(((class color)
|
||||
(background light))
|
||||
(:foreground "SteelBlue4"))
|
||||
(t
|
||||
(:italic t)))
|
||||
"Citation face.")
|
||||
|
||||
(defface gnus-cite-face-8 '((((class color)
|
||||
(background dark))
|
||||
(:foreground "magenta"))
|
||||
(((class color)
|
||||
(background light))
|
||||
(:foreground "magenta"))
|
||||
(t
|
||||
(:italic t)))
|
||||
"Citation face.")
|
||||
|
||||
(defface gnus-cite-face-9 '((((class color)
|
||||
(background dark))
|
||||
(:foreground "violet"))
|
||||
(((class color)
|
||||
(background light))
|
||||
(:foreground "violet"))
|
||||
(t
|
||||
(:italic t)))
|
||||
"Citation face.")
|
||||
|
||||
(defface gnus-cite-face-10 '((((class color)
|
||||
(background dark))
|
||||
(:foreground "medium purple"))
|
||||
(((class color)
|
||||
(background light))
|
||||
(:foreground "medium purple"))
|
||||
(t
|
||||
(:italic t)))
|
||||
"Citation face.")
|
||||
|
||||
(defface gnus-cite-face-11 '((((class color)
|
||||
(background dark))
|
||||
(:foreground "turquoise"))
|
||||
(((class color)
|
||||
(background light))
|
||||
(:foreground "turquoise"))
|
||||
(t
|
||||
(:italic t)))
|
||||
"Citation face.")
|
||||
|
||||
(defcustom gnus-cite-face-list
|
||||
'(gnus-cite-face-1 gnus-cite-face-2 gnus-cite-face-3 gnus-cite-face-4
|
||||
gnus-cite-face-5 gnus-cite-face-6 gnus-cite-face-7 gnus-cite-face-8
|
||||
gnus-cite-face-9 gnus-cite-face-10 gnus-cite-face-11)
|
||||
"List of faces used for highlighting citations.
|
||||
|
||||
When there are citations from multiple articles in the same message,
|
||||
Gnus will try to give each citation from each article its own face.
|
||||
This should make it easier to see who wrote what."
|
||||
:group 'gnus-cite
|
||||
:type '(repeat face))
|
||||
|
||||
(defcustom gnus-cite-hide-percentage 50
|
||||
"Only hide excess citation if above this percentage of the body."
|
||||
:group 'gnus-cite
|
||||
:type 'number)
|
||||
|
||||
(defcustom gnus-cite-hide-absolute 10
|
||||
"Only hide excess citation if above this number of lines in the body."
|
||||
:group 'gnus-cite
|
||||
:type 'integer)
|
||||
|
||||
;;; Internal Variables:
|
||||
|
||||
(defvar gnus-cite-article nil)
|
||||
|
||||
(defvar gnus-cite-prefix-alist nil)
|
||||
;; Alist of citation prefixes.
|
||||
;; The cdr is a list of lines with that prefix.
|
||||
|
||||
(defvar gnus-cite-attribution-alist nil)
|
||||
;; Alist of attribution lines.
|
||||
;; The car is a line number.
|
||||
;; The cdr is the prefix for the citation started by that line.
|
||||
|
||||
(defvar gnus-cite-loose-prefix-alist nil)
|
||||
;; Alist of citation prefixes that have no matching attribution.
|
||||
;; The cdr is a list of lines with that prefix.
|
||||
|
||||
(defvar gnus-cite-loose-attribution-alist nil)
|
||||
;; Alist of attribution lines that have no matching citation.
|
||||
;; Each member has the form (WROTE IN PREFIX TAG), where
|
||||
;; WROTE: is the attribution line number
|
||||
;; IN: is the line number of the previous line if part of the same attribution,
|
||||
;; PREFIX: Is the citation prefix of the attribution line(s), and
|
||||
;; TAG: Is a Supercite tag, if any.
|
||||
|
||||
(defvar gnus-cited-text-button-line-format-alist
|
||||
`((?b (marker-position beg) ?d)
|
||||
(?e (marker-position end) ?d)
|
||||
(?l (- end beg) ?d)))
|
||||
(defvar gnus-cited-text-button-line-format-spec nil)
|
||||
|
||||
;;; Commands:
|
||||
|
||||
(defun gnus-article-highlight-citation (&optional force)
|
||||
"Highlight cited text.
|
||||
Each citation in the article will be highlighted with a different face.
|
||||
The faces are taken from `gnus-cite-face-list'.
|
||||
Attribution lines are highlighted with the same face as the
|
||||
corresponding citation merged with `gnus-cite-attribution-face'.
|
||||
|
||||
Text is considered cited if at least `gnus-cite-minimum-match-count'
|
||||
lines matches `gnus-cite-prefix-regexp' with the same prefix.
|
||||
|
||||
Lines matching `gnus-cite-attribution-suffix' and perhaps
|
||||
`gnus-cite-attribution-prefix' are considered attribution lines."
|
||||
(interactive (list 'force))
|
||||
(save-excursion
|
||||
(set-buffer gnus-article-buffer)
|
||||
(gnus-cite-parse-maybe force)
|
||||
(let ((buffer-read-only nil)
|
||||
(alist gnus-cite-prefix-alist)
|
||||
(faces gnus-cite-face-list)
|
||||
(inhibit-point-motion-hooks t)
|
||||
face entry prefix skip numbers number face-alist)
|
||||
;; Loop through citation prefixes.
|
||||
(while alist
|
||||
(setq entry (car alist)
|
||||
alist (cdr alist)
|
||||
prefix (car entry)
|
||||
numbers (cdr entry)
|
||||
face (car faces)
|
||||
faces (or (cdr faces) gnus-cite-face-list)
|
||||
face-alist (cons (cons prefix face) face-alist))
|
||||
(while numbers
|
||||
(setq number (car numbers)
|
||||
numbers (cdr numbers))
|
||||
(and (not (assq number gnus-cite-attribution-alist))
|
||||
(not (assq number gnus-cite-loose-attribution-alist))
|
||||
(gnus-cite-add-face number prefix face))))
|
||||
;; Loop through attribution lines.
|
||||
(setq alist gnus-cite-attribution-alist)
|
||||
(while alist
|
||||
(setq entry (car alist)
|
||||
alist (cdr alist)
|
||||
number (car entry)
|
||||
prefix (cdr entry)
|
||||
skip (gnus-cite-find-prefix number)
|
||||
face (cdr (assoc prefix face-alist)))
|
||||
;; Add attribution button.
|
||||
(goto-line number)
|
||||
(when (re-search-forward gnus-cite-attribution-suffix
|
||||
(save-excursion (end-of-line 1) (point))
|
||||
t)
|
||||
(gnus-article-add-button (match-beginning 1) (match-end 1)
|
||||
'gnus-cite-toggle prefix))
|
||||
;; Highlight attribution line.
|
||||
(gnus-cite-add-face number skip face)
|
||||
(gnus-cite-add-face number skip gnus-cite-attribution-face))
|
||||
;; Loop through attribution lines.
|
||||
(setq alist gnus-cite-loose-attribution-alist)
|
||||
(while alist
|
||||
(setq entry (car alist)
|
||||
alist (cdr alist)
|
||||
number (car entry)
|
||||
skip (gnus-cite-find-prefix number))
|
||||
(gnus-cite-add-face number skip gnus-cite-attribution-face)))))
|
||||
|
||||
(defun gnus-dissect-cited-text ()
|
||||
"Dissect the article buffer looking for cited text."
|
||||
(save-excursion
|
||||
(set-buffer gnus-article-buffer)
|
||||
(gnus-cite-parse-maybe)
|
||||
(let ((alist gnus-cite-prefix-alist)
|
||||
prefix numbers number marks m)
|
||||
;; Loop through citation prefixes.
|
||||
(while alist
|
||||
(setq numbers (pop alist)
|
||||
prefix (pop numbers))
|
||||
(while numbers
|
||||
(setq number (pop numbers))
|
||||
(goto-char (point-min))
|
||||
(forward-line number)
|
||||
(push (cons (point-marker) "") marks)
|
||||
(while (and numbers
|
||||
(= (1- number) (car numbers)))
|
||||
(setq number (pop numbers)))
|
||||
(goto-char (point-min))
|
||||
(forward-line (1- number))
|
||||
(push (cons (point-marker) prefix) marks)))
|
||||
;; Skip to the beginning of the body.
|
||||
(goto-char (point-min))
|
||||
(search-forward "\n\n" nil t)
|
||||
(push (cons (point-marker) "") marks)
|
||||
;; Find the end of the body.
|
||||
(goto-char (point-max))
|
||||
(gnus-article-search-signature)
|
||||
(push (cons (point-marker) "") marks)
|
||||
;; Sort the marks.
|
||||
(setq marks (sort marks (lambda (m1 m2) (< (car m1) (car m2)))))
|
||||
(let ((omarks marks))
|
||||
(setq marks nil)
|
||||
(while (cdr omarks)
|
||||
(if (= (caar omarks) (caadr omarks))
|
||||
(progn
|
||||
(unless (equal (cdar omarks) "")
|
||||
(push (car omarks) marks))
|
||||
(unless (equal (cdadr omarks) "")
|
||||
(push (cadr omarks) marks))
|
||||
(unless (and (equal (cdar omarks) "")
|
||||
(equal (cdadr omarks) "")
|
||||
(not (cddr omarks)))
|
||||
(setq omarks (cdr omarks))))
|
||||
(push (car omarks) marks))
|
||||
(setq omarks (cdr omarks)))
|
||||
(when (car omarks)
|
||||
(push (car omarks) marks))
|
||||
(setq marks (setq m (nreverse marks)))
|
||||
(while (cddr m)
|
||||
(if (and (equal (cdadr m) "")
|
||||
(equal (cdar m) (cdaddr m))
|
||||
(goto-char (caadr m))
|
||||
(forward-line 1)
|
||||
(= (point) (caaddr m)))
|
||||
(setcdr m (cdddr m))
|
||||
(setq m (cdr m))))
|
||||
marks))))
|
||||
|
||||
(defun gnus-article-fill-cited-article (&optional force width)
|
||||
"Do word wrapping in the current article.
|
||||
If WIDTH (the numerical prefix), use that text width when filling."
|
||||
(interactive (list t current-prefix-arg))
|
||||
(save-excursion
|
||||
(set-buffer gnus-article-buffer)
|
||||
(let ((buffer-read-only nil)
|
||||
(inhibit-point-motion-hooks t)
|
||||
(marks (gnus-dissect-cited-text))
|
||||
(adaptive-fill-mode nil)
|
||||
(filladapt-mode nil)
|
||||
(fill-column (if width (prefix-numeric-value width) fill-column)))
|
||||
(save-restriction
|
||||
(while (cdr marks)
|
||||
(widen)
|
||||
(narrow-to-region (caar marks) (caadr marks))
|
||||
(let ((adaptive-fill-regexp
|
||||
(concat "^" (regexp-quote (cdar marks)) " *"))
|
||||
(fill-prefix (cdar marks)))
|
||||
(fill-region (point-min) (point-max)))
|
||||
(set-marker (caar marks) nil)
|
||||
(setq marks (cdr marks)))
|
||||
(when marks
|
||||
(set-marker (caar marks) nil))
|
||||
;; All this information is now incorrect.
|
||||
(setq gnus-cite-prefix-alist nil
|
||||
gnus-cite-attribution-alist nil
|
||||
gnus-cite-loose-prefix-alist nil
|
||||
gnus-cite-loose-attribution-alist nil)))))
|
||||
|
||||
(defun gnus-article-hide-citation (&optional arg force)
|
||||
"Toggle hiding of all cited text except attribution lines.
|
||||
See the documentation for `gnus-article-highlight-citation'.
|
||||
If given a negative prefix, always show; if given a positive prefix,
|
||||
always hide."
|
||||
(interactive (append (gnus-article-hidden-arg) (list 'force)))
|
||||
(setq gnus-cited-text-button-line-format-spec
|
||||
(gnus-parse-format gnus-cited-text-button-line-format
|
||||
gnus-cited-text-button-line-format-alist t))
|
||||
(save-excursion
|
||||
(set-buffer gnus-article-buffer)
|
||||
(cond
|
||||
((gnus-article-check-hidden-text 'cite arg)
|
||||
t)
|
||||
((gnus-article-text-type-exists-p 'cite)
|
||||
(let ((buffer-read-only nil))
|
||||
(gnus-article-hide-text-of-type 'cite)))
|
||||
(t
|
||||
(let ((buffer-read-only nil)
|
||||
(marks (gnus-dissect-cited-text))
|
||||
(inhibit-point-motion-hooks t)
|
||||
(props (nconc (list 'article-type 'cite)
|
||||
gnus-hidden-properties))
|
||||
beg end)
|
||||
(while marks
|
||||
(setq beg nil
|
||||
end nil)
|
||||
(while (and marks (string= (cdar marks) ""))
|
||||
(setq marks (cdr marks)))
|
||||
(when marks
|
||||
(setq beg (caar marks)))
|
||||
(while (and marks (not (string= (cdar marks) "")))
|
||||
(setq marks (cdr marks)))
|
||||
(when marks
|
||||
(setq end (caar marks)))
|
||||
;; Skip past lines we want to leave visible.
|
||||
(when (and beg end gnus-cited-lines-visible)
|
||||
(goto-char beg)
|
||||
(forward-line gnus-cited-lines-visible)
|
||||
(if (>= (point) end)
|
||||
(setq beg nil)
|
||||
(setq beg (point-marker))))
|
||||
(when (and beg end)
|
||||
(gnus-add-text-properties beg end props)
|
||||
(goto-char beg)
|
||||
(unless (save-excursion (search-backward "\n\n" nil t))
|
||||
(insert "\n"))
|
||||
(put-text-property
|
||||
(point)
|
||||
(progn
|
||||
(gnus-article-add-button
|
||||
(point)
|
||||
(progn (eval gnus-cited-text-button-line-format-spec) (point))
|
||||
`gnus-article-toggle-cited-text (cons beg end))
|
||||
(point))
|
||||
'article-type 'annotation)
|
||||
(set-marker beg (point)))))))))
|
||||
|
||||
(defun gnus-article-toggle-cited-text (region)
|
||||
"Toggle hiding the text in REGION."
|
||||
(let (buffer-read-only)
|
||||
(funcall
|
||||
(if (text-property-any
|
||||
(car region) (1- (cdr region))
|
||||
(car gnus-hidden-properties) (cadr gnus-hidden-properties))
|
||||
'remove-text-properties 'gnus-add-text-properties)
|
||||
(car region) (cdr region) gnus-hidden-properties)))
|
||||
|
||||
(defun gnus-article-hide-citation-maybe (&optional arg force)
|
||||
"Toggle hiding of cited text that has an attribution line.
|
||||
If given a negative prefix, always show; if given a positive prefix,
|
||||
always hide.
|
||||
This will do nothing unless at least `gnus-cite-hide-percentage'
|
||||
percent and at least `gnus-cite-hide-absolute' lines of the body is
|
||||
cited text with attributions. When called interactively, these two
|
||||
variables are ignored.
|
||||
See also the documentation for `gnus-article-highlight-citation'."
|
||||
(interactive (append (gnus-article-hidden-arg) (list 'force)))
|
||||
(unless (gnus-article-check-hidden-text 'cite arg)
|
||||
(save-excursion
|
||||
(set-buffer gnus-article-buffer)
|
||||
(gnus-cite-parse-maybe force)
|
||||
(goto-char (point-min))
|
||||
(search-forward "\n\n" nil t)
|
||||
(let ((start (point))
|
||||
(atts gnus-cite-attribution-alist)
|
||||
(buffer-read-only nil)
|
||||
(inhibit-point-motion-hooks t)
|
||||
(hiden 0)
|
||||
total)
|
||||
(goto-char (point-max))
|
||||
(gnus-article-search-signature)
|
||||
(setq total (count-lines start (point)))
|
||||
(while atts
|
||||
(setq hiden (+ hiden (length (cdr (assoc (cdar atts)
|
||||
gnus-cite-prefix-alist))))
|
||||
atts (cdr atts)))
|
||||
(when (or force
|
||||
(and (> (* 100 hiden) (* gnus-cite-hide-percentage total))
|
||||
(> hiden gnus-cite-hide-absolute)))
|
||||
(setq atts gnus-cite-attribution-alist)
|
||||
(while atts
|
||||
(setq total (cdr (assoc (cdar atts) gnus-cite-prefix-alist))
|
||||
atts (cdr atts))
|
||||
(while total
|
||||
(setq hiden (car total)
|
||||
total (cdr total))
|
||||
(goto-line hiden)
|
||||
(unless (assq hiden gnus-cite-attribution-alist)
|
||||
(gnus-add-text-properties
|
||||
(point) (progn (forward-line 1) (point))
|
||||
(nconc (list 'article-type 'cite)
|
||||
gnus-hidden-properties))))))))))
|
||||
|
||||
(defun gnus-article-hide-citation-in-followups ()
|
||||
"Hide cited text in non-root articles."
|
||||
(interactive)
|
||||
(save-excursion
|
||||
(set-buffer gnus-article-buffer)
|
||||
(let ((article (cdr gnus-article-current)))
|
||||
(unless (save-excursion
|
||||
(set-buffer gnus-summary-buffer)
|
||||
(gnus-article-displayed-root-p article))
|
||||
(gnus-article-hide-citation)))))
|
||||
|
||||
;;; Internal functions:
|
||||
|
||||
(defun gnus-cite-parse-maybe (&optional force)
|
||||
;; Parse if the buffer has changes since last time.
|
||||
(if (equal gnus-cite-article gnus-article-current)
|
||||
()
|
||||
;;Reset parser information.
|
||||
(setq gnus-cite-prefix-alist nil
|
||||
gnus-cite-attribution-alist nil
|
||||
gnus-cite-loose-prefix-alist nil
|
||||
gnus-cite-loose-attribution-alist nil)
|
||||
;; Parse if not too large.
|
||||
(if (and (not force)
|
||||
gnus-cite-parse-max-size
|
||||
(> (buffer-size) gnus-cite-parse-max-size))
|
||||
()
|
||||
(setq gnus-cite-article (cons (car gnus-article-current)
|
||||
(cdr gnus-article-current)))
|
||||
(gnus-cite-parse-wrapper))))
|
||||
|
||||
(defun gnus-cite-parse-wrapper ()
|
||||
;; Wrap chopped gnus-cite-parse
|
||||
(goto-char (point-min))
|
||||
(unless (search-forward "\n\n" nil t)
|
||||
(goto-char (point-max)))
|
||||
(save-excursion
|
||||
(gnus-cite-parse-attributions))
|
||||
;; Try to avoid check citation if there is no reason to believe
|
||||
;; that article has citations
|
||||
(if (or gnus-cite-always-check
|
||||
(save-excursion
|
||||
(re-search-backward gnus-cite-reply-regexp nil t))
|
||||
gnus-cite-loose-attribution-alist)
|
||||
(progn (save-excursion
|
||||
(gnus-cite-parse))
|
||||
(save-excursion
|
||||
(gnus-cite-connect-attributions)))))
|
||||
|
||||
(defun gnus-cite-parse ()
|
||||
;; Parse and connect citation prefixes and attribution lines.
|
||||
|
||||
;; Parse current buffer searching for citation prefixes.
|
||||
(let ((line (1+ (count-lines (point-min) (point))))
|
||||
(case-fold-search t)
|
||||
(max (save-excursion
|
||||
(goto-char (point-max))
|
||||
(gnus-article-search-signature)
|
||||
(point)))
|
||||
alist entry start begin end numbers prefix)
|
||||
;; Get all potential prefixes in `alist'.
|
||||
(while (< (point) max)
|
||||
;; Each line.
|
||||
(setq begin (point)
|
||||
end (progn (beginning-of-line 2) (point))
|
||||
start end)
|
||||
(goto-char begin)
|
||||
;; Ignore standard Supercite attribution prefix.
|
||||
(when (looking-at gnus-supercite-regexp)
|
||||
(if (match-end 1)
|
||||
(setq end (1+ (match-end 1)))
|
||||
(setq end (1+ begin))))
|
||||
;; Ignore very long prefixes.
|
||||
(when (> end (+ (point) gnus-cite-max-prefix))
|
||||
(setq end (+ (point) gnus-cite-max-prefix)))
|
||||
(while (re-search-forward gnus-cite-prefix-regexp (1- end) t)
|
||||
;; Each prefix.
|
||||
(setq end (match-end 0)
|
||||
prefix (buffer-substring begin end))
|
||||
(gnus-set-text-properties 0 (length prefix) nil prefix)
|
||||
(setq entry (assoc prefix alist))
|
||||
(if entry
|
||||
(setcdr entry (cons line (cdr entry)))
|
||||
(push (list prefix line) alist))
|
||||
(goto-char begin))
|
||||
(goto-char start)
|
||||
(setq line (1+ line)))
|
||||
;; We got all the potential prefixes. Now create
|
||||
;; `gnus-cite-prefix-alist' containing the oldest prefix for each
|
||||
;; line that appears at least gnus-cite-minimum-match-count
|
||||
;; times. First sort them by length. Longer is older.
|
||||
(setq alist (sort alist (lambda (a b)
|
||||
(> (length (car a)) (length (car b))))))
|
||||
(while alist
|
||||
(setq entry (car alist)
|
||||
prefix (car entry)
|
||||
numbers (cdr entry)
|
||||
alist (cdr alist))
|
||||
(cond ((null numbers)
|
||||
;; No lines with this prefix that wasn't also part of
|
||||
;; a longer prefix.
|
||||
)
|
||||
((< (length numbers) gnus-cite-minimum-match-count)
|
||||
;; Too few lines with this prefix. We keep it a bit
|
||||
;; longer in case it is an exact match for an attribution
|
||||
;; line, but we don't remove the line from other
|
||||
;; prefixes.
|
||||
(push entry gnus-cite-prefix-alist))
|
||||
(t
|
||||
(push entry
|
||||
gnus-cite-prefix-alist)
|
||||
;; Remove articles from other prefixes.
|
||||
(let ((loop alist)
|
||||
current)
|
||||
(while loop
|
||||
(setq current (car loop)
|
||||
loop (cdr loop))
|
||||
(setcdr current
|
||||
(gnus-set-difference (cdr current) numbers)))))))))
|
||||
|
||||
(defun gnus-cite-parse-attributions ()
|
||||
(let (al-alist)
|
||||
;; Parse attributions
|
||||
(while (re-search-forward gnus-cite-attribution-suffix (point-max) t)
|
||||
(let* ((start (match-beginning 0))
|
||||
(end (match-end 0))
|
||||
(wrote (count-lines (point-min) end))
|
||||
(prefix (gnus-cite-find-prefix wrote))
|
||||
;; Check previous line for an attribution leader.
|
||||
(tag (progn
|
||||
(beginning-of-line 1)
|
||||
(when (looking-at gnus-supercite-secondary-regexp)
|
||||
(buffer-substring (match-beginning 1)
|
||||
(match-end 1)))))
|
||||
(in (progn
|
||||
(goto-char start)
|
||||
(and (re-search-backward gnus-cite-attribution-prefix
|
||||
(save-excursion
|
||||
(beginning-of-line 0)
|
||||
(point))
|
||||
t)
|
||||
(not (re-search-forward gnus-cite-attribution-suffix
|
||||
start t))
|
||||
(count-lines (point-min) (1+ (point)))))))
|
||||
(when (eq wrote in)
|
||||
(setq in nil))
|
||||
(goto-char end)
|
||||
;; don't add duplicates
|
||||
(let ((al (buffer-substring (save-excursion (beginning-of-line 0)
|
||||
(1+ (point)))
|
||||
end)))
|
||||
(if (not (assoc al al-alist))
|
||||
(progn
|
||||
(push (list wrote in prefix tag)
|
||||
gnus-cite-loose-attribution-alist)
|
||||
(push (cons al t) al-alist))))))))
|
||||
|
||||
(defun gnus-cite-connect-attributions ()
|
||||
;; Connect attributions to citations
|
||||
|
||||
;; No citations have been connected to attribution lines yet.
|
||||
(setq gnus-cite-loose-prefix-alist (append gnus-cite-prefix-alist nil))
|
||||
|
||||
;; Parse current buffer searching for attribution lines.
|
||||
;; Find exact supercite citations.
|
||||
(gnus-cite-match-attributions 'small nil
|
||||
(lambda (prefix tag)
|
||||
(when tag
|
||||
(concat "\\`"
|
||||
(regexp-quote prefix) "[ \t]*"
|
||||
(regexp-quote tag) ">"))))
|
||||
;; Find loose supercite citations after attributions.
|
||||
(gnus-cite-match-attributions 'small t
|
||||
(lambda (prefix tag)
|
||||
(when tag
|
||||
(concat "\\<"
|
||||
(regexp-quote tag)
|
||||
"\\>"))))
|
||||
;; Find loose supercite citations anywhere.
|
||||
(gnus-cite-match-attributions 'small nil
|
||||
(lambda (prefix tag)
|
||||
(when tag
|
||||
(concat "\\<"
|
||||
(regexp-quote tag)
|
||||
"\\>"))))
|
||||
;; Find nested citations after attributions.
|
||||
(gnus-cite-match-attributions 'small-if-unique t
|
||||
(lambda (prefix tag)
|
||||
(concat "\\`" (regexp-quote prefix) ".+")))
|
||||
;; Find nested citations anywhere.
|
||||
(gnus-cite-match-attributions 'small nil
|
||||
(lambda (prefix tag)
|
||||
(concat "\\`" (regexp-quote prefix) ".+")))
|
||||
;; Remove loose prefixes with too few lines.
|
||||
(let ((alist gnus-cite-loose-prefix-alist)
|
||||
entry)
|
||||
(while alist
|
||||
(setq entry (car alist)
|
||||
alist (cdr alist))
|
||||
(when (< (length (cdr entry)) gnus-cite-minimum-match-count)
|
||||
(setq gnus-cite-prefix-alist
|
||||
(delq entry gnus-cite-prefix-alist)
|
||||
gnus-cite-loose-prefix-alist
|
||||
(delq entry gnus-cite-loose-prefix-alist)))))
|
||||
;; Find flat attributions.
|
||||
(gnus-cite-match-attributions 'first t nil)
|
||||
;; Find any attributions (are we getting desperate yet?).
|
||||
(gnus-cite-match-attributions 'first nil nil))
|
||||
|
||||
(defun gnus-cite-match-attributions (sort after fun)
|
||||
;; Match all loose attributions and citations (SORT AFTER FUN) .
|
||||
;;
|
||||
;; If SORT is `small', the citation with the shortest prefix will be
|
||||
;; used, if it is `first' the first prefix will be used, if it is
|
||||
;; `small-if-unique' the shortest prefix will be used if the
|
||||
;; attribution line does not share its own prefix with other
|
||||
;; loose attribution lines, otherwise the first prefix will be used.
|
||||
;;
|
||||
;; If AFTER is non-nil, only citations after the attribution line
|
||||
;; will be considered.
|
||||
;;
|
||||
;; If FUN is non-nil, it will be called with the arguments (WROTE
|
||||
;; PREFIX TAG) and expected to return a regular expression. Only
|
||||
;; citations whose prefix matches the regular expression will be
|
||||
;; considered.
|
||||
;;
|
||||
;; WROTE is the attribution line number.
|
||||
;; PREFIX is the attribution line prefix.
|
||||
;; TAG is the Supercite tag on the attribution line.
|
||||
(let ((atts gnus-cite-loose-attribution-alist)
|
||||
(case-fold-search t)
|
||||
att wrote in prefix tag regexp limit smallest best size)
|
||||
(while atts
|
||||
(setq att (car atts)
|
||||
atts (cdr atts)
|
||||
wrote (nth 0 att)
|
||||
in (nth 1 att)
|
||||
prefix (nth 2 att)
|
||||
tag (nth 3 att)
|
||||
regexp (if fun (funcall fun prefix tag) "")
|
||||
size (cond ((eq sort 'small) t)
|
||||
((eq sort 'first) nil)
|
||||
(t (< (length (gnus-cite-find-loose prefix)) 2)))
|
||||
limit (if after wrote -1)
|
||||
smallest 1000000
|
||||
best nil)
|
||||
(let ((cites gnus-cite-loose-prefix-alist)
|
||||
cite candidate numbers first compare)
|
||||
(while cites
|
||||
(setq cite (car cites)
|
||||
cites (cdr cites)
|
||||
candidate (car cite)
|
||||
numbers (cdr cite)
|
||||
first (apply 'min numbers)
|
||||
compare (if size (length candidate) first))
|
||||
(and (> first limit)
|
||||
regexp
|
||||
(string-match regexp candidate)
|
||||
(< compare smallest)
|
||||
(setq best cite
|
||||
smallest compare))))
|
||||
(if (null best)
|
||||
()
|
||||
(setq gnus-cite-loose-attribution-alist
|
||||
(delq att gnus-cite-loose-attribution-alist))
|
||||
(push (cons wrote (car best)) gnus-cite-attribution-alist)
|
||||
(when in
|
||||
(push (cons in (car best)) gnus-cite-attribution-alist))
|
||||
(when (memq best gnus-cite-loose-prefix-alist)
|
||||
(let ((loop gnus-cite-prefix-alist)
|
||||
(numbers (cdr best))
|
||||
current)
|
||||
(setq gnus-cite-loose-prefix-alist
|
||||
(delq best gnus-cite-loose-prefix-alist))
|
||||
(while loop
|
||||
(setq current (car loop)
|
||||
loop (cdr loop))
|
||||
(if (eq current best)
|
||||
()
|
||||
(setcdr current (gnus-set-difference (cdr current) numbers))
|
||||
(when (null (cdr current))
|
||||
(setq gnus-cite-loose-prefix-alist
|
||||
(delq current gnus-cite-loose-prefix-alist)
|
||||
atts (delq current atts)))))))))))
|
||||
|
||||
(defun gnus-cite-find-loose (prefix)
|
||||
;; Return a list of loose attribution lines prefixed by PREFIX.
|
||||
(let* ((atts gnus-cite-loose-attribution-alist)
|
||||
att line lines)
|
||||
(while atts
|
||||
(setq att (car atts)
|
||||
line (car att)
|
||||
atts (cdr atts))
|
||||
(when (string-equal (gnus-cite-find-prefix line) prefix)
|
||||
(push line lines)))
|
||||
lines))
|
||||
|
||||
(defun gnus-cite-add-face (number prefix face)
|
||||
;; At line NUMBER, ignore PREFIX and add FACE to the rest of the line.
|
||||
(when face
|
||||
(let ((inhibit-point-motion-hooks t)
|
||||
from to)
|
||||
(goto-line number)
|
||||
(unless (eobp);; Sometimes things become confused.
|
||||
(forward-char (length prefix))
|
||||
(skip-chars-forward " \t")
|
||||
(setq from (point))
|
||||
(end-of-line 1)
|
||||
(skip-chars-backward " \t")
|
||||
(setq to (point))
|
||||
(when (< from to)
|
||||
(gnus-overlay-put (gnus-make-overlay from to) 'face face))))))
|
||||
|
||||
(defun gnus-cite-toggle (prefix)
|
||||
(save-excursion
|
||||
(set-buffer gnus-article-buffer)
|
||||
(let ((buffer-read-only nil)
|
||||
(numbers (cdr (assoc prefix gnus-cite-prefix-alist)))
|
||||
(inhibit-point-motion-hooks t)
|
||||
number)
|
||||
(while numbers
|
||||
(setq number (car numbers)
|
||||
numbers (cdr numbers))
|
||||
(goto-line number)
|
||||
(cond ((get-text-property (point) 'invisible)
|
||||
(remove-text-properties (point) (progn (forward-line 1) (point))
|
||||
gnus-hidden-properties))
|
||||
((assq number gnus-cite-attribution-alist))
|
||||
(t
|
||||
(gnus-add-text-properties
|
||||
(point) (progn (forward-line 1) (point))
|
||||
(nconc (list 'article-type 'cite)
|
||||
gnus-hidden-properties))))))))
|
||||
|
||||
(defun gnus-cite-find-prefix (line)
|
||||
;; Return citation prefix for LINE.
|
||||
(let ((alist gnus-cite-prefix-alist)
|
||||
(prefix "")
|
||||
entry)
|
||||
(while alist
|
||||
(setq entry (car alist)
|
||||
alist (cdr alist))
|
||||
(when (memq line (cdr entry))
|
||||
(setq prefix (car entry))))
|
||||
prefix))
|
||||
|
||||
(gnus-add-shutdown 'gnus-cache-close 'gnus)
|
||||
|
||||
(defun gnus-cache-close ()
|
||||
(setq gnus-cite-prefix-alist nil))
|
||||
|
||||
(gnus-ems-redefine)
|
||||
|
||||
(provide 'gnus-cite)
|
||||
|
||||
;;; gnus-cite.el ends here
|
||||
650
lisp/gnus/gnus-cus.el
Normal file
650
lisp/gnus/gnus-cus.el
Normal file
|
|
@ -0,0 +1,650 @@
|
|||
;;; gnus-cus.el --- customization commands for Gnus
|
||||
;;
|
||||
;; Copyright (C) 1996 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Per Abrahamsen <abraham@dina.kvl.dk>
|
||||
;; Keywords: news
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'wid-edit)
|
||||
(require 'gnus-score)
|
||||
|
||||
;;; Widgets:
|
||||
|
||||
;; There should be special validation for this.
|
||||
(define-widget 'gnus-email-address 'string
|
||||
"An email address")
|
||||
|
||||
(defun gnus-custom-mode ()
|
||||
"Major mode for editing Gnus customization buffers.
|
||||
|
||||
The following commands are available:
|
||||
|
||||
\\[widget-forward] Move to next button or editable field.
|
||||
\\[widget-backward] Move to previous button or editable field.
|
||||
\\[widget-button-click] Activate button under the mouse pointer.
|
||||
\\[widget-button-press] Activate button under point.
|
||||
|
||||
Entry to this mode calls the value of `gnus-custom-mode-hook'
|
||||
if that value is non-nil."
|
||||
(kill-all-local-variables)
|
||||
(setq major-mode 'gnus-custom-mode
|
||||
mode-name "Gnus Customize")
|
||||
(use-local-map widget-keymap)
|
||||
(run-hooks 'gnus-custom-mode-hook))
|
||||
|
||||
;;; Group Customization:
|
||||
|
||||
(defconst gnus-group-parameters
|
||||
'((to-address (gnus-email-address :tag "To Address") "\
|
||||
This will be used when doing followups and posts.
|
||||
|
||||
This is primarily useful in mail groups that represent closed
|
||||
mailing lists--mailing lists where it's expected that everybody that
|
||||
writes to the mailing list is subscribed to it. Since using this
|
||||
parameter ensures that the mail only goes to the mailing list itself,
|
||||
it means that members won't receive two copies of your followups.
|
||||
|
||||
Using `to-address' will actually work whether the group is foreign or
|
||||
not. Let's say there's a group on the server that is called
|
||||
`fa.4ad-l'. This is a real newsgroup, but the server has gotten the
|
||||
articles from a mail-to-news gateway. Posting directly to this group
|
||||
is therefore impossible--you have to send mail to the mailing list
|
||||
address instead.")
|
||||
|
||||
(to-list (gnus-email-address :tag "To List") "\
|
||||
This address will be used when doing a `a' in the group.
|
||||
|
||||
It is totally ignored when doing a followup--except that if it is
|
||||
present in a news group, you'll get mail group semantics when doing
|
||||
`f'.")
|
||||
|
||||
(broken-reply-to (const :tag "Broken Reply To" t) "\
|
||||
Ignore `Reply-To' headers in this group.
|
||||
|
||||
That can be useful if you're reading a mailing list group where the
|
||||
listserv has inserted `Reply-To' headers that point back to the
|
||||
listserv itself. This is broken behavior. So there!")
|
||||
|
||||
(to-group (string :tag "To Group") "\
|
||||
All posts will be send to the specified group.")
|
||||
|
||||
(gcc-self (choice :tag "GCC"
|
||||
:value t
|
||||
(const t)
|
||||
(const none)
|
||||
(string :format "%v" :hide-front-space t)) "\
|
||||
Specify default value for GCC header.
|
||||
|
||||
If this symbol is present in the group parameter list and set to `t',
|
||||
new composed messages will be `Gcc''d to the current group. If it is
|
||||
present and set to `none', no `Gcc:' header will be generated, if it
|
||||
is present and a string, this string will be inserted literally as a
|
||||
`gcc' header (this symbol takes precedence over any default `Gcc'
|
||||
rules as described later).")
|
||||
|
||||
(auto-expire (const :tag "Automatic Expire" t) "\
|
||||
All articles that are read will be marked as expirable.")
|
||||
|
||||
(total-expire (const :tag "Total Expire" t) "\
|
||||
All read articles will be put through the expiry process
|
||||
|
||||
This happens even if they are not marked as expirable.
|
||||
Use with caution.")
|
||||
|
||||
(expiry-wait (choice :tag "Expire Wait"
|
||||
:value never
|
||||
(const never)
|
||||
(const immediate)
|
||||
(number :hide-front-space t
|
||||
:format "%v")) "\
|
||||
When to expire.
|
||||
|
||||
Overrides any `nnmail-expiry-wait' and `nnmail-expiry-wait-function'
|
||||
when expiring expirable messages. The value can either be a number of
|
||||
days (not necessarily an integer) or the symbols `never' or
|
||||
`immediate'.")
|
||||
|
||||
(score-file (file :tag "Score File") "\
|
||||
Make the specified file into the current score file.
|
||||
This means that all score commands you issue will end up in this file.")
|
||||
|
||||
(adapt-file (file :tag "Adapt File") "\
|
||||
Make the specified file into the current adaptive file.
|
||||
All adaptive score entries will be put into this file.")
|
||||
|
||||
(admin-address (gnus-email-address :tag "Admin Address") "\
|
||||
Administration address for a mailing list.
|
||||
|
||||
When unsubscribing to a mailing list you should never send the
|
||||
unsubscription notice to the mailing list itself. Instead, you'd
|
||||
send messages to the administrative address. This parameter allows
|
||||
you to put the admin address somewhere convenient.")
|
||||
|
||||
(display (choice :tag "Display"
|
||||
:value default
|
||||
(const all)
|
||||
(const default)) "\
|
||||
Which articles to display on entering the group.
|
||||
|
||||
`all'
|
||||
Display all articles, both read and unread.
|
||||
|
||||
`default'
|
||||
Display the default visible articles, which normally includes
|
||||
unread and ticked articles.")
|
||||
|
||||
(comment (string :tag "Comment") "\
|
||||
An arbitrary comment on the group."))
|
||||
"Alist of valid group parameters.
|
||||
|
||||
Each entry has the form (NAME TYPE DOC), where NAME is the parameter
|
||||
itself (a symbol), TYPE is the parameters type (a sexp widget), and
|
||||
DOC is a documentation string for the parameter.")
|
||||
|
||||
(defvar gnus-custom-params)
|
||||
(defvar gnus-custom-method)
|
||||
(defvar gnus-custom-group)
|
||||
|
||||
(defun gnus-group-customize (group &optional part)
|
||||
"Edit the group on the current line."
|
||||
(interactive (list (gnus-group-group-name)))
|
||||
(let ((part (or part 'info))
|
||||
info
|
||||
(types (mapcar (lambda (entry)
|
||||
`(cons :format "%v%h\n"
|
||||
:doc ,(nth 2 entry)
|
||||
(const :format "" ,(nth 0 entry))
|
||||
,(nth 1 entry)))
|
||||
gnus-group-parameters)))
|
||||
(unless group
|
||||
(error "No group on current line"))
|
||||
(unless (setq info (gnus-get-info group))
|
||||
(error "Killed group; can't be edited"))
|
||||
;; Ready.
|
||||
(kill-buffer (get-buffer-create "*Gnus Customize*"))
|
||||
(switch-to-buffer (get-buffer-create "*Gnus Customize*"))
|
||||
(gnus-custom-mode)
|
||||
(make-local-variable 'gnus-custom-group)
|
||||
(setq gnus-custom-group group)
|
||||
(widget-insert "Customize the ")
|
||||
(widget-create 'info-link
|
||||
:help-echo "Push me to learn more."
|
||||
:tag "group parameters"
|
||||
"(gnus)Group Parameters")
|
||||
(widget-insert " for <")
|
||||
(widget-insert group)
|
||||
(widget-insert "> and press ")
|
||||
(widget-create 'push-button
|
||||
:tag "done"
|
||||
:help-echo "Push me when done customizing."
|
||||
:action 'gnus-group-customize-done)
|
||||
(widget-insert ".\n\n")
|
||||
(make-local-variable 'gnus-custom-params)
|
||||
(setq gnus-custom-params
|
||||
(widget-create 'group
|
||||
:value (gnus-info-params info)
|
||||
`(set :inline t
|
||||
:greedy t
|
||||
:tag "Parameters"
|
||||
:format "%t:\n%h%v"
|
||||
:doc "\
|
||||
These special paramerters are recognized by Gnus.
|
||||
Check the [ ] for the parameters you want to apply to this group, then
|
||||
edit the value to suit your taste."
|
||||
,@types)
|
||||
'(repeat :inline t
|
||||
:tag "Variables"
|
||||
:format "%t:\n%h%v%i\n\n"
|
||||
:doc "\
|
||||
Set variables local to the group you are entering.
|
||||
|
||||
If you want to turn threading off in `news.answers', you could put
|
||||
`(gnus-show-threads nil)' in the group parameters of that group.
|
||||
`gnus-show-threads' will be made into a local variable in the summary
|
||||
buffer you enter, and the form `nil' will be `eval'ed there.
|
||||
|
||||
This can also be used as a group-specific hook function, if you'd
|
||||
like. If you want to hear a beep when you enter a group, you could
|
||||
put something like `(dummy-variable (ding))' in the parameters of that
|
||||
group. `dummy-variable' will be set to the result of the `(ding)'
|
||||
form, but who cares?"
|
||||
(group :value (nil nil)
|
||||
(symbol :tag "Variable")
|
||||
(sexp :tag
|
||||
"Value")))
|
||||
|
||||
'(repeat :inline t
|
||||
:tag "Unknown entries"
|
||||
sexp)))
|
||||
(widget-insert "\n\nYou can also edit the ")
|
||||
(widget-create 'info-link
|
||||
:tag "select method"
|
||||
:help-echo "Push me to learn more about select methods."
|
||||
"(gnus)Select Methods")
|
||||
(widget-insert " for the group.\n")
|
||||
(setq gnus-custom-method
|
||||
(widget-create 'sexp
|
||||
:tag "Method"
|
||||
:value (gnus-info-method info)))
|
||||
(use-local-map widget-keymap)
|
||||
(widget-setup)))
|
||||
|
||||
(defun gnus-group-customize-done (&rest ignore)
|
||||
"Apply changes and bury the buffer."
|
||||
(interactive)
|
||||
(gnus-group-edit-group-done 'params gnus-custom-group
|
||||
(widget-value gnus-custom-params))
|
||||
(gnus-group-edit-group-done 'method gnus-custom-group
|
||||
(widget-value gnus-custom-method))
|
||||
(bury-buffer))
|
||||
|
||||
;;; Score Customization:
|
||||
|
||||
(defconst gnus-score-parameters
|
||||
'((mark (number :tag "Mark") "\
|
||||
The value of this entry should be a number.
|
||||
Any articles with a score lower than this number will be marked as read.")
|
||||
|
||||
(expunge (number :tag "Expunge") "\
|
||||
The value of this entry should be a number.
|
||||
Any articles with a score lower than this number will be removed from
|
||||
the summary buffer.")
|
||||
|
||||
(mark-and-expunge (number :tag "Mark-and-expunge") "\
|
||||
The value of this entry should be a number.
|
||||
Any articles with a score lower than this number will be marked as
|
||||
read and removed from the summary buffer.")
|
||||
|
||||
(thread-mark-and-expunge (number :tag "Thread-mark-and-expunge") "\
|
||||
The value of this entry should be a number.
|
||||
All articles that belong to a thread that has a total score below this
|
||||
number will be marked as read and removed from the summary buffer.
|
||||
`gnus-thread-score-function' says how to compute the total score
|
||||
for a thread.")
|
||||
|
||||
(files (repeat :tag "Files" file) "\
|
||||
The value of this entry should be any number of file names.
|
||||
These files are assumed to be score files as well, and will be loaded
|
||||
the same way this one was.")
|
||||
|
||||
(exclude-files (repeat :tag "Exclude-files" file) "\
|
||||
The clue of this entry should be any number of files.
|
||||
These files will not be loaded, even though they would normally be so,
|
||||
for some reason or other.")
|
||||
|
||||
(eval (sexp :tag "Eval" :value nil) "\
|
||||
The value of this entry will be `eval'el.
|
||||
This element will be ignored when handling global score files.")
|
||||
|
||||
(read-only (boolean :tag "Read-only" :value t) "\
|
||||
Read-only score files will not be updated or saved.
|
||||
Global score files should feature this atom.")
|
||||
|
||||
(orphan (number :tag "Orphan") "\
|
||||
The value of this entry should be a number.
|
||||
Articles that do not have parents will get this number added to their
|
||||
scores. Imagine you follow some high-volume newsgroup, like
|
||||
`comp.lang.c'. Most likely you will only follow a few of the threads,
|
||||
also want to see any new threads.
|
||||
|
||||
You can do this with the following two score file entries:
|
||||
|
||||
(orphan -500)
|
||||
(mark-and-expunge -100)
|
||||
|
||||
When you enter the group the first time, you will only see the new
|
||||
threads. You then raise the score of the threads that you find
|
||||
interesting (with `I T' or `I S'), and ignore (`C y') the rest.
|
||||
Next time you enter the group, you will see new articles in the
|
||||
interesting threads, plus any new threads.
|
||||
|
||||
I.e.---the orphan score atom is for high-volume groups where there
|
||||
exist a few interesting threads which can't be found automatically
|
||||
by ordinary scoring rules.")
|
||||
|
||||
(adapt (choice :tag "Adapt"
|
||||
(const t)
|
||||
(const ignore)
|
||||
(sexp :format "%v"
|
||||
:hide-front-space t)) "\
|
||||
This entry controls the adaptive scoring.
|
||||
If it is `t', the default adaptive scoring rules will be used. If it
|
||||
is `ignore', no adaptive scoring will be performed on this group. If
|
||||
it is a list, this list will be used as the adaptive scoring rules.
|
||||
If it isn't present, or is something other than `t' or `ignore', the
|
||||
default adaptive scoring rules will be used. If you want to use
|
||||
adaptive scoring on most groups, you'd set `gnus-use-adaptive-scoring'
|
||||
to `t', and insert an `(adapt ignore)' in the groups where you do not
|
||||
want adaptive scoring. If you only want adaptive scoring in a few
|
||||
groups, you'd set `gnus-use-adaptive-scoring' to `nil', and insert
|
||||
`(adapt t)' in the score files of the groups where you want it.")
|
||||
|
||||
(adapt-file (file :tag "Adapt-file") "\
|
||||
All adaptive score entries will go to the file named by this entry.
|
||||
It will also be applied when entering the group. This atom might
|
||||
be handy if you want to adapt on several groups at once, using the
|
||||
same adaptive file for a number of groups.")
|
||||
|
||||
(local (repeat :tag "Local"
|
||||
(group :value (nil nil)
|
||||
(symbol :tag "Variable")
|
||||
(sexp :tag "Value"))) "\
|
||||
The value of this entry should be a list of `(VAR VALUE)' pairs.
|
||||
Each VAR will be made buffer-local to the current summary buffer,
|
||||
and set to the value specified. This is a convenient, if somewhat
|
||||
strange, way of setting variables in some groups if you don't like
|
||||
hooks much.")
|
||||
(touched (sexp :format "Touched\n") "Internal variable."))
|
||||
"Alist of valid symbolic score parameters.
|
||||
|
||||
Each entry has the form (NAME TYPE DOC), where NAME is the parameter
|
||||
itself (a symbol), TYPE is the parameters type (a sexp widget), and DOC is a
|
||||
documentation string for the parameter.")
|
||||
|
||||
(define-widget 'gnus-score-string 'group
|
||||
"Edit score entries for string-valued headers."
|
||||
:convert-widget 'gnus-score-string-convert)
|
||||
|
||||
(defun gnus-score-string-convert (widget)
|
||||
;; Set args appropriately.
|
||||
(let* ((tag (widget-get widget :tag))
|
||||
(item `(const :format "" :value ,(downcase tag)))
|
||||
(match '(string :tag "Match"))
|
||||
(score '(choice :tag "Score"
|
||||
(const :tag "default" nil)
|
||||
(integer :format "%v"
|
||||
:hide-front-space t)))
|
||||
(expire '(choice :tag "Expire"
|
||||
(const :tag "off" nil)
|
||||
(integer :format "%v"
|
||||
:hide-front-space t)))
|
||||
(type '(choice :tag "Type"
|
||||
:value s
|
||||
;; I should really create a forgiving :match
|
||||
;; function for each type below, that only
|
||||
;; looked at the first letter.
|
||||
(const :tag "Regexp" r)
|
||||
(const :tag "Regexp (fixed case)" R)
|
||||
(const :tag "Substring" s)
|
||||
(const :tag "Substring (fixed case)" S)
|
||||
(const :tag "Exact" e)
|
||||
(const :tag "Exact (fixed case)" E)
|
||||
(const :tag "Word" w)
|
||||
(const :tag "Word (fixed case)" W)
|
||||
(const :tag "default" nil)))
|
||||
(group `(group ,match ,score ,expire ,type))
|
||||
(doc (concat (or (widget-get widget :doc)
|
||||
(concat "Change score based on the " tag
|
||||
" header.\n"))
|
||||
"
|
||||
You can have an arbitrary number of score entries for this header,
|
||||
each score entry has four elements:
|
||||
|
||||
1. The \"match element\". This should be the string to look for in the
|
||||
header.
|
||||
|
||||
2. The \"score element\". This number should be an integer in the
|
||||
neginf to posinf interval. This number is added to the score
|
||||
of the article if the match is successful. If this element is
|
||||
not present, the `gnus-score-interactive-default-score' number
|
||||
will be used instead. This is 1000 by default.
|
||||
|
||||
3. The \"date element\". This date says when the last time this score
|
||||
entry matched, which provides a mechanism for expiring the
|
||||
score entries. It this element is not present, the score
|
||||
entry is permanent. The date is represented by the number of
|
||||
days since December 31, 1 ce.
|
||||
|
||||
4. The \"type element\". This element specifies what function should
|
||||
be used to see whether this score entry matches the article.
|
||||
|
||||
There are the regexp, as well as substring types, and exact match,
|
||||
and word match types. If this element is not present, Gnus will
|
||||
assume that substring matching should be used. There is case
|
||||
sensitive variants of all match types.")))
|
||||
(widget-put widget :args `(,item
|
||||
(repeat :inline t
|
||||
:indent 0
|
||||
:tag ,tag
|
||||
:doc ,doc
|
||||
:format "%t:\n%h%v%i\n\n"
|
||||
(choice :format "%v"
|
||||
:value ("" nil nil s)
|
||||
,group
|
||||
sexp)))))
|
||||
widget)
|
||||
|
||||
(define-widget 'gnus-score-integer 'group
|
||||
"Edit score entries for integer-valued headers."
|
||||
:convert-widget 'gnus-score-integer-convert)
|
||||
|
||||
(defun gnus-score-integer-convert (widget)
|
||||
;; Set args appropriately.
|
||||
(let* ((tag (widget-get widget :tag))
|
||||
(item `(const :format "" :value ,(downcase tag)))
|
||||
(match '(integer :tag "Match"))
|
||||
(score '(choice :tag "Score"
|
||||
(const :tag "default" nil)
|
||||
(integer :format "%v"
|
||||
:hide-front-space t)))
|
||||
(expire '(choice :tag "Expire"
|
||||
(const :tag "off" nil)
|
||||
(integer :format "%v"
|
||||
:hide-front-space t)))
|
||||
(type '(choice :tag "Type"
|
||||
:value <
|
||||
(const <)
|
||||
(const >)
|
||||
(const =)
|
||||
(const >=)
|
||||
(const <=)))
|
||||
(group `(group ,match ,score ,expire ,type))
|
||||
(doc (concat (or (widget-get widget :doc)
|
||||
(concat "Change score based on the " tag
|
||||
" header.")))))
|
||||
(widget-put widget :args `(,item
|
||||
(repeat :inline t
|
||||
:indent 0
|
||||
:tag ,tag
|
||||
:doc ,doc
|
||||
:format "%t:\n%h%v%i\n\n"
|
||||
,group))))
|
||||
widget)
|
||||
|
||||
(define-widget 'gnus-score-date 'group
|
||||
"Edit score entries for date-valued headers."
|
||||
:convert-widget 'gnus-score-date-convert)
|
||||
|
||||
(defun gnus-score-date-convert (widget)
|
||||
;; Set args appropriately.
|
||||
(let* ((tag (widget-get widget :tag))
|
||||
(item `(const :format "" :value ,(downcase tag)))
|
||||
(match '(string :tag "Match"))
|
||||
(score '(choice :tag "Score"
|
||||
(const :tag "default" nil)
|
||||
(integer :format "%v"
|
||||
:hide-front-space t)))
|
||||
(expire '(choice :tag "Expire"
|
||||
(const :tag "off" nil)
|
||||
(integer :format "%v"
|
||||
:hide-front-space t)))
|
||||
(type '(choice :tag "Type"
|
||||
:value regexp
|
||||
(const regexp)
|
||||
(const before)
|
||||
(const at)
|
||||
(const after)))
|
||||
(group `(group ,match ,score ,expire ,type))
|
||||
(doc (concat (or (widget-get widget :doc)
|
||||
(concat "Change score based on the " tag
|
||||
" header."))
|
||||
"
|
||||
For the Date header we have three kinda silly match types: `before',
|
||||
`at' and `after'. I can't really imagine this ever being useful, but,
|
||||
like, it would feel kinda silly not to provide this function. Just in
|
||||
case. You never know. Better safe than sorry. Once burnt, twice
|
||||
shy. Don't judge a book by its cover. Never not have sex on a first
|
||||
date. (I have been told that at least one person, and I quote,
|
||||
\"found this function indispensable\", however.)
|
||||
|
||||
A more useful match type is `regexp'. With it, you can match the date
|
||||
string using a regular expression. The date is normalized to ISO8601
|
||||
compact format first---`YYYYMMDDTHHMMSS'. If you want to match all
|
||||
articles that have been posted on April 1st in every year, you could
|
||||
use `....0401.........' as a match string, for instance. (Note that
|
||||
the date is kept in its original time zone, so this will match
|
||||
articles that were posted when it was April 1st where the article was
|
||||
posted from. Time zones are such wholesome fun for the whole family,
|
||||
eh?")))
|
||||
(widget-put widget :args `(,item
|
||||
(repeat :inline t
|
||||
:indent 0
|
||||
:tag ,tag
|
||||
:doc ,doc
|
||||
:format "%t:\n%h%v%i\n\n"
|
||||
,group))))
|
||||
widget)
|
||||
|
||||
(defvar gnus-custom-scores)
|
||||
(defvar gnus-custom-score-alist)
|
||||
|
||||
(defun gnus-score-customize (file)
|
||||
"Customize score file FILE."
|
||||
(interactive (list gnus-current-score-file))
|
||||
(let ((scores (gnus-score-load file))
|
||||
(types (mapcar (lambda (entry)
|
||||
`(group :format "%v%h\n"
|
||||
:doc ,(nth 2 entry)
|
||||
(const :format "" ,(nth 0 entry))
|
||||
,(nth 1 entry)))
|
||||
gnus-score-parameters)))
|
||||
;; Ready.
|
||||
(kill-buffer (get-buffer-create "*Gnus Customize*"))
|
||||
(switch-to-buffer (get-buffer-create "*Gnus Customize*"))
|
||||
(gnus-custom-mode)
|
||||
(make-local-variable 'gnus-custom-score-alist)
|
||||
(setq gnus-custom-score-alist scores)
|
||||
(widget-insert "Customize the ")
|
||||
(widget-create 'info-link
|
||||
:help-echo "Push me to learn more."
|
||||
:tag "score entries"
|
||||
"(gnus)Score File Format")
|
||||
(widget-insert " for\n\t")
|
||||
(widget-insert file)
|
||||
(widget-insert "\nand press ")
|
||||
(widget-create 'push-button
|
||||
:tag "done"
|
||||
:help-echo "Push me when done customizing."
|
||||
:action 'gnus-score-customize-done)
|
||||
(widget-insert ".\n
|
||||
Check the [ ] for the entries you want to apply to this score file, then
|
||||
edit the value to suit your taste. Don't forget to mark the checkbox,
|
||||
if you do all your changes will be lost. ")
|
||||
(widget-create 'push-button
|
||||
:action (lambda (&rest ignore)
|
||||
(require 'gnus-audio)
|
||||
(gnus-audio-play "Evil_Laugh.au"))
|
||||
"Bhahahah!")
|
||||
(widget-insert "\n\n")
|
||||
(make-local-variable 'gnus-custom-scores)
|
||||
(setq gnus-custom-scores
|
||||
(widget-create 'group
|
||||
:value scores
|
||||
`(checklist :inline t
|
||||
:greedy t
|
||||
(gnus-score-string :tag "From")
|
||||
(gnus-score-string :tag "Subject")
|
||||
(gnus-score-string :tag "References")
|
||||
(gnus-score-string :tag "Xref")
|
||||
(gnus-score-string :tag "Message-ID")
|
||||
(gnus-score-integer :tag "Lines")
|
||||
(gnus-score-integer :tag "Chars")
|
||||
(gnus-score-date :tag "Date")
|
||||
(gnus-score-string :tag "Head"
|
||||
:doc "\
|
||||
Match all headers in the article.
|
||||
|
||||
Using one of `Head', `Body', `All' will slow down scoring considerable.
|
||||
")
|
||||
(gnus-score-string :tag "Body"
|
||||
:doc "\
|
||||
Match the body sans header of the article.
|
||||
|
||||
Using one of `Head', `Body', `All' will slow down scoring considerable.
|
||||
")
|
||||
(gnus-score-string :tag "All"
|
||||
:doc "\
|
||||
Match the entire article, including both headers and body.
|
||||
|
||||
Using one of `Head', `Body', `All' will slow down scoring
|
||||
considerable.
|
||||
")
|
||||
(gnus-score-string :tag
|
||||
"Followup"
|
||||
:doc "\
|
||||
Score all followups to the specified authors.
|
||||
|
||||
This entry is somewhat special, in that it will match the `From:'
|
||||
header, and affect the score of not only the matching articles, but
|
||||
also all followups to the matching articles. This allows you
|
||||
e.g. increase the score of followups to your own articles, or decrease
|
||||
the score of followups to the articles of some known trouble-maker.
|
||||
")
|
||||
(gnus-score-string :tag "Thread"
|
||||
:doc "\
|
||||
Add a score entry on all articles that are part of a thread.
|
||||
|
||||
This match key works along the same lines as the `Followup' match key.
|
||||
If you say that you want to score on a (sub-)thread that is started by
|
||||
an article with a `Message-ID' X, then you add a `thread' match. This
|
||||
will add a new `thread' match for each article that has X in its
|
||||
`References' header. (These new `thread' matches will use the
|
||||
`Message-ID's of these matching articles.) This will ensure that you
|
||||
can raise/lower the score of an entire thread, even though some
|
||||
articles in the thread may not have complete `References' headers.
|
||||
Note that using this may lead to undeterministic scores of the
|
||||
articles in the thread.
|
||||
")
|
||||
,@types)
|
||||
'(repeat :inline t
|
||||
:tag "Unknown entries"
|
||||
sexp)))
|
||||
(use-local-map widget-keymap)
|
||||
(widget-setup)))
|
||||
|
||||
(defun gnus-score-customize-done (&rest ignore)
|
||||
"Reset the score alist with the present value."
|
||||
(let ((alist gnus-custom-score-alist)
|
||||
(value (widget-value gnus-custom-scores)))
|
||||
(setcar alist (car value))
|
||||
(setcdr alist (cdr value))
|
||||
(gnus-score-set 'touched '(t) alist))
|
||||
(bury-buffer))
|
||||
|
||||
;;; The End:
|
||||
|
||||
(provide 'gnus-cus)
|
||||
|
||||
;;; gnus-cus.el ends here
|
||||
|
||||
290
lisp/gnus/gnus-demon.el
Normal file
290
lisp/gnus/gnus-demon.el
Normal file
|
|
@ -0,0 +1,290 @@
|
|||
;;; gnus-demon.el --- daemonic Gnus behaviour
|
||||
;; Copyright (C) 1995,96,97 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
|
||||
;; Keywords: news
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'gnus)
|
||||
(require 'gnus-int)
|
||||
(require 'nnheader)
|
||||
(eval-and-compile
|
||||
(if (string-match "XEmacs" (emacs-version))
|
||||
(require 'itimer)
|
||||
(require 'timer)))
|
||||
|
||||
(defgroup gnus-demon nil
|
||||
"Demonic behaviour."
|
||||
:group 'gnus)
|
||||
|
||||
(defcustom gnus-demon-handlers nil
|
||||
"Alist of daemonic handlers to be run at intervals.
|
||||
Each handler is a list on the form
|
||||
|
||||
\(FUNCTION TIME IDLE)
|
||||
|
||||
FUNCTION is the function to be called.
|
||||
TIME is the number of `gnus-demon-timestep's between each call.
|
||||
If nil, never call. If t, call each `gnus-demon-timestep'.
|
||||
If IDLE is t, only call if Emacs has been idle for a while. If IDLE
|
||||
is a number, only call when Emacs has been idle more than this number
|
||||
of `gnus-demon-timestep's. If IDLE is nil, don't care about
|
||||
idleness. If IDLE is a number and TIME is nil, then call once each
|
||||
time Emacs has been idle for IDLE `gnus-demon-timestep's."
|
||||
:group 'gnus-demon
|
||||
:type '(repeat (list function
|
||||
(choice :tag "Time"
|
||||
(const :tag "never" nil)
|
||||
(const :tag "one" t)
|
||||
(integer :tag "steps" 1))
|
||||
(choice :tag "Idle"
|
||||
(const :tag "don't care" nil)
|
||||
(const :tag "for a while" t)
|
||||
(integer :tag "steps" 1)))))
|
||||
|
||||
(defcustom gnus-demon-timestep 60
|
||||
"*Number of seconds in each demon timestep."
|
||||
:group 'gnus-demon
|
||||
:type 'integer)
|
||||
|
||||
;;; Internal variables.
|
||||
|
||||
(defvar gnus-demon-timer nil)
|
||||
(defvar gnus-demon-idle-has-been-called nil)
|
||||
(defvar gnus-demon-idle-time 0)
|
||||
(defvar gnus-demon-handler-state nil)
|
||||
(defvar gnus-demon-last-keys nil)
|
||||
(defvar gnus-inhibit-demon nil
|
||||
"*If non-nil, no daemonic function will be run.")
|
||||
|
||||
(eval-and-compile
|
||||
(autoload 'timezone-parse-date "timezone")
|
||||
(autoload 'timezone-make-arpa-date "timezone"))
|
||||
|
||||
;;; Functions.
|
||||
|
||||
(defun gnus-demon-add-handler (function time idle)
|
||||
"Add the handler FUNCTION to be run at TIME and IDLE."
|
||||
;; First remove any old handlers that use this function.
|
||||
(gnus-demon-remove-handler function)
|
||||
;; Then add the new one.
|
||||
(push (list function time idle) gnus-demon-handlers)
|
||||
(gnus-demon-init))
|
||||
|
||||
(defun gnus-demon-remove-handler (function &optional no-init)
|
||||
"Remove the handler FUNCTION from the list of handlers."
|
||||
(setq gnus-demon-handlers
|
||||
(delq (assq function gnus-demon-handlers)
|
||||
gnus-demon-handlers))
|
||||
(unless no-init
|
||||
(gnus-demon-init)))
|
||||
|
||||
(defun gnus-demon-init ()
|
||||
"Initialize the Gnus daemon."
|
||||
(interactive)
|
||||
(gnus-demon-cancel)
|
||||
(if (null gnus-demon-handlers)
|
||||
() ; Nothing to do.
|
||||
;; Set up timer.
|
||||
(setq gnus-demon-timer
|
||||
(nnheader-run-at-time
|
||||
gnus-demon-timestep gnus-demon-timestep 'gnus-demon))
|
||||
;; Reset control variables.
|
||||
(setq gnus-demon-handler-state
|
||||
(mapcar
|
||||
(lambda (handler)
|
||||
(list (car handler) (gnus-demon-time-to-step (nth 1 handler))
|
||||
(nth 2 handler)))
|
||||
gnus-demon-handlers))
|
||||
(setq gnus-demon-idle-time 0)
|
||||
(setq gnus-demon-idle-has-been-called nil)
|
||||
(setq gnus-use-demon t)))
|
||||
|
||||
(gnus-add-shutdown 'gnus-demon-cancel 'gnus)
|
||||
|
||||
(defun gnus-demon-cancel ()
|
||||
"Cancel any Gnus daemons."
|
||||
(interactive)
|
||||
(when gnus-demon-timer
|
||||
(nnheader-cancel-timer gnus-demon-timer))
|
||||
(setq gnus-demon-timer nil
|
||||
gnus-use-demon nil)
|
||||
(condition-case ()
|
||||
(nnheader-cancel-function-timers 'gnus-demon)
|
||||
(error t)))
|
||||
|
||||
(defun gnus-demon-is-idle-p ()
|
||||
"Whether Emacs is idle or not."
|
||||
;; We do this simply by comparing the 100 most recent keystrokes
|
||||
;; with the ones we had last time. If they are the same, one might
|
||||
;; guess that Emacs is indeed idle. This only makes sense if one
|
||||
;; calls this function seldom -- like once a minute, which is what
|
||||
;; we do here.
|
||||
(let ((keys (recent-keys)))
|
||||
(or (equal keys gnus-demon-last-keys)
|
||||
(progn
|
||||
(setq gnus-demon-last-keys keys)
|
||||
nil))))
|
||||
|
||||
(defun gnus-demon-time-to-step (time)
|
||||
"Find out how many seconds to TIME, which is on the form \"17:43\"."
|
||||
(if (not (stringp time))
|
||||
time
|
||||
(let* ((date (current-time-string))
|
||||
(dv (timezone-parse-date date))
|
||||
(tdate (timezone-make-arpa-date
|
||||
(string-to-number (aref dv 0))
|
||||
(string-to-number (aref dv 1))
|
||||
(string-to-number (aref dv 2)) time
|
||||
(or (aref dv 4) "UT")))
|
||||
(nseconds (gnus-time-minus
|
||||
(gnus-encode-date tdate) (gnus-encode-date date))))
|
||||
(round
|
||||
(/ (+ (if (< (car nseconds) 0)
|
||||
86400 0)
|
||||
(* 65536 (car nseconds))
|
||||
(nth 1 nseconds))
|
||||
gnus-demon-timestep)))))
|
||||
|
||||
(defun gnus-demon ()
|
||||
"The Gnus daemon that takes care of running all Gnus handlers."
|
||||
;; Increase or reset the time Emacs has been idle.
|
||||
(if (gnus-demon-is-idle-p)
|
||||
(incf gnus-demon-idle-time)
|
||||
(setq gnus-demon-idle-time 0)
|
||||
(setq gnus-demon-idle-has-been-called nil))
|
||||
;; Disable all daemonic stuff if we're in the minibuffer
|
||||
(when (and (not (window-minibuffer-p (selected-window)))
|
||||
(not gnus-inhibit-demon))
|
||||
;; Then we go through all the handler and call those that are
|
||||
;; sufficiently ripe.
|
||||
(let ((handlers gnus-demon-handler-state)
|
||||
(gnus-inhibit-demon t)
|
||||
handler time idle)
|
||||
(while handlers
|
||||
(setq handler (pop handlers))
|
||||
(cond
|
||||
((numberp (setq time (nth 1 handler)))
|
||||
;; These handlers use a regular timeout mechanism. We decrease
|
||||
;; the timer if it hasn't reached zero yet.
|
||||
(unless (zerop time)
|
||||
(setcar (nthcdr 1 handler) (decf time)))
|
||||
(and (zerop time) ; If the timer now is zero...
|
||||
;; Test for appropriate idleness
|
||||
(progn
|
||||
(setq idle (nth 2 handler))
|
||||
(cond
|
||||
((null idle) t) ; Don't care about idle.
|
||||
((numberp idle) ; Numerical idle...
|
||||
(< idle gnus-demon-idle-time)) ; Idle timed out.
|
||||
(t (< 0 gnus-demon-idle-time)))) ; Or just need to be idle.
|
||||
;; So we call the handler.
|
||||
(progn
|
||||
(funcall (car handler))
|
||||
;; And reset the timer.
|
||||
(setcar (nthcdr 1 handler)
|
||||
(gnus-demon-time-to-step
|
||||
(nth 1 (assq (car handler) gnus-demon-handlers)))))))
|
||||
;; These are only supposed to be called when Emacs is idle.
|
||||
((null (setq idle (nth 2 handler)))
|
||||
;; We do nothing.
|
||||
)
|
||||
((not (numberp idle))
|
||||
;; We want to call this handler each and every time that
|
||||
;; Emacs is idle.
|
||||
(funcall (car handler)))
|
||||
(t
|
||||
;; We want to call this handler only if Emacs has been idle
|
||||
;; for a specified number of timesteps.
|
||||
(and (not (memq (car handler) gnus-demon-idle-has-been-called))
|
||||
(< idle gnus-demon-idle-time)
|
||||
(progn
|
||||
(funcall (car handler))
|
||||
;; Make sure the handler won't be called once more in
|
||||
;; this idle-cycle.
|
||||
(push (car handler) gnus-demon-idle-has-been-called)))))))))
|
||||
|
||||
(defun gnus-demon-add-nocem ()
|
||||
"Add daemonic NoCeM handling to Gnus."
|
||||
(gnus-demon-add-handler 'gnus-demon-scan-nocem 60 t))
|
||||
|
||||
(defun gnus-demon-scan-nocem ()
|
||||
"Scan NoCeM groups for NoCeM messages."
|
||||
(save-window-excursion
|
||||
(gnus-nocem-scan-groups)))
|
||||
|
||||
(defun gnus-demon-add-disconnection ()
|
||||
"Add daemonic server disconnection to Gnus."
|
||||
(gnus-demon-add-handler 'gnus-demon-close-connections nil 30))
|
||||
|
||||
(defun gnus-demon-close-connections ()
|
||||
(save-window-excursion
|
||||
(gnus-close-backends)))
|
||||
|
||||
(defun gnus-demon-add-scanmail ()
|
||||
"Add daemonic scanning of mail from the mail backends."
|
||||
(gnus-demon-add-handler 'gnus-demon-scan-mail 120 60))
|
||||
|
||||
(defun gnus-demon-scan-mail ()
|
||||
(save-window-excursion
|
||||
(let ((servers gnus-opened-servers)
|
||||
server)
|
||||
(while (setq server (car (pop servers)))
|
||||
(and (gnus-check-backend-function 'request-scan (car server))
|
||||
(or (gnus-server-opened server)
|
||||
(gnus-open-server server))
|
||||
(gnus-request-scan nil server))))))
|
||||
|
||||
(defun gnus-demon-add-rescan ()
|
||||
"Add daemonic scanning of new articles from all backends."
|
||||
(gnus-demon-add-handler 'gnus-demon-scan-news 120 60))
|
||||
|
||||
(defun gnus-demon-scan-news ()
|
||||
(save-window-excursion
|
||||
(when (gnus-alive-p)
|
||||
(save-excursion
|
||||
(set-buffer gnus-group-buffer)
|
||||
(gnus-group-get-new-news)))))
|
||||
|
||||
(defun gnus-demon-add-scan-timestamps ()
|
||||
"Add daemonic updating of timestamps in empty newgroups."
|
||||
(gnus-demon-add-handler 'gnus-demon-scan-timestamps nil 30))
|
||||
|
||||
(defun gnus-demon-scan-timestamps ()
|
||||
"Set the timestamp on all newsgroups with no unread and no ticked articles."
|
||||
(when (gnus-alive-p)
|
||||
(let ((cur-time (current-time))
|
||||
(newsrc (cdr gnus-newsrc-alist))
|
||||
info group unread has-ticked)
|
||||
(while (setq info (pop newsrc))
|
||||
(setq group (gnus-info-group info)
|
||||
unread (gnus-group-unread group)
|
||||
has-ticked (cdr (assq 'tick (gnus-info-marks info))))
|
||||
(when (and (numberp unread)
|
||||
(= unread 0)
|
||||
(not has-ticked))
|
||||
(gnus-group-set-parameter group 'timestamp cur-time))))))
|
||||
|
||||
(provide 'gnus-demon)
|
||||
|
||||
;;; gnus-demon.el ends here
|
||||
158
lisp/gnus/gnus-dup.el
Normal file
158
lisp/gnus/gnus-dup.el
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
;;; gnus-dup.el --- suppression of duplicate articles in Gnus
|
||||
;; Copyright (C) 1996,97 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
|
||||
;; Keywords: news
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; This package tries to mark articles as read the second time the
|
||||
;; user reads a copy. This is useful if the server doesn't support
|
||||
;; Xref properly, or if the user reads the same group from several
|
||||
;; servers.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'gnus)
|
||||
(require 'gnus-art)
|
||||
|
||||
(defgroup gnus-duplicate nil
|
||||
"Suppression of duplicate articles."
|
||||
:group 'gnus)
|
||||
|
||||
(defcustom gnus-save-duplicate-list nil
|
||||
"*If non-nil, save the duplicate list when shutting down Gnus.
|
||||
If nil, duplicate suppression will only work on duplicates
|
||||
seen in the same session."
|
||||
:group 'gnus-duplicate
|
||||
:type 'boolean)
|
||||
|
||||
(defcustom gnus-duplicate-list-length 10000
|
||||
"*The number of Message-IDs to keep in the duplicate suppression list."
|
||||
:group 'gnus-duplicate
|
||||
:type 'integer)
|
||||
|
||||
(defcustom gnus-duplicate-file (nnheader-concat gnus-directory "suppression")
|
||||
"*The name of the file to store the duplicate suppression list."
|
||||
:group 'gnus-duplicate
|
||||
:type 'file)
|
||||
|
||||
;;; Internal variables
|
||||
|
||||
(defvar gnus-dup-list nil)
|
||||
(defvar gnus-dup-hashtb nil)
|
||||
|
||||
(defvar gnus-dup-list-dirty nil)
|
||||
|
||||
;;;
|
||||
;;; Starting and stopping
|
||||
;;;
|
||||
|
||||
(gnus-add-shutdown 'gnus-dup-close 'gnus)
|
||||
|
||||
(defun gnus-dup-close ()
|
||||
"Possibly save the duplicate suppression list and shut down the subsystem."
|
||||
(gnus-dup-save)
|
||||
(setq gnus-dup-list nil
|
||||
gnus-dup-hashtb nil
|
||||
gnus-dup-list-dirty nil))
|
||||
|
||||
(defun gnus-dup-open ()
|
||||
"Possibly read the duplicate suppression list and start the subsystem."
|
||||
(if gnus-save-duplicate-list
|
||||
(gnus-dup-read)
|
||||
(setq gnus-dup-list nil))
|
||||
(setq gnus-dup-hashtb (gnus-make-hashtable gnus-duplicate-list-length))
|
||||
;; Enter all Message-IDs into the hash table.
|
||||
(let ((list gnus-dup-list)
|
||||
(obarray gnus-dup-hashtb))
|
||||
(while list
|
||||
(intern (pop list)))))
|
||||
|
||||
(defun gnus-dup-read ()
|
||||
"Read the duplicate suppression list."
|
||||
(setq gnus-dup-list nil)
|
||||
(when (file-exists-p gnus-duplicate-file)
|
||||
(load gnus-duplicate-file t t t)))
|
||||
|
||||
(defun gnus-dup-save ()
|
||||
"Save the duplicate suppression list."
|
||||
(when (and gnus-save-duplicate-list
|
||||
gnus-dup-list-dirty)
|
||||
(nnheader-temp-write gnus-duplicate-file
|
||||
(gnus-prin1 `(setq gnus-dup-list ',gnus-dup-list))))
|
||||
(setq gnus-dup-list-dirty nil))
|
||||
|
||||
;;;
|
||||
;;; Interface functions
|
||||
;;;
|
||||
|
||||
(defun gnus-dup-enter-articles ()
|
||||
"Enter articles from the current group for future duplicate suppression."
|
||||
(unless gnus-dup-list
|
||||
(gnus-dup-open))
|
||||
(setq gnus-dup-list-dirty t) ; mark list for saving
|
||||
(let ((data gnus-newsgroup-data)
|
||||
datum msgid)
|
||||
;; Enter the Message-IDs of all read articles into the list
|
||||
;; and hash table.
|
||||
(while (setq datum (pop data))
|
||||
(when (and (not (gnus-data-pseudo-p datum))
|
||||
(> (gnus-data-number datum) 0)
|
||||
(gnus-data-read-p datum)
|
||||
(not (= (gnus-data-mark datum) gnus-canceled-mark))
|
||||
(setq msgid (mail-header-id (gnus-data-header datum)))
|
||||
(not (nnheader-fake-message-id-p msgid))
|
||||
(not (intern-soft msgid gnus-dup-hashtb)))
|
||||
(push msgid gnus-dup-list)
|
||||
(intern msgid gnus-dup-hashtb))))
|
||||
;; Chop off excess Message-IDs from the list.
|
||||
(let ((end (nthcdr gnus-duplicate-list-length gnus-dup-list)))
|
||||
(when end
|
||||
(setcdr end nil))))
|
||||
|
||||
(defun gnus-dup-suppress-articles ()
|
||||
"Mark duplicate articles as read."
|
||||
(unless gnus-dup-list
|
||||
(gnus-dup-open))
|
||||
(gnus-message 6 "Suppressing duplicates...")
|
||||
(let ((headers gnus-newsgroup-headers)
|
||||
number header)
|
||||
(while (setq header (pop headers))
|
||||
(when (and (intern-soft (mail-header-id header) gnus-dup-hashtb)
|
||||
(gnus-summary-article-unread-p (mail-header-number header)))
|
||||
(setq gnus-newsgroup-unreads
|
||||
(delq (setq number (mail-header-number header))
|
||||
gnus-newsgroup-unreads))
|
||||
(push (cons number gnus-duplicate-mark)
|
||||
gnus-newsgroup-reads))))
|
||||
(gnus-message 6 "Suppressing duplicates...done"))
|
||||
|
||||
(defun gnus-dup-unsuppress-article (article)
|
||||
"Stop suppression of ARTICLE."
|
||||
(let ((id (mail-header-id (gnus-data-header (gnus-data-find article)))))
|
||||
(when id
|
||||
(setq gnus-dup-list-dirty t)
|
||||
(setq gnus-dup-list (delete id gnus-dup-list))
|
||||
(unintern id gnus-dup-hashtb))))
|
||||
|
||||
(provide 'gnus-dup)
|
||||
|
||||
;;; gnus-dup.el ends here
|
||||
130
lisp/gnus/gnus-eform.el
Normal file
130
lisp/gnus/gnus-eform.el
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
;;; gnus-eform.el --- a mode for editing forms for Gnus
|
||||
;; Copyright (C) 1996,97 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
|
||||
;; Keywords: news
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'gnus)
|
||||
(require 'gnus-win)
|
||||
|
||||
;;;
|
||||
;;; Editing forms
|
||||
;;;
|
||||
|
||||
(defgroup gnus-edit-form nil
|
||||
"A mode for editing forms."
|
||||
:group 'gnus)
|
||||
|
||||
(defcustom gnus-edit-form-mode-hook nil
|
||||
"Hook run in `gnus-edit-form-mode' buffers."
|
||||
:group 'gnus-edit-form
|
||||
:type 'hook)
|
||||
|
||||
(defcustom gnus-edit-form-menu-hook nil
|
||||
"Hook run when creating menus in `gnus-edit-form-mode' buffers."
|
||||
:group 'gnus-edit-form
|
||||
:type 'hook)
|
||||
|
||||
;;; Internal variables
|
||||
|
||||
(defvar gnus-edit-form-done-function nil)
|
||||
(defvar gnus-edit-form-buffer "*Gnus edit form*")
|
||||
|
||||
(defvar gnus-edit-form-mode-map nil)
|
||||
(unless gnus-edit-form-mode-map
|
||||
(setq gnus-edit-form-mode-map (copy-keymap emacs-lisp-mode-map))
|
||||
(gnus-define-keys gnus-edit-form-mode-map
|
||||
"\C-c\C-c" gnus-edit-form-done
|
||||
"\C-c\C-k" gnus-edit-form-exit))
|
||||
|
||||
(defun gnus-edit-form-make-menu-bar ()
|
||||
(unless (boundp 'gnus-edit-form-menu)
|
||||
(easy-menu-define
|
||||
gnus-edit-form-menu gnus-edit-form-mode-map ""
|
||||
'("Edit Form"
|
||||
["Exit and save changes" gnus-edit-form-done t]
|
||||
["Exit" gnus-edit-form-exit t]))
|
||||
(run-hooks 'gnus-edit-form-menu-hook)))
|
||||
|
||||
(defun gnus-edit-form-mode ()
|
||||
"Major mode for editing forms.
|
||||
It is a slightly enhanced emacs-lisp-mode.
|
||||
|
||||
\\{gnus-edit-form-mode-map}"
|
||||
(interactive)
|
||||
(when (gnus-visual-p 'group-menu 'menu)
|
||||
(gnus-edit-form-make-menu-bar))
|
||||
(kill-all-local-variables)
|
||||
(setq major-mode 'gnus-edit-form-mode)
|
||||
(setq mode-name "Edit Form")
|
||||
(use-local-map gnus-edit-form-mode-map)
|
||||
(make-local-variable 'gnus-edit-form-done-function)
|
||||
(make-local-variable 'gnus-prev-winconf)
|
||||
(run-hooks 'gnus-edit-form-mode-hook))
|
||||
|
||||
(defun gnus-edit-form (form documentation exit-func)
|
||||
"Edit FORM in a new buffer.
|
||||
Call EXIT-FUNC on exit. Display DOCUMENTATION in the beginning
|
||||
of the buffer."
|
||||
(let ((winconf (current-window-configuration)))
|
||||
(set-buffer (get-buffer-create gnus-edit-form-buffer))
|
||||
(gnus-configure-windows 'edit-form)
|
||||
(gnus-add-current-to-buffer-list)
|
||||
(gnus-edit-form-mode)
|
||||
(setq gnus-prev-winconf winconf)
|
||||
(setq gnus-edit-form-done-function exit-func)
|
||||
(erase-buffer)
|
||||
(insert documentation)
|
||||
(unless (bolp)
|
||||
(insert "\n"))
|
||||
(goto-char (point-min))
|
||||
(while (not (eobp))
|
||||
(insert ";;; ")
|
||||
(forward-line 1))
|
||||
(insert ";; Type `C-c C-c' after you've finished editing.\n")
|
||||
(insert "\n")
|
||||
(let ((p (point)))
|
||||
(pp form (current-buffer))
|
||||
(insert "\n")
|
||||
(goto-char p))))
|
||||
|
||||
(defun gnus-edit-form-done ()
|
||||
"Update changes and kill the current buffer."
|
||||
(interactive)
|
||||
(goto-char (point-min))
|
||||
(let ((form (read (current-buffer)))
|
||||
(func gnus-edit-form-done-function))
|
||||
(gnus-edit-form-exit)
|
||||
(funcall func form)))
|
||||
|
||||
(defun gnus-edit-form-exit ()
|
||||
"Kill the current buffer."
|
||||
(interactive)
|
||||
(let ((winconf gnus-prev-winconf))
|
||||
(kill-buffer (current-buffer))
|
||||
(set-window-configuration winconf)))
|
||||
|
||||
(provide 'gnus-eform)
|
||||
|
||||
;;; gnus-eform.el ends here
|
||||
212
lisp/gnus/gnus-ems.el
Normal file
212
lisp/gnus/gnus-ems.el
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
;;; gnus-ems.el --- functions for making Gnus work under different Emacsen
|
||||
;; Copyright (C) 1995,96,97 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
|
||||
;; Keywords: news
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
(eval-when-compile (require 'cl))
|
||||
|
||||
;;; Function aliases later to be redefined for XEmacs usage.
|
||||
|
||||
(defvar gnus-xemacs (string-match "XEmacs\\|Lucid" emacs-version)
|
||||
"Non-nil if running under XEmacs.")
|
||||
|
||||
(defvar gnus-mouse-2 [mouse-2])
|
||||
(defvar gnus-down-mouse-2 [down-mouse-2])
|
||||
|
||||
(eval-and-compile
|
||||
(autoload 'gnus-xmas-define "gnus-xmas")
|
||||
(autoload 'gnus-xmas-redefine "gnus-xmas")
|
||||
(autoload 'appt-select-lowest-window "appt.el"))
|
||||
|
||||
(or (fboundp 'mail-file-babyl-p)
|
||||
(fset 'mail-file-babyl-p 'rmail-file-p))
|
||||
|
||||
;;; Mule functions.
|
||||
|
||||
(defun gnus-mule-cite-add-face (number prefix face)
|
||||
;; At line NUMBER, ignore PREFIX and add FACE to the rest of the line.
|
||||
(when face
|
||||
(let ((inhibit-point-motion-hooks t)
|
||||
from to)
|
||||
(goto-line number)
|
||||
(if (boundp 'MULE)
|
||||
(forward-char (chars-in-string prefix))
|
||||
(forward-char (length prefix)))
|
||||
(skip-chars-forward " \t")
|
||||
(setq from (point))
|
||||
(end-of-line 1)
|
||||
(skip-chars-backward " \t")
|
||||
(setq to (point))
|
||||
(when (< from to)
|
||||
(gnus-overlay-put (gnus-make-overlay from to) 'face face)))))
|
||||
|
||||
(defun gnus-mule-max-width-function (el max-width)
|
||||
(` (let* ((val (eval (, el)))
|
||||
(valstr (if (numberp val)
|
||||
(int-to-string val) val)))
|
||||
(if (> (length valstr) (, max-width))
|
||||
(truncate-string valstr (, max-width))
|
||||
valstr))))
|
||||
|
||||
(eval-and-compile
|
||||
(if (string-match "XEmacs\\|Lucid" emacs-version)
|
||||
nil
|
||||
|
||||
(defvar gnus-mouse-face-prop 'mouse-face
|
||||
"Property used for highlighting mouse regions.")
|
||||
|
||||
(defvar gnus-article-x-face-command
|
||||
"{ echo '/* Width=48, Height=48 */'; uncompface; } | icontopbm | xv -quit -"
|
||||
"String or function to be executed to display an X-Face header.
|
||||
If it is a string, the command will be executed in a sub-shell
|
||||
asynchronously. The compressed face will be piped to this command."))
|
||||
|
||||
(cond
|
||||
((string-match "XEmacs\\|Lucid" emacs-version)
|
||||
(gnus-xmas-define))
|
||||
|
||||
((or (not (boundp 'emacs-minor-version))
|
||||
(< emacs-minor-version 30))
|
||||
;; Remove the `intangible' prop.
|
||||
(let ((props (and (boundp 'gnus-hidden-properties)
|
||||
gnus-hidden-properties)))
|
||||
(while (and props (not (eq (car (cdr props)) 'intangible)))
|
||||
(setq props (cdr props)))
|
||||
(when props
|
||||
(setcdr props (cdr (cdr (cdr props))))))
|
||||
(unless (fboundp 'buffer-substring-no-properties)
|
||||
(defun buffer-substring-no-properties (beg end)
|
||||
(format "%s" (buffer-substring beg end)))))
|
||||
|
||||
((boundp 'MULE)
|
||||
(provide 'gnusutil))))
|
||||
|
||||
(eval-and-compile
|
||||
(cond
|
||||
((not window-system)
|
||||
(defun gnus-dummy-func (&rest args))
|
||||
(let ((funcs '(mouse-set-point set-face-foreground
|
||||
set-face-background x-popup-menu)))
|
||||
(while funcs
|
||||
(unless (fboundp (car funcs))
|
||||
(fset (car funcs) 'gnus-dummy-func))
|
||||
(setq funcs (cdr funcs))))))
|
||||
(unless (fboundp 'file-regular-p)
|
||||
(defun file-regular-p (file)
|
||||
(and (not (file-directory-p file))
|
||||
(not (file-symlink-p file))
|
||||
(file-exists-p file))))
|
||||
(unless (fboundp 'face-list)
|
||||
(defun face-list (&rest args))))
|
||||
|
||||
(eval-and-compile
|
||||
(let ((case-fold-search t))
|
||||
(cond
|
||||
((string-match "windows-nt\\|os/2\\|emx" (format "%s" system-type))
|
||||
(setq nnheader-file-name-translation-alist
|
||||
(append nnheader-file-name-translation-alist
|
||||
'((?: . ?_)
|
||||
(?+ . ?-))))))))
|
||||
|
||||
(defvar gnus-tmp-unread)
|
||||
(defvar gnus-tmp-replied)
|
||||
(defvar gnus-tmp-score-char)
|
||||
(defvar gnus-tmp-indentation)
|
||||
(defvar gnus-tmp-opening-bracket)
|
||||
(defvar gnus-tmp-lines)
|
||||
(defvar gnus-tmp-name)
|
||||
(defvar gnus-tmp-closing-bracket)
|
||||
(defvar gnus-tmp-subject-or-nil)
|
||||
|
||||
(defun gnus-ems-redefine ()
|
||||
(cond
|
||||
((string-match "XEmacs\\|Lucid" emacs-version)
|
||||
(gnus-xmas-redefine))
|
||||
|
||||
((featurep 'mule)
|
||||
;; Mule and new Emacs definitions
|
||||
|
||||
;; [Note] Now there are three kinds of mule implementations,
|
||||
;; original MULE, XEmacs/mule and beta version of Emacs including
|
||||
;; some mule features. Unfortunately these API are different. In
|
||||
;; particular, Emacs (including original MULE) and XEmacs are
|
||||
;; quite different.
|
||||
;; Predicates to check are following:
|
||||
;; (boundp 'MULE) is t only if MULE (original; anything older than
|
||||
;; Mule 2.3) is running.
|
||||
;; (featurep 'mule) is t when every mule variants are running.
|
||||
|
||||
;; These implementations may be able to share between original
|
||||
;; MULE and beta version of new Emacs. In addition, it is able to
|
||||
;; detect XEmacs/mule by (featurep 'mule) and to check variable
|
||||
;; `emacs-version'. In this case, implementation for XEmacs/mule
|
||||
;; may be able to share between XEmacs and XEmacs/mule.
|
||||
|
||||
(defalias 'gnus-truncate-string 'truncate-string)
|
||||
|
||||
(defvar gnus-summary-display-table nil
|
||||
"Display table used in summary mode buffers.")
|
||||
(fset 'gnus-cite-add-face 'gnus-mule-cite-add-face)
|
||||
(fset 'gnus-max-width-function 'gnus-mule-max-width-function)
|
||||
(fset 'gnus-summary-set-display-table 'ignore)
|
||||
|
||||
(when (boundp 'gnus-check-before-posting)
|
||||
(setq gnus-check-before-posting
|
||||
(delq 'long-lines
|
||||
(delq 'control-chars gnus-check-before-posting))))
|
||||
|
||||
(defun gnus-summary-line-format-spec ()
|
||||
(insert gnus-tmp-unread gnus-tmp-replied
|
||||
gnus-tmp-score-char gnus-tmp-indentation)
|
||||
(put-text-property
|
||||
(point)
|
||||
(progn
|
||||
(insert
|
||||
gnus-tmp-opening-bracket
|
||||
(format "%4d: %-20s"
|
||||
gnus-tmp-lines
|
||||
(if (> (length gnus-tmp-name) 20)
|
||||
(truncate-string gnus-tmp-name 20)
|
||||
gnus-tmp-name))
|
||||
gnus-tmp-closing-bracket)
|
||||
(point))
|
||||
gnus-mouse-face-prop gnus-mouse-face)
|
||||
(insert " " gnus-tmp-subject-or-nil "\n"))
|
||||
)))
|
||||
|
||||
(defun gnus-region-active-p ()
|
||||
"Say whether the region is active."
|
||||
(and (boundp 'transient-mark-mode)
|
||||
transient-mark-mode
|
||||
(boundp 'mark-active)
|
||||
mark-active))
|
||||
|
||||
(provide 'gnus-ems)
|
||||
|
||||
;; Local Variables:
|
||||
;; byte-compile-warnings: '(redefine callargs)
|
||||
;; End:
|
||||
|
||||
;;; gnus-ems.el ends here
|
||||
862
lisp/gnus/gnus-gl.el
Normal file
862
lisp/gnus/gnus-gl.el
Normal file
|
|
@ -0,0 +1,862 @@
|
|||
;;; gnus-gl.el --- an interface to GroupLens for Gnus
|
||||
;; Copyright (C) 1995,96,97 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Brad Miller <bmiller@cs.umn.edu>
|
||||
;; Keywords: news, score
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; GroupLens software and documentation is copyright (c) 1995 by Paul
|
||||
;; Resnick (Massachusetts Institute of Technology); Brad Miller, John
|
||||
;; Riedl, Jon Herlocker, and Joseph Konstan (University of Minnesota),
|
||||
;; and David Maltz (Carnegie-Mellon University).
|
||||
;;
|
||||
;; Permission to use, copy, modify, and distribute this documentation
|
||||
;; for non-commercial and commercial purposes without fee is hereby
|
||||
;; granted provided that this copyright notice and permission notice
|
||||
;; appears in all copies and that the names of the individuals and
|
||||
;; institutions holding this copyright are not used in advertising or
|
||||
;; publicity pertaining to this software without specific, written
|
||||
;; prior permission. The copyright holders make no representations
|
||||
;; about the suitability of this software and documentation for any
|
||||
;; purpose. It is provided ``as is'' without express or implied
|
||||
;; warranty.
|
||||
;;
|
||||
;; The copyright holders request that they be notified of
|
||||
;; modifications of this code. Please send electronic mail to
|
||||
;; grouplens@cs.umn.edu for more information or to announce derived
|
||||
;; works.
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Author: Brad Miller
|
||||
;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;
|
||||
;; User Documentation:
|
||||
;; To use GroupLens you must load this file.
|
||||
;; You must also register a pseudonym with the Better Bit Bureau.
|
||||
;; http://www.cs.umn.edu/Research/GroupLens
|
||||
;;
|
||||
;; ---------------- For your .emacs or .gnus file ----------------
|
||||
;;
|
||||
;; As of version 2.5, grouplens now works as a minor mode of
|
||||
;; gnus-summary-mode. To get make that work you just need a couple of
|
||||
;; hooks.
|
||||
;; (setq gnus-use-grouplens t)
|
||||
;; (setq grouplens-pseudonym "")
|
||||
;; (setq grouplens-bbb-host "grouplens.cs.umn.edu")
|
||||
;;
|
||||
;; (setq gnus-summary-default-score 0)
|
||||
;;
|
||||
;; USING GROUPLENS
|
||||
;; How do I Rate an article??
|
||||
;; Before you type n to go to the next article, hit a number from 1-5
|
||||
;; Type r in the summary buffer and you will be prompted.
|
||||
;; Note that when you're in grouplens-minor-mode 'r' masks the
|
||||
;; usual reply binding for 'r'
|
||||
;;
|
||||
;; What if, Gasp, I find a bug???
|
||||
;; Please type M-x gnus-gl-submit-bug-report. This will set up a
|
||||
;; mail buffer with the state of variables and buffers that will help
|
||||
;; me debug the problem. A short description up front would help too!
|
||||
;;
|
||||
;; How do I display the prediction for an article:
|
||||
;; If you set the gnus-summary-line-format as shown above, the score
|
||||
;; (prediction) will be shown automatically.
|
||||
;;
|
||||
;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Programmer Notes
|
||||
;; 10/9/95
|
||||
;; gnus-scores-articles contains the articles
|
||||
;; When scoring is done, the call tree looks something like:
|
||||
;; gnus-possibly-score-headers
|
||||
;; ==> gnus-score-headers
|
||||
;; ==> gnus-score-load-file
|
||||
;; ==> get-all-mids (from the eval form)
|
||||
;;
|
||||
;; it would be nice to have one that gets called after all the other
|
||||
;; headers have been scored.
|
||||
;; we may want a variable gnus-grouplens-scale-factor
|
||||
;; and gnus-grouplens-offset this would probably be either -3 or 0
|
||||
;; to make the scores centered around zero or not.
|
||||
;; Notes 10/12/95
|
||||
;; According to Lars, Norse god of gnus, the simple way to insert a
|
||||
;; call to an external function is to have a function added to the
|
||||
;; variable gnus-score-find-files-function This new function
|
||||
;; gnus-grouplens-score-alist will return a core alist that
|
||||
;; has (("message-id" ("<message-id-xxxx>" score) ("<message-id-xxxy>" score))
|
||||
;; This seems like it would be pretty inefficient, though workable.
|
||||
;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; TODO
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;
|
||||
;; 3. Add some more ways to rate messages
|
||||
;; 4. Better error handling for token timeouts.
|
||||
;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; bugs
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'gnus-score)
|
||||
(require 'cl)
|
||||
(require 'gnus)
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;; User variables
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defvar gnus-summary-grouplens-line-format
|
||||
"%U\%R\%z%l%I\%(%[%4L: %-20,20n%]%) %s\n"
|
||||
"*The line format spec in summary GroupLens mode buffers.")
|
||||
|
||||
(defvar grouplens-pseudonym ""
|
||||
"User's pseudonym.
|
||||
This pseudonym is obtained during the registration process")
|
||||
|
||||
(defvar grouplens-bbb-host "grouplens.cs.umn.edu"
|
||||
"Host where the bbbd is running" )
|
||||
|
||||
(defvar grouplens-bbb-port 9000
|
||||
"Port where the bbbd is listening" )
|
||||
|
||||
(defvar grouplens-newsgroups
|
||||
'("comp.groupware" "comp.human-factors" "comp.lang.c++"
|
||||
"comp.lang.java" "comp.os.linux.admin" "comp.os.linux.advocacy"
|
||||
"comp.os.linux.announce" "comp.os.linux.answers"
|
||||
"comp.os.linux.development" "comp.os.linux.development.apps"
|
||||
"comp.os.linux.development.system" "comp.os.linux.hardware"
|
||||
"comp.os.linux.help" "comp.os.linux.m68k" "comp.os.linux.misc"
|
||||
"comp.os.linux.networking" "comp.os.linux.setup" "comp.os.linux.x"
|
||||
"mn.general" "rec.arts.movies" "rec.arts.movies.current-films"
|
||||
"rec.food.recipes" "rec.humor")
|
||||
"*Groups that are part of the GroupLens experiment.")
|
||||
|
||||
(defvar grouplens-prediction-display 'prediction-spot
|
||||
"valid values are:
|
||||
prediction-spot -- an * corresponding to the prediction between 1 and 5,
|
||||
confidence-interval -- a numeric confidence interval
|
||||
prediction-bar -- |##### | the longer the bar, the better the article,
|
||||
confidence-bar -- | ----- } the prediction is in the middle of the bar,
|
||||
confidence-spot -- ) * | the spot gets bigger with more confidence,
|
||||
prediction-num -- plain-old numeric value,
|
||||
confidence-plus-minus -- prediction +/i confidence")
|
||||
|
||||
(defvar grouplens-score-offset 0
|
||||
"Offset the prediction by this value.
|
||||
Setting this variable to -2 would have the following effect on
|
||||
GroupLens scores:
|
||||
|
||||
1 --> -2
|
||||
2 --> -1
|
||||
3 --> 0
|
||||
4 --> 1
|
||||
5 --> 2
|
||||
|
||||
The reason is that a user might want to do this is to combine
|
||||
GroupLens predictions with scores calculated by other score methods.")
|
||||
|
||||
(defvar grouplens-score-scale-factor 1
|
||||
"This variable allows the user to magnify the effect of GroupLens scores.
|
||||
The scale factor is applied after the offset.")
|
||||
|
||||
(defvar gnus-grouplens-override-scoring 'override
|
||||
"Tell GroupLens to override the normal Gnus scoring mechanism.
|
||||
GroupLens scores can be combined with gnus scores in one of three ways.
|
||||
'override -- just use grouplens predictions for grouplens groups
|
||||
'combine -- combine grouplens scores with gnus scores
|
||||
'separate -- treat grouplens scores completely separate from gnus")
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;; Program global variables
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
(defvar grouplens-bbb-token nil
|
||||
"Current session token number")
|
||||
|
||||
(defvar grouplens-bbb-process nil
|
||||
"Process Id of current bbbd network stream process")
|
||||
|
||||
(defvar grouplens-bbb-buffer nil
|
||||
"Buffer associated with the BBBD process")
|
||||
|
||||
(defvar grouplens-rating-alist nil
|
||||
"Current set of message-id rating pairs")
|
||||
|
||||
(defvar grouplens-current-hashtable nil
|
||||
"A hashtable to hold predictions from the BBB")
|
||||
|
||||
(defvar grouplens-current-group nil)
|
||||
|
||||
;;(defvar bbb-alist nil)
|
||||
|
||||
(defvar bbb-timeout-secs 10
|
||||
"Number of seconds to wait for some response from the BBB.
|
||||
If this times out we give up and assume that something has died..." )
|
||||
|
||||
(defvar grouplens-previous-article nil
|
||||
"Message-ID of the last article read.")
|
||||
|
||||
(defvar bbb-read-point)
|
||||
(defvar bbb-response-point)
|
||||
|
||||
(defun bbb-renew-hash-table ()
|
||||
(setq grouplens-current-hashtable (make-vector 100 0)))
|
||||
|
||||
(bbb-renew-hash-table)
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;; Utility Functions
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defun bbb-connect-to-bbbd (host port)
|
||||
(unless grouplens-bbb-buffer
|
||||
(setq grouplens-bbb-buffer
|
||||
(get-buffer-create (format " *BBBD trace: %s*" host)))
|
||||
(save-excursion
|
||||
(set-buffer grouplens-bbb-buffer)
|
||||
(make-local-variable 'bbb-read-point)
|
||||
(make-local-variable 'bbb-response-point)
|
||||
(setq bbb-read-point (point-min))))
|
||||
|
||||
;; if an old process is still running for some reason, kill it
|
||||
(when grouplens-bbb-process
|
||||
(ignore-errors
|
||||
(when (eq 'open (process-status grouplens-bbb-process))
|
||||
(set-process-buffer grouplens-bbb-process nil)
|
||||
(delete-process grouplens-bbb-process))))
|
||||
|
||||
;; clear the trace buffer of old output
|
||||
(save-excursion
|
||||
(set-buffer grouplens-bbb-buffer)
|
||||
(erase-buffer))
|
||||
|
||||
;; open the connection to the server
|
||||
(catch 'done
|
||||
(condition-case error
|
||||
(setq grouplens-bbb-process
|
||||
(open-network-stream "BBBD" grouplens-bbb-buffer host port))
|
||||
(error (gnus-message 3 "Error: Failed to connect to BBB")
|
||||
nil))
|
||||
(and (null grouplens-bbb-process)
|
||||
(throw 'done nil))
|
||||
(save-excursion
|
||||
(set-buffer grouplens-bbb-buffer)
|
||||
(setq bbb-read-point (point-min))
|
||||
(or (bbb-read-response grouplens-bbb-process)
|
||||
(throw 'done nil))))
|
||||
|
||||
;; return the process
|
||||
grouplens-bbb-process)
|
||||
|
||||
(defun bbb-send-command (process command)
|
||||
(goto-char (point-max))
|
||||
(insert command)
|
||||
(insert "\r\n")
|
||||
(setq bbb-read-point (point))
|
||||
(setq bbb-response-point (point))
|
||||
(set-marker (process-mark process) (point)) ; process output also comes here
|
||||
(process-send-string process command)
|
||||
(process-send-string process "\r\n")
|
||||
(process-send-eof process))
|
||||
|
||||
(defun bbb-read-response (process)
|
||||
"This function eats the initial response of OK or ERROR from the BBB."
|
||||
(let ((case-fold-search nil)
|
||||
match-end)
|
||||
(goto-char bbb-read-point)
|
||||
(while (and (not (search-forward "\r\n" nil t))
|
||||
(accept-process-output process bbb-timeout-secs))
|
||||
(goto-char bbb-read-point))
|
||||
(setq match-end (point))
|
||||
(goto-char bbb-read-point)
|
||||
(setq bbb-read-point match-end)
|
||||
(looking-at "OK")))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;; Login Functions
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
(defun bbb-login ()
|
||||
"return the token number if login is successful, otherwise return nil"
|
||||
(interactive)
|
||||
(setq grouplens-bbb-token nil)
|
||||
(if (not (equal grouplens-pseudonym ""))
|
||||
(let ((bbb-process
|
||||
(bbb-connect-to-bbbd grouplens-bbb-host grouplens-bbb-port)))
|
||||
(if bbb-process
|
||||
(save-excursion
|
||||
(set-buffer (process-buffer bbb-process))
|
||||
(bbb-send-command bbb-process
|
||||
(concat "login " grouplens-pseudonym))
|
||||
(if (bbb-read-response bbb-process)
|
||||
(setq grouplens-bbb-token (bbb-extract-token-number))
|
||||
(gnus-message 3 "Error: GroupLens login failed")))))
|
||||
(gnus-message 3 "Error: you must set a pseudonym"))
|
||||
grouplens-bbb-token)
|
||||
|
||||
(defun bbb-extract-token-number ()
|
||||
(let ((token-pos (search-forward "token=" nil t)))
|
||||
(when (looking-at "[0-9]+")
|
||||
(buffer-substring token-pos (match-end 0)))))
|
||||
|
||||
(gnus-add-shutdown 'bbb-logout 'gnus)
|
||||
|
||||
(defun bbb-logout ()
|
||||
"logout of bbb session"
|
||||
(when grouplens-bbb-token
|
||||
(let ((bbb-process
|
||||
(bbb-connect-to-bbbd grouplens-bbb-host grouplens-bbb-port)))
|
||||
(when bbb-process
|
||||
(save-excursion
|
||||
(set-buffer (process-buffer bbb-process))
|
||||
(bbb-send-command bbb-process (concat "logout " grouplens-bbb-token))
|
||||
(bbb-read-response bbb-process))))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;; Get Predictions
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defun bbb-build-mid-scores-alist (groupname)
|
||||
"this function can be called as part of the function to return the
|
||||
list of score files to use. See the gnus variable
|
||||
gnus-score-find-score-files-function.
|
||||
|
||||
*Note:* If you want to use grouplens scores along with calculated scores,
|
||||
you should see the offset and scale variables. At this point, I don't
|
||||
recommend using both scores and grouplens predictions together."
|
||||
(setq grouplens-current-group groupname)
|
||||
(when (member groupname grouplens-newsgroups)
|
||||
(setq grouplens-previous-article nil)
|
||||
;; scores-alist should be a list of lists:
|
||||
;; ((("message-id" ("<mid1>" score1 nil s) ("<mid2> score2 nil s))))
|
||||
;;`((("message-id" . ,predict-list))) ; Yes, this is the return value
|
||||
(list
|
||||
(list
|
||||
(list (append (list "message-id")
|
||||
(bbb-get-predictions (bbb-get-all-mids) groupname)))))))
|
||||
|
||||
(defun bbb-get-predictions (midlist groupname)
|
||||
"Ask the bbb for predictions, and build up the score alist."
|
||||
(gnus-message 5 "Fetching Predictions...")
|
||||
(if grouplens-bbb-token
|
||||
(let ((bbb-process (bbb-connect-to-bbbd grouplens-bbb-host
|
||||
grouplens-bbb-port)))
|
||||
(when bbb-process
|
||||
(save-excursion
|
||||
(set-buffer (process-buffer bbb-process))
|
||||
(bbb-send-command bbb-process
|
||||
(bbb-build-predict-command midlist groupname
|
||||
grouplens-bbb-token))
|
||||
(if (bbb-read-response bbb-process)
|
||||
(bbb-get-prediction-response bbb-process)
|
||||
(gnus-message 1 "Invalid Token, login and try again")
|
||||
(ding)))))
|
||||
(gnus-message 3 "Error: You are not logged in to a BBB")
|
||||
(ding)))
|
||||
|
||||
(defun bbb-get-all-mids ()
|
||||
(mapcar (function (lambda (x) (mail-header-id x))) gnus-newsgroup-headers))
|
||||
|
||||
(defun bbb-build-predict-command (mlist grpname token)
|
||||
(concat "getpredictions " token " " grpname "\r\n"
|
||||
(mapconcat 'identity mlist "\r\n") "\r\n.\r\n"))
|
||||
|
||||
(defun bbb-get-prediction-response (process)
|
||||
(let ((case-fold-search nil))
|
||||
(goto-char bbb-read-point)
|
||||
(while (and (not (search-forward ".\r\n" nil t))
|
||||
(accept-process-output process bbb-timeout-secs))
|
||||
(goto-char bbb-read-point))
|
||||
(goto-char (+ bbb-response-point 4));; we ought to be right before OK
|
||||
(bbb-build-response-alist)))
|
||||
|
||||
;; build-response-alist assumes that the cursor has been positioned at
|
||||
;; the first line of the list of mid/rating pairs.
|
||||
(defun bbb-build-response-alist ()
|
||||
(let (resp mid pred)
|
||||
(while
|
||||
(cond
|
||||
((looking-at "\\(<.*>\\) :nopred=")
|
||||
;;(push `(,(bbb-get-mid) ,gnus-summary-default-score nil s) resp)
|
||||
(forward-line 1)
|
||||
t)
|
||||
((looking-at "\\(<.*>\\) :pred=\\([0-9]\.[0-9][0-9]\\) :conflow=\\([0-9]\.[0-9][0-9]\\) :confhigh=\\([0-9]\.[0-9][0-9]\\)")
|
||||
(setq mid (bbb-get-mid)
|
||||
pred (bbb-get-pred))
|
||||
(push `(,mid ,pred nil s) resp)
|
||||
(gnus-sethash mid (list pred (bbb-get-confl) (bbb-get-confh))
|
||||
grouplens-current-hashtable)
|
||||
(forward-line 1)
|
||||
t)
|
||||
((looking-at "\\(<.*>\\) :pred=\\([0-9]\.[0-9][0-9]\\)")
|
||||
(setq mid (bbb-get-mid)
|
||||
pred (bbb-get-pred))
|
||||
(push `(,mid ,pred nil s) resp)
|
||||
(gnus-sethash mid (list pred 0 0) grouplens-current-hashtable)
|
||||
(forward-line 1)
|
||||
t)
|
||||
(t nil)))
|
||||
resp))
|
||||
|
||||
;; these "get" functions assume that there is an active match lying
|
||||
;; around. Where the first parenthesized expression is the
|
||||
;; message-id, and the second is the prediction, the third and fourth
|
||||
;; are the confidence interval
|
||||
;;
|
||||
;; Since gnus assumes that scores are integer values?? we round the
|
||||
;; prediction.
|
||||
(defun bbb-get-mid ()
|
||||
(buffer-substring (match-beginning 1) (match-end 1)))
|
||||
|
||||
(defun bbb-get-pred ()
|
||||
(let ((tpred (string-to-number (buffer-substring (match-beginning 2)
|
||||
(match-end 2)))))
|
||||
(if (> tpred 0)
|
||||
(round (* grouplens-score-scale-factor
|
||||
(+ grouplens-score-offset tpred)))
|
||||
1)))
|
||||
|
||||
(defun bbb-get-confl ()
|
||||
(string-to-number (buffer-substring (match-beginning 4) (match-end 4))))
|
||||
|
||||
(defun bbb-get-confh ()
|
||||
(string-to-number (buffer-substring (match-beginning 4) (match-end 4))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;; Prediction Display
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
(defconst grplens-rating-range 4.0)
|
||||
(defconst grplens-maxrating 5)
|
||||
(defconst grplens-minrating 1)
|
||||
(defconst grplens-predstringsize 12)
|
||||
|
||||
(defvar gnus-tmp-score)
|
||||
(defun bbb-grouplens-score (header)
|
||||
(if (eq gnus-grouplens-override-scoring 'separate)
|
||||
(bbb-grouplens-other-score header)
|
||||
(let* ((rate-string (make-string 12 ?\ ))
|
||||
(mid (mail-header-id header))
|
||||
(hashent (gnus-gethash mid grouplens-current-hashtable))
|
||||
(iscore gnus-tmp-score)
|
||||
(low (car (cdr hashent)))
|
||||
(high (car (cdr (cdr hashent)))))
|
||||
(aset rate-string 0 ?|)
|
||||
(aset rate-string 11 ?|)
|
||||
(unless (member grouplens-current-group grouplens-newsgroups)
|
||||
(unless (equal grouplens-prediction-display 'prediction-num)
|
||||
(cond ((< iscore 0)
|
||||
(setq iscore 1))
|
||||
((> iscore 5)
|
||||
(setq iscore 5))))
|
||||
(setq low 0)
|
||||
(setq high 0))
|
||||
(if (and (bbb-valid-score iscore)
|
||||
(not (null mid)))
|
||||
(cond
|
||||
;; prediction-spot
|
||||
((equal grouplens-prediction-display 'prediction-spot)
|
||||
(setq rate-string (bbb-fmt-prediction-spot rate-string iscore)))
|
||||
;; confidence-interval
|
||||
((equal grouplens-prediction-display 'confidence-interval)
|
||||
(setq rate-string (bbb-fmt-confidence-interval iscore low high)))
|
||||
;; prediction-bar
|
||||
((equal grouplens-prediction-display 'prediction-bar)
|
||||
(setq rate-string (bbb-fmt-prediction-bar rate-string iscore)))
|
||||
;; confidence-bar
|
||||
((equal grouplens-prediction-display 'confidence-bar)
|
||||
(setq rate-string (format "| %4.2f |" iscore)))
|
||||
;; confidence-spot
|
||||
((equal grouplens-prediction-display 'confidence-spot)
|
||||
(setq rate-string (format "| %4.2f |" iscore)))
|
||||
;; prediction-num
|
||||
((equal grouplens-prediction-display 'prediction-num)
|
||||
(setq rate-string (bbb-fmt-prediction-num iscore)))
|
||||
;; confidence-plus-minus
|
||||
((equal grouplens-prediction-display 'confidence-plus-minus)
|
||||
(setq rate-string (bbb-fmt-confidence-plus-minus iscore low high))
|
||||
)
|
||||
(t (gnus-message 3 "Invalid prediction display type")))
|
||||
(aset rate-string 5 ?N) (aset rate-string 6 ?A))
|
||||
rate-string)))
|
||||
|
||||
;; Gnus user format function that doesn't depend on
|
||||
;; bbb-build-mid-scores-alist being used as the score function, but is
|
||||
;; instead called from gnus-select-group-hook. -- LAB
|
||||
(defun bbb-grouplens-other-score (header)
|
||||
(if (not (member grouplens-current-group grouplens-newsgroups))
|
||||
;; Return an empty string
|
||||
""
|
||||
(let* ((rate-string (make-string 12 ?\ ))
|
||||
(mid (mail-header-id header))
|
||||
(hashent (gnus-gethash mid grouplens-current-hashtable))
|
||||
(pred (or (nth 0 hashent) 0))
|
||||
(low (nth 1 hashent))
|
||||
(high (nth 2 hashent)))
|
||||
;; Init rate-string
|
||||
(aset rate-string 0 ?|)
|
||||
(aset rate-string 11 ?|)
|
||||
(unless (equal grouplens-prediction-display 'prediction-num)
|
||||
(cond ((< pred 0)
|
||||
(setq pred 1))
|
||||
((> pred 5)
|
||||
(setq pred 5))))
|
||||
;; If no entry in BBB hash mark rate string as NA and return
|
||||
(cond
|
||||
((null hashent)
|
||||
(aset rate-string 5 ?N)
|
||||
(aset rate-string 6 ?A)
|
||||
rate-string)
|
||||
|
||||
((equal grouplens-prediction-display 'prediction-spot)
|
||||
(bbb-fmt-prediction-spot rate-string pred))
|
||||
|
||||
((equal grouplens-prediction-display 'confidence-interval)
|
||||
(bbb-fmt-confidence-interval pred low high))
|
||||
|
||||
((equal grouplens-prediction-display 'prediction-bar)
|
||||
(bbb-fmt-prediction-bar rate-string pred))
|
||||
|
||||
((equal grouplens-prediction-display 'confidence-bar)
|
||||
(format "| %4.2f |" pred))
|
||||
|
||||
((equal grouplens-prediction-display 'confidence-spot)
|
||||
(format "| %4.2f |" pred))
|
||||
|
||||
((equal grouplens-prediction-display 'prediction-num)
|
||||
(bbb-fmt-prediction-num pred))
|
||||
|
||||
((equal grouplens-prediction-display 'confidence-plus-minus)
|
||||
(bbb-fmt-confidence-plus-minus pred low high))
|
||||
|
||||
(t
|
||||
(gnus-message 3 "Invalid prediction display type")
|
||||
(aset rate-string 0 ?|)
|
||||
(aset rate-string 11 ?|)
|
||||
rate-string)))))
|
||||
|
||||
(defun bbb-valid-score (score)
|
||||
(or (equal grouplens-prediction-display 'prediction-num)
|
||||
(and (>= score grplens-minrating)
|
||||
(<= score grplens-maxrating))))
|
||||
|
||||
(defun bbb-requires-confidence (format-type)
|
||||
(or (equal format-type 'confidence-plus-minus)
|
||||
(equal format-type 'confidence-spot)
|
||||
(equal format-type 'confidence-interval)))
|
||||
|
||||
(defun bbb-have-confidence (clow chigh)
|
||||
(not (or (null clow)
|
||||
(null chigh))))
|
||||
|
||||
(defun bbb-fmt-prediction-spot (rate-string score)
|
||||
(aset rate-string
|
||||
(round (* (/ (- score grplens-minrating) grplens-rating-range)
|
||||
(+ (- grplens-predstringsize 4) 1.49)))
|
||||
?*)
|
||||
rate-string)
|
||||
|
||||
(defun bbb-fmt-confidence-interval (score low high)
|
||||
(if (bbb-have-confidence low high)
|
||||
(format "|%4.2f-%4.2f |" low high)
|
||||
(bbb-fmt-prediction-num score)))
|
||||
|
||||
(defun bbb-fmt-confidence-plus-minus (score low high)
|
||||
(if (bbb-have-confidence low high)
|
||||
(format "|%3.1f+/-%4.2f|" score (/ (- high low) 2.0))
|
||||
(bbb-fmt-prediction-num score)))
|
||||
|
||||
(defun bbb-fmt-prediction-bar (rate-string score)
|
||||
(let* ((i 1)
|
||||
(step (/ grplens-rating-range (- grplens-predstringsize 4)))
|
||||
(half-step (/ step 2))
|
||||
(loc (- grplens-minrating half-step)))
|
||||
(while (< i (- grplens-predstringsize 2))
|
||||
(if (> score loc)
|
||||
(aset rate-string i ?#)
|
||||
(aset rate-string i ?\ ))
|
||||
(setq i (+ i 1))
|
||||
(setq loc (+ loc step)))
|
||||
)
|
||||
rate-string)
|
||||
|
||||
(defun bbb-fmt-prediction-num (score)
|
||||
(format "| %4.2f |" score))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;; Put Ratings
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defun bbb-put-ratings ()
|
||||
(if (and grouplens-bbb-token
|
||||
grouplens-rating-alist
|
||||
(member gnus-newsgroup-name grouplens-newsgroups))
|
||||
(let ((bbb-process (bbb-connect-to-bbbd grouplens-bbb-host
|
||||
grouplens-bbb-port))
|
||||
(rate-command (bbb-build-rate-command grouplens-rating-alist)))
|
||||
(if bbb-process
|
||||
(save-excursion
|
||||
(set-buffer (process-buffer bbb-process))
|
||||
(gnus-message 5 "Sending Ratings...")
|
||||
(bbb-send-command bbb-process rate-command)
|
||||
(if (bbb-read-response bbb-process)
|
||||
(setq grouplens-rating-alist nil)
|
||||
(gnus-message 1
|
||||
"Token timed out: call bbb-login and quit again")
|
||||
(ding))
|
||||
(gnus-message 5 "Sending Ratings...Done"))
|
||||
(gnus-message 3 "No BBB connection")))
|
||||
(setq grouplens-rating-alist nil)))
|
||||
|
||||
(defun bbb-build-rate-command (rate-alist)
|
||||
(concat "putratings " grouplens-bbb-token " " grouplens-current-group " \r\n"
|
||||
(mapconcat '(lambda (this) ; form (mid . (score . time))
|
||||
(concat (car this)
|
||||
" :rating=" (cadr this) ".00"
|
||||
" :time=" (cddr this)))
|
||||
rate-alist "\r\n")
|
||||
"\r\n.\r\n"))
|
||||
|
||||
;; Interactive rating functions.
|
||||
(defun bbb-summary-rate-article (rating &optional midin)
|
||||
(interactive "nRating: ")
|
||||
(when (member gnus-newsgroup-name grouplens-newsgroups)
|
||||
(let ((mid (or midin (bbb-get-current-id))))
|
||||
(if (and rating
|
||||
(>= rating grplens-minrating)
|
||||
(<= rating grplens-maxrating)
|
||||
mid)
|
||||
(let ((oldrating (assoc mid grouplens-rating-alist)))
|
||||
(if oldrating
|
||||
(setcdr oldrating (cons rating 0))
|
||||
(push `(,mid . (,rating . 0)) grouplens-rating-alist))
|
||||
(gnus-summary-mark-article nil (int-to-string rating)))
|
||||
(gnus-message 3 "Invalid rating")))))
|
||||
|
||||
(defun grouplens-next-unread-article (rating)
|
||||
"Select unread article after current one."
|
||||
(interactive "P")
|
||||
(when rating
|
||||
(bbb-summary-rate-article rating))
|
||||
(gnus-summary-next-unread-article))
|
||||
|
||||
(defun grouplens-best-unread-article (rating)
|
||||
"Select unread article after current one."
|
||||
(interactive "P")
|
||||
(when rating
|
||||
(bbb-summary-rate-article rating))
|
||||
(gnus-summary-best-unread-article))
|
||||
|
||||
(defun grouplens-summary-catchup-and-exit (rating)
|
||||
"Mark all articles not marked as unread in this newsgroup as read,
|
||||
then exit. If prefix argument ALL is non-nil, all articles are
|
||||
marked as read."
|
||||
(interactive "P")
|
||||
(when rating
|
||||
(bbb-summary-rate-article rating))
|
||||
(if (numberp rating)
|
||||
(gnus-summary-catchup-and-exit)
|
||||
(gnus-summary-catchup-and-exit rating)))
|
||||
|
||||
(defun grouplens-score-thread (score)
|
||||
"Raise the score of the articles in the current thread with SCORE."
|
||||
(interactive "nRating: ")
|
||||
(let (e)
|
||||
(save-excursion
|
||||
(let ((articles (gnus-summary-articles-in-thread))
|
||||
article)
|
||||
(while (setq article (pop articles))
|
||||
(gnus-summary-goto-subject article)
|
||||
(gnus-set-global-variables)
|
||||
(bbb-summary-rate-article score
|
||||
(mail-header-id
|
||||
(gnus-summary-article-header article)))))
|
||||
(setq e (point)))
|
||||
(let ((gnus-summary-check-current t))
|
||||
(or (zerop (gnus-summary-next-subject 1 t))
|
||||
(goto-char e))))
|
||||
(gnus-summary-recenter)
|
||||
(gnus-summary-position-point)
|
||||
(gnus-set-mode-line 'summary))
|
||||
|
||||
(defun bbb-exit-group ()
|
||||
(bbb-put-ratings)
|
||||
(bbb-renew-hash-table))
|
||||
|
||||
(defun bbb-get-current-id ()
|
||||
(if gnus-current-headers
|
||||
(mail-header-id gnus-current-headers)
|
||||
(gnus-message 3 "You must select an article before you rate it")))
|
||||
|
||||
(defun bbb-grouplens-group-p (group)
|
||||
"Say whether GROUP is a GroupLens group."
|
||||
(if (member group grouplens-newsgroups) " (GroupLens Enhanced)" ""))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; TIME SPENT READING
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
(defvar grouplens-current-starting-time nil)
|
||||
|
||||
(defun grouplens-start-timer ()
|
||||
(setq grouplens-current-starting-time (current-time)))
|
||||
|
||||
(defun grouplens-elapsed-time ()
|
||||
(let ((et (bbb-time-float (current-time))))
|
||||
(- et (bbb-time-float grouplens-current-starting-time))))
|
||||
|
||||
(defun bbb-time-float (timeval)
|
||||
(+ (* (car timeval) 65536)
|
||||
(cadr timeval)))
|
||||
|
||||
(defun grouplens-do-time ()
|
||||
(when (member gnus-newsgroup-name grouplens-newsgroups)
|
||||
(when grouplens-previous-article
|
||||
(let ((elapsed-time (grouplens-elapsed-time))
|
||||
(oldrating (assoc grouplens-previous-article
|
||||
grouplens-rating-alist)))
|
||||
(if (not oldrating)
|
||||
(push `(,grouplens-previous-article . (0 . ,elapsed-time))
|
||||
grouplens-rating-alist)
|
||||
(setcdr oldrating (cons (cadr oldrating) elapsed-time)))))
|
||||
(grouplens-start-timer)
|
||||
(setq grouplens-previous-article (bbb-get-current-id))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; BUG REPORTING
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defconst gnus-gl-version "gnus-gl.el 2.50")
|
||||
(defconst gnus-gl-maintainer-address "grouplens-bug@cs.umn.edu")
|
||||
(defun gnus-gl-submit-bug-report ()
|
||||
"Submit via mail a bug report on gnus-gl"
|
||||
(interactive)
|
||||
(require 'reporter)
|
||||
(reporter-submit-bug-report gnus-gl-maintainer-address
|
||||
(concat "gnus-gl.el " gnus-gl-version)
|
||||
(list 'grouplens-pseudonym
|
||||
'grouplens-bbb-host
|
||||
'grouplens-bbb-port
|
||||
'grouplens-newsgroups
|
||||
'grouplens-bbb-token
|
||||
'grouplens-bbb-process
|
||||
'grouplens-current-group
|
||||
'grouplens-previous-article)
|
||||
nil
|
||||
'gnus-gl-get-trace))
|
||||
|
||||
(defun gnus-gl-get-trace ()
|
||||
"Insert the contents of the BBBD trace buffer"
|
||||
(when grouplens-bbb-buffer
|
||||
(insert-buffer grouplens-bbb-buffer)))
|
||||
|
||||
;;
|
||||
;; GroupLens minor mode
|
||||
;;
|
||||
|
||||
(defvar gnus-grouplens-mode nil
|
||||
"Minor mode for providing a GroupLens interface in Gnus summary buffers.")
|
||||
|
||||
(defvar gnus-grouplens-mode-map nil)
|
||||
|
||||
(unless gnus-grouplens-mode-map
|
||||
(setq gnus-grouplens-mode-map (make-keymap))
|
||||
(gnus-define-keys
|
||||
gnus-grouplens-mode-map
|
||||
"n" grouplens-next-unread-article
|
||||
"r" bbb-summary-rate-article
|
||||
"k" grouplens-score-thread
|
||||
"c" grouplens-summary-catchup-and-exit
|
||||
"," grouplens-best-unread-article))
|
||||
|
||||
(defun gnus-grouplens-make-menu-bar ()
|
||||
(unless (boundp 'gnus-grouplens-menu)
|
||||
(easy-menu-define
|
||||
gnus-grouplens-menu gnus-grouplens-mode-map ""
|
||||
'("GroupLens"
|
||||
["Login" bbb-login t]
|
||||
["Rate" bbb-summary-rate-article t]
|
||||
["Next article" grouplens-next-unread-article t]
|
||||
["Best article" grouplens-best-unread-article t]
|
||||
["Raise thread" grouplens-score-thread t]
|
||||
["Report bugs" gnus-gl-submit-bug-report t]))))
|
||||
|
||||
(defun gnus-grouplens-mode (&optional arg)
|
||||
"Minor mode for providing a GroupLens interface in Gnus summary buffers."
|
||||
(interactive "P")
|
||||
(when (and (eq major-mode 'gnus-summary-mode)
|
||||
(member gnus-newsgroup-name grouplens-newsgroups))
|
||||
(make-local-variable 'gnus-grouplens-mode)
|
||||
(setq gnus-grouplens-mode
|
||||
(if (null arg) (not gnus-grouplens-mode)
|
||||
(> (prefix-numeric-value arg) 0)))
|
||||
(when gnus-grouplens-mode
|
||||
(make-local-hook 'gnus-select-article-hook)
|
||||
(add-hook 'gnus-select-article-hook 'grouplens-do-time nil 'local)
|
||||
(make-local-hook 'gnus-exit-group-hook)
|
||||
(add-hook 'gnus-exit-group-hook 'bbb-exit-group nil 'local)
|
||||
(make-local-variable 'gnus-score-find-score-files-function)
|
||||
|
||||
(cond
|
||||
((eq gnus-grouplens-override-scoring 'combine)
|
||||
;; either add bbb-buld-mid-scores-alist to a list
|
||||
;; or make a list
|
||||
(if (listp gnus-score-find-score-files-function)
|
||||
(setq gnus-score-find-score-files-function
|
||||
(append 'bbb-build-mid-scores-alist
|
||||
gnus-score-find-score-files-function))
|
||||
(setq gnus-score-find-score-files-function
|
||||
(list gnus-score-find-score-files-function
|
||||
'bbb-build-mid-scores-alist))))
|
||||
;; leave the gnus-score-find-score-files variable alone
|
||||
((eq gnus-grouplens-override-scoring 'separate)
|
||||
(add-hook 'gnus-select-group-hook
|
||||
(lambda ()
|
||||
(bbb-get-predictions (bbb-get-all-mids)
|
||||
gnus-newsgroup-name))))
|
||||
;; default is to override
|
||||
(t
|
||||
(setq gnus-score-find-score-files-function
|
||||
'bbb-build-mid-scores-alist)))
|
||||
|
||||
;; Change how summary lines look
|
||||
(make-local-variable 'gnus-summary-line-format)
|
||||
(make-local-variable 'gnus-summary-line-format-spec)
|
||||
(setq gnus-summary-line-format gnus-summary-grouplens-line-format)
|
||||
(setq gnus-summary-line-format-spec nil)
|
||||
(gnus-update-format-specifications nil 'summary)
|
||||
(gnus-update-summary-mark-positions)
|
||||
|
||||
;; Set up the menu.
|
||||
(when (and menu-bar-mode
|
||||
(gnus-visual-p 'grouplens-menu 'menu))
|
||||
(gnus-grouplens-make-menu-bar))
|
||||
(unless (assq 'gnus-grouplens-mode minor-mode-alist)
|
||||
(push '(gnus-grouplens-mode " GroupLens") minor-mode-alist))
|
||||
(unless (assq 'gnus-grouplens-mode minor-mode-map-alist)
|
||||
(push (cons 'gnus-grouplens-mode gnus-grouplens-mode-map)
|
||||
minor-mode-map-alist))
|
||||
(run-hooks 'gnus-grouplens-mode-hook))))
|
||||
|
||||
(provide 'gnus-gl)
|
||||
|
||||
;;; gnus-gl.el ends here
|
||||
3335
lisp/gnus/gnus-group.el
Normal file
3335
lisp/gnus/gnus-group.el
Normal file
File diff suppressed because it is too large
Load diff
438
lisp/gnus/gnus-int.el
Normal file
438
lisp/gnus/gnus-int.el
Normal file
|
|
@ -0,0 +1,438 @@
|
|||
;;; gnus-int.el --- backend interface functions for Gnus
|
||||
;; Copyright (C) 1996,97 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
|
||||
;; Keywords: news
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'gnus)
|
||||
|
||||
(defcustom gnus-open-server-hook nil
|
||||
"Hook called just before opening connection to the news server."
|
||||
:group 'gnus-start
|
||||
:type 'hook)
|
||||
|
||||
;;;
|
||||
;;; Server Communication
|
||||
;;;
|
||||
|
||||
(defun gnus-start-news-server (&optional confirm)
|
||||
"Open a method for getting news.
|
||||
If CONFIRM is non-nil, the user will be asked for an NNTP server."
|
||||
(let (how)
|
||||
(if gnus-current-select-method
|
||||
;; Stream is already opened.
|
||||
nil
|
||||
;; Open NNTP server.
|
||||
(unless gnus-nntp-service
|
||||
(setq gnus-nntp-server nil))
|
||||
(when confirm
|
||||
;; Read server name with completion.
|
||||
(setq gnus-nntp-server
|
||||
(completing-read "NNTP server: "
|
||||
(mapcar (lambda (server) (list server))
|
||||
(cons (list gnus-nntp-server)
|
||||
gnus-secondary-servers))
|
||||
nil nil gnus-nntp-server)))
|
||||
|
||||
(when (and gnus-nntp-server
|
||||
(stringp gnus-nntp-server)
|
||||
(not (string= gnus-nntp-server "")))
|
||||
(setq gnus-select-method
|
||||
(cond ((or (string= gnus-nntp-server "")
|
||||
(string= gnus-nntp-server "::"))
|
||||
(list 'nnspool (system-name)))
|
||||
((string-match "^:" gnus-nntp-server)
|
||||
(list 'nnmh gnus-nntp-server
|
||||
(list 'nnmh-directory
|
||||
(file-name-as-directory
|
||||
(expand-file-name
|
||||
(concat "~/" (substring
|
||||
gnus-nntp-server 1)))))
|
||||
(list 'nnmh-get-new-mail nil)))
|
||||
(t
|
||||
(list 'nntp gnus-nntp-server)))))
|
||||
|
||||
(setq how (car gnus-select-method))
|
||||
(cond
|
||||
((eq how 'nnspool)
|
||||
(require 'nnspool)
|
||||
(gnus-message 5 "Looking up local news spool..."))
|
||||
((eq how 'nnmh)
|
||||
(require 'nnmh)
|
||||
(gnus-message 5 "Looking up mh spool..."))
|
||||
(t
|
||||
(require 'nntp)))
|
||||
(setq gnus-current-select-method gnus-select-method)
|
||||
(run-hooks 'gnus-open-server-hook)
|
||||
(or
|
||||
;; gnus-open-server-hook might have opened it
|
||||
(gnus-server-opened gnus-select-method)
|
||||
(gnus-open-server gnus-select-method)
|
||||
(gnus-y-or-n-p
|
||||
(format
|
||||
"%s (%s) open error: '%s'. Continue? "
|
||||
(car gnus-select-method) (cadr gnus-select-method)
|
||||
(gnus-status-message gnus-select-method)))
|
||||
(gnus-error 1 "Couldn't open server on %s"
|
||||
(nth 1 gnus-select-method))))))
|
||||
|
||||
(defun gnus-check-group (group)
|
||||
"Try to make sure that the server where GROUP exists is alive."
|
||||
(let ((method (gnus-find-method-for-group group)))
|
||||
(or (gnus-server-opened method)
|
||||
(gnus-open-server method))))
|
||||
|
||||
(defun gnus-check-server (&optional method silent)
|
||||
"Check whether the connection to METHOD is down.
|
||||
If METHOD is nil, use `gnus-select-method'.
|
||||
If it is down, start it up (again)."
|
||||
(let ((method (or method gnus-select-method)))
|
||||
;; Transform virtual server names into select methods.
|
||||
(when (stringp method)
|
||||
(setq method (gnus-server-to-method method)))
|
||||
(if (gnus-server-opened method)
|
||||
;; The stream is already opened.
|
||||
t
|
||||
;; Open the server.
|
||||
(unless silent
|
||||
(gnus-message 5 "Opening %s server%s..." (car method)
|
||||
(if (equal (nth 1 method) "") ""
|
||||
(format " on %s" (nth 1 method)))))
|
||||
(run-hooks 'gnus-open-server-hook)
|
||||
(prog1
|
||||
(gnus-open-server method)
|
||||
(unless silent
|
||||
(message ""))))))
|
||||
|
||||
(defun gnus-get-function (method function &optional noerror)
|
||||
"Return a function symbol based on METHOD and FUNCTION."
|
||||
;; Translate server names into methods.
|
||||
(unless method
|
||||
(error "Attempted use of a nil select method"))
|
||||
(when (stringp method)
|
||||
(setq method (gnus-server-to-method method)))
|
||||
(let ((func (intern (format "%s-%s" (car method) function))))
|
||||
;; If the functions isn't bound, we require the backend in
|
||||
;; question.
|
||||
(unless (fboundp func)
|
||||
(require (car method))
|
||||
(when (and (not (fboundp func))
|
||||
(not noerror))
|
||||
;; This backend doesn't implement this function.
|
||||
(error "No such function: %s" func)))
|
||||
func))
|
||||
|
||||
|
||||
;;;
|
||||
;;; Interface functions to the backends.
|
||||
;;;
|
||||
|
||||
(defun gnus-open-server (method)
|
||||
"Open a connection to METHOD."
|
||||
(when (stringp method)
|
||||
(setq method (gnus-server-to-method method)))
|
||||
(let ((elem (assoc method gnus-opened-servers)))
|
||||
;; If this method was previously denied, we just return nil.
|
||||
(if (eq (nth 1 elem) 'denied)
|
||||
(progn
|
||||
(gnus-message 1 "Denied server")
|
||||
nil)
|
||||
;; Open the server.
|
||||
(let ((result
|
||||
(funcall (gnus-get-function method 'open-server)
|
||||
(nth 1 method) (nthcdr 2 method))))
|
||||
;; If this hasn't been opened before, we add it to the list.
|
||||
(unless elem
|
||||
(setq elem (list method nil)
|
||||
gnus-opened-servers (cons elem gnus-opened-servers)))
|
||||
;; Set the status of this server.
|
||||
(setcar (cdr elem) (if result 'ok 'denied))
|
||||
;; Return the result from the "open" call.
|
||||
result))))
|
||||
|
||||
(defun gnus-close-server (method)
|
||||
"Close the connection to METHOD."
|
||||
(when (stringp method)
|
||||
(setq method (gnus-server-to-method method)))
|
||||
(funcall (gnus-get-function method 'close-server) (nth 1 method)))
|
||||
|
||||
(defun gnus-request-list (method)
|
||||
"Request the active file from METHOD."
|
||||
(when (stringp method)
|
||||
(setq method (gnus-server-to-method method)))
|
||||
(funcall (gnus-get-function method 'request-list) (nth 1 method)))
|
||||
|
||||
(defun gnus-request-list-newsgroups (method)
|
||||
"Request the newsgroups file from METHOD."
|
||||
(when (stringp method)
|
||||
(setq method (gnus-server-to-method method)))
|
||||
(funcall (gnus-get-function method 'request-list-newsgroups) (nth 1 method)))
|
||||
|
||||
(defun gnus-request-newgroups (date method)
|
||||
"Request all new groups since DATE from METHOD."
|
||||
(when (stringp method)
|
||||
(setq method (gnus-server-to-method method)))
|
||||
(let ((func (gnus-get-function method 'request-newgroups t)))
|
||||
(when func
|
||||
(funcall func date (nth 1 method)))))
|
||||
|
||||
(defun gnus-server-opened (method)
|
||||
"Check whether a connection to METHOD has been opened."
|
||||
(when (stringp method)
|
||||
(setq method (gnus-server-to-method method)))
|
||||
(funcall (inline (gnus-get-function method 'server-opened)) (nth 1 method)))
|
||||
|
||||
(defun gnus-status-message (method)
|
||||
"Return the status message from METHOD.
|
||||
If METHOD is a string, it is interpreted as a group name. The method
|
||||
this group uses will be queried."
|
||||
(let ((method (if (stringp method) (gnus-find-method-for-group method)
|
||||
method)))
|
||||
(funcall (gnus-get-function method 'status-message) (nth 1 method))))
|
||||
|
||||
(defun gnus-request-regenerate (method)
|
||||
"Request a data generation from METHOD."
|
||||
(when (stringp method)
|
||||
(setq method (gnus-server-to-method method)))
|
||||
(funcall (gnus-get-function method 'request-regenerate) (nth 1 method)))
|
||||
|
||||
(defun gnus-request-group (group &optional dont-check method)
|
||||
"Request GROUP. If DONT-CHECK, no information is required."
|
||||
(let ((method (or method (inline (gnus-find-method-for-group group)))))
|
||||
(when (stringp method)
|
||||
(setq method (inline (gnus-server-to-method method))))
|
||||
(funcall (inline (gnus-get-function method 'request-group))
|
||||
(gnus-group-real-name group) (nth 1 method) dont-check)))
|
||||
|
||||
(defun gnus-list-active-group (group)
|
||||
"Request active information on GROUP."
|
||||
(let ((method (gnus-find-method-for-group group))
|
||||
(func 'list-active-group))
|
||||
(when (gnus-check-backend-function func group)
|
||||
(funcall (gnus-get-function method func)
|
||||
(gnus-group-real-name group) (nth 1 method)))))
|
||||
|
||||
(defun gnus-request-group-description (group)
|
||||
"Request a description of GROUP."
|
||||
(let ((method (gnus-find-method-for-group group))
|
||||
(func 'request-group-description))
|
||||
(when (gnus-check-backend-function func group)
|
||||
(funcall (gnus-get-function method func)
|
||||
(gnus-group-real-name group) (nth 1 method)))))
|
||||
|
||||
(defun gnus-close-group (group)
|
||||
"Request the GROUP be closed."
|
||||
(let ((method (inline (gnus-find-method-for-group group))))
|
||||
(funcall (gnus-get-function method 'close-group)
|
||||
(gnus-group-real-name group) (nth 1 method))))
|
||||
|
||||
(defun gnus-retrieve-headers (articles group &optional fetch-old)
|
||||
"Request headers for ARTICLES in GROUP.
|
||||
If FETCH-OLD, retrieve all headers (or some subset thereof) in the group."
|
||||
(let ((method (gnus-find-method-for-group group)))
|
||||
(if (and gnus-use-cache (numberp (car articles)))
|
||||
(gnus-cache-retrieve-headers articles group fetch-old)
|
||||
(funcall (gnus-get-function method 'retrieve-headers)
|
||||
articles (gnus-group-real-name group) (nth 1 method)
|
||||
fetch-old))))
|
||||
|
||||
(defun gnus-retrieve-groups (groups method)
|
||||
"Request active information on GROUPS from METHOD."
|
||||
(when (stringp method)
|
||||
(setq method (gnus-server-to-method method)))
|
||||
(funcall (gnus-get-function method 'retrieve-groups) groups (nth 1 method)))
|
||||
|
||||
(defun gnus-request-type (group &optional article)
|
||||
"Return the type (`post' or `mail') of GROUP (and ARTICLE)."
|
||||
(let ((method (gnus-find-method-for-group group)))
|
||||
(if (not (gnus-check-backend-function 'request-type (car method)))
|
||||
'unknown
|
||||
(funcall (gnus-get-function method 'request-type)
|
||||
(gnus-group-real-name group) article))))
|
||||
|
||||
(defun gnus-request-update-mark (group article mark)
|
||||
"Return the type (`post' or `mail') of GROUP (and ARTICLE)."
|
||||
(let ((method (gnus-find-method-for-group group)))
|
||||
(if (not (gnus-check-backend-function 'request-update-mark (car method)))
|
||||
mark
|
||||
(funcall (gnus-get-function method 'request-update-mark)
|
||||
(gnus-group-real-name group) article mark))))
|
||||
|
||||
(defun gnus-request-article (article group &optional buffer)
|
||||
"Request the ARTICLE in GROUP.
|
||||
ARTICLE can either be an article number or an article Message-ID.
|
||||
If BUFFER, insert the article in that group."
|
||||
(let ((method (gnus-find-method-for-group group)))
|
||||
(funcall (gnus-get-function method 'request-article)
|
||||
article (gnus-group-real-name group) (nth 1 method) buffer)))
|
||||
|
||||
(defun gnus-request-head (article group)
|
||||
"Request the head of ARTICLE in GROUP."
|
||||
(let* ((method (gnus-find-method-for-group group))
|
||||
(head (gnus-get-function method 'request-head t))
|
||||
res clean-up)
|
||||
(cond
|
||||
;; Check the cache.
|
||||
((and gnus-use-cache
|
||||
(numberp article)
|
||||
(gnus-cache-request-article article group))
|
||||
(setq res (cons group article)
|
||||
clean-up t))
|
||||
;; Use `head' function.
|
||||
((fboundp head)
|
||||
(setq res (funcall head article (gnus-group-real-name group)
|
||||
(nth 1 method))))
|
||||
;; Use `article' function.
|
||||
(t
|
||||
(setq res (gnus-request-article article group)
|
||||
clean-up t)))
|
||||
(when clean-up
|
||||
(save-excursion
|
||||
(set-buffer nntp-server-buffer)
|
||||
(goto-char (point-min))
|
||||
(when (search-forward "\n\n" nil t)
|
||||
(delete-region (1- (point)) (point-max)))
|
||||
(nnheader-fold-continuation-lines)))
|
||||
res))
|
||||
|
||||
(defun gnus-request-body (article group)
|
||||
"Request the body of ARTICLE in GROUP."
|
||||
(let ((method (gnus-find-method-for-group group)))
|
||||
(funcall (gnus-get-function method 'request-body)
|
||||
article (gnus-group-real-name group) (nth 1 method))))
|
||||
|
||||
(defun gnus-request-post (method)
|
||||
"Post the current buffer using METHOD."
|
||||
(when (stringp method)
|
||||
(setq method (gnus-server-to-method method)))
|
||||
(funcall (gnus-get-function method 'request-post) (nth 1 method)))
|
||||
|
||||
(defun gnus-request-scan (group method)
|
||||
"Request a SCAN being performed in GROUP from METHOD.
|
||||
If GROUP is nil, all groups on METHOD are scanned."
|
||||
(let ((method (if group (gnus-find-method-for-group group) method))
|
||||
(gnus-inhibit-demon t))
|
||||
(funcall (gnus-get-function method 'request-scan)
|
||||
(and group (gnus-group-real-name group)) (nth 1 method))))
|
||||
|
||||
(defsubst gnus-request-update-info (info method)
|
||||
"Request that METHOD update INFO."
|
||||
(when (stringp method)
|
||||
(setq method (gnus-server-to-method method)))
|
||||
(when (gnus-check-backend-function 'request-update-info (car method))
|
||||
(funcall (gnus-get-function method 'request-update-info)
|
||||
(gnus-group-real-name (gnus-info-group info))
|
||||
info (nth 1 method))))
|
||||
|
||||
(defun gnus-request-expire-articles (articles group &optional force)
|
||||
(let ((method (gnus-find-method-for-group group)))
|
||||
(funcall (gnus-get-function method 'request-expire-articles)
|
||||
articles (gnus-group-real-name group) (nth 1 method)
|
||||
force)))
|
||||
|
||||
(defun gnus-request-move-article
|
||||
(article group server accept-function &optional last)
|
||||
(let ((method (gnus-find-method-for-group group)))
|
||||
(funcall (gnus-get-function method 'request-move-article)
|
||||
article (gnus-group-real-name group)
|
||||
(nth 1 method) accept-function last)))
|
||||
|
||||
(defun gnus-request-accept-article (group method &optional last)
|
||||
;; Make sure there's a newline at the end of the article.
|
||||
(when (stringp method)
|
||||
(setq method (gnus-server-to-method method)))
|
||||
(when (and (not method)
|
||||
(stringp group))
|
||||
(setq method (gnus-group-name-to-method group)))
|
||||
(goto-char (point-max))
|
||||
(unless (bolp)
|
||||
(insert "\n"))
|
||||
(let ((func (car (or method (gnus-find-method-for-group group)))))
|
||||
(funcall (intern (format "%s-request-accept-article" func))
|
||||
(if (stringp group) (gnus-group-real-name group) group)
|
||||
(cadr method)
|
||||
last)))
|
||||
|
||||
(defun gnus-request-replace-article (article group buffer)
|
||||
(let ((func (car (gnus-find-method-for-group group))))
|
||||
(funcall (intern (format "%s-request-replace-article" func))
|
||||
article (gnus-group-real-name group) buffer)))
|
||||
|
||||
(defun gnus-request-associate-buffer (group)
|
||||
(let ((method (gnus-find-method-for-group group)))
|
||||
(funcall (gnus-get-function method 'request-associate-buffer)
|
||||
(gnus-group-real-name group))))
|
||||
|
||||
(defun gnus-request-restore-buffer (article group)
|
||||
"Request a new buffer restored to the state of ARTICLE."
|
||||
(let ((method (gnus-find-method-for-group group)))
|
||||
(funcall (gnus-get-function method 'request-restore-buffer)
|
||||
article (gnus-group-real-name group) (nth 1 method))))
|
||||
|
||||
(defun gnus-request-create-group (group &optional method args)
|
||||
(when (stringp method)
|
||||
(setq method (gnus-server-to-method method)))
|
||||
(let ((method (or method (gnus-find-method-for-group group))))
|
||||
(funcall (gnus-get-function method 'request-create-group)
|
||||
(gnus-group-real-name group) (nth 1 method) args)))
|
||||
|
||||
(defun gnus-request-delete-group (group &optional force)
|
||||
(let ((method (gnus-find-method-for-group group)))
|
||||
(funcall (gnus-get-function method 'request-delete-group)
|
||||
(gnus-group-real-name group) force (nth 1 method))))
|
||||
|
||||
(defun gnus-request-rename-group (group new-name)
|
||||
(let ((method (gnus-find-method-for-group group)))
|
||||
(funcall (gnus-get-function method 'request-rename-group)
|
||||
(gnus-group-real-name group)
|
||||
(gnus-group-real-name new-name) (nth 1 method))))
|
||||
|
||||
(defun gnus-close-backends ()
|
||||
;; Send a close request to all backends that support such a request.
|
||||
(let ((methods gnus-valid-select-methods)
|
||||
(gnus-inhibit-demon t)
|
||||
func method)
|
||||
(while (setq method (pop methods))
|
||||
(when (fboundp (setq func (intern
|
||||
(concat (car method) "-request-close"))))
|
||||
(funcall func)))))
|
||||
|
||||
(defun gnus-asynchronous-p (method)
|
||||
(let ((func (gnus-get-function method 'asynchronous-p t)))
|
||||
(when (fboundp func)
|
||||
(funcall func))))
|
||||
|
||||
(defun gnus-remove-denial (method)
|
||||
(when (stringp method)
|
||||
(setq method (gnus-server-to-method method)))
|
||||
(let* ((elem (assoc method gnus-opened-servers))
|
||||
(status (cadr elem)))
|
||||
;; If this hasn't been opened before, we add it to the list.
|
||||
(when (eq status 'denied)
|
||||
;; Set the status of this server.
|
||||
(setcar (cdr elem) 'closed))))
|
||||
|
||||
(provide 'gnus-int)
|
||||
|
||||
;;; gnus-int.el ends here
|
||||
717
lisp/gnus/gnus-kill.el
Normal file
717
lisp/gnus/gnus-kill.el
Normal file
|
|
@ -0,0 +1,717 @@
|
|||
;;; gnus-kill.el --- kill commands for Gnus
|
||||
;; Copyright (C) 1995,96,97 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
|
||||
;; Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
|
||||
;; Keywords: news
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'gnus)
|
||||
(require 'gnus-art)
|
||||
(require 'gnus-range)
|
||||
|
||||
(defcustom gnus-kill-file-mode-hook nil
|
||||
"Hook for Gnus kill file mode."
|
||||
:group 'gnus-score-kill
|
||||
:type 'hook)
|
||||
|
||||
(defcustom gnus-kill-expiry-days 7
|
||||
"*Number of days before expiring unused kill file entries."
|
||||
:group 'gnus-score-kill
|
||||
:group 'gnus-score-expire
|
||||
:type 'integer)
|
||||
|
||||
(defcustom gnus-kill-save-kill-file nil
|
||||
"*If non-nil, will save kill files after processing them."
|
||||
:group 'gnus-score-kill
|
||||
:type 'boolean)
|
||||
|
||||
(defcustom gnus-winconf-kill-file nil
|
||||
"What does this do, Lars?"
|
||||
:group 'gnus-score-kill
|
||||
:type 'sexp)
|
||||
|
||||
(defcustom gnus-kill-killed t
|
||||
"*If non-nil, Gnus will apply kill files to already killed articles.
|
||||
If it is nil, Gnus will never apply kill files to articles that have
|
||||
already been through the scoring process, which might very well save lots
|
||||
of time."
|
||||
:group 'gnus-score-kill
|
||||
:type 'boolean)
|
||||
|
||||
|
||||
|
||||
(defmacro gnus-raise (field expression level)
|
||||
`(gnus-kill ,field ,expression
|
||||
(function (gnus-summary-raise-score ,level)) t))
|
||||
|
||||
(defmacro gnus-lower (field expression level)
|
||||
`(gnus-kill ,field ,expression
|
||||
(function (gnus-summary-raise-score (- ,level))) t))
|
||||
|
||||
;;;
|
||||
;;; Gnus Kill File Mode
|
||||
;;;
|
||||
|
||||
(defvar gnus-kill-file-mode-map nil)
|
||||
|
||||
(unless gnus-kill-file-mode-map
|
||||
(gnus-define-keymap (setq gnus-kill-file-mode-map
|
||||
(copy-keymap emacs-lisp-mode-map))
|
||||
"\C-c\C-k\C-s" gnus-kill-file-kill-by-subject
|
||||
"\C-c\C-k\C-a" gnus-kill-file-kill-by-author
|
||||
"\C-c\C-k\C-t" gnus-kill-file-kill-by-thread
|
||||
"\C-c\C-k\C-x" gnus-kill-file-kill-by-xref
|
||||
"\C-c\C-a" gnus-kill-file-apply-buffer
|
||||
"\C-c\C-e" gnus-kill-file-apply-last-sexp
|
||||
"\C-c\C-c" gnus-kill-file-exit))
|
||||
|
||||
(defun gnus-kill-file-mode ()
|
||||
"Major mode for editing kill files.
|
||||
|
||||
If you are using this mode - you probably shouldn't. Kill files
|
||||
perform badly and paint with a pretty broad brush. Score files, on
|
||||
the other hand, are vastly faster (40x speedup) and give you more
|
||||
control over what to do.
|
||||
|
||||
In addition to Emacs-Lisp Mode, the following commands are available:
|
||||
|
||||
\\{gnus-kill-file-mode-map}
|
||||
|
||||
A kill file contains Lisp expressions to be applied to a selected
|
||||
newsgroup. The purpose is to mark articles as read on the basis of
|
||||
some set of regexps. A global kill file is applied to every newsgroup,
|
||||
and a local kill file is applied to a specified newsgroup. Since a
|
||||
global kill file is applied to every newsgroup, for better performance
|
||||
use a local one.
|
||||
|
||||
A kill file can contain any kind of Emacs Lisp expressions expected
|
||||
to be evaluated in the Summary buffer. Writing Lisp programs for this
|
||||
purpose is not so easy because the internal working of Gnus must be
|
||||
well-known. For this reason, Gnus provides a general function which
|
||||
does this easily for non-Lisp programmers.
|
||||
|
||||
The `gnus-kill' function executes commands available in Summary Mode
|
||||
by their key sequences. `gnus-kill' should be called with FIELD,
|
||||
REGEXP and optional COMMAND and ALL. FIELD is a string representing
|
||||
the header field or an empty string. If FIELD is an empty string, the
|
||||
entire article body is searched for. REGEXP is a string which is
|
||||
compared with FIELD value. COMMAND is a string representing a valid
|
||||
key sequence in Summary mode or Lisp expression. COMMAND defaults to
|
||||
'(gnus-summary-mark-as-read nil \"X\"). Make sure that COMMAND is
|
||||
executed in the Summary buffer. If the second optional argument ALL
|
||||
is non-nil, the COMMAND is applied to articles which are already
|
||||
marked as read or unread. Articles which are marked are skipped over
|
||||
by default.
|
||||
|
||||
For example, if you want to mark articles of which subjects contain
|
||||
the string `AI' as read, a possible kill file may look like:
|
||||
|
||||
(gnus-kill \"Subject\" \"AI\")
|
||||
|
||||
If you want to mark articles with `D' instead of `X', you can use
|
||||
the following expression:
|
||||
|
||||
(gnus-kill \"Subject\" \"AI\" \"d\")
|
||||
|
||||
In this example it is assumed that the command
|
||||
`gnus-summary-mark-as-read-forward' is assigned to `d' in Summary Mode.
|
||||
|
||||
It is possible to delete unnecessary headers which are marked with
|
||||
`X' in a kill file as follows:
|
||||
|
||||
(gnus-expunge \"X\")
|
||||
|
||||
If the Summary buffer is empty after applying kill files, Gnus will
|
||||
exit the selected newsgroup normally. If headers which are marked
|
||||
with `D' are deleted in a kill file, it is impossible to read articles
|
||||
which are marked as read in the previous Gnus sessions. Marks other
|
||||
than `D' should be used for articles which should really be deleted.
|
||||
|
||||
Entry to this mode calls emacs-lisp-mode-hook and
|
||||
gnus-kill-file-mode-hook with no arguments, if that value is non-nil."
|
||||
(interactive)
|
||||
(kill-all-local-variables)
|
||||
(use-local-map gnus-kill-file-mode-map)
|
||||
(set-syntax-table emacs-lisp-mode-syntax-table)
|
||||
(setq major-mode 'gnus-kill-file-mode)
|
||||
(setq mode-name "Kill")
|
||||
(lisp-mode-variables nil)
|
||||
(run-hooks 'emacs-lisp-mode-hook 'gnus-kill-file-mode-hook))
|
||||
|
||||
(defun gnus-kill-file-edit-file (newsgroup)
|
||||
"Begin editing a kill file for NEWSGROUP.
|
||||
If NEWSGROUP is nil, the global kill file is selected."
|
||||
(interactive "sNewsgroup: ")
|
||||
(let ((file (gnus-newsgroup-kill-file newsgroup)))
|
||||
(gnus-make-directory (file-name-directory file))
|
||||
;; Save current window configuration if this is first invocation.
|
||||
(or (and (get-file-buffer file)
|
||||
(get-buffer-window (get-file-buffer file)))
|
||||
(setq gnus-winconf-kill-file (current-window-configuration)))
|
||||
;; Hack windows.
|
||||
(let ((buffer (find-file-noselect file)))
|
||||
(cond ((get-buffer-window buffer)
|
||||
(pop-to-buffer buffer))
|
||||
((eq major-mode 'gnus-group-mode)
|
||||
(gnus-configure-windows 'group) ;Take all windows.
|
||||
(pop-to-buffer buffer))
|
||||
((eq major-mode 'gnus-summary-mode)
|
||||
(gnus-configure-windows 'article)
|
||||
(pop-to-buffer gnus-article-buffer)
|
||||
(bury-buffer gnus-article-buffer)
|
||||
(switch-to-buffer buffer))
|
||||
(t ;No good rules.
|
||||
(find-file-other-window file))))
|
||||
(gnus-kill-file-mode)))
|
||||
|
||||
;; Fix by Sudish Joseph <joseph@cis.ohio-state.edu>.
|
||||
(defun gnus-kill-set-kill-buffer ()
|
||||
(let* ((file (gnus-newsgroup-kill-file gnus-newsgroup-name))
|
||||
(buffer (find-file-noselect file)))
|
||||
(set-buffer buffer)
|
||||
(gnus-kill-file-mode)
|
||||
(bury-buffer buffer)))
|
||||
|
||||
(defun gnus-kill-file-enter-kill (field regexp &optional dont-move)
|
||||
;; Enter kill file entry.
|
||||
;; FIELD: String containing the name of the header field to kill.
|
||||
;; REGEXP: The string to kill.
|
||||
(save-excursion
|
||||
(let (string)
|
||||
(unless (eq major-mode 'gnus-kill-file-mode)
|
||||
(gnus-kill-set-kill-buffer))
|
||||
(unless dont-move
|
||||
(goto-char (point-max)))
|
||||
(insert (setq string (format "(gnus-kill %S %S)\n" field regexp)))
|
||||
(gnus-kill-file-apply-string string))))
|
||||
|
||||
(defun gnus-kill-file-kill-by-subject ()
|
||||
"Kill by subject."
|
||||
(interactive)
|
||||
(gnus-kill-file-enter-kill
|
||||
"Subject"
|
||||
(if (vectorp gnus-current-headers)
|
||||
(regexp-quote
|
||||
(gnus-simplify-subject (mail-header-subject gnus-current-headers)))
|
||||
"")
|
||||
t))
|
||||
|
||||
(defun gnus-kill-file-kill-by-author ()
|
||||
"Kill by author."
|
||||
(interactive)
|
||||
(gnus-kill-file-enter-kill
|
||||
"From"
|
||||
(if (vectorp gnus-current-headers)
|
||||
(regexp-quote (mail-header-from gnus-current-headers))
|
||||
"") t))
|
||||
|
||||
(defun gnus-kill-file-kill-by-thread ()
|
||||
"Kill by author."
|
||||
(interactive)
|
||||
(gnus-kill-file-enter-kill
|
||||
"References"
|
||||
(if (vectorp gnus-current-headers)
|
||||
(regexp-quote (mail-header-id gnus-current-headers))
|
||||
"")))
|
||||
|
||||
(defun gnus-kill-file-kill-by-xref ()
|
||||
"Kill by Xref."
|
||||
(interactive)
|
||||
(let ((xref (and (vectorp gnus-current-headers)
|
||||
(mail-header-xref gnus-current-headers)))
|
||||
(start 0)
|
||||
group)
|
||||
(if xref
|
||||
(while (string-match " \\([^ \t]+\\):" xref start)
|
||||
(setq start (match-end 0))
|
||||
(when (not (string=
|
||||
(setq group
|
||||
(substring xref (match-beginning 1) (match-end 1)))
|
||||
gnus-newsgroup-name))
|
||||
(gnus-kill-file-enter-kill
|
||||
"Xref" (concat " " (regexp-quote group) ":") t)))
|
||||
(gnus-kill-file-enter-kill "Xref" "" t))))
|
||||
|
||||
(defun gnus-kill-file-raise-followups-to-author (level)
|
||||
"Raise score for all followups to the current author."
|
||||
(interactive "p")
|
||||
(let ((name (mail-header-from gnus-current-headers))
|
||||
string)
|
||||
(save-excursion
|
||||
(gnus-kill-set-kill-buffer)
|
||||
(goto-char (point-min))
|
||||
(setq name (read-string (concat "Add " level
|
||||
" to followup articles to: ")
|
||||
(regexp-quote name)))
|
||||
(setq
|
||||
string
|
||||
(format
|
||||
"(gnus-kill %S %S '(gnus-summary-temporarily-raise-by-thread %S))\n"
|
||||
"From" name level))
|
||||
(insert string)
|
||||
(gnus-kill-file-apply-string string))
|
||||
(gnus-message
|
||||
6 "Added temporary score file entry for followups to %s." name)))
|
||||
|
||||
(defun gnus-kill-file-apply-buffer ()
|
||||
"Apply current buffer to current newsgroup."
|
||||
(interactive)
|
||||
(if (and gnus-current-kill-article
|
||||
(get-buffer gnus-summary-buffer))
|
||||
;; Assume newsgroup is selected.
|
||||
(gnus-kill-file-apply-string (buffer-string))
|
||||
(ding) (gnus-message 2 "No newsgroup is selected.")))
|
||||
|
||||
(defun gnus-kill-file-apply-string (string)
|
||||
"Apply STRING to current newsgroup."
|
||||
(interactive)
|
||||
(let ((string (concat "(progn \n" string "\n)")))
|
||||
(save-excursion
|
||||
(save-window-excursion
|
||||
(pop-to-buffer gnus-summary-buffer)
|
||||
(eval (car (read-from-string string)))))))
|
||||
|
||||
(defun gnus-kill-file-apply-last-sexp ()
|
||||
"Apply sexp before point in current buffer to current newsgroup."
|
||||
(interactive)
|
||||
(if (and gnus-current-kill-article
|
||||
(get-buffer gnus-summary-buffer))
|
||||
;; Assume newsgroup is selected.
|
||||
(let ((string
|
||||
(buffer-substring
|
||||
(save-excursion (forward-sexp -1) (point)) (point))))
|
||||
(save-excursion
|
||||
(save-window-excursion
|
||||
(pop-to-buffer gnus-summary-buffer)
|
||||
(eval (car (read-from-string string))))))
|
||||
(ding) (gnus-message 2 "No newsgroup is selected.")))
|
||||
|
||||
(defun gnus-kill-file-exit ()
|
||||
"Save a kill file, then return to the previous buffer."
|
||||
(interactive)
|
||||
(save-buffer)
|
||||
(let ((killbuf (current-buffer)))
|
||||
;; We don't want to return to article buffer.
|
||||
(when (get-buffer gnus-article-buffer)
|
||||
(bury-buffer gnus-article-buffer))
|
||||
;; Delete the KILL file windows.
|
||||
(delete-windows-on killbuf)
|
||||
;; Restore last window configuration if available.
|
||||
(when gnus-winconf-kill-file
|
||||
(set-window-configuration gnus-winconf-kill-file))
|
||||
(setq gnus-winconf-kill-file nil)
|
||||
;; Kill the KILL file buffer. Suggested by tale@pawl.rpi.edu.
|
||||
(kill-buffer killbuf)))
|
||||
|
||||
;; For kill files
|
||||
|
||||
(defun gnus-Newsgroup-kill-file (newsgroup)
|
||||
"Return the name of a kill file for NEWSGROUP.
|
||||
If NEWSGROUP is nil, return the global kill file instead."
|
||||
(cond ((or (null newsgroup)
|
||||
(string-equal newsgroup ""))
|
||||
;; The global kill file is placed at top of the directory.
|
||||
(expand-file-name gnus-kill-file-name gnus-kill-files-directory))
|
||||
(gnus-use-long-file-name
|
||||
;; Append ".KILL" to capitalized newsgroup name.
|
||||
(expand-file-name (concat (gnus-capitalize-newsgroup newsgroup)
|
||||
"." gnus-kill-file-name)
|
||||
gnus-kill-files-directory))
|
||||
(t
|
||||
;; Place "KILL" under the hierarchical directory.
|
||||
(expand-file-name (concat (gnus-newsgroup-directory-form newsgroup)
|
||||
"/" gnus-kill-file-name)
|
||||
gnus-kill-files-directory))))
|
||||
|
||||
(defun gnus-expunge (marks)
|
||||
"Remove lines marked with MARKS."
|
||||
(save-excursion
|
||||
(set-buffer gnus-summary-buffer)
|
||||
(gnus-summary-limit-to-marks marks 'reverse)))
|
||||
|
||||
(defun gnus-apply-kill-file-unless-scored ()
|
||||
"Apply .KILL file, unless a .SCORE file for the same newsgroup exists."
|
||||
(cond ((file-exists-p (gnus-score-file-name gnus-newsgroup-name))
|
||||
;; Ignores global KILL.
|
||||
(when (file-exists-p (gnus-newsgroup-kill-file gnus-newsgroup-name))
|
||||
(gnus-message 3 "Note: Ignoring %s.KILL; preferring .SCORE"
|
||||
gnus-newsgroup-name))
|
||||
0)
|
||||
((or (file-exists-p (gnus-newsgroup-kill-file nil))
|
||||
(file-exists-p (gnus-newsgroup-kill-file gnus-newsgroup-name)))
|
||||
(gnus-apply-kill-file-internal))
|
||||
(t
|
||||
0)))
|
||||
|
||||
(defun gnus-apply-kill-file-internal ()
|
||||
"Apply a kill file to the current newsgroup.
|
||||
Returns the number of articles marked as read."
|
||||
(let* ((kill-files (list (gnus-newsgroup-kill-file nil)
|
||||
(gnus-newsgroup-kill-file gnus-newsgroup-name)))
|
||||
(unreads (length gnus-newsgroup-unreads))
|
||||
(gnus-summary-inhibit-highlight t)
|
||||
beg)
|
||||
(setq gnus-newsgroup-kill-headers nil)
|
||||
;; If there are any previously scored articles, we remove these
|
||||
;; from the `gnus-newsgroup-headers' list that the score functions
|
||||
;; will see. This is probably pretty wasteful when it comes to
|
||||
;; conses, but is, I think, faster than having to assq in every
|
||||
;; single score function.
|
||||
(let ((files kill-files))
|
||||
(while files
|
||||
(if (file-exists-p (car files))
|
||||
(let ((headers gnus-newsgroup-headers))
|
||||
(if gnus-kill-killed
|
||||
(setq gnus-newsgroup-kill-headers
|
||||
(mapcar (lambda (header) (mail-header-number header))
|
||||
headers))
|
||||
(while headers
|
||||
(unless (gnus-member-of-range
|
||||
(mail-header-number (car headers))
|
||||
gnus-newsgroup-killed)
|
||||
(push (mail-header-number (car headers))
|
||||
gnus-newsgroup-kill-headers))
|
||||
(setq headers (cdr headers))))
|
||||
(setq files nil))
|
||||
(setq files (cdr files)))))
|
||||
(if (not gnus-newsgroup-kill-headers)
|
||||
()
|
||||
(save-window-excursion
|
||||
(save-excursion
|
||||
(while kill-files
|
||||
(if (not (file-exists-p (car kill-files)))
|
||||
()
|
||||
(gnus-message 6 "Processing kill file %s..." (car kill-files))
|
||||
(find-file (car kill-files))
|
||||
(gnus-add-current-to-buffer-list)
|
||||
(goto-char (point-min))
|
||||
|
||||
(if (consp (ignore-errors (read (current-buffer))))
|
||||
(gnus-kill-parse-gnus-kill-file)
|
||||
(gnus-kill-parse-rn-kill-file))
|
||||
|
||||
(gnus-message
|
||||
6 "Processing kill file %s...done" (car kill-files)))
|
||||
(setq kill-files (cdr kill-files)))))
|
||||
|
||||
(gnus-set-mode-line 'summary)
|
||||
|
||||
(if beg
|
||||
(let ((nunreads (- unreads (length gnus-newsgroup-unreads))))
|
||||
(or (eq nunreads 0)
|
||||
(gnus-message 6 "Marked %d articles as read" nunreads))
|
||||
nunreads)
|
||||
0))))
|
||||
|
||||
;; Parse a Gnus killfile.
|
||||
(defun gnus-score-insert-help (string alist idx)
|
||||
(save-excursion
|
||||
(pop-to-buffer "*Score Help*")
|
||||
(buffer-disable-undo (current-buffer))
|
||||
(erase-buffer)
|
||||
(insert string ":\n\n")
|
||||
(while alist
|
||||
(insert (format " %c: %s\n" (caar alist) (nth idx (car alist))))
|
||||
(setq alist (cdr alist)))))
|
||||
|
||||
(defun gnus-kill-parse-gnus-kill-file ()
|
||||
(goto-char (point-min))
|
||||
(gnus-kill-file-mode)
|
||||
(let (beg form)
|
||||
(while (progn
|
||||
(setq beg (point))
|
||||
(setq form (ignore-errors (read (current-buffer)))))
|
||||
(unless (listp form)
|
||||
(error "Illegal kill entry (possibly rn kill file?): %s" form))
|
||||
(if (or (eq (car form) 'gnus-kill)
|
||||
(eq (car form) 'gnus-raise)
|
||||
(eq (car form) 'gnus-lower))
|
||||
(progn
|
||||
(delete-region beg (point))
|
||||
(insert (or (eval form) "")))
|
||||
(save-excursion
|
||||
(set-buffer gnus-summary-buffer)
|
||||
(ignore-errors (eval form)))))
|
||||
(and (buffer-modified-p)
|
||||
gnus-kill-save-kill-file
|
||||
(save-buffer))
|
||||
(set-buffer-modified-p nil)))
|
||||
|
||||
;; Parse an rn killfile.
|
||||
(defun gnus-kill-parse-rn-kill-file ()
|
||||
(goto-char (point-min))
|
||||
(gnus-kill-file-mode)
|
||||
(let ((mod-to-header
|
||||
'((?a . "")
|
||||
(?h . "")
|
||||
(?f . "from")
|
||||
(?: . "subject")))
|
||||
(com-to-com
|
||||
'((?m . " ")
|
||||
(?j . "X")))
|
||||
pattern modifier commands)
|
||||
(while (not (eobp))
|
||||
(if (not (looking-at "[ \t]*/\\([^/]*\\)/\\([ahfcH]\\)?:\\([a-z=:]*\\)"))
|
||||
()
|
||||
(setq pattern (buffer-substring (match-beginning 1) (match-end 1)))
|
||||
(setq modifier (if (match-beginning 2) (char-after (match-beginning 2))
|
||||
?s))
|
||||
(setq commands (buffer-substring (match-beginning 3) (match-end 3)))
|
||||
|
||||
;; The "f:+" command marks everything *but* the matches as read,
|
||||
;; so we simply first match everything as read, and then unmark
|
||||
;; PATTERN later.
|
||||
(when (string-match "\\+" commands)
|
||||
(gnus-kill "from" ".")
|
||||
(setq commands "m"))
|
||||
|
||||
(gnus-kill
|
||||
(or (cdr (assq modifier mod-to-header)) "subject")
|
||||
pattern
|
||||
(if (string-match "m" commands)
|
||||
'(gnus-summary-mark-as-unread nil " ")
|
||||
'(gnus-summary-mark-as-read nil "X"))
|
||||
nil t))
|
||||
(forward-line 1))))
|
||||
|
||||
;; Kill changes and new format by suggested by JWZ and Sudish Joseph
|
||||
;; <joseph@cis.ohio-state.edu>.
|
||||
(defun gnus-kill (field regexp &optional exe-command all silent)
|
||||
"If FIELD of an article matches REGEXP, execute COMMAND.
|
||||
Optional 1st argument COMMAND is default to
|
||||
(gnus-summary-mark-as-read nil \"X\").
|
||||
If optional 2nd argument ALL is non-nil, articles marked are also applied to.
|
||||
If FIELD is an empty string (or nil), entire article body is searched for.
|
||||
COMMAND must be a lisp expression or a string representing a key sequence."
|
||||
;; We don't want to change current point nor window configuration.
|
||||
(let ((old-buffer (current-buffer)))
|
||||
(save-excursion
|
||||
(save-window-excursion
|
||||
;; Selected window must be summary buffer to execute keyboard
|
||||
;; macros correctly. See command_loop_1.
|
||||
(switch-to-buffer gnus-summary-buffer 'norecord)
|
||||
(goto-char (point-min)) ;From the beginning.
|
||||
(let ((kill-list regexp)
|
||||
(date (current-time-string))
|
||||
(command (or exe-command '(gnus-summary-mark-as-read
|
||||
nil gnus-kill-file-mark)))
|
||||
kill kdate prev)
|
||||
(if (listp kill-list)
|
||||
;; It is a list.
|
||||
(if (not (consp (cdr kill-list)))
|
||||
;; It's on the form (regexp . date).
|
||||
(if (zerop (gnus-execute field (car kill-list)
|
||||
command nil (not all)))
|
||||
(when (> (gnus-days-between date (cdr kill-list))
|
||||
gnus-kill-expiry-days)
|
||||
(setq regexp nil))
|
||||
(setcdr kill-list date))
|
||||
(while (setq kill (car kill-list))
|
||||
(if (consp kill)
|
||||
;; It's a temporary kill.
|
||||
(progn
|
||||
(setq kdate (cdr kill))
|
||||
(if (zerop (gnus-execute
|
||||
field (car kill) command nil (not all)))
|
||||
(when (> (gnus-days-between date kdate)
|
||||
gnus-kill-expiry-days)
|
||||
;; Time limit has been exceeded, so we
|
||||
;; remove the match.
|
||||
(if prev
|
||||
(setcdr prev (cdr kill-list))
|
||||
(setq regexp (cdr regexp))))
|
||||
;; Successful kill. Set the date to today.
|
||||
(setcdr kill date)))
|
||||
;; It's a permanent kill.
|
||||
(gnus-execute field kill command nil (not all)))
|
||||
(setq prev kill-list)
|
||||
(setq kill-list (cdr kill-list))))
|
||||
(gnus-execute field kill-list command nil (not all))))))
|
||||
(switch-to-buffer old-buffer)
|
||||
(when (and (eq major-mode 'gnus-kill-file-mode) regexp (not silent))
|
||||
(gnus-pp-gnus-kill
|
||||
(nconc (list 'gnus-kill field
|
||||
(if (consp regexp) (list 'quote regexp) regexp))
|
||||
(when (or exe-command all)
|
||||
(list (list 'quote exe-command)))
|
||||
(if all (list t) nil))))))
|
||||
|
||||
(defun gnus-pp-gnus-kill (object)
|
||||
(if (or (not (consp (nth 2 object)))
|
||||
(not (consp (cdr (nth 2 object))))
|
||||
(and (eq 'quote (car (nth 2 object)))
|
||||
(not (consp (cdadr (nth 2 object))))))
|
||||
(concat "\n" (gnus-prin1-to-string object))
|
||||
(save-excursion
|
||||
(set-buffer (get-buffer-create "*Gnus PP*"))
|
||||
(buffer-disable-undo (current-buffer))
|
||||
(erase-buffer)
|
||||
(insert (format "\n(%S %S\n '(" (nth 0 object) (nth 1 object)))
|
||||
(let ((klist (cadr (nth 2 object)))
|
||||
(first t))
|
||||
(while klist
|
||||
(insert (if first (progn (setq first nil) "") "\n ")
|
||||
(gnus-prin1-to-string (car klist)))
|
||||
(setq klist (cdr klist))))
|
||||
(insert ")")
|
||||
(and (nth 3 object)
|
||||
(insert "\n "
|
||||
(if (and (consp (nth 3 object))
|
||||
(not (eq 'quote (car (nth 3 object)))))
|
||||
"'" "")
|
||||
(gnus-prin1-to-string (nth 3 object))))
|
||||
(when (nth 4 object)
|
||||
(insert "\n t"))
|
||||
(insert ")")
|
||||
(prog1
|
||||
(buffer-substring (point-min) (point-max))
|
||||
(kill-buffer (current-buffer))))))
|
||||
|
||||
(defun gnus-execute-1 (function regexp form header)
|
||||
(save-excursion
|
||||
(let (did-kill)
|
||||
(if (null header)
|
||||
nil ;Nothing to do.
|
||||
(if function
|
||||
;; Compare with header field.
|
||||
(let (value)
|
||||
(and header
|
||||
(progn
|
||||
(setq value (funcall function header))
|
||||
;; Number (Lines:) or symbol must be converted to string.
|
||||
(unless (stringp value)
|
||||
(setq value (gnus-prin1-to-string value)))
|
||||
(setq did-kill (string-match regexp value)))
|
||||
(cond ((stringp form) ;Keyboard macro.
|
||||
(execute-kbd-macro form))
|
||||
((gnus-functionp form)
|
||||
(funcall form))
|
||||
(t
|
||||
(eval form)))))
|
||||
;; Search article body.
|
||||
(let ((gnus-current-article nil) ;Save article pointer.
|
||||
(gnus-last-article nil)
|
||||
(gnus-break-pages nil) ;No need to break pages.
|
||||
(gnus-mark-article-hook nil)) ;Inhibit marking as read.
|
||||
(gnus-message
|
||||
6 "Searching for article: %d..." (mail-header-number header))
|
||||
(gnus-article-setup-buffer)
|
||||
(gnus-article-prepare (mail-header-number header) t)
|
||||
(when (save-excursion
|
||||
(set-buffer gnus-article-buffer)
|
||||
(goto-char (point-min))
|
||||
(setq did-kill (re-search-forward regexp nil t)))
|
||||
(cond ((stringp form) ;Keyboard macro.
|
||||
(execute-kbd-macro form))
|
||||
((gnus-functionp form)
|
||||
(funcall form))
|
||||
(t
|
||||
(eval form)))))))
|
||||
did-kill)))
|
||||
|
||||
(defun gnus-execute (field regexp form &optional backward unread)
|
||||
"If FIELD of article header matches REGEXP, execute lisp FORM (or a string).
|
||||
If FIELD is an empty string (or nil), entire article body is searched for.
|
||||
If optional 1st argument BACKWARD is non-nil, do backward instead.
|
||||
If optional 2nd argument UNREAD is non-nil, articles which are
|
||||
marked as read or ticked are ignored."
|
||||
(save-excursion
|
||||
(let ((killed-no 0)
|
||||
function article header)
|
||||
(cond
|
||||
;; Search body.
|
||||
((or (null field)
|
||||
(string-equal field ""))
|
||||
(setq function nil))
|
||||
;; Get access function of header field.
|
||||
((fboundp
|
||||
(setq function
|
||||
(intern-soft
|
||||
(concat "mail-header-" (downcase field)))))
|
||||
(setq function `(lambda (h) (,function h))))
|
||||
;; Signal error.
|
||||
(t
|
||||
(error "Unknown header field: \"%s\"" field)))
|
||||
;; Starting from the current article.
|
||||
(while (or
|
||||
;; First article.
|
||||
(and (not article)
|
||||
(setq article (gnus-summary-article-number)))
|
||||
;; Find later articles.
|
||||
(setq article
|
||||
(gnus-summary-search-forward unread nil backward)))
|
||||
(and (or (null gnus-newsgroup-kill-headers)
|
||||
(memq article gnus-newsgroup-kill-headers))
|
||||
(vectorp (setq header (gnus-summary-article-header article)))
|
||||
(gnus-execute-1 function regexp form header)
|
||||
(setq killed-no (1+ killed-no))))
|
||||
;; Return the number of killed articles.
|
||||
killed-no)))
|
||||
|
||||
;;;###autoload
|
||||
(defalias 'gnus-batch-kill 'gnus-batch-score)
|
||||
;;;###autoload
|
||||
(defun gnus-batch-score ()
|
||||
"Run batched scoring.
|
||||
Usage: emacs -batch -l gnus -f gnus-batch-score <newsgroups> ...
|
||||
Newsgroups is a list of strings in Bnews format. If you want to score
|
||||
the comp hierarchy, you'd say \"comp.all\". If you would not like to
|
||||
score the alt hierarchy, you'd say \"!alt.all\"."
|
||||
(interactive)
|
||||
(let* ((gnus-newsrc-options-n
|
||||
(gnus-newsrc-parse-options
|
||||
(concat "options -n "
|
||||
(mapconcat 'identity command-line-args-left " "))))
|
||||
(gnus-expert-user t)
|
||||
(nnmail-spool-file nil)
|
||||
(gnus-use-dribble-file nil)
|
||||
(gnus-batch-mode t)
|
||||
group newsrc entry
|
||||
;; Disable verbose message.
|
||||
gnus-novice-user gnus-large-newsgroup
|
||||
gnus-options-subscribe gnus-auto-subscribed-groups
|
||||
gnus-options-not-subscribe)
|
||||
;; Eat all arguments.
|
||||
(setq command-line-args-left nil)
|
||||
(gnus-slave)
|
||||
;; Apply kills to specified newsgroups in command line arguments.
|
||||
(setq newsrc (cdr gnus-newsrc-alist))
|
||||
(while (setq group (car (pop newsrc)))
|
||||
(setq entry (gnus-gethash group gnus-newsrc-hashtb))
|
||||
(when (and (<= (gnus-info-level (car newsrc)) gnus-level-subscribed)
|
||||
(and (car entry)
|
||||
(or (eq (car entry) t)
|
||||
(not (zerop (car entry)))))
|
||||
;;(eq (gnus-matches-options-n group) 'subscribe)
|
||||
)
|
||||
(gnus-summary-read-group group nil t nil t)
|
||||
(when (eq (current-buffer) (get-buffer gnus-summary-buffer))
|
||||
(gnus-summary-exit))))
|
||||
;; Exit Emacs.
|
||||
(switch-to-buffer gnus-group-buffer)
|
||||
(gnus-group-save-newsrc)))
|
||||
|
||||
(provide 'gnus-kill)
|
||||
|
||||
;;; gnus-kill.el ends here
|
||||
103
lisp/gnus/gnus-load.el
Normal file
103
lisp/gnus/gnus-load.el
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
;;; gnus-load.el --- automatically extracted custom dependencies
|
||||
;;
|
||||
;;; Code:
|
||||
|
||||
(put 'nnmail 'custom-loads '("nnmail"))
|
||||
(put 'gnus-article-emphasis 'custom-loads '("gnus-art"))
|
||||
(put 'gnus-article-headers 'custom-loads '("gnus-sum" "gnus-art"))
|
||||
(put 'nnmail-procmail 'custom-loads '("nnmail"))
|
||||
(put 'gnus-score-kill 'custom-loads '("gnus-kill"))
|
||||
(put 'gnus-visual 'custom-loads '("smiley" "gnus" "gnus-picon" "gnus-art" "earcon"))
|
||||
(put 'gnus-score-expire 'custom-loads '("gnus-score" "gnus-kill"))
|
||||
(put 'gnus-summary-maneuvering 'custom-loads '("gnus-sum"))
|
||||
(put 'gnus-start 'custom-loads '("gnus" "gnus-util" "gnus-start" "gnus-int" "gnus-group"))
|
||||
(put 'gnus-extract-view 'custom-loads '("gnus-uu" "gnus-sum"))
|
||||
(put 'gnus-various 'custom-loads '("gnus-sum"))
|
||||
(put 'gnus-article-washing 'custom-loads '("gnus-art"))
|
||||
(put 'gnus-score-files 'custom-loads '("gnus-score"))
|
||||
(put 'message-news 'custom-loads '("message"))
|
||||
(put 'gnus-thread 'custom-loads '("gnus-sum"))
|
||||
(put 'languages 'custom-loads '("cus-edit"))
|
||||
(put 'development 'custom-loads '("cus-edit"))
|
||||
(put 'gnus-treading 'custom-loads '("gnus-sum"))
|
||||
(put 'nnmail-various 'custom-loads '("nnmail"))
|
||||
(put 'extensions 'custom-loads '("wid-edit"))
|
||||
(put 'message-various 'custom-loads '("message"))
|
||||
(put 'gnus-summary-exit 'custom-loads '("gnus-sum"))
|
||||
(put 'news 'custom-loads '("message" "gnus"))
|
||||
(put 'gnus 'custom-loads '("nnmail" "gnus" "gnus-win" "gnus-uu" "gnus-eform" "gnus-dup" "gnus-demon" "gnus-cache" "gnus-async" "gnus-art"))
|
||||
(put 'gnus-summary-visual 'custom-loads '("gnus-sum"))
|
||||
(put 'gnus-group-listing 'custom-loads '("gnus-group"))
|
||||
(put 'gnus-score 'custom-loads '("gnus" "gnus-nocem"))
|
||||
(put 'gnus-group-select 'custom-loads '("gnus-sum"))
|
||||
(put 'message-buffers 'custom-loads '("message"))
|
||||
(put 'gnus-threading 'custom-loads '("gnus-sum"))
|
||||
(put 'gnus-score-decay 'custom-loads '("gnus-score"))
|
||||
(put 'help 'custom-loads '("cus-edit"))
|
||||
(put 'gnus-nocem 'custom-loads '("gnus-nocem"))
|
||||
(put 'gnus-cite 'custom-loads '("gnus-cite"))
|
||||
(put 'gnus-demon 'custom-loads '("gnus-demon"))
|
||||
(put 'gnus-message 'custom-loads '("message"))
|
||||
(put 'gnus-score-default 'custom-loads '("gnus-sum" "gnus-score"))
|
||||
(put 'nnmail-duplicate 'custom-loads '("nnmail"))
|
||||
(put 'message-interface 'custom-loads '("message"))
|
||||
(put 'nnmail-files 'custom-loads '("nnmail"))
|
||||
(put 'gnus-edit-form 'custom-loads '("gnus-eform"))
|
||||
(put 'emacs 'custom-loads '("cus-edit"))
|
||||
(put 'gnus-summary-mail 'custom-loads '("gnus-sum"))
|
||||
(put 'gnus-topic 'custom-loads '("gnus-topic"))
|
||||
(put 'wp 'custom-loads '("cus-edit"))
|
||||
(put 'gnus-summary-choose 'custom-loads '("gnus-sum"))
|
||||
(put 'widget-browse 'custom-loads '("wid-browse"))
|
||||
(put 'external 'custom-loads '("cus-edit"))
|
||||
(put 'message-headers 'custom-loads '("message"))
|
||||
(put 'message-forwarding 'custom-loads '("message"))
|
||||
(put 'message-faces 'custom-loads '("message"))
|
||||
(put 'environment 'custom-loads '("cus-edit"))
|
||||
(put 'gnus-article-mime 'custom-loads '("gnus-sum" "gnus-art"))
|
||||
(put 'gnus-duplicate 'custom-loads '("gnus-dup"))
|
||||
(put 'nnmail-retrieve 'custom-loads '("nnmail"))
|
||||
(put 'widgets 'custom-loads '("wid-edit" "wid-browse"))
|
||||
(put 'earcon 'custom-loads '("earcon"))
|
||||
(put 'hypermedia 'custom-loads '("wid-edit"))
|
||||
(put 'gnus-group-levels 'custom-loads '("gnus-group"))
|
||||
(put 'gnus-summary-format 'custom-loads '("gnus-sum"))
|
||||
(put 'gnus-files 'custom-loads '("nnmail" "gnus"))
|
||||
(put 'gnus-windows 'custom-loads '("gnus-win"))
|
||||
(put 'gnus-article-buttons 'custom-loads '("gnus-art"))
|
||||
(put 'gnus-summary 'custom-loads '("gnus" "gnus-sum"))
|
||||
(put 'gnus-article-hiding 'custom-loads '("gnus-sum" "gnus-art"))
|
||||
(put 'gnus-group 'custom-loads '("gnus" "gnus-topic"))
|
||||
(put 'gnus-article-various 'custom-loads '("gnus-sum" "gnus-art"))
|
||||
(put 'gnus-summary-marks 'custom-loads '("gnus-sum"))
|
||||
(put 'gnus-article-saving 'custom-loads '("gnus-art"))
|
||||
(put 'nnmail-expire 'custom-loads '("nnmail"))
|
||||
(put 'message-mail 'custom-loads '("message"))
|
||||
(put 'faces 'custom-loads '("wid-edit" "cus-edit" "message" "gnus"))
|
||||
(put 'gnus-summary-various 'custom-loads '("gnus-sum"))
|
||||
(put 'applications 'custom-loads '("cus-edit"))
|
||||
(put 'gnus-extract-archive 'custom-loads '("gnus-uu"))
|
||||
(put 'message 'custom-loads '("message"))
|
||||
(put 'message-sending 'custom-loads '("message"))
|
||||
(put 'editing 'custom-loads '("cus-edit"))
|
||||
(put 'gnus-score-adapt 'custom-loads '("gnus-score"))
|
||||
(put 'message-insertion 'custom-loads '("message"))
|
||||
(put 'gnus-extract-post 'custom-loads '("gnus-uu"))
|
||||
(put 'mail 'custom-loads '("message" "gnus"))
|
||||
(put 'gnus-summary-sort 'custom-loads '("gnus-sum"))
|
||||
(put 'customize 'custom-loads '("wid-edit" "custom" "cus-face" "cus-edit"))
|
||||
(put 'nnmail-split 'custom-loads '("nnmail"))
|
||||
(put 'gnus-asynchronous 'custom-loads '("gnus-async"))
|
||||
(put 'gnus-article-highlight 'custom-loads '("gnus-art"))
|
||||
(put 'gnus-extract 'custom-loads '("gnus-uu"))
|
||||
(put 'gnus-article 'custom-loads '("gnus-cite" "gnus-art"))
|
||||
(put 'gnus-group-foreign 'custom-loads '("gnus-group"))
|
||||
(put 'programming 'custom-loads '("cus-edit"))
|
||||
(put 'nnmail-prepare 'custom-loads '("nnmail"))
|
||||
(put 'picons 'custom-loads '("gnus-picon"))
|
||||
(put 'gnus-article-signature 'custom-loads '("gnus-art"))
|
||||
(put 'gnus-group-various 'custom-loads '("gnus-group"))
|
||||
|
||||
(provide 'gnus-load)
|
||||
|
||||
;;; gnus-load.el ends here
|
||||
227
lisp/gnus/gnus-logic.el
Normal file
227
lisp/gnus/gnus-logic.el
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
;;; gnus-logic.el --- advanced scoring code for Gnus
|
||||
;; Copyright (C) 1996,97 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
|
||||
;; Keywords: news
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'gnus)
|
||||
(require 'gnus-score)
|
||||
(require 'gnus-util)
|
||||
|
||||
;;; Internal variables.
|
||||
|
||||
(defvar gnus-advanced-headers nil)
|
||||
|
||||
;; To avoid having 8-bit characters in the source file.
|
||||
(defvar gnus-advanced-not (intern (format "%c" 172)))
|
||||
|
||||
(defconst gnus-advanced-index
|
||||
;; Name to index alist.
|
||||
'(("number" 0 gnus-advanced-integer)
|
||||
("subject" 1 gnus-advanced-string)
|
||||
("from" 2 gnus-advanced-string)
|
||||
("date" 3 gnus-advanced-date)
|
||||
("message-id" 4 gnus-advanced-string)
|
||||
("references" 5 gnus-advanced-string)
|
||||
("chars" 6 gnus-advanced-integer)
|
||||
("lines" 7 gnus-advanced-integer)
|
||||
("xref" 8 gnus-advanced-string)
|
||||
("head" nil gnus-advanced-body)
|
||||
("body" nil gnus-advanced-body)
|
||||
("all" nil gnus-advanced-body)))
|
||||
|
||||
(eval-and-compile
|
||||
(autoload 'parse-time-string "parse-time"))
|
||||
|
||||
(defun gnus-score-advanced (rule &optional trace)
|
||||
"Apply advanced scoring RULE to all the articles in the current group."
|
||||
(let ((headers gnus-newsgroup-headers)
|
||||
gnus-advanced-headers score)
|
||||
(while (setq gnus-advanced-headers (pop headers))
|
||||
(when (gnus-advanced-score-rule (car rule))
|
||||
;; This rule was successful, so we add the score to
|
||||
;; this article.
|
||||
(if (setq score (assq (mail-header-number gnus-advanced-headers)
|
||||
gnus-newsgroup-scored))
|
||||
(setcdr score
|
||||
(+ (cdr score)
|
||||
(or (nth 1 rule)
|
||||
gnus-score-interactive-default-score)))
|
||||
(push (cons (mail-header-number gnus-advanced-headers)
|
||||
(or (nth 1 rule)
|
||||
gnus-score-interactive-default-score))
|
||||
gnus-newsgroup-scored)
|
||||
(when trace
|
||||
(push (cons "A file" rule)
|
||||
gnus-score-trace)))))))
|
||||
|
||||
(defun gnus-advanced-score-rule (rule)
|
||||
"Apply RULE to `gnus-advanced-headers'."
|
||||
(let ((type (car rule)))
|
||||
(cond
|
||||
;; "And" rule.
|
||||
((or (eq type '&) (eq type 'and))
|
||||
(pop rule)
|
||||
(if (not rule)
|
||||
t ; Empty rule is true.
|
||||
(while (and rule
|
||||
(gnus-advanced-score-rule (car rule)))
|
||||
(pop rule))
|
||||
;; If all the rules were true, then `rule' should be nil.
|
||||
(not rule)))
|
||||
;; "Or" rule.
|
||||
((or (eq type '|) (eq type 'or))
|
||||
(pop rule)
|
||||
(if (not rule)
|
||||
nil
|
||||
(while (and rule
|
||||
(not (gnus-advanced-score-rule (car rule))))
|
||||
(pop rule))
|
||||
;; If one of the rules returned true, then `rule' should be non-nil.
|
||||
rule))
|
||||
;; "Not" rule.
|
||||
((or (eq type '!) (eq type 'not) (eq type gnus-advanced-not))
|
||||
(not (gnus-advanced-score-rule (nth 1 rule))))
|
||||
;; This is a `1-'-type redirection rule.
|
||||
((and (symbolp type)
|
||||
(string-match "^[0-9]+-$\\|^\\^+$" (symbol-name type)))
|
||||
(let ((gnus-advanced-headers
|
||||
(gnus-parent-headers
|
||||
gnus-advanced-headers
|
||||
(if (string-match "^\\([0-9]+\\)-$" (symbol-name type))
|
||||
;; 1- type redirection.
|
||||
(string-to-number
|
||||
(substring (symbol-name type)
|
||||
(match-beginning 0) (match-end 0)))
|
||||
;; ^^^ type redirection.
|
||||
(length (symbol-name type))))))
|
||||
(when gnus-advanced-headers
|
||||
(gnus-advanced-score-rule (nth 1 rule)))))
|
||||
;; Plain scoring rule.
|
||||
((stringp type)
|
||||
(gnus-advanced-score-article rule))
|
||||
;; Bug-out time!
|
||||
(t
|
||||
(error "Unknown advanced score type: %s" rule)))))
|
||||
|
||||
(defun gnus-advanced-score-article (rule)
|
||||
;; `rule' is a semi-normal score rule, so we find out
|
||||
;; what function that's supposed to do the actual
|
||||
;; processing.
|
||||
(let* ((header (car rule))
|
||||
(func (assoc (downcase header) gnus-advanced-index)))
|
||||
(if (not func)
|
||||
(error "No such header: %s" rule)
|
||||
;; Call the score function.
|
||||
(funcall (caddr func) (or (cadr func) header)
|
||||
(cadr rule) (caddr rule)))))
|
||||
|
||||
(defun gnus-advanced-string (index match type)
|
||||
"See whether string MATCH of TYPE matches `gnus-advanced-headers' in INDEX."
|
||||
(let* ((type (or type 's))
|
||||
(case-fold-search (not (eq (downcase (symbol-name type))
|
||||
(symbol-name type))))
|
||||
(header (aref gnus-advanced-headers index)))
|
||||
(cond
|
||||
((memq type '(r R regexp Regexp))
|
||||
(string-match match header))
|
||||
((memq type '(s S string String))
|
||||
(string-match (regexp-quote match) header))
|
||||
((memq type '(e E exact Exact))
|
||||
(string= match header))
|
||||
((memq type '(f F fuzzy Fuzzy))
|
||||
(string-match (regexp-quote (gnus-simplify-subject-fuzzy match))
|
||||
header))
|
||||
(t
|
||||
(error "No such string match type: %s" type)))))
|
||||
|
||||
(defun gnus-advanced-integer (index match type)
|
||||
(if (not (memq type '(< > <= >= =)))
|
||||
(error "No such integer score type: %s" type)
|
||||
(funcall type match (or (aref gnus-advanced-headers index) 0))))
|
||||
|
||||
(defun gnus-advanced-date (index match type)
|
||||
(let ((date (encode-time (parse-time-string
|
||||
(aref gnus-advanced-headers index))))
|
||||
(match (encode-time (parse-time-string match))))
|
||||
(cond
|
||||
((eq type 'at)
|
||||
(equal date match))
|
||||
((eq type 'before)
|
||||
(gnus-time-less match date))
|
||||
((eq type 'after)
|
||||
(gnus-time-less date match))
|
||||
(t
|
||||
(error "No such date score type: %s" type)))))
|
||||
|
||||
(defun gnus-advanced-body (header match type)
|
||||
(when (string= header "all")
|
||||
(setq header "article"))
|
||||
(save-excursion
|
||||
(set-buffer nntp-server-buffer)
|
||||
(let* ((request-func (cond ((string= "head" header)
|
||||
'gnus-request-head)
|
||||
((string= "body" header)
|
||||
'gnus-request-body)
|
||||
(t 'gnus-request-article)))
|
||||
ofunc article)
|
||||
;; Not all backends support partial fetching. In that case,
|
||||
;; we just fetch the entire article.
|
||||
(unless (gnus-check-backend-function
|
||||
(intern (concat "request-" header))
|
||||
gnus-newsgroup-name)
|
||||
(setq ofunc request-func)
|
||||
(setq request-func 'gnus-request-article))
|
||||
(setq article (mail-header-number gnus-advanced-headers))
|
||||
(gnus-message 7 "Scoring article %s..." article)
|
||||
(when (funcall request-func article gnus-newsgroup-name)
|
||||
(goto-char (point-min))
|
||||
;; If just parts of the article is to be searched and the
|
||||
;; backend didn't support partial fetching, we just narrow
|
||||
;; to the relevant parts.
|
||||
(when ofunc
|
||||
(if (eq ofunc 'gnus-request-head)
|
||||
(narrow-to-region
|
||||
(point)
|
||||
(or (search-forward "\n\n" nil t) (point-max)))
|
||||
(narrow-to-region
|
||||
(or (search-forward "\n\n" nil t) (point))
|
||||
(point-max))))
|
||||
(let* ((case-fold-search (not (eq (downcase (symbol-name type))
|
||||
(symbol-name type))))
|
||||
(search-func
|
||||
(cond ((memq type '(r R regexp Regexp))
|
||||
're-search-forward)
|
||||
((memq type '(s S string String))
|
||||
'search-forward)
|
||||
(t
|
||||
(error "Illegal match type: %s" type)))))
|
||||
(goto-char (point-min))
|
||||
(prog1
|
||||
(funcall search-func match nil t)
|
||||
(widen)))))))
|
||||
|
||||
(provide 'gnus-logic)
|
||||
|
||||
;;; gnus-logic.el ends here.
|
||||
105
lisp/gnus/gnus-mh.el
Normal file
105
lisp/gnus/gnus-mh.el
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
;;; gnus-mh.el --- mh-e interface for Gnus
|
||||
;; Copyright (C) 1994,95,96,97 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
|
||||
;; Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
|
||||
;; Keywords: news
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Send mail using mh-e.
|
||||
|
||||
;; The following mh-e interface is all cooperative works of
|
||||
;; tanaka@flab.fujitsu.CO.JP (TANAKA Hiroshi), kawabe@sra.CO.JP
|
||||
;; (Yoshikatsu Kawabe), and shingu@casund.cpr.canon.co.jp (Toshiaki
|
||||
;; SHINGU).
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'gnus)
|
||||
(require 'mh-e)
|
||||
(require 'mh-comp)
|
||||
(require 'gnus-msg)
|
||||
(require 'gnus-sum)
|
||||
|
||||
(defun gnus-summary-save-article-folder (&optional arg)
|
||||
"Append the current article to an mh folder.
|
||||
If N is a positive number, save the N next articles.
|
||||
If N is a negative number, save the N previous articles.
|
||||
If N is nil and any articles have been marked with the process mark,
|
||||
save those articles instead."
|
||||
(interactive "P")
|
||||
(let ((gnus-default-article-saver 'gnus-summary-save-in-folder))
|
||||
(gnus-summary-save-article arg)))
|
||||
|
||||
(defun gnus-summary-save-in-folder (&optional folder)
|
||||
"Save this article to MH folder (using `rcvstore' in MH library).
|
||||
Optional argument FOLDER specifies folder name."
|
||||
;; Thanks to yuki@flab.Fujitsu.JUNET and ohm@kaba.junet.
|
||||
(mh-find-path)
|
||||
(let ((folder
|
||||
(cond ((and (eq folder 'default)
|
||||
gnus-newsgroup-last-folder)
|
||||
gnus-newsgroup-last-folder)
|
||||
(folder folder)
|
||||
(t (mh-prompt-for-folder
|
||||
"Save article in"
|
||||
(funcall gnus-folder-save-name gnus-newsgroup-name
|
||||
gnus-current-headers gnus-newsgroup-last-folder)
|
||||
t))))
|
||||
(errbuf (get-buffer-create " *Gnus rcvstore*"))
|
||||
;; Find the rcvstore program.
|
||||
(exec-path (if mh-lib (cons mh-lib exec-path) exec-path)))
|
||||
(gnus-eval-in-buffer-window gnus-original-article-buffer
|
||||
(save-restriction
|
||||
(widen)
|
||||
(unwind-protect
|
||||
(call-process-region
|
||||
(point-min) (point-max) "rcvstore" nil errbuf nil folder)
|
||||
(set-buffer errbuf)
|
||||
(if (zerop (buffer-size))
|
||||
(message "Article saved in folder: %s" folder)
|
||||
(message "%s" (buffer-string)))
|
||||
(kill-buffer errbuf))))
|
||||
(setq gnus-newsgroup-last-folder folder)))
|
||||
|
||||
(defun gnus-Folder-save-name (newsgroup headers &optional last-folder)
|
||||
"Generate folder name from NEWSGROUP, HEADERS, and optional LAST-FOLDER.
|
||||
If variable `gnus-use-long-file-name' is nil, it is +News.group.
|
||||
Otherwise, it is like +news/group."
|
||||
(or last-folder
|
||||
(concat "+"
|
||||
(if gnus-use-long-file-name
|
||||
(gnus-capitalize-newsgroup newsgroup)
|
||||
(gnus-newsgroup-directory-form newsgroup)))))
|
||||
|
||||
(defun gnus-folder-save-name (newsgroup headers &optional last-folder)
|
||||
"Generate folder name from NEWSGROUP, HEADERS, and optional LAST-FOLDER.
|
||||
If variable `gnus-use-long-file-name' is nil, it is +news.group.
|
||||
Otherwise, it is like +news/group."
|
||||
(or last-folder
|
||||
(concat "+"
|
||||
(if gnus-use-long-file-name
|
||||
newsgroup
|
||||
(gnus-newsgroup-directory-form newsgroup)))))
|
||||
|
||||
(provide 'gnus-mh)
|
||||
|
||||
;;; gnus-mh.el ends here
|
||||
172
lisp/gnus/gnus-move.el
Normal file
172
lisp/gnus/gnus-move.el
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
;;; gnus-move.el --- commands for moving Gnus from one server to another
|
||||
;; Copyright (C) 1996,97 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
|
||||
;; Keywords: news
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'gnus)
|
||||
(require 'gnus-start)
|
||||
(require 'gnus-int)
|
||||
(require 'gnus-range)
|
||||
|
||||
;;;
|
||||
;;; Moving by comparing Message-ID's.
|
||||
;;;
|
||||
|
||||
;;;###autoload
|
||||
(defun gnus-change-server (from-server to-server)
|
||||
"Move from FROM-SERVER to TO-SERVER.
|
||||
Update the .newsrc.eld file to reflect the change of nntp server."
|
||||
(interactive
|
||||
(list gnus-select-method (gnus-read-method "Move to method: ")))
|
||||
|
||||
;; First start Gnus.
|
||||
(let ((gnus-activate-level 0)
|
||||
(nnmail-spool-file nil))
|
||||
(gnus))
|
||||
|
||||
(save-excursion
|
||||
;; Go through all groups and translate.
|
||||
(let ((newsrc gnus-newsrc-alist)
|
||||
(nntp-nov-gap nil)
|
||||
info)
|
||||
(while (setq info (pop newsrc))
|
||||
(when (gnus-group-native-p (gnus-info-group info))
|
||||
(gnus-move-group-to-server info from-server to-server))))))
|
||||
|
||||
(defun gnus-move-group-to-server (info from-server to-server)
|
||||
"Move group INFO from FROM-SERVER to TO-SERVER."
|
||||
(let ((group (gnus-info-group info))
|
||||
to-active hashtb type mark marks
|
||||
to-article to-reads to-marks article)
|
||||
(gnus-message 7 "Translating %s..." group)
|
||||
(when (gnus-request-group group nil to-server)
|
||||
(setq to-active (gnus-parse-active)
|
||||
hashtb (gnus-make-hashtable 1024))
|
||||
;; Fetch the headers from the `to-server'.
|
||||
(when (and to-active
|
||||
(setq type (gnus-retrieve-headers
|
||||
(gnus-uncompress-range to-active)
|
||||
group to-server)))
|
||||
;; Convert HEAD headers. I don't care.
|
||||
(when (eq type 'headers)
|
||||
(nnvirtual-convert-headers))
|
||||
;; Create a mapping from Message-ID to article number.
|
||||
(set-buffer nntp-server-buffer)
|
||||
(goto-char (point-min))
|
||||
(while (looking-at
|
||||
"^[0-9]+\t[^\t]*\t[^\t]*\t[^\t]*\t\\([^\t]*\\)\t")
|
||||
(gnus-sethash
|
||||
(buffer-substring (match-beginning 1) (match-end 1))
|
||||
(read (current-buffer))
|
||||
hashtb)
|
||||
(forward-line 1))
|
||||
;; Then we read the headers from the `from-server'.
|
||||
(when (and (gnus-request-group group nil from-server)
|
||||
(gnus-active group)
|
||||
(setq type (gnus-retrieve-headers
|
||||
(gnus-uncompress-range
|
||||
(gnus-active group))
|
||||
group from-server)))
|
||||
;; Make it easier to map marks.
|
||||
(let ((mark-lists (gnus-info-marks info))
|
||||
ms type m)
|
||||
(while mark-lists
|
||||
(setq type (caar mark-lists)
|
||||
ms (gnus-uncompress-range (cdr (pop mark-lists))))
|
||||
(while ms
|
||||
(if (setq m (assq (car ms) marks))
|
||||
(setcdr m (cons type (cdr m)))
|
||||
(push (list (car ms) type) marks))
|
||||
(pop ms))))
|
||||
;; Convert.
|
||||
(when (eq type 'headers)
|
||||
(nnvirtual-convert-headers))
|
||||
;; Go through the headers and map away.
|
||||
(set-buffer nntp-server-buffer)
|
||||
(goto-char (point-min))
|
||||
(while (looking-at
|
||||
"^[0-9]+\t[^\t]*\t[^\t]*\t[^\t]*\t\\([^\t]*\\)\t")
|
||||
(setq to-article
|
||||
(gnus-gethash
|
||||
(buffer-substring (match-beginning 1) (match-end 1))
|
||||
hashtb))
|
||||
;; Add this article to the list of read articles.
|
||||
(push to-article to-reads)
|
||||
;; See if there are any marks and then add them.
|
||||
(when (setq mark (assq (read (current-buffer)) marks))
|
||||
(setq marks (delq mark marks))
|
||||
(setcar mark to-article)
|
||||
(push mark to-marks))
|
||||
(forward-line 1))
|
||||
;; Now we know what the read articles are and what the
|
||||
;; article marks are. We transform the information
|
||||
;; into the Gnus info format.
|
||||
(setq to-reads
|
||||
(gnus-range-add
|
||||
(gnus-compress-sequence (sort to-reads '<) t)
|
||||
(cons 1 (1- (car to-active)))))
|
||||
(gnus-info-set-read info to-reads)
|
||||
;; Do the marks. I'm sure y'all understand what's
|
||||
;; going on down below, so I won't bother with any
|
||||
;; further comments. <duck>
|
||||
(let ((mlists gnus-article-mark-lists)
|
||||
lists ms a)
|
||||
(while mlists
|
||||
(push (list (cdr (pop mlists))) lists))
|
||||
(while (setq ms (pop marks))
|
||||
(setq article (pop ms))
|
||||
(while ms
|
||||
(setcdr (setq a (assq (pop ms) lists))
|
||||
(cons article (cdr a)))))
|
||||
(setq a lists)
|
||||
(while a
|
||||
(setcdr (car a) (gnus-compress-sequence (sort (cdar a) '<)))
|
||||
(pop a))
|
||||
(gnus-info-set-marks info lists t)))))
|
||||
(gnus-message 7 "Translating %s...done" group)))
|
||||
|
||||
(defun gnus-group-move-group-to-server (info from-server to-server)
|
||||
"Move the group on the current line from FROM-SERVER to TO-SERVER."
|
||||
(interactive
|
||||
(let ((info (gnus-get-info (gnus-group-group-name))))
|
||||
(list info (gnus-find-method-for-group (gnus-info-group info))
|
||||
(gnus-read-method (format "Move group %s to method: "
|
||||
(gnus-info-group info))))))
|
||||
(save-excursion
|
||||
(gnus-move-group-to-server info from-server to-server)
|
||||
;; We have to update the group info to point use the right server.
|
||||
(gnus-info-set-method info to-server t)
|
||||
;; We also have to change the name of the group and stuff.
|
||||
(let* ((group (gnus-info-group info))
|
||||
(new-name (gnus-group-prefixed-name
|
||||
(gnus-group-real-name group) to-server)))
|
||||
(gnus-info-set-group info new-name)
|
||||
(gnus-sethash new-name (gnus-gethash group gnus-newsrc-hashtb)
|
||||
gnus-newsrc-hashtb)
|
||||
(gnus-sethash group nil gnus-newsrc-hashtb))))
|
||||
|
||||
(provide 'gnus-move)
|
||||
|
||||
;;; gnus-move.el ends here
|
||||
1074
lisp/gnus/gnus-msg.el
Normal file
1074
lisp/gnus/gnus-msg.el
Normal file
File diff suppressed because it is too large
Load diff
303
lisp/gnus/gnus-nocem.el
Normal file
303
lisp/gnus/gnus-nocem.el
Normal file
|
|
@ -0,0 +1,303 @@
|
|||
;;; gnus-nocem.el --- NoCeM pseudo-cancellation treatment
|
||||
;; Copyright (C) 1995,96,97 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
|
||||
;; Keywords: news
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'gnus)
|
||||
(require 'nnmail)
|
||||
(require 'gnus-art)
|
||||
(require 'gnus-sum)
|
||||
(require 'gnus-range)
|
||||
|
||||
(defgroup gnus-nocem nil
|
||||
"NoCeM pseudo-cancellation treatment"
|
||||
:group 'gnus-score)
|
||||
|
||||
(defcustom gnus-nocem-groups
|
||||
'("news.lists.filters" "news.admin.net-abuse.bulletins"
|
||||
"alt.nocem.misc" "news.admin.net-abuse.announce")
|
||||
"List of groups that will be searched for NoCeM messages."
|
||||
:group 'gnus-nocem
|
||||
:type '(repeat (string :tag "Group")))
|
||||
|
||||
(defcustom gnus-nocem-issuers
|
||||
'("AutoMoose-1" "Automoose-1" ; CancelMoose[tm]
|
||||
"rbraver@ohww.norman.ok.us" ; Robert Braver
|
||||
"clewis@ferret.ocunix.on.ca;" ; Chris Lewis
|
||||
"jem@xpat.com;" ; Despammer from Korea
|
||||
"snowhare@xmission.com" ; Benjamin "Snowhare" Franz
|
||||
"red@redpoll.mrfs.oh.us (Richard E. Depew)" ; ARMM! ARMM!
|
||||
)
|
||||
"List of NoCeM issuers to pay attention to."
|
||||
:group 'gnus-nocem
|
||||
:type '(repeat string))
|
||||
|
||||
(defcustom gnus-nocem-directory
|
||||
(nnheader-concat gnus-article-save-directory "NoCeM/")
|
||||
"*Directory where NoCeM files will be stored."
|
||||
:group 'gnus-nocem
|
||||
:type 'directory)
|
||||
|
||||
(defcustom gnus-nocem-expiry-wait 15
|
||||
"*Number of days to keep NoCeM headers in the cache."
|
||||
:group 'gnus-nocem
|
||||
:type 'integer)
|
||||
|
||||
(defcustom gnus-nocem-verifyer 'mc-verify
|
||||
"*Function called to verify that the NoCeM message is valid.
|
||||
One likely value is `mc-verify'. If the function in this variable
|
||||
isn't bound, the message will be used unconditionally."
|
||||
:group 'gnus-nocem
|
||||
:type '(radio (function-item mc-verify)
|
||||
(function :tag "other")))
|
||||
|
||||
(defcustom gnus-nocem-liberal-fetch nil
|
||||
"*If t try to fetch all messages which have @@NCM in the subject.
|
||||
Otherwise don't fetch messages which have references or whose message-id
|
||||
matches an previously scanned and verified nocem message."
|
||||
:group 'gnus-nocem
|
||||
:type 'boolean)
|
||||
|
||||
;;; Internal variables
|
||||
|
||||
(defvar gnus-nocem-active nil)
|
||||
(defvar gnus-nocem-alist nil)
|
||||
(defvar gnus-nocem-touched-alist nil)
|
||||
(defvar gnus-nocem-hashtb nil)
|
||||
(defvar gnus-nocem-seen-message-ids nil)
|
||||
|
||||
;;; Functions
|
||||
|
||||
(defun gnus-nocem-active-file ()
|
||||
(concat (file-name-as-directory gnus-nocem-directory) "active"))
|
||||
|
||||
(defun gnus-nocem-cache-file ()
|
||||
(concat (file-name-as-directory gnus-nocem-directory) "cache"))
|
||||
|
||||
(defun gnus-nocem-scan-groups ()
|
||||
"Scan all NoCeM groups for new NoCeM messages."
|
||||
(interactive)
|
||||
(let ((groups gnus-nocem-groups)
|
||||
(gnus-inhibit-demon t)
|
||||
group active gactive articles)
|
||||
(gnus-make-directory gnus-nocem-directory)
|
||||
;; Load any previous NoCeM headers.
|
||||
(gnus-nocem-load-cache)
|
||||
;; Read the active file if it hasn't been read yet.
|
||||
(and (file-exists-p (gnus-nocem-active-file))
|
||||
(not gnus-nocem-active)
|
||||
(ignore-errors
|
||||
(load (gnus-nocem-active-file) t t t)))
|
||||
;; Go through all groups and see whether new articles have
|
||||
;; arrived.
|
||||
(while (setq group (pop groups))
|
||||
(if (not (setq gactive (gnus-activate-group group)))
|
||||
() ; This group doesn't exist.
|
||||
(setq active (nth 1 (assoc group gnus-nocem-active)))
|
||||
(when (and (not (< (cdr gactive) (car gactive))) ; Empty group.
|
||||
(or (not active)
|
||||
(< (cdr active) (cdr gactive))))
|
||||
;; Ok, there are new articles in this group, se we fetch the
|
||||
;; headers.
|
||||
(save-excursion
|
||||
(let ((dependencies (make-vector 10 nil))
|
||||
headers header)
|
||||
(nnheader-temp-write nil
|
||||
(setq headers
|
||||
(if (eq 'nov
|
||||
(gnus-retrieve-headers
|
||||
(setq articles
|
||||
(gnus-uncompress-range
|
||||
(cons
|
||||
(if active (1+ (cdr active))
|
||||
(car gactive))
|
||||
(cdr gactive))))
|
||||
group))
|
||||
(gnus-get-newsgroup-headers-xover
|
||||
articles nil dependencies)
|
||||
(gnus-get-newsgroup-headers dependencies)))
|
||||
(while (setq header (pop headers))
|
||||
;; We take a closer look on all articles that have
|
||||
;; "@@NCM" in the subject. Unless we already read
|
||||
;; this cross posted message. Nocem messages
|
||||
;; are not allowed to have references, so we can
|
||||
;; ignore scanning followups.
|
||||
(and (string-match "@@NCM" (mail-header-subject header))
|
||||
(or gnus-nocem-liberal-fetch
|
||||
(and (or (string= "" (mail-header-references
|
||||
header))
|
||||
(null (mail-header-references header)))
|
||||
(not (member (mail-header-message-id header)
|
||||
gnus-nocem-seen-message-ids))))
|
||||
(gnus-nocem-check-article group header)))))))
|
||||
(setq gnus-nocem-active
|
||||
(cons (list group gactive)
|
||||
(delq (assoc group gnus-nocem-active)
|
||||
gnus-nocem-active)))))
|
||||
;; Save the results, if any.
|
||||
(gnus-nocem-save-cache)
|
||||
(gnus-nocem-save-active)))
|
||||
|
||||
(defun gnus-nocem-check-article (group header)
|
||||
"Check whether the current article is an NCM article and that we want it."
|
||||
;; Get the article.
|
||||
(gnus-message 7 "Checking article %d in %s for NoCeM..."
|
||||
(mail-header-number header) group)
|
||||
(let ((date (mail-header-date header))
|
||||
issuer b e)
|
||||
(when (or (not date)
|
||||
(nnmail-time-less
|
||||
(nnmail-time-since (nnmail-date-to-time date))
|
||||
(nnmail-days-to-time gnus-nocem-expiry-wait)))
|
||||
(gnus-request-article-this-buffer (mail-header-number header) group)
|
||||
(goto-char (point-min))
|
||||
(when (re-search-forward "-----BEGIN PGP MESSAGE-----" nil t)
|
||||
(delete-region (point-min) (match-beginning 0)))
|
||||
(when (re-search-forward "-----END PGP MESSAGE-----\n?" nil t)
|
||||
(delete-region (match-end 0) (point-max)))
|
||||
(goto-char (point-min))
|
||||
;; The article has to have proper NoCeM headers.
|
||||
(when (and (setq b (search-forward "\n@@BEGIN NCM HEADERS\n" nil t))
|
||||
(setq e (search-forward "\n@@BEGIN NCM BODY\n" nil t)))
|
||||
;; We get the name of the issuer.
|
||||
(narrow-to-region b e)
|
||||
(setq issuer (mail-fetch-field "issuer"))
|
||||
(widen)
|
||||
(and (member issuer gnus-nocem-issuers) ; We like her....
|
||||
(gnus-nocem-verify-issuer issuer) ; She is who she says she is...
|
||||
(gnus-nocem-enter-article) ; We gobble the message..
|
||||
(push (mail-header-message-id header) ; But don't come back for
|
||||
gnus-nocem-seen-message-ids)))))) ; second helpings.
|
||||
|
||||
(defun gnus-nocem-verify-issuer (person)
|
||||
"Verify using PGP that the canceler is who she says she is."
|
||||
(if (fboundp gnus-nocem-verifyer)
|
||||
(funcall gnus-nocem-verifyer)
|
||||
;; If we don't have Mailcrypt, then we use the message anyway.
|
||||
t))
|
||||
|
||||
(defun gnus-nocem-enter-article ()
|
||||
"Enter the current article into the NoCeM cache."
|
||||
(goto-char (point-min))
|
||||
(let ((b (search-forward "\n@@BEGIN NCM BODY\n" nil t))
|
||||
(e (search-forward "\n@@END NCM BODY\n" nil t))
|
||||
(buf (current-buffer))
|
||||
ncm id group)
|
||||
(when (and b e)
|
||||
(narrow-to-region b (1+ (match-beginning 0)))
|
||||
(goto-char (point-min))
|
||||
(while (search-forward "\t" nil t)
|
||||
(cond
|
||||
((not (ignore-errors
|
||||
(setq group (let ((obarray gnus-active-hashtb)) (read buf)))))
|
||||
;; An error.
|
||||
)
|
||||
((not (symbolp group))
|
||||
;; Ignore invalid entries.
|
||||
)
|
||||
((not (boundp group))
|
||||
;; Make sure all entries in the hashtb are bound.
|
||||
(set group nil))
|
||||
(t
|
||||
(when (gnus-gethash (symbol-name group) gnus-newsrc-hashtb)
|
||||
;; Valid group.
|
||||
(beginning-of-line)
|
||||
(while (= (following-char) ?\t)
|
||||
(forward-line -1))
|
||||
(setq id (buffer-substring (point) (1- (search-forward "\t"))))
|
||||
(unless (gnus-gethash id gnus-nocem-hashtb)
|
||||
;; only store if not already present
|
||||
(gnus-sethash id t gnus-nocem-hashtb)
|
||||
(push id ncm))
|
||||
(forward-line 1)
|
||||
(while (= (following-char) ?\t)
|
||||
(forward-line 1))))))
|
||||
(when ncm
|
||||
(setq gnus-nocem-touched-alist t)
|
||||
(push (cons (let ((time (current-time))) (setcdr (cdr time) nil) time)
|
||||
ncm)
|
||||
gnus-nocem-alist))
|
||||
t)))
|
||||
|
||||
(defun gnus-nocem-load-cache ()
|
||||
"Load the NoCeM cache."
|
||||
(interactive)
|
||||
(unless gnus-nocem-alist
|
||||
;; The buffer doesn't exist, so we create it and load the NoCeM
|
||||
;; cache.
|
||||
(when (file-exists-p (gnus-nocem-cache-file))
|
||||
(load (gnus-nocem-cache-file) t t t)
|
||||
(gnus-nocem-alist-to-hashtb))))
|
||||
|
||||
(defun gnus-nocem-save-cache ()
|
||||
"Save the NoCeM cache."
|
||||
(when (and gnus-nocem-alist
|
||||
gnus-nocem-touched-alist)
|
||||
(nnheader-temp-write (gnus-nocem-cache-file)
|
||||
(gnus-prin1 `(setq gnus-nocem-alist ',gnus-nocem-alist)))
|
||||
(setq gnus-nocem-touched-alist nil)))
|
||||
|
||||
(defun gnus-nocem-save-active ()
|
||||
"Save the NoCeM active file."
|
||||
(nnheader-temp-write (gnus-nocem-active-file)
|
||||
(gnus-prin1 `(setq gnus-nocem-active ',gnus-nocem-active))))
|
||||
|
||||
(defun gnus-nocem-alist-to-hashtb ()
|
||||
"Create a hashtable from the Message-IDs we have."
|
||||
(let* ((alist gnus-nocem-alist)
|
||||
(pprev (cons nil alist))
|
||||
(prev pprev)
|
||||
(expiry (nnmail-days-to-time gnus-nocem-expiry-wait))
|
||||
entry)
|
||||
(setq gnus-nocem-hashtb (gnus-make-hashtable (* (length alist) 51)))
|
||||
(while (setq entry (car alist))
|
||||
(if (not (nnmail-time-less (nnmail-time-since (car entry)) expiry))
|
||||
;; This entry has expired, so we remove it.
|
||||
(setcdr prev (cdr alist))
|
||||
(setq prev alist)
|
||||
;; This is ok, so we enter it into the hashtable.
|
||||
(setq entry (cdr entry))
|
||||
(while entry
|
||||
(gnus-sethash (car entry) t gnus-nocem-hashtb)
|
||||
(setq entry (cdr entry))))
|
||||
(setq alist (cdr alist)))))
|
||||
|
||||
(gnus-add-shutdown 'gnus-nocem-close 'gnus)
|
||||
|
||||
(defun gnus-nocem-close ()
|
||||
"Clear internal NoCeM variables."
|
||||
(setq gnus-nocem-alist nil
|
||||
gnus-nocem-hashtb nil
|
||||
gnus-nocem-active nil
|
||||
gnus-nocem-touched-alist nil
|
||||
gnus-nocem-seen-message-ids nil))
|
||||
|
||||
(defun gnus-nocem-unwanted-article-p (id)
|
||||
"Say whether article ID in the current group is wanted."
|
||||
(gnus-gethash id gnus-nocem-hashtb))
|
||||
|
||||
(provide 'gnus-nocem)
|
||||
|
||||
;;; gnus-nocem.el ends here
|
||||
281
lisp/gnus/gnus-range.el
Normal file
281
lisp/gnus/gnus-range.el
Normal file
|
|
@ -0,0 +1,281 @@
|
|||
;;; gnus-range.el --- range and sequence functions for Gnus
|
||||
;; Copyright (C) 1996,97 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
|
||||
;; Keywords: news
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
;;; List and range functions
|
||||
|
||||
(defun gnus-last-element (list)
|
||||
"Return last element of LIST."
|
||||
(while (cdr list)
|
||||
(setq list (cdr list)))
|
||||
(car list))
|
||||
|
||||
(defun gnus-copy-sequence (list)
|
||||
"Do a complete, total copy of a list."
|
||||
(let (out)
|
||||
(while (consp list)
|
||||
(if (consp (car list))
|
||||
(push (gnus-copy-sequence (pop list)) out)
|
||||
(push (pop list) out)))
|
||||
(if list
|
||||
(nconc (nreverse out) list)
|
||||
(nreverse out))))
|
||||
|
||||
(defun gnus-set-difference (list1 list2)
|
||||
"Return a list of elements of LIST1 that do not appear in LIST2."
|
||||
(let ((list1 (copy-sequence list1)))
|
||||
(while list2
|
||||
(setq list1 (delq (car list2) list1))
|
||||
(setq list2 (cdr list2)))
|
||||
list1))
|
||||
|
||||
(defun gnus-sorted-complement (list1 list2)
|
||||
"Return a list of elements of LIST1 that do not appear in LIST2.
|
||||
Both lists have to be sorted over <."
|
||||
(let (out)
|
||||
(if (or (null list1) (null list2))
|
||||
(or list1 list2)
|
||||
(while (and list1 list2)
|
||||
(cond ((= (car list1) (car list2))
|
||||
(setq list1 (cdr list1)
|
||||
list2 (cdr list2)))
|
||||
((< (car list1) (car list2))
|
||||
(setq out (cons (car list1) out))
|
||||
(setq list1 (cdr list1)))
|
||||
(t
|
||||
(setq out (cons (car list2) out))
|
||||
(setq list2 (cdr list2)))))
|
||||
(nconc (nreverse out) (or list1 list2)))))
|
||||
|
||||
(defun gnus-intersection (list1 list2)
|
||||
(let ((result nil))
|
||||
(while list2
|
||||
(when (memq (car list2) list1)
|
||||
(setq result (cons (car list2) result)))
|
||||
(setq list2 (cdr list2)))
|
||||
result))
|
||||
|
||||
(defun gnus-sorted-intersection (list1 list2)
|
||||
;; LIST1 and LIST2 have to be sorted over <.
|
||||
(let (out)
|
||||
(while (and list1 list2)
|
||||
(cond ((= (car list1) (car list2))
|
||||
(setq out (cons (car list1) out)
|
||||
list1 (cdr list1)
|
||||
list2 (cdr list2)))
|
||||
((< (car list1) (car list2))
|
||||
(setq list1 (cdr list1)))
|
||||
(t
|
||||
(setq list2 (cdr list2)))))
|
||||
(nreverse out)))
|
||||
|
||||
(defun gnus-set-sorted-intersection (list1 list2)
|
||||
;; LIST1 and LIST2 have to be sorted over <.
|
||||
;; This function modifies LIST1.
|
||||
(let* ((top (cons nil list1))
|
||||
(prev top))
|
||||
(while (and list1 list2)
|
||||
(cond ((= (car list1) (car list2))
|
||||
(setq prev list1
|
||||
list1 (cdr list1)
|
||||
list2 (cdr list2)))
|
||||
((< (car list1) (car list2))
|
||||
(setcdr prev (cdr list1))
|
||||
(setq list1 (cdr list1)))
|
||||
(t
|
||||
(setq list2 (cdr list2)))))
|
||||
(setcdr prev nil)
|
||||
(cdr top)))
|
||||
|
||||
(defun gnus-compress-sequence (numbers &optional always-list)
|
||||
"Convert list of numbers to a list of ranges or a single range.
|
||||
If ALWAYS-LIST is non-nil, this function will always release a list of
|
||||
ranges."
|
||||
(let* ((first (car numbers))
|
||||
(last (car numbers))
|
||||
result)
|
||||
(if (null numbers)
|
||||
nil
|
||||
(if (not (listp (cdr numbers)))
|
||||
numbers
|
||||
(while numbers
|
||||
(cond ((= last (car numbers)) nil) ;Omit duplicated number
|
||||
((= (1+ last) (car numbers)) ;Still in sequence
|
||||
(setq last (car numbers)))
|
||||
(t ;End of one sequence
|
||||
(setq result
|
||||
(cons (if (= first last) first
|
||||
(cons first last))
|
||||
result))
|
||||
(setq first (car numbers))
|
||||
(setq last (car numbers))))
|
||||
(setq numbers (cdr numbers)))
|
||||
(if (and (not always-list) (null result))
|
||||
(if (= first last) (list first) (cons first last))
|
||||
(nreverse (cons (if (= first last) first (cons first last))
|
||||
result)))))))
|
||||
|
||||
(defalias 'gnus-uncompress-sequence 'gnus-uncompress-range)
|
||||
(defun gnus-uncompress-range (ranges)
|
||||
"Expand a list of ranges into a list of numbers.
|
||||
RANGES is either a single range on the form `(num . num)' or a list of
|
||||
these ranges."
|
||||
(let (first last result)
|
||||
(cond
|
||||
((null ranges)
|
||||
nil)
|
||||
((not (listp (cdr ranges)))
|
||||
(setq first (car ranges))
|
||||
(setq last (cdr ranges))
|
||||
(while (<= first last)
|
||||
(setq result (cons first result))
|
||||
(setq first (1+ first)))
|
||||
(nreverse result))
|
||||
(t
|
||||
(while ranges
|
||||
(if (atom (car ranges))
|
||||
(when (numberp (car ranges))
|
||||
(setq result (cons (car ranges) result)))
|
||||
(setq first (caar ranges))
|
||||
(setq last (cdar ranges))
|
||||
(while (<= first last)
|
||||
(setq result (cons first result))
|
||||
(setq first (1+ first))))
|
||||
(setq ranges (cdr ranges)))
|
||||
(nreverse result)))))
|
||||
|
||||
(defun gnus-add-to-range (ranges list)
|
||||
"Return a list of ranges that has all articles from both RANGES and LIST.
|
||||
Note: LIST has to be sorted over `<'."
|
||||
(if (not ranges)
|
||||
(gnus-compress-sequence list t)
|
||||
(setq list (copy-sequence list))
|
||||
(unless (listp (cdr ranges))
|
||||
(setq ranges (list ranges)))
|
||||
(let ((out ranges)
|
||||
ilist lowest highest temp)
|
||||
(while (and ranges list)
|
||||
(setq ilist list)
|
||||
(setq lowest (or (and (atom (car ranges)) (car ranges))
|
||||
(caar ranges)))
|
||||
(while (and list (cdr list) (< (cadr list) lowest))
|
||||
(setq list (cdr list)))
|
||||
(when (< (car ilist) lowest)
|
||||
(setq temp list)
|
||||
(setq list (cdr list))
|
||||
(setcdr temp nil)
|
||||
(setq out (nconc (gnus-compress-sequence ilist t) out)))
|
||||
(setq highest (or (and (atom (car ranges)) (car ranges))
|
||||
(cdar ranges)))
|
||||
(while (and list (<= (car list) highest))
|
||||
(setq list (cdr list)))
|
||||
(setq ranges (cdr ranges)))
|
||||
(when list
|
||||
(setq out (nconc (gnus-compress-sequence list t) out)))
|
||||
(setq out (sort out (lambda (r1 r2)
|
||||
(< (or (and (atom r1) r1) (car r1))
|
||||
(or (and (atom r2) r2) (car r2))))))
|
||||
(setq ranges out)
|
||||
(while ranges
|
||||
(if (atom (car ranges))
|
||||
(when (cdr ranges)
|
||||
(if (atom (cadr ranges))
|
||||
(when (= (1+ (car ranges)) (cadr ranges))
|
||||
(setcar ranges (cons (car ranges)
|
||||
(cadr ranges)))
|
||||
(setcdr ranges (cddr ranges)))
|
||||
(when (= (1+ (car ranges)) (caadr ranges))
|
||||
(setcar (cadr ranges) (car ranges))
|
||||
(setcar ranges (cadr ranges))
|
||||
(setcdr ranges (cddr ranges)))))
|
||||
(when (cdr ranges)
|
||||
(if (atom (cadr ranges))
|
||||
(when (= (1+ (cdar ranges)) (cadr ranges))
|
||||
(setcdr (car ranges) (cadr ranges))
|
||||
(setcdr ranges (cddr ranges)))
|
||||
(when (= (1+ (cdar ranges)) (caadr ranges))
|
||||
(setcdr (car ranges) (cdadr ranges))
|
||||
(setcdr ranges (cddr ranges))))))
|
||||
(setq ranges (cdr ranges)))
|
||||
out)))
|
||||
|
||||
(defun gnus-remove-from-range (ranges list)
|
||||
"Return a list of ranges that has all articles from LIST removed from RANGES.
|
||||
Note: LIST has to be sorted over `<'."
|
||||
;; !!! This function shouldn't look like this, but I've got a headache.
|
||||
(gnus-compress-sequence
|
||||
(gnus-sorted-complement
|
||||
(gnus-uncompress-range ranges) list)))
|
||||
|
||||
(defun gnus-member-of-range (number ranges)
|
||||
(if (not (listp (cdr ranges)))
|
||||
(and (>= number (car ranges))
|
||||
(<= number (cdr ranges)))
|
||||
(let ((not-stop t))
|
||||
(while (and ranges
|
||||
(if (numberp (car ranges))
|
||||
(>= number (car ranges))
|
||||
(>= number (caar ranges)))
|
||||
not-stop)
|
||||
(when (if (numberp (car ranges))
|
||||
(= number (car ranges))
|
||||
(and (>= number (caar ranges))
|
||||
(<= number (cdar ranges))))
|
||||
(setq not-stop nil))
|
||||
(setq ranges (cdr ranges)))
|
||||
(not not-stop))))
|
||||
|
||||
(defun gnus-range-length (range)
|
||||
"Return the length RANGE would have if uncompressed."
|
||||
(length (gnus-uncompress-range range)))
|
||||
|
||||
(defun gnus-sublist-p (list sublist)
|
||||
"Test whether all elements in SUBLIST are members of LIST."
|
||||
(let ((sublistp t))
|
||||
(while sublist
|
||||
(unless (memq (pop sublist) list)
|
||||
(setq sublistp nil
|
||||
sublist nil)))
|
||||
sublistp))
|
||||
|
||||
(defun gnus-range-add (range1 range2)
|
||||
"Add RANGE2 to RANGE1 destructively."
|
||||
(cond
|
||||
;; If either are nil, then the job is quite easy.
|
||||
((or (null range1) (null range2))
|
||||
(or range1 range2))
|
||||
(t
|
||||
;; I don't like thinking.
|
||||
(gnus-compress-sequence
|
||||
(sort
|
||||
(nconc
|
||||
(gnus-uncompress-range range1)
|
||||
(gnus-uncompress-range range2))
|
||||
'<)))))
|
||||
|
||||
(provide 'gnus-range)
|
||||
|
||||
;;; gnus-range.el ends here
|
||||
991
lisp/gnus/gnus-salt.el
Normal file
991
lisp/gnus/gnus-salt.el
Normal file
|
|
@ -0,0 +1,991 @@
|
|||
;;; gnus-salt.el --- alternate summary mode interfaces for Gnus
|
||||
;; Copyright (C) 1996,97 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'gnus)
|
||||
(require 'gnus-sum)
|
||||
|
||||
;;;
|
||||
;;; gnus-pick-mode
|
||||
;;;
|
||||
|
||||
(defvar gnus-pick-mode nil
|
||||
"Minor mode for providing a pick-and-read interface in Gnus summary buffers.")
|
||||
|
||||
(defvar gnus-pick-display-summary nil
|
||||
"*Display summary while reading.")
|
||||
|
||||
(defvar gnus-pick-mode-hook nil
|
||||
"Hook run in summary pick mode buffers.")
|
||||
|
||||
(defvar gnus-mark-unpicked-articles-as-read nil
|
||||
"*If non-nil, mark all unpicked articles as read.")
|
||||
|
||||
(defvar gnus-pick-elegant-flow t
|
||||
"If non-nil, gnus-pick-start-reading will run gnus-summary-next-group when no articles have been picked.")
|
||||
|
||||
(defvar gnus-summary-pick-line-format
|
||||
"%-5P %U\%R\%z\%I\%(%[%4L: %-20,20n%]%) %s\n"
|
||||
"*The format specification of the lines in pick buffers.
|
||||
It accepts the same format specs that `gnus-summary-line-format' does.")
|
||||
|
||||
;;; Internal variables.
|
||||
|
||||
(defvar gnus-pick-mode-map nil)
|
||||
|
||||
(unless gnus-pick-mode-map
|
||||
(setq gnus-pick-mode-map (make-sparse-keymap))
|
||||
|
||||
(gnus-define-keys
|
||||
gnus-pick-mode-map
|
||||
"t" gnus-uu-mark-thread
|
||||
"T" gnus-uu-unmark-thread
|
||||
" " gnus-pick-next-page
|
||||
"u" gnus-summary-unmark-as-processable
|
||||
"U" gnus-summary-unmark-all-processable
|
||||
"v" gnus-uu-mark-over
|
||||
"r" gnus-uu-mark-region
|
||||
"R" gnus-uu-unmark-region
|
||||
"e" gnus-uu-mark-by-regexp
|
||||
"E" gnus-uu-mark-by-regexp
|
||||
"b" gnus-uu-mark-buffer
|
||||
"B" gnus-uu-unmark-buffer
|
||||
"." gnus-pick-article
|
||||
gnus-down-mouse-2 gnus-pick-mouse-pick-region
|
||||
;;gnus-mouse-2 gnus-pick-mouse-pick
|
||||
"X" gnus-pick-start-reading
|
||||
"\r" gnus-pick-start-reading))
|
||||
|
||||
(defun gnus-pick-make-menu-bar ()
|
||||
(unless (boundp 'gnus-pick-menu)
|
||||
(easy-menu-define
|
||||
gnus-pick-menu gnus-pick-mode-map ""
|
||||
'("Pick"
|
||||
("Pick"
|
||||
["Article" gnus-summary-mark-as-processable t]
|
||||
["Thread" gnus-uu-mark-thread t]
|
||||
["Region" gnus-uu-mark-region t]
|
||||
["Regexp" gnus-uu-mark-regexp t]
|
||||
["Buffer" gnus-uu-mark-buffer t])
|
||||
("Unpick"
|
||||
["Article" gnus-summary-unmark-as-processable t]
|
||||
["Thread" gnus-uu-unmark-thread t]
|
||||
["Region" gnus-uu-unmark-region t]
|
||||
["Regexp" gnus-uu-unmark-regexp t]
|
||||
["Buffer" gnus-uu-unmark-buffer t])
|
||||
["Start reading" gnus-pick-start-reading t]
|
||||
["Switch pick mode off" gnus-pick-mode gnus-pick-mode]))))
|
||||
|
||||
(defun gnus-pick-mode (&optional arg)
|
||||
"Minor mode for providing a pick-and-read interface in Gnus summary buffers.
|
||||
|
||||
\\{gnus-pick-mode-map}"
|
||||
(interactive "P")
|
||||
(when (eq major-mode 'gnus-summary-mode)
|
||||
(if (not (set (make-local-variable 'gnus-pick-mode)
|
||||
(if (null arg) (not gnus-pick-mode)
|
||||
(> (prefix-numeric-value arg) 0))))
|
||||
(remove-hook 'gnus-message-setup-hook 'gnus-pick-setup-message)
|
||||
;; Make sure that we don't select any articles upon group entry.
|
||||
(set (make-local-variable 'gnus-auto-select-first) nil)
|
||||
;; Change line format.
|
||||
(setq gnus-summary-line-format gnus-summary-pick-line-format)
|
||||
(setq gnus-summary-line-format-spec nil)
|
||||
(gnus-update-format-specifications nil 'summary)
|
||||
(gnus-update-summary-mark-positions)
|
||||
(add-hook 'gnus-message-setup-hook 'gnus-pick-setup-message)
|
||||
(set (make-local-variable 'gnus-summary-goto-unread) 'never)
|
||||
;; Set up the menu.
|
||||
(when (gnus-visual-p 'pick-menu 'menu)
|
||||
(gnus-pick-make-menu-bar))
|
||||
(unless (assq 'gnus-pick-mode minor-mode-alist)
|
||||
(push '(gnus-pick-mode " Pick") minor-mode-alist))
|
||||
(unless (assq 'gnus-pick-mode minor-mode-map-alist)
|
||||
(push (cons 'gnus-pick-mode gnus-pick-mode-map)
|
||||
minor-mode-map-alist))
|
||||
(run-hooks 'gnus-pick-mode-hook))))
|
||||
|
||||
(defun gnus-pick-setup-message ()
|
||||
"Make Message do the right thing on exit."
|
||||
(when (and (gnus-buffer-live-p gnus-summary-buffer)
|
||||
(save-excursion
|
||||
(set-buffer gnus-summary-buffer)
|
||||
gnus-pick-mode))
|
||||
(message-add-action
|
||||
'(gnus-configure-windows 'pick t) 'send 'exit 'postpone 'kill)))
|
||||
|
||||
(defvar gnus-pick-line-number 1)
|
||||
(defun gnus-pick-line-number ()
|
||||
"Return the current line number."
|
||||
(if (bobp)
|
||||
(setq gnus-pick-line-number 1)
|
||||
(incf gnus-pick-line-number)))
|
||||
|
||||
(defun gnus-pick-start-reading (&optional catch-up)
|
||||
"Start reading the picked articles.
|
||||
If given a prefix, mark all unpicked articles as read."
|
||||
(interactive "P")
|
||||
(if gnus-newsgroup-processable
|
||||
(progn
|
||||
(gnus-summary-limit-to-articles nil)
|
||||
(when (or catch-up gnus-mark-unpicked-articles-as-read)
|
||||
(gnus-summary-limit-mark-excluded-as-read))
|
||||
(gnus-summary-first-article)
|
||||
(gnus-configure-windows
|
||||
(if gnus-pick-display-summary 'article 'pick) t))
|
||||
(if gnus-pick-elegant-flow
|
||||
(progn
|
||||
(when (or catch-up gnus-mark-unpicked-articles-as-read)
|
||||
(gnus-summary-limit-mark-excluded-as-read))
|
||||
(if (gnus-group-quit-config gnus-newsgroup-name)
|
||||
(gnus-summary-exit)
|
||||
(gnus-summary-next-group)))
|
||||
(error "No articles have been picked"))))
|
||||
|
||||
(defun gnus-pick-article (&optional arg)
|
||||
"Pick the article on the current line.
|
||||
If ARG, pick the article on that line instead."
|
||||
(interactive "P")
|
||||
(when arg
|
||||
(let (pos)
|
||||
(save-excursion
|
||||
(goto-char (point-min))
|
||||
(when (zerop (forward-line (1- (prefix-numeric-value arg))))
|
||||
(setq pos (point))))
|
||||
(if (not pos)
|
||||
(gnus-error 2 "No such line: %s" arg)
|
||||
(goto-char pos))))
|
||||
(gnus-summary-mark-as-processable 1))
|
||||
|
||||
(defun gnus-pick-mouse-pick (e)
|
||||
(interactive "e")
|
||||
(mouse-set-point e)
|
||||
(save-excursion
|
||||
(gnus-summary-mark-as-processable 1)))
|
||||
|
||||
(defun gnus-pick-mouse-pick-region (start-event)
|
||||
"Pick articles that the mouse is dragged over.
|
||||
This must be bound to a button-down mouse event."
|
||||
(interactive "e")
|
||||
(mouse-minibuffer-check start-event)
|
||||
(let* ((echo-keystrokes 0)
|
||||
(start-posn (event-start start-event))
|
||||
(start-point (posn-point start-posn))
|
||||
(start-line (1+ (count-lines 1 start-point)))
|
||||
(start-window (posn-window start-posn))
|
||||
(start-frame (window-frame start-window))
|
||||
(bounds (window-edges start-window))
|
||||
(top (nth 1 bounds))
|
||||
(bottom (if (window-minibuffer-p start-window)
|
||||
(nth 3 bounds)
|
||||
;; Don't count the mode line.
|
||||
(1- (nth 3 bounds))))
|
||||
(click-count (1- (event-click-count start-event))))
|
||||
(setq mouse-selection-click-count click-count)
|
||||
(setq mouse-selection-click-count-buffer (current-buffer))
|
||||
(mouse-set-point start-event)
|
||||
;; In case the down click is in the middle of some intangible text,
|
||||
;; use the end of that text, and put it in START-POINT.
|
||||
(when (< (point) start-point)
|
||||
(goto-char start-point))
|
||||
(gnus-pick-article)
|
||||
(setq start-point (point))
|
||||
;; end-of-range is used only in the single-click case.
|
||||
;; It is the place where the drag has reached so far
|
||||
;; (but not outside the window where the drag started).
|
||||
(let (event end end-point last-end-point (end-of-range (point)))
|
||||
(track-mouse
|
||||
(while (progn
|
||||
(setq event (read-event))
|
||||
(or (mouse-movement-p event)
|
||||
(eq (car-safe event) 'switch-frame)))
|
||||
(if (eq (car-safe event) 'switch-frame)
|
||||
nil
|
||||
(setq end (event-end event)
|
||||
end-point (posn-point end))
|
||||
(when end-point
|
||||
(setq last-end-point end-point))
|
||||
|
||||
(cond
|
||||
;; Are we moving within the original window?
|
||||
((and (eq (posn-window end) start-window)
|
||||
(integer-or-marker-p end-point))
|
||||
;; Go to START-POINT first, so that when we move to END-POINT,
|
||||
;; if it's in the middle of intangible text,
|
||||
;; point jumps in the direction away from START-POINT.
|
||||
(goto-char start-point)
|
||||
(goto-char end-point)
|
||||
(gnus-pick-article)
|
||||
;; In case the user moved his mouse really fast, pick
|
||||
;; articles on the line between this one and the last one.
|
||||
(let* ((this-line (1+ (count-lines 1 end-point)))
|
||||
(min-line (min this-line start-line))
|
||||
(max-line (max this-line start-line)))
|
||||
(while (< min-line max-line)
|
||||
(goto-line min-line)
|
||||
(gnus-pick-article)
|
||||
(setq min-line (1+ min-line)))
|
||||
(setq start-line this-line))
|
||||
(when (zerop (% click-count 3))
|
||||
(setq end-of-range (point))))
|
||||
(t
|
||||
(let ((mouse-row (cdr (cdr (mouse-position)))))
|
||||
(cond
|
||||
((null mouse-row))
|
||||
((< mouse-row top)
|
||||
(mouse-scroll-subr start-window (- mouse-row top)))
|
||||
((>= mouse-row bottom)
|
||||
(mouse-scroll-subr start-window
|
||||
(1+ (- mouse-row bottom)))))))))))
|
||||
(when (consp event)
|
||||
(let ((fun (key-binding (vector (car event)))))
|
||||
;; Run the binding of the terminating up-event, if possible.
|
||||
;; In the case of a multiple click, it gives the wrong results,
|
||||
;; because it would fail to set up a region.
|
||||
(when nil
|
||||
;; (and (= (mod mouse-selection-click-count 3) 0) (fboundp fun))
|
||||
;; In this case, we can just let the up-event execute normally.
|
||||
(let ((end (event-end event)))
|
||||
;; Set the position in the event before we replay it,
|
||||
;; because otherwise it may have a position in the wrong
|
||||
;; buffer.
|
||||
(setcar (cdr end) end-of-range)
|
||||
;; Delete the overlay before calling the function,
|
||||
;; because delete-overlay increases buffer-modified-tick.
|
||||
(push event unread-command-events))))))))
|
||||
|
||||
(defun gnus-pick-next-page ()
|
||||
"Go to the next page. If at the end of the buffer, start reading articles."
|
||||
(interactive)
|
||||
(let ((scroll-in-place nil))
|
||||
(condition-case nil
|
||||
(scroll-up)
|
||||
(end-of-buffer (gnus-pick-start-reading)))))
|
||||
|
||||
;;;
|
||||
;;; gnus-binary-mode
|
||||
;;;
|
||||
|
||||
(defvar gnus-binary-mode nil
|
||||
"Minor mode for providing a binary group interface in Gnus summary buffers.")
|
||||
|
||||
(defvar gnus-binary-mode-hook nil
|
||||
"Hook run in summary binary mode buffers.")
|
||||
|
||||
(defvar gnus-binary-mode-map nil)
|
||||
|
||||
(unless gnus-binary-mode-map
|
||||
(setq gnus-binary-mode-map (make-sparse-keymap))
|
||||
|
||||
(gnus-define-keys
|
||||
gnus-binary-mode-map
|
||||
"g" gnus-binary-show-article))
|
||||
|
||||
(defun gnus-binary-make-menu-bar ()
|
||||
(unless (boundp 'gnus-binary-menu)
|
||||
(easy-menu-define
|
||||
gnus-binary-menu gnus-binary-mode-map ""
|
||||
'("Pick"
|
||||
["Switch binary mode off" gnus-binary-mode t]))))
|
||||
|
||||
(defun gnus-binary-mode (&optional arg)
|
||||
"Minor mode for providing a binary group interface in Gnus summary buffers."
|
||||
(interactive "P")
|
||||
(when (eq major-mode 'gnus-summary-mode)
|
||||
(make-local-variable 'gnus-binary-mode)
|
||||
(setq gnus-binary-mode
|
||||
(if (null arg) (not gnus-binary-mode)
|
||||
(> (prefix-numeric-value arg) 0)))
|
||||
(when gnus-binary-mode
|
||||
;; Make sure that we don't select any articles upon group entry.
|
||||
(make-local-variable 'gnus-auto-select-first)
|
||||
(setq gnus-auto-select-first nil)
|
||||
(make-local-variable 'gnus-summary-display-article-function)
|
||||
(setq gnus-summary-display-article-function 'gnus-binary-display-article)
|
||||
;; Set up the menu.
|
||||
(when (gnus-visual-p 'binary-menu 'menu)
|
||||
(gnus-binary-make-menu-bar))
|
||||
(unless (assq 'gnus-binary-mode minor-mode-alist)
|
||||
(push '(gnus-binary-mode " Binary") minor-mode-alist))
|
||||
(unless (assq 'gnus-binary-mode minor-mode-map-alist)
|
||||
(push (cons 'gnus-binary-mode gnus-binary-mode-map)
|
||||
minor-mode-map-alist))
|
||||
(run-hooks 'gnus-binary-mode-hook))))
|
||||
|
||||
(defun gnus-binary-display-article (article &optional all-header)
|
||||
"Run ARTICLE through the binary decode functions."
|
||||
(when (gnus-summary-goto-subject article)
|
||||
(let ((gnus-view-pseudos 'automatic))
|
||||
(gnus-uu-decode-uu))))
|
||||
|
||||
(defun gnus-binary-show-article (&optional arg)
|
||||
"Bypass the binary functions and show the article."
|
||||
(interactive "P")
|
||||
(let (gnus-summary-display-article-function)
|
||||
(gnus-summary-show-article arg)))
|
||||
|
||||
;;;
|
||||
;;; gnus-tree-mode
|
||||
;;;
|
||||
|
||||
(defvar gnus-tree-line-format "%(%[%3,3n%]%)"
|
||||
"Format of tree elements.")
|
||||
|
||||
(defvar gnus-tree-minimize-window t
|
||||
"If non-nil, minimize the tree buffer window.
|
||||
If a number, never let the tree buffer grow taller than that number of
|
||||
lines.")
|
||||
|
||||
(defvar gnus-selected-tree-face 'modeline
|
||||
"*Face used for highlighting selected articles in the thread tree.")
|
||||
|
||||
(defvar gnus-tree-brackets '((?\[ . ?\]) (?\( . ?\))
|
||||
(?\{ . ?\}) (?< . ?>))
|
||||
"Brackets used in tree nodes.")
|
||||
|
||||
(defvar gnus-tree-parent-child-edges '(?- ?\\ ?|)
|
||||
"Characters used to connect parents with children.")
|
||||
|
||||
(defvar gnus-tree-mode-line-format "Gnus: %%b %S %Z"
|
||||
"*The format specification for the tree mode line.")
|
||||
|
||||
(defvar gnus-generate-tree-function 'gnus-generate-vertical-tree
|
||||
"*Function for generating a thread tree.
|
||||
Two predefined functions are available:
|
||||
`gnus-generate-horizontal-tree' and `gnus-generate-vertical-tree'.")
|
||||
|
||||
(defvar gnus-tree-mode-hook nil
|
||||
"*Hook run in tree mode buffers.")
|
||||
|
||||
;;; Internal variables.
|
||||
|
||||
(defvar gnus-tree-line-format-alist
|
||||
`((?n gnus-tmp-name ?s)
|
||||
(?f gnus-tmp-from ?s)
|
||||
(?N gnus-tmp-number ?d)
|
||||
(?\[ gnus-tmp-open-bracket ?c)
|
||||
(?\] gnus-tmp-close-bracket ?c)
|
||||
(?s gnus-tmp-subject ?s)))
|
||||
|
||||
(defvar gnus-tree-mode-line-format-alist gnus-summary-mode-line-format-alist)
|
||||
|
||||
(defvar gnus-tree-mode-line-format-spec nil)
|
||||
(defvar gnus-tree-line-format-spec nil)
|
||||
|
||||
(defvar gnus-tree-node-length nil)
|
||||
(defvar gnus-selected-tree-overlay nil)
|
||||
|
||||
(defvar gnus-tree-displayed-thread nil)
|
||||
|
||||
(defvar gnus-tree-mode-map nil)
|
||||
(put 'gnus-tree-mode 'mode-class 'special)
|
||||
|
||||
(unless gnus-tree-mode-map
|
||||
(setq gnus-tree-mode-map (make-keymap))
|
||||
(suppress-keymap gnus-tree-mode-map)
|
||||
(gnus-define-keys
|
||||
gnus-tree-mode-map
|
||||
"\r" gnus-tree-select-article
|
||||
gnus-mouse-2 gnus-tree-pick-article
|
||||
"\C-?" gnus-tree-read-summary-keys
|
||||
|
||||
"\C-c\C-i" gnus-info-find-node)
|
||||
|
||||
(substitute-key-definition
|
||||
'undefined 'gnus-tree-read-summary-keys gnus-tree-mode-map))
|
||||
|
||||
(defun gnus-tree-make-menu-bar ()
|
||||
(unless (boundp 'gnus-tree-menu)
|
||||
(easy-menu-define
|
||||
gnus-tree-menu gnus-tree-mode-map ""
|
||||
'("Tree"
|
||||
["Select article" gnus-tree-select-article t]))))
|
||||
|
||||
(defun gnus-tree-mode ()
|
||||
"Major mode for displaying thread trees."
|
||||
(interactive)
|
||||
(setq gnus-tree-mode-line-format-spec
|
||||
(gnus-parse-format gnus-tree-mode-line-format
|
||||
gnus-summary-mode-line-format-alist))
|
||||
(setq gnus-tree-line-format-spec
|
||||
(gnus-parse-format gnus-tree-line-format
|
||||
gnus-tree-line-format-alist t))
|
||||
(when (gnus-visual-p 'tree-menu 'menu)
|
||||
(gnus-tree-make-menu-bar))
|
||||
(kill-all-local-variables)
|
||||
(gnus-simplify-mode-line)
|
||||
(setq mode-name "Tree")
|
||||
(setq major-mode 'gnus-tree-mode)
|
||||
(use-local-map gnus-tree-mode-map)
|
||||
(buffer-disable-undo (current-buffer))
|
||||
(setq buffer-read-only t)
|
||||
(setq truncate-lines t)
|
||||
(save-excursion
|
||||
(gnus-set-work-buffer)
|
||||
(gnus-tree-node-insert (make-mail-header "") nil)
|
||||
(setq gnus-tree-node-length (1- (point))))
|
||||
(run-hooks 'gnus-tree-mode-hook))
|
||||
|
||||
(defun gnus-tree-read-summary-keys (&optional arg)
|
||||
"Read a summary buffer key sequence and execute it."
|
||||
(interactive "P")
|
||||
(let ((buf (current-buffer))
|
||||
win)
|
||||
(gnus-article-read-summary-keys arg nil t)
|
||||
(when (setq win (get-buffer-window buf))
|
||||
(select-window win)
|
||||
(when gnus-selected-tree-overlay
|
||||
(goto-char (or (gnus-overlay-end gnus-selected-tree-overlay) 1)))
|
||||
(gnus-tree-minimize))))
|
||||
|
||||
(defun gnus-tree-select-article (article)
|
||||
"Select the article under point, if any."
|
||||
(interactive (list (gnus-tree-article-number)))
|
||||
(let ((buf (current-buffer)))
|
||||
(when article
|
||||
(save-excursion
|
||||
(set-buffer gnus-summary-buffer)
|
||||
(gnus-summary-goto-article article))
|
||||
(select-window (get-buffer-window buf)))))
|
||||
|
||||
(defun gnus-tree-pick-article (e)
|
||||
"Select the article under the mouse pointer."
|
||||
(interactive "e")
|
||||
(mouse-set-point e)
|
||||
(gnus-tree-select-article (gnus-tree-article-number)))
|
||||
|
||||
(defun gnus-tree-article-number ()
|
||||
(get-text-property (point) 'gnus-number))
|
||||
|
||||
(defun gnus-tree-article-region (article)
|
||||
"Return a cons with BEG and END of the article region."
|
||||
(let ((pos (text-property-any (point-min) (point-max) 'gnus-number article)))
|
||||
(when pos
|
||||
(cons pos (next-single-property-change pos 'gnus-number)))))
|
||||
|
||||
(defun gnus-tree-goto-article (article)
|
||||
(let ((pos (text-property-any (point-min) (point-max) 'gnus-number article)))
|
||||
(when pos
|
||||
(goto-char pos))))
|
||||
|
||||
(defun gnus-tree-recenter ()
|
||||
"Center point in the tree window."
|
||||
(let ((selected (selected-window))
|
||||
(tree-window (get-buffer-window gnus-tree-buffer t)))
|
||||
(when tree-window
|
||||
(select-window tree-window)
|
||||
(when gnus-selected-tree-overlay
|
||||
(goto-char (or (gnus-overlay-end gnus-selected-tree-overlay) 1)))
|
||||
(let* ((top (cond ((< (window-height) 4) 0)
|
||||
((< (window-height) 7) 1)
|
||||
(t 2)))
|
||||
(height (1- (window-height)))
|
||||
(bottom (save-excursion (goto-char (point-max))
|
||||
(forward-line (- height))
|
||||
(point))))
|
||||
;; Set the window start to either `bottom', which is the biggest
|
||||
;; possible valid number, or the second line from the top,
|
||||
;; whichever is the least.
|
||||
(set-window-start
|
||||
tree-window (min bottom (save-excursion
|
||||
(forward-line (- top)) (point)))))
|
||||
(select-window selected))))
|
||||
|
||||
(defun gnus-get-tree-buffer ()
|
||||
"Return the tree buffer properly initialized."
|
||||
(save-excursion
|
||||
(set-buffer (get-buffer-create gnus-tree-buffer))
|
||||
(unless (eq major-mode 'gnus-tree-mode)
|
||||
(gnus-add-current-to-buffer-list)
|
||||
(gnus-tree-mode))
|
||||
(current-buffer)))
|
||||
|
||||
(defun gnus-tree-minimize ()
|
||||
(when (and gnus-tree-minimize-window
|
||||
(not (one-window-p)))
|
||||
(let ((windows 0)
|
||||
tot-win-height)
|
||||
(walk-windows (lambda (window) (incf windows)))
|
||||
(setq tot-win-height
|
||||
(- (frame-height)
|
||||
(* window-min-height (1- windows))
|
||||
2))
|
||||
(let* ((window-min-height 2)
|
||||
(height (count-lines (point-min) (point-max)))
|
||||
(min (max (1- window-min-height) height))
|
||||
(tot (if (numberp gnus-tree-minimize-window)
|
||||
(min gnus-tree-minimize-window min)
|
||||
min))
|
||||
(win (get-buffer-window (current-buffer)))
|
||||
(wh (and win (1- (window-height win)))))
|
||||
(setq tot (min tot tot-win-height))
|
||||
(when (and win
|
||||
(not (eq tot wh)))
|
||||
(let ((selected (selected-window)))
|
||||
(when (ignore-errors (select-window win))
|
||||
(enlarge-window (- tot wh))
|
||||
(select-window selected))))))))
|
||||
|
||||
;;; Generating the tree.
|
||||
|
||||
(defun gnus-tree-node-insert (header sparse &optional adopted)
|
||||
(let* ((dummy (stringp header))
|
||||
(header (if (vectorp header) header
|
||||
(progn
|
||||
(setq header (make-mail-header "*****"))
|
||||
(mail-header-set-number header 0)
|
||||
(mail-header-set-lines header 0)
|
||||
(mail-header-set-chars header 0)
|
||||
header)))
|
||||
(gnus-tmp-from (mail-header-from header))
|
||||
(gnus-tmp-subject (mail-header-subject header))
|
||||
(gnus-tmp-number (mail-header-number header))
|
||||
(gnus-tmp-name
|
||||
(cond
|
||||
((string-match "(.+)" gnus-tmp-from)
|
||||
(substring gnus-tmp-from
|
||||
(1+ (match-beginning 0)) (1- (match-end 0))))
|
||||
((string-match "<[^>]+> *$" gnus-tmp-from)
|
||||
(let ((beg (match-beginning 0)))
|
||||
(or (and (string-match "^\"[^\"]*\"" gnus-tmp-from)
|
||||
(substring gnus-tmp-from (1+ (match-beginning 0))
|
||||
(1- (match-end 0))))
|
||||
(substring gnus-tmp-from 0 beg))))
|
||||
((memq gnus-tmp-number sparse)
|
||||
"***")
|
||||
(t gnus-tmp-from)))
|
||||
(gnus-tmp-open-bracket
|
||||
(cond ((memq gnus-tmp-number sparse)
|
||||
(caadr gnus-tree-brackets))
|
||||
(dummy (caaddr gnus-tree-brackets))
|
||||
(adopted (car (nth 3 gnus-tree-brackets)))
|
||||
(t (caar gnus-tree-brackets))))
|
||||
(gnus-tmp-close-bracket
|
||||
(cond ((memq gnus-tmp-number sparse)
|
||||
(cdadr gnus-tree-brackets))
|
||||
(adopted (cdr (nth 3 gnus-tree-brackets)))
|
||||
(dummy
|
||||
(cdaddr gnus-tree-brackets))
|
||||
(t (cdar gnus-tree-brackets))))
|
||||
(buffer-read-only nil)
|
||||
beg end)
|
||||
(gnus-add-text-properties
|
||||
(setq beg (point))
|
||||
(setq end (progn (eval gnus-tree-line-format-spec) (point)))
|
||||
(list 'gnus-number gnus-tmp-number))
|
||||
(when (or t (gnus-visual-p 'tree-highlight 'highlight))
|
||||
(gnus-tree-highlight-node gnus-tmp-number beg end))))
|
||||
|
||||
(defun gnus-tree-highlight-node (article beg end)
|
||||
"Highlight current line according to `gnus-summary-highlight'."
|
||||
(let ((list gnus-summary-highlight)
|
||||
face)
|
||||
(save-excursion
|
||||
(set-buffer gnus-summary-buffer)
|
||||
(let* ((score (or (cdr (assq article gnus-newsgroup-scored))
|
||||
gnus-summary-default-score 0))
|
||||
(default gnus-summary-default-score)
|
||||
(mark (or (gnus-summary-article-mark article) gnus-unread-mark)))
|
||||
;; Eval the cars of the lists until we find a match.
|
||||
(while (and list
|
||||
(not (eval (caar list))))
|
||||
(setq list (cdr list)))))
|
||||
(unless (eq (setq face (cdar list)) (get-text-property beg 'face))
|
||||
(gnus-put-text-property
|
||||
beg end 'face
|
||||
(if (boundp face) (symbol-value face) face)))))
|
||||
|
||||
(defun gnus-tree-indent (level)
|
||||
(insert (make-string (1- (* (1+ gnus-tree-node-length) level)) ? )))
|
||||
|
||||
(defvar gnus-tmp-limit)
|
||||
(defvar gnus-tmp-sparse)
|
||||
(defvar gnus-tmp-indent)
|
||||
|
||||
(defun gnus-generate-tree (thread)
|
||||
"Generate a thread tree for THREAD."
|
||||
(save-excursion
|
||||
(set-buffer (gnus-get-tree-buffer))
|
||||
(let ((buffer-read-only nil)
|
||||
(gnus-tmp-indent 0))
|
||||
(erase-buffer)
|
||||
(funcall gnus-generate-tree-function thread 0)
|
||||
(gnus-set-mode-line 'tree)
|
||||
(goto-char (point-min))
|
||||
(gnus-tree-minimize)
|
||||
(gnus-tree-recenter)
|
||||
(let ((selected (selected-window)))
|
||||
(when (get-buffer-window (set-buffer gnus-tree-buffer) t)
|
||||
(select-window (get-buffer-window (set-buffer gnus-tree-buffer) t))
|
||||
(gnus-horizontal-recenter)
|
||||
(select-window selected))))))
|
||||
|
||||
(defun gnus-generate-horizontal-tree (thread level &optional dummyp adopted)
|
||||
"Generate a horizontal tree."
|
||||
(let* ((dummy (stringp (car thread)))
|
||||
(do (or dummy
|
||||
(memq (mail-header-number (car thread)) gnus-tmp-limit)))
|
||||
col beg)
|
||||
(if (not do)
|
||||
;; We don't want this article.
|
||||
(setq thread (cdr thread))
|
||||
(if (not (bolp))
|
||||
;; Not the first article on the line, so we insert a "-".
|
||||
(insert (car gnus-tree-parent-child-edges))
|
||||
;; If the level isn't zero, then we insert some indentation.
|
||||
(unless (zerop level)
|
||||
(gnus-tree-indent level)
|
||||
(insert (cadr gnus-tree-parent-child-edges))
|
||||
(setq col (- (setq beg (point)) (gnus-point-at-bol) 1))
|
||||
;; Draw "|" lines upwards.
|
||||
(while (progn
|
||||
(forward-line -1)
|
||||
(forward-char col)
|
||||
(= (following-char) ? ))
|
||||
(delete-char 1)
|
||||
(insert (caddr gnus-tree-parent-child-edges)))
|
||||
(goto-char beg)))
|
||||
(setq dummyp nil)
|
||||
;; Insert the article node.
|
||||
(gnus-tree-node-insert (pop thread) gnus-tmp-sparse adopted))
|
||||
(if (null thread)
|
||||
;; End of the thread, so we go to the next line.
|
||||
(unless (bolp)
|
||||
(insert "\n"))
|
||||
;; Recurse downwards in all children of this article.
|
||||
(while thread
|
||||
(gnus-generate-horizontal-tree
|
||||
(pop thread) (if do (1+ level) level)
|
||||
(or dummyp dummy) dummy)))))
|
||||
|
||||
(defsubst gnus-tree-indent-vertical ()
|
||||
(let ((len (- (* (1+ gnus-tree-node-length) gnus-tmp-indent)
|
||||
(- (point) (gnus-point-at-bol)))))
|
||||
(when (> len 0)
|
||||
(insert (make-string len ? )))))
|
||||
|
||||
(defsubst gnus-tree-forward-line (n)
|
||||
(while (>= (decf n) 0)
|
||||
(unless (zerop (forward-line 1))
|
||||
(end-of-line)
|
||||
(insert "\n")))
|
||||
(end-of-line))
|
||||
|
||||
(defun gnus-generate-vertical-tree (thread level &optional dummyp adopted)
|
||||
"Generate a vertical tree."
|
||||
(let* ((dummy (stringp (car thread)))
|
||||
(do (or dummy
|
||||
(and (car thread)
|
||||
(memq (mail-header-number (car thread))
|
||||
gnus-tmp-limit))))
|
||||
beg)
|
||||
(if (not do)
|
||||
;; We don't want this article.
|
||||
(setq thread (cdr thread))
|
||||
(if (not (save-excursion (beginning-of-line) (bobp)))
|
||||
;; Not the first article on the line, so we insert a "-".
|
||||
(progn
|
||||
(gnus-tree-indent-vertical)
|
||||
(insert (make-string (/ gnus-tree-node-length 2) ? ))
|
||||
(insert (caddr gnus-tree-parent-child-edges))
|
||||
(gnus-tree-forward-line 1))
|
||||
;; If the level isn't zero, then we insert some indentation.
|
||||
(unless (zerop gnus-tmp-indent)
|
||||
(gnus-tree-forward-line (1- (* 2 level)))
|
||||
(gnus-tree-indent-vertical)
|
||||
(delete-char -1)
|
||||
(insert (cadr gnus-tree-parent-child-edges))
|
||||
(setq beg (point))
|
||||
;; Draw "-" lines leftwards.
|
||||
(while (progn
|
||||
(unless (bolp)
|
||||
(forward-char -2))
|
||||
(= (following-char) ? ))
|
||||
(delete-char 1)
|
||||
(insert (car gnus-tree-parent-child-edges)))
|
||||
(goto-char beg)
|
||||
(gnus-tree-forward-line 1)))
|
||||
(setq dummyp nil)
|
||||
;; Insert the article node.
|
||||
(gnus-tree-indent-vertical)
|
||||
(gnus-tree-node-insert (pop thread) gnus-tmp-sparse adopted)
|
||||
(gnus-tree-forward-line 1))
|
||||
(if (null thread)
|
||||
;; End of the thread, so we go to the next line.
|
||||
(progn
|
||||
(goto-char (point-min))
|
||||
(end-of-line)
|
||||
(incf gnus-tmp-indent))
|
||||
;; Recurse downwards in all children of this article.
|
||||
(while thread
|
||||
(gnus-generate-vertical-tree
|
||||
(pop thread) (if do (1+ level) level)
|
||||
(or dummyp dummy) dummy)))))
|
||||
|
||||
;;; Interface functions.
|
||||
|
||||
(defun gnus-possibly-generate-tree (article &optional force)
|
||||
"Generate the thread tree for ARTICLE if it isn't displayed already."
|
||||
(when (save-excursion
|
||||
(set-buffer gnus-summary-buffer)
|
||||
(and gnus-use-trees
|
||||
gnus-show-threads
|
||||
(vectorp (gnus-summary-article-header article))))
|
||||
(save-excursion
|
||||
(let ((top (save-excursion
|
||||
(set-buffer gnus-summary-buffer)
|
||||
(gnus-cut-thread
|
||||
(gnus-remove-thread
|
||||
(mail-header-id
|
||||
(gnus-summary-article-header article))
|
||||
t))))
|
||||
(gnus-tmp-limit gnus-newsgroup-limit)
|
||||
(gnus-tmp-sparse gnus-newsgroup-sparse))
|
||||
(when (or force
|
||||
(not (eq top gnus-tree-displayed-thread)))
|
||||
(gnus-generate-tree top)
|
||||
(setq gnus-tree-displayed-thread top))))))
|
||||
|
||||
(defun gnus-tree-open (group)
|
||||
(gnus-get-tree-buffer))
|
||||
|
||||
(defun gnus-tree-close (group)
|
||||
;(gnus-kill-buffer gnus-tree-buffer)
|
||||
)
|
||||
|
||||
(defun gnus-highlight-selected-tree (article)
|
||||
"Highlight the selected article in the tree."
|
||||
(let ((buf (current-buffer))
|
||||
region)
|
||||
(set-buffer gnus-tree-buffer)
|
||||
(when (setq region (gnus-tree-article-region article))
|
||||
(when (or (not gnus-selected-tree-overlay)
|
||||
(gnus-extent-detached-p gnus-selected-tree-overlay))
|
||||
;; Create a new overlay.
|
||||
(gnus-overlay-put
|
||||
(setq gnus-selected-tree-overlay (gnus-make-overlay 1 2))
|
||||
'face gnus-selected-tree-face))
|
||||
;; Move the overlay to the article.
|
||||
(gnus-move-overlay
|
||||
gnus-selected-tree-overlay (goto-char (car region)) (cdr region))
|
||||
(gnus-tree-minimize)
|
||||
(gnus-tree-recenter)
|
||||
(let ((selected (selected-window)))
|
||||
(when (get-buffer-window (set-buffer gnus-tree-buffer) t)
|
||||
(select-window (get-buffer-window (set-buffer gnus-tree-buffer) t))
|
||||
(gnus-horizontal-recenter)
|
||||
(select-window selected))))
|
||||
;; If we remove this save-excursion, it updates the wrong mode lines?!?
|
||||
(save-excursion
|
||||
(set-buffer gnus-tree-buffer)
|
||||
(gnus-set-mode-line 'tree))
|
||||
(set-buffer buf)))
|
||||
|
||||
(defun gnus-tree-highlight-article (article face)
|
||||
(save-excursion
|
||||
(set-buffer (gnus-get-tree-buffer))
|
||||
(let (region)
|
||||
(when (setq region (gnus-tree-article-region article))
|
||||
(gnus-put-text-property (car region) (cdr region) 'face face)
|
||||
(set-window-point
|
||||
(get-buffer-window (current-buffer) t) (cdr region))))))
|
||||
|
||||
;;;
|
||||
;;; gnus-carpal
|
||||
;;;
|
||||
|
||||
(defvar gnus-carpal-group-buffer-buttons
|
||||
'(("next" . gnus-group-next-unread-group)
|
||||
("prev" . gnus-group-prev-unread-group)
|
||||
("read" . gnus-group-read-group)
|
||||
("select" . gnus-group-select-group)
|
||||
("catch-up" . gnus-group-catchup-current)
|
||||
("new-news" . gnus-group-get-new-news-this-group)
|
||||
("toggle-sub" . gnus-group-unsubscribe-current-group)
|
||||
("subscribe" . gnus-group-unsubscribe-group)
|
||||
("kill" . gnus-group-kill-group)
|
||||
("yank" . gnus-group-yank-group)
|
||||
("describe" . gnus-group-describe-group)
|
||||
"list"
|
||||
("subscribed" . gnus-group-list-groups)
|
||||
("all" . gnus-group-list-all-groups)
|
||||
("killed" . gnus-group-list-killed)
|
||||
("zombies" . gnus-group-list-zombies)
|
||||
("matching" . gnus-group-list-matching)
|
||||
("post" . gnus-group-post-news)
|
||||
("mail" . gnus-group-mail)
|
||||
("rescan" . gnus-group-get-new-news)
|
||||
("browse-foreign" . gnus-group-browse-foreign)
|
||||
("exit" . gnus-group-exit)))
|
||||
|
||||
(defvar gnus-carpal-summary-buffer-buttons
|
||||
'("mark"
|
||||
("read" . gnus-summary-mark-as-read-forward)
|
||||
("tick" . gnus-summary-tick-article-forward)
|
||||
("clear" . gnus-summary-clear-mark-forward)
|
||||
("expirable" . gnus-summary-mark-as-expirable)
|
||||
"move"
|
||||
("scroll" . gnus-summary-next-page)
|
||||
("next-unread" . gnus-summary-next-unread-article)
|
||||
("prev-unread" . gnus-summary-prev-unread-article)
|
||||
("first" . gnus-summary-first-unread-article)
|
||||
("best" . gnus-summary-best-unread-article)
|
||||
"article"
|
||||
("headers" . gnus-summary-toggle-header)
|
||||
("uudecode" . gnus-uu-decode-uu)
|
||||
("enter-digest" . gnus-summary-enter-digest-group)
|
||||
("fetch-parent" . gnus-summary-refer-parent-article)
|
||||
"mail"
|
||||
("move" . gnus-summary-move-article)
|
||||
("copy" . gnus-summary-copy-article)
|
||||
("respool" . gnus-summary-respool-article)
|
||||
"threads"
|
||||
("lower" . gnus-summary-lower-thread)
|
||||
("kill" . gnus-summary-kill-thread)
|
||||
"post"
|
||||
("post" . gnus-summary-post-news)
|
||||
("mail" . gnus-summary-mail)
|
||||
("followup" . gnus-summary-followup-with-original)
|
||||
("reply" . gnus-summary-reply-with-original)
|
||||
("cancel" . gnus-summary-cancel-article)
|
||||
"misc"
|
||||
("exit" . gnus-summary-exit)
|
||||
("fed-up" . gnus-summary-catchup-and-goto-next-group)))
|
||||
|
||||
(defvar gnus-carpal-server-buffer-buttons
|
||||
'(("add" . gnus-server-add-server)
|
||||
("browse" . gnus-server-browse-server)
|
||||
("list" . gnus-server-list-servers)
|
||||
("kill" . gnus-server-kill-server)
|
||||
("yank" . gnus-server-yank-server)
|
||||
("copy" . gnus-server-copy-server)
|
||||
("exit" . gnus-server-exit)))
|
||||
|
||||
(defvar gnus-carpal-browse-buffer-buttons
|
||||
'(("subscribe" . gnus-browse-unsubscribe-current-group)
|
||||
("exit" . gnus-browse-exit)))
|
||||
|
||||
(defvar gnus-carpal-group-buffer "*Carpal Group*")
|
||||
(defvar gnus-carpal-summary-buffer "*Carpal Summary*")
|
||||
(defvar gnus-carpal-server-buffer "*Carpal Server*")
|
||||
(defvar gnus-carpal-browse-buffer "*Carpal Browse*")
|
||||
|
||||
(defvar gnus-carpal-attached-buffer nil)
|
||||
|
||||
(defvar gnus-carpal-mode-hook nil
|
||||
"*Hook run in carpal mode buffers.")
|
||||
|
||||
(defvar gnus-carpal-button-face 'bold
|
||||
"*Face used on carpal buttons.")
|
||||
|
||||
(defvar gnus-carpal-header-face 'bold-italic
|
||||
"*Face used on carpal buffer headers.")
|
||||
|
||||
(defvar gnus-carpal-mode-map nil)
|
||||
(put 'gnus-carpal-mode 'mode-class 'special)
|
||||
|
||||
(if gnus-carpal-mode-map
|
||||
nil
|
||||
(setq gnus-carpal-mode-map (make-keymap))
|
||||
(suppress-keymap gnus-carpal-mode-map)
|
||||
(define-key gnus-carpal-mode-map " " 'gnus-carpal-select)
|
||||
(define-key gnus-carpal-mode-map "\r" 'gnus-carpal-select)
|
||||
(define-key gnus-carpal-mode-map gnus-mouse-2 'gnus-carpal-mouse-select))
|
||||
|
||||
(defun gnus-carpal-mode ()
|
||||
"Major mode for clicking buttons.
|
||||
|
||||
All normal editing commands are switched off.
|
||||
\\<gnus-carpal-mode-map>
|
||||
The following commands are available:
|
||||
|
||||
\\{gnus-carpal-mode-map}"
|
||||
(interactive)
|
||||
(kill-all-local-variables)
|
||||
(setq mode-line-modified "-- ")
|
||||
(setq major-mode 'gnus-carpal-mode)
|
||||
(setq mode-name "Gnus Carpal")
|
||||
(setq mode-line-process nil)
|
||||
(use-local-map gnus-carpal-mode-map)
|
||||
(buffer-disable-undo (current-buffer))
|
||||
(setq buffer-read-only t)
|
||||
(make-local-variable 'gnus-carpal-attached-buffer)
|
||||
(run-hooks 'gnus-carpal-mode-hook))
|
||||
|
||||
(defun gnus-carpal-setup-buffer (type)
|
||||
(let ((buffer (symbol-value (intern (format "gnus-carpal-%s-buffer" type)))))
|
||||
(if (get-buffer buffer)
|
||||
()
|
||||
(save-excursion
|
||||
(set-buffer (get-buffer-create buffer))
|
||||
(gnus-carpal-mode)
|
||||
(setq gnus-carpal-attached-buffer
|
||||
(intern (format "gnus-%s-buffer" type)))
|
||||
(gnus-add-current-to-buffer-list)
|
||||
(let ((buttons (symbol-value
|
||||
(intern (format "gnus-carpal-%s-buffer-buttons"
|
||||
type))))
|
||||
(buffer-read-only nil)
|
||||
button)
|
||||
(while buttons
|
||||
(setq button (car buttons)
|
||||
buttons (cdr buttons))
|
||||
(if (stringp button)
|
||||
(gnus-set-text-properties
|
||||
(point)
|
||||
(prog2 (insert button) (point) (insert " "))
|
||||
(list 'face gnus-carpal-header-face))
|
||||
(gnus-set-text-properties
|
||||
(point)
|
||||
(prog2 (insert (car button)) (point) (insert " "))
|
||||
(list 'gnus-callback (cdr button)
|
||||
'face gnus-carpal-button-face
|
||||
gnus-mouse-face-prop 'highlight))))
|
||||
(let ((fill-column (- (window-width) 2)))
|
||||
(fill-region (point-min) (point-max)))
|
||||
(set-window-point (get-buffer-window (current-buffer))
|
||||
(point-min)))))))
|
||||
|
||||
(defun gnus-carpal-select ()
|
||||
"Select the button under point."
|
||||
(interactive)
|
||||
(let ((func (get-text-property (point) 'gnus-callback)))
|
||||
(if (null func)
|
||||
()
|
||||
(pop-to-buffer (symbol-value gnus-carpal-attached-buffer))
|
||||
(call-interactively func))))
|
||||
|
||||
(defun gnus-carpal-mouse-select (event)
|
||||
"Select the button under the mouse pointer."
|
||||
(interactive "e")
|
||||
(mouse-set-point event)
|
||||
(gnus-carpal-select))
|
||||
|
||||
;;; Allow redefinition of functions.
|
||||
(gnus-ems-redefine)
|
||||
|
||||
(provide 'gnus-salt)
|
||||
|
||||
;;; gnus-salt.el ends here
|
||||
2761
lisp/gnus/gnus-score.el
Normal file
2761
lisp/gnus/gnus-score.el
Normal file
File diff suppressed because it is too large
Load diff
217
lisp/gnus/gnus-setup.el
Normal file
217
lisp/gnus/gnus-setup.el
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
;;; gnus-setup.el --- Initialization & Setup for Gnus 5
|
||||
;; Copyright (C) 1995, 96 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Steven L. Baur <steve@miranova.com>
|
||||
;; Keywords: news
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
;; My head is starting to spin with all the different mail/news packages.
|
||||
;; Stop The Madness!
|
||||
|
||||
;; Given that Emacs Lisp byte codes may be diverging, it is probably best
|
||||
;; not to byte compile this, and just arrange to have the .el loaded out
|
||||
;; of .emacs.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'cl)
|
||||
|
||||
(defvar running-xemacs (string-match "XEmacs\\|Lucid" emacs-version))
|
||||
|
||||
(defvar gnus-use-installed-gnus t
|
||||
"*If non-nil Use installed version of Gnus.")
|
||||
|
||||
(defvar gnus-use-installed-tm running-xemacs
|
||||
"*If non-nil use installed version of tm.")
|
||||
|
||||
(defvar gnus-use-installed-mailcrypt running-xemacs
|
||||
"*If non-nil use installed version of mailcrypt.")
|
||||
|
||||
(defvar gnus-emacs-lisp-directory (if running-xemacs
|
||||
"/usr/local/lib/xemacs/"
|
||||
"/usr/local/share/emacs/")
|
||||
"Directory where Emacs site lisp is located.")
|
||||
|
||||
(defvar gnus-gnus-lisp-directory (concat gnus-emacs-lisp-directory
|
||||
"gnus-5.0.15/lisp/")
|
||||
"Directory where Gnus Emacs lisp is found.")
|
||||
|
||||
(defvar gnus-tm-lisp-directory (concat gnus-emacs-lisp-directory
|
||||
"site-lisp/")
|
||||
"Directory where TM Emacs lisp is found.")
|
||||
|
||||
(defvar gnus-mailcrypt-lisp-directory (concat gnus-emacs-lisp-directory
|
||||
"site-lisp/mailcrypt-3.4/")
|
||||
"Directory where Mailcrypt Emacs Lisp is found.")
|
||||
|
||||
(defvar gnus-bbdb-lisp-directory (concat gnus-emacs-lisp-directory
|
||||
"site-lisp/bbdb-1.51/")
|
||||
"Directory where Big Brother Database is found.")
|
||||
|
||||
(defvar gnus-use-tm running-xemacs
|
||||
"Set this if you want MIME support for Gnus")
|
||||
(defvar gnus-use-mhe nil
|
||||
"Set this if you want to use MH-E for mail reading")
|
||||
(defvar gnus-use-rmail nil
|
||||
"Set this if you want to use RMAIL for mail reading")
|
||||
(defvar gnus-use-sendmail t
|
||||
"Set this if you want to use SENDMAIL for mail reading")
|
||||
(defvar gnus-use-vm nil
|
||||
"Set this if you want to use the VM package for mail reading")
|
||||
(defvar gnus-use-sc nil
|
||||
"Set this if you want to use Supercite")
|
||||
(defvar gnus-use-mailcrypt t
|
||||
"Set this if you want to use Mailcrypt for dealing with PGP messages")
|
||||
(defvar gnus-use-bbdb nil
|
||||
"Set this if you want to use the Big Brother DataBase")
|
||||
|
||||
(when (and (not gnus-use-installed-gnus)
|
||||
(null (member gnus-gnus-lisp-directory load-path)))
|
||||
(push gnus-gnus-lisp-directory load-path))
|
||||
|
||||
;;; We can't do this until we know where Gnus is.
|
||||
(require 'message)
|
||||
|
||||
;;; Tools for MIME by
|
||||
;;; UMEDA Masanobu <umerin@mse.kyutech.ac.jp>
|
||||
;;; MORIOKA Tomohiko <morioka@jaist.ac.jp>
|
||||
|
||||
(when gnus-use-tm
|
||||
(when (and (not gnus-use-installed-tm)
|
||||
(null (member gnus-tm-lisp-directory load-path)))
|
||||
(setq load-path (cons gnus-tm-lisp-directory load-path)))
|
||||
;; tm may or may not be dumped with XEmacs. In Sunpro it is, otherwise
|
||||
;; it isn't.
|
||||
(unless (featurep 'mime-setup)
|
||||
(load "mime-setup")))
|
||||
|
||||
;;; Mailcrypt by
|
||||
;;; Jin Choi <jin@atype.com>
|
||||
;;; Patrick LoPresti <patl@lcs.mit.edu>
|
||||
|
||||
(when gnus-use-mailcrypt
|
||||
(when (and (not gnus-use-installed-mailcrypt)
|
||||
(null (member gnus-mailcrypt-lisp-directory load-path)))
|
||||
(setq load-path (cons gnus-mailcrypt-lisp-directory load-path)))
|
||||
(autoload 'mc-install-write-mode "mailcrypt" nil t)
|
||||
(autoload 'mc-install-read-mode "mailcrypt" nil t)
|
||||
(add-hook 'message-mode-hook 'mc-install-write-mode)
|
||||
(add-hook 'gnus-summary-mode-hook 'mc-install-read-mode)
|
||||
(when gnus-use-mhe
|
||||
(add-hook 'mh-folder-mode-hook 'mc-install-read-mode)
|
||||
(add-hook 'mh-letter-mode-hook 'mc-install-write-mode)))
|
||||
|
||||
;;; BBDB by
|
||||
;;; Jamie Zawinski <jwz@lucid.com>
|
||||
|
||||
(when gnus-use-bbdb
|
||||
;; bbdb will never be installed with emacs.
|
||||
(when (null (member gnus-bbdb-lisp-directory load-path))
|
||||
(setq load-path (cons gnus-bbdb-lisp-directory load-path)))
|
||||
(autoload 'bbdb "bbdb-com"
|
||||
"Insidious Big Brother Database" t)
|
||||
(autoload 'bbdb-name "bbdb-com"
|
||||
"Insidious Big Brother Database" t)
|
||||
(autoload 'bbdb-company "bbdb-com"
|
||||
"Insidious Big Brother Database" t)
|
||||
(autoload 'bbdb-net "bbdb-com"
|
||||
"Insidious Big Brother Database" t)
|
||||
(autoload 'bbdb-notes "bbdb-com"
|
||||
"Insidious Big Brother Database" t)
|
||||
|
||||
(when gnus-use-vm
|
||||
(autoload 'bbdb-insinuate-vm "bbdb-vm"
|
||||
"Hook BBDB into VM" t))
|
||||
|
||||
(when gnus-use-rmail
|
||||
(autoload 'bbdb-insinuate-rmail "bbdb-rmail"
|
||||
"Hook BBDB into RMAIL" t)
|
||||
(add-hook 'rmail-mode-hook 'bbdb-insinuate-rmail))
|
||||
|
||||
(when gnus-use-mhe
|
||||
(autoload 'bbdb-insinuate-mh "bbdb-mh"
|
||||
"Hook BBDB into MH-E" t)
|
||||
(add-hook 'mh-folder-mode-hook 'bbdb-insinuate-mh))
|
||||
|
||||
(autoload 'bbdb-insinuate-gnus "bbdb-gnus"
|
||||
"Hook BBDB into Gnus" t)
|
||||
(add-hook 'gnus-startup-hook 'bbdb-insinuate-gnus)
|
||||
|
||||
(when gnus-use-sendmail
|
||||
(autoload 'bbdb-insinuate-sendmail "bbdb"
|
||||
"Insidious Big Brother Database" t)
|
||||
(add-hook 'mail-setup-hook 'bbdb-insinuate-sendmail)
|
||||
(add-hook 'message-setup-hook 'bbdb-insinuate-sendmail)))
|
||||
|
||||
(when gnus-use-sc
|
||||
(add-hook 'mail-citation-hook 'sc-cite-original)
|
||||
(setq message-cite-function 'sc-cite-original)
|
||||
(autoload 'sc-cite-original "supercite"))
|
||||
|
||||
;;;### (autoloads (gnus gnus-slave gnus-no-server) "gnus" "lisp/gnus.el" (12473 2137))
|
||||
;;; Generated autoloads from lisp/gnus.el
|
||||
|
||||
;; Don't redo this if autoloads already exist
|
||||
(unless (fboundp 'gnus)
|
||||
(autoload 'gnus-slave-no-server "gnus" "\
|
||||
Read network news as a slave without connecting to local server." t nil)
|
||||
|
||||
(autoload 'gnus-no-server "gnus" "\
|
||||
Read network news.
|
||||
If ARG is a positive number, Gnus will use that as the
|
||||
startup level. If ARG is nil, Gnus will be started at level 2.
|
||||
If ARG is non-nil and not a positive number, Gnus will
|
||||
prompt the user for the name of an NNTP server to use.
|
||||
As opposed to `gnus', this command will not connect to the local server." t nil)
|
||||
|
||||
(autoload 'gnus-slave "gnus" "\
|
||||
Read news as a slave." t nil)
|
||||
|
||||
(autoload 'gnus "gnus" "\
|
||||
Read network news.
|
||||
If ARG is non-nil and a positive number, Gnus will use that as the
|
||||
startup level. If ARG is non-nil and not a positive number, Gnus will
|
||||
prompt the user for the name of an NNTP server to use." t nil)
|
||||
|
||||
;;;***
|
||||
|
||||
;;; These have moved out of gnus.el into other files.
|
||||
;;; FIX FIX FIX: should other things be in gnus-setup? or these not in it?
|
||||
(autoload 'gnus-update-format "gnus-spec" "\
|
||||
Update the format specification near point." t nil)
|
||||
|
||||
(autoload 'gnus-fetch-group "gnus-group" "\
|
||||
Start Gnus if necessary and enter GROUP.
|
||||
Returns whether the fetching was successful or not." t nil)
|
||||
|
||||
(defalias 'gnus-batch-kill 'gnus-batch-score)
|
||||
|
||||
(autoload 'gnus-batch-score "gnus-kill" "\
|
||||
Run batched scoring.
|
||||
Usage: emacs -batch -l gnus -f gnus-batch-score <newsgroups> ...
|
||||
Newsgroups is a list of strings in Bnews format. If you want to score
|
||||
the comp hierarchy, you'd say \"comp.all\". If you would not like to
|
||||
score the alt hierarchy, you'd say \"!alt.all\"." t nil))
|
||||
|
||||
(provide 'gnus-setup)
|
||||
|
||||
(run-hooks 'gnus-setup-load-hook)
|
||||
|
||||
;;; gnus-setup.el ends here
|
||||
565
lisp/gnus/gnus-soup.el
Normal file
565
lisp/gnus/gnus-soup.el
Normal file
|
|
@ -0,0 +1,565 @@
|
|||
;;; gnus-soup.el --- SOUP packet writing support for Gnus
|
||||
;; Copyright (C) 1995,96,97 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Per Abrahamsen <abraham@iesd.auc.dk>
|
||||
;; Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
|
||||
;; Keywords: news, mail
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'gnus)
|
||||
(require 'gnus-art)
|
||||
(require 'message)
|
||||
(require 'gnus-start)
|
||||
(require 'gnus-range)
|
||||
|
||||
;;; User Variables:
|
||||
|
||||
(defvar gnus-soup-directory (nnheader-concat gnus-home-directory "SoupBrew/")
|
||||
"*Directory containing an unpacked SOUP packet.")
|
||||
|
||||
(defvar gnus-soup-replies-directory
|
||||
(nnheader-concat gnus-soup-directory "SoupReplies/")
|
||||
"*Directory where Gnus will do processing of replies.")
|
||||
|
||||
(defvar gnus-soup-prefix-file "gnus-prefix"
|
||||
"*Name of the file where Gnus stores the last used prefix.")
|
||||
|
||||
(defvar gnus-soup-packer "tar cf - %s | gzip > $HOME/Soupout%d.tgz"
|
||||
"Format string command for packing a SOUP packet.
|
||||
The SOUP files will be inserted where the %s is in the string.
|
||||
This string MUST contain both %s and %d. The file number will be
|
||||
inserted where %d appears.")
|
||||
|
||||
(defvar gnus-soup-unpacker "gunzip -c %s | tar xvf -"
|
||||
"*Format string command for unpacking a SOUP packet.
|
||||
The SOUP packet file name will be inserted at the %s.")
|
||||
|
||||
(defvar gnus-soup-packet-directory gnus-home-directory
|
||||
"*Where gnus-soup will look for REPLIES packets.")
|
||||
|
||||
(defvar gnus-soup-packet-regexp "Soupin"
|
||||
"*Regular expression matching SOUP REPLIES packets in `gnus-soup-packet-directory'.")
|
||||
|
||||
(defvar gnus-soup-ignored-headers "^Xref:"
|
||||
"*Regexp to match headers to be removed when brewing SOUP packets.")
|
||||
|
||||
;;; Internal Variables:
|
||||
|
||||
(defvar gnus-soup-encoding-type ?n
|
||||
"*Soup encoding type.
|
||||
`n' is news format, `m' is Unix mbox format, and `M' is MMDF mailbox
|
||||
format.")
|
||||
|
||||
(defvar gnus-soup-index-type ?c
|
||||
"*Soup index type.
|
||||
`n' means no index file and `c' means standard Cnews overview
|
||||
format.")
|
||||
|
||||
(defvar gnus-soup-areas nil)
|
||||
(defvar gnus-soup-last-prefix nil)
|
||||
(defvar gnus-soup-prev-prefix nil)
|
||||
(defvar gnus-soup-buffers nil)
|
||||
|
||||
;;; Access macros:
|
||||
|
||||
(defmacro gnus-soup-area-prefix (area)
|
||||
`(aref ,area 0))
|
||||
(defmacro gnus-soup-set-area-prefix (area prefix)
|
||||
`(aset ,area 0 ,prefix))
|
||||
(defmacro gnus-soup-area-name (area)
|
||||
`(aref ,area 1))
|
||||
(defmacro gnus-soup-area-encoding (area)
|
||||
`(aref ,area 2))
|
||||
(defmacro gnus-soup-area-description (area)
|
||||
`(aref ,area 3))
|
||||
(defmacro gnus-soup-area-number (area)
|
||||
`(aref ,area 4))
|
||||
(defmacro gnus-soup-area-set-number (area value)
|
||||
`(aset ,area 4 ,value))
|
||||
|
||||
(defmacro gnus-soup-encoding-format (encoding)
|
||||
`(aref ,encoding 0))
|
||||
(defmacro gnus-soup-encoding-index (encoding)
|
||||
`(aref ,encoding 1))
|
||||
(defmacro gnus-soup-encoding-kind (encoding)
|
||||
`(aref ,encoding 2))
|
||||
|
||||
(defmacro gnus-soup-reply-prefix (reply)
|
||||
`(aref ,reply 0))
|
||||
(defmacro gnus-soup-reply-kind (reply)
|
||||
`(aref ,reply 1))
|
||||
(defmacro gnus-soup-reply-encoding (reply)
|
||||
`(aref ,reply 2))
|
||||
|
||||
;;; Commands:
|
||||
|
||||
(defun gnus-soup-send-replies ()
|
||||
"Unpack and send all replies in the reply packet."
|
||||
(interactive)
|
||||
(let ((packets (directory-files
|
||||
gnus-soup-packet-directory t gnus-soup-packet-regexp)))
|
||||
(while packets
|
||||
(when (gnus-soup-send-packet (car packets))
|
||||
(delete-file (car packets)))
|
||||
(setq packets (cdr packets)))))
|
||||
|
||||
(defun gnus-soup-add-article (n)
|
||||
"Add the current article to SOUP packet.
|
||||
If N is a positive number, add the N next articles.
|
||||
If N is a negative number, add the N previous articles.
|
||||
If N is nil and any articles have been marked with the process mark,
|
||||
move those articles instead."
|
||||
(interactive "P")
|
||||
(gnus-set-global-variables)
|
||||
(let* ((articles (gnus-summary-work-articles n))
|
||||
(tmp-buf (get-buffer-create "*soup work*"))
|
||||
(area (gnus-soup-area gnus-newsgroup-name))
|
||||
(prefix (gnus-soup-area-prefix area))
|
||||
headers)
|
||||
(buffer-disable-undo tmp-buf)
|
||||
(save-excursion
|
||||
(while articles
|
||||
;; Find the header of the article.
|
||||
(set-buffer gnus-summary-buffer)
|
||||
(when (setq headers (gnus-summary-article-header (car articles)))
|
||||
;; Put the article in a buffer.
|
||||
(set-buffer tmp-buf)
|
||||
(when (gnus-request-article-this-buffer
|
||||
(car articles) gnus-newsgroup-name)
|
||||
(save-restriction
|
||||
(message-narrow-to-head)
|
||||
(message-remove-header gnus-soup-ignored-headers t))
|
||||
(gnus-soup-store gnus-soup-directory prefix headers
|
||||
gnus-soup-encoding-type
|
||||
gnus-soup-index-type)
|
||||
(gnus-soup-area-set-number
|
||||
area (1+ (or (gnus-soup-area-number area) 0)))))
|
||||
;; Mark article as read.
|
||||
(set-buffer gnus-summary-buffer)
|
||||
(gnus-summary-remove-process-mark (car articles))
|
||||
(gnus-summary-mark-as-read (car articles) gnus-souped-mark)
|
||||
(setq articles (cdr articles)))
|
||||
(kill-buffer tmp-buf))
|
||||
(gnus-soup-save-areas)))
|
||||
|
||||
(defun gnus-soup-pack-packet ()
|
||||
"Make a SOUP packet from the SOUP areas."
|
||||
(interactive)
|
||||
(gnus-soup-read-areas)
|
||||
(unless (file-exists-p gnus-soup-directory)
|
||||
(message "No such directory: %s" gnus-soup-directory))
|
||||
(when (null (directory-files gnus-soup-directory nil "\\.MSG$"))
|
||||
(message "No files to pack."))
|
||||
(gnus-soup-pack gnus-soup-directory gnus-soup-packer))
|
||||
|
||||
(defun gnus-group-brew-soup (n)
|
||||
"Make a soup packet from the current group.
|
||||
Uses the process/prefix convention."
|
||||
(interactive "P")
|
||||
(let ((groups (gnus-group-process-prefix n)))
|
||||
(while groups
|
||||
(gnus-group-remove-mark (car groups))
|
||||
(gnus-soup-group-brew (car groups) t)
|
||||
(setq groups (cdr groups)))
|
||||
(gnus-soup-save-areas)))
|
||||
|
||||
(defun gnus-brew-soup (&optional level)
|
||||
"Go through all groups on LEVEL or less and make a soup packet."
|
||||
(interactive "P")
|
||||
(let ((level (or level gnus-level-subscribed))
|
||||
(newsrc (cdr gnus-newsrc-alist)))
|
||||
(while newsrc
|
||||
(when (<= (nth 1 (car newsrc)) level)
|
||||
(gnus-soup-group-brew (caar newsrc) t))
|
||||
(setq newsrc (cdr newsrc)))
|
||||
(gnus-soup-save-areas)))
|
||||
|
||||
;;;###autoload
|
||||
(defun gnus-batch-brew-soup ()
|
||||
"Brew a SOUP packet from groups mention on the command line.
|
||||
Will use the remaining command line arguments as regular expressions
|
||||
for matching on group names.
|
||||
|
||||
For instance, if you want to brew on all the nnml groups, as well as
|
||||
groups with \"emacs\" in the name, you could say something like:
|
||||
|
||||
$ emacs -batch -f gnus-batch-brew-soup ^nnml \".*emacs.*\""
|
||||
(interactive)
|
||||
nil)
|
||||
|
||||
;;; Internal Functions:
|
||||
|
||||
;; Store the current buffer.
|
||||
(defun gnus-soup-store (directory prefix headers format index)
|
||||
;; Create the directory, if needed.
|
||||
(gnus-make-directory directory)
|
||||
(let* ((msg-buf (nnheader-find-file-noselect
|
||||
(concat directory prefix ".MSG")))
|
||||
(idx-buf (if (= index ?n)
|
||||
nil
|
||||
(nnheader-find-file-noselect
|
||||
(concat directory prefix ".IDX"))))
|
||||
(article-buf (current-buffer))
|
||||
from head-line beg type)
|
||||
(setq gnus-soup-buffers (cons msg-buf (delq msg-buf gnus-soup-buffers)))
|
||||
(buffer-disable-undo msg-buf)
|
||||
(when idx-buf
|
||||
(push idx-buf gnus-soup-buffers)
|
||||
(buffer-disable-undo idx-buf))
|
||||
(save-excursion
|
||||
;; Make sure the last char in the buffer is a newline.
|
||||
(goto-char (point-max))
|
||||
(unless (= (current-column) 0)
|
||||
(insert "\n"))
|
||||
;; Find the "from".
|
||||
(goto-char (point-min))
|
||||
(setq from
|
||||
(gnus-mail-strip-quoted-names
|
||||
(or (mail-fetch-field "from")
|
||||
(mail-fetch-field "really-from")
|
||||
(mail-fetch-field "sender"))))
|
||||
(goto-char (point-min))
|
||||
;; Depending on what encoding is supposed to be used, we make
|
||||
;; a soup header.
|
||||
(setq head-line
|
||||
(cond
|
||||
((= gnus-soup-encoding-type ?n)
|
||||
(format "#! rnews %d\n" (buffer-size)))
|
||||
((= gnus-soup-encoding-type ?m)
|
||||
(while (search-forward "\nFrom " nil t)
|
||||
(replace-match "\n>From " t t))
|
||||
(concat "From " (or from "unknown")
|
||||
" " (current-time-string) "\n"))
|
||||
((= gnus-soup-encoding-type ?M)
|
||||
"\^a\^a\^a\^a\n")
|
||||
(t (error "Unsupported type: %c" gnus-soup-encoding-type))))
|
||||
;; Insert the soup header and the article in the MSG buf.
|
||||
(set-buffer msg-buf)
|
||||
(goto-char (point-max))
|
||||
(insert head-line)
|
||||
(setq beg (point))
|
||||
(insert-buffer-substring article-buf)
|
||||
;; Insert the index in the IDX buf.
|
||||
(cond ((= index ?c)
|
||||
(set-buffer idx-buf)
|
||||
(gnus-soup-insert-idx beg headers))
|
||||
((/= index ?n)
|
||||
(error "Unknown index type: %c" type)))
|
||||
;; Return the MSG buf.
|
||||
msg-buf)))
|
||||
|
||||
(defun gnus-soup-group-brew (group &optional not-all)
|
||||
"Enter GROUP and add all articles to a SOUP package.
|
||||
If NOT-ALL, don't pack ticked articles."
|
||||
(let ((gnus-expert-user t)
|
||||
(gnus-large-newsgroup nil)
|
||||
(entry (gnus-gethash group gnus-newsrc-hashtb)))
|
||||
(when (or (null entry)
|
||||
(eq (car entry) t)
|
||||
(and (car entry)
|
||||
(> (car entry) 0))
|
||||
(and (not not-all)
|
||||
(gnus-range-length (cdr (assq 'tick (gnus-info-marks
|
||||
(nth 2 entry)))))))
|
||||
(when (gnus-summary-read-group group nil t)
|
||||
(setq gnus-newsgroup-processable
|
||||
(reverse
|
||||
(if (not not-all)
|
||||
(append gnus-newsgroup-marked gnus-newsgroup-unreads)
|
||||
gnus-newsgroup-unreads)))
|
||||
(gnus-soup-add-article nil)
|
||||
(gnus-summary-exit)))))
|
||||
|
||||
(defun gnus-soup-insert-idx (offset header)
|
||||
;; [number subject from date id references chars lines xref]
|
||||
(goto-char (point-max))
|
||||
(insert
|
||||
(format "%d\t%s\t%s\t%s\t%s\t%s\t%d\t%s\t\t\n"
|
||||
offset
|
||||
(or (mail-header-subject header) "(none)")
|
||||
(or (mail-header-from header) "(nobody)")
|
||||
(or (mail-header-date header) "")
|
||||
(or (mail-header-id header)
|
||||
(concat "soup-dummy-id-"
|
||||
(mapconcat
|
||||
(lambda (time) (int-to-string time))
|
||||
(current-time) "-")))
|
||||
(or (mail-header-references header) "")
|
||||
(or (mail-header-chars header) 0)
|
||||
(or (mail-header-lines header) "0"))))
|
||||
|
||||
(defun gnus-soup-save-areas ()
|
||||
(gnus-soup-write-areas)
|
||||
(save-excursion
|
||||
(let (buf)
|
||||
(while gnus-soup-buffers
|
||||
(setq buf (car gnus-soup-buffers)
|
||||
gnus-soup-buffers (cdr gnus-soup-buffers))
|
||||
(if (not (buffer-name buf))
|
||||
()
|
||||
(set-buffer buf)
|
||||
(when (buffer-modified-p)
|
||||
(save-buffer))
|
||||
(kill-buffer (current-buffer)))))
|
||||
(gnus-soup-write-prefixes)))
|
||||
|
||||
(defun gnus-soup-write-prefixes ()
|
||||
(let ((prefixes gnus-soup-last-prefix)
|
||||
prefix)
|
||||
(save-excursion
|
||||
(gnus-set-work-buffer)
|
||||
(while (setq prefix (pop prefixes))
|
||||
(erase-buffer)
|
||||
(insert (format "(setq gnus-soup-prev-prefix %d)\n" (cdr prefix)))
|
||||
(gnus-write-buffer (concat (car prefix) gnus-soup-prefix-file))))))
|
||||
|
||||
(defun gnus-soup-pack (dir packer)
|
||||
(let* ((files (mapconcat 'identity
|
||||
'("AREAS" "*.MSG" "*.IDX" "INFO"
|
||||
"LIST" "REPLIES" "COMMANDS" "ERRORS")
|
||||
" "))
|
||||
(packer (if (< (string-match "%s" packer)
|
||||
(string-match "%d" packer))
|
||||
(format packer files
|
||||
(string-to-int (gnus-soup-unique-prefix dir)))
|
||||
(format packer
|
||||
(string-to-int (gnus-soup-unique-prefix dir))
|
||||
files)))
|
||||
(dir (expand-file-name dir)))
|
||||
(gnus-make-directory dir)
|
||||
(setq gnus-soup-areas nil)
|
||||
(gnus-message 4 "Packing %s..." packer)
|
||||
(if (zerop (call-process shell-file-name
|
||||
nil nil nil shell-command-switch
|
||||
(concat "cd " dir " ; " packer)))
|
||||
(progn
|
||||
(call-process shell-file-name nil nil nil shell-command-switch
|
||||
(concat "cd " dir " ; rm " files))
|
||||
(gnus-message 4 "Packing...done" packer))
|
||||
(error "Couldn't pack packet."))))
|
||||
|
||||
(defun gnus-soup-parse-areas (file)
|
||||
"Parse soup area file FILE.
|
||||
The result is a of vectors, each containing one entry from the AREA file.
|
||||
The vector contain five strings,
|
||||
[prefix name encoding description number]
|
||||
though the two last may be nil if they are missing."
|
||||
(let (areas)
|
||||
(save-excursion
|
||||
(set-buffer (nnheader-find-file-noselect file 'force))
|
||||
(buffer-disable-undo (current-buffer))
|
||||
(goto-char (point-min))
|
||||
(while (not (eobp))
|
||||
(push (vector (gnus-soup-field)
|
||||
(gnus-soup-field)
|
||||
(gnus-soup-field)
|
||||
(and (eq (preceding-char) ?\t)
|
||||
(gnus-soup-field))
|
||||
(and (eq (preceding-char) ?\t)
|
||||
(string-to-int (gnus-soup-field))))
|
||||
areas)
|
||||
(when (eq (preceding-char) ?\t)
|
||||
(beginning-of-line 2)))
|
||||
(kill-buffer (current-buffer)))
|
||||
areas))
|
||||
|
||||
(defun gnus-soup-parse-replies (file)
|
||||
"Parse soup REPLIES file FILE.
|
||||
The result is a of vectors, each containing one entry from the REPLIES
|
||||
file. The vector contain three strings, [prefix name encoding]."
|
||||
(let (replies)
|
||||
(save-excursion
|
||||
(set-buffer (nnheader-find-file-noselect file))
|
||||
(buffer-disable-undo (current-buffer))
|
||||
(goto-char (point-min))
|
||||
(while (not (eobp))
|
||||
(push (vector (gnus-soup-field) (gnus-soup-field)
|
||||
(gnus-soup-field))
|
||||
replies)
|
||||
(when (eq (preceding-char) ?\t)
|
||||
(beginning-of-line 2)))
|
||||
(kill-buffer (current-buffer)))
|
||||
replies))
|
||||
|
||||
(defun gnus-soup-field ()
|
||||
(prog1
|
||||
(buffer-substring (point) (progn (skip-chars-forward "^\t\n") (point)))
|
||||
(forward-char 1)))
|
||||
|
||||
(defun gnus-soup-read-areas ()
|
||||
(or gnus-soup-areas
|
||||
(setq gnus-soup-areas
|
||||
(gnus-soup-parse-areas (concat gnus-soup-directory "AREAS")))))
|
||||
|
||||
(defun gnus-soup-write-areas ()
|
||||
"Write the AREAS file."
|
||||
(interactive)
|
||||
(when gnus-soup-areas
|
||||
(nnheader-temp-write (concat gnus-soup-directory "AREAS")
|
||||
(let ((areas gnus-soup-areas)
|
||||
area)
|
||||
(while (setq area (pop areas))
|
||||
(insert
|
||||
(format
|
||||
"%s\t%s\t%s%s\n"
|
||||
(gnus-soup-area-prefix area)
|
||||
(gnus-soup-area-name area)
|
||||
(gnus-soup-area-encoding area)
|
||||
(if (or (gnus-soup-area-description area)
|
||||
(gnus-soup-area-number area))
|
||||
(concat "\t" (or (gnus-soup-area-description
|
||||
area) "")
|
||||
(if (gnus-soup-area-number area)
|
||||
(concat "\t" (int-to-string
|
||||
(gnus-soup-area-number area)))
|
||||
"")) ""))))))))
|
||||
|
||||
(defun gnus-soup-write-replies (dir areas)
|
||||
"Write a REPLIES file in DIR containing AREAS."
|
||||
(nnheader-temp-write (concat dir "REPLIES")
|
||||
(let (area)
|
||||
(while (setq area (pop areas))
|
||||
(insert (format "%s\t%s\t%s\n"
|
||||
(gnus-soup-reply-prefix area)
|
||||
(gnus-soup-reply-kind area)
|
||||
(gnus-soup-reply-encoding area)))))))
|
||||
|
||||
(defun gnus-soup-area (group)
|
||||
(gnus-soup-read-areas)
|
||||
(let ((areas gnus-soup-areas)
|
||||
(real-group (gnus-group-real-name group))
|
||||
area result)
|
||||
(while areas
|
||||
(setq area (car areas)
|
||||
areas (cdr areas))
|
||||
(when (equal (gnus-soup-area-name area) real-group)
|
||||
(setq result area)))
|
||||
(unless result
|
||||
(setq result
|
||||
(vector (gnus-soup-unique-prefix)
|
||||
real-group
|
||||
(format "%c%c%c"
|
||||
gnus-soup-encoding-type
|
||||
gnus-soup-index-type
|
||||
(if (gnus-member-of-valid 'mail group) ?m ?n))
|
||||
nil nil)
|
||||
gnus-soup-areas (cons result gnus-soup-areas)))
|
||||
result))
|
||||
|
||||
(defun gnus-soup-unique-prefix (&optional dir)
|
||||
(let* ((dir (file-name-as-directory (or dir gnus-soup-directory)))
|
||||
(entry (assoc dir gnus-soup-last-prefix))
|
||||
gnus-soup-prev-prefix)
|
||||
(if entry
|
||||
()
|
||||
(when (file-exists-p (concat dir gnus-soup-prefix-file))
|
||||
(ignore-errors
|
||||
(load (concat dir gnus-soup-prefix-file) nil t t)))
|
||||
(push (setq entry (cons dir (or gnus-soup-prev-prefix 0)))
|
||||
gnus-soup-last-prefix))
|
||||
(setcdr entry (1+ (cdr entry)))
|
||||
(gnus-soup-write-prefixes)
|
||||
(int-to-string (cdr entry))))
|
||||
|
||||
(defun gnus-soup-unpack-packet (dir unpacker packet)
|
||||
"Unpack PACKET into DIR using UNPACKER.
|
||||
Return whether the unpacking was successful."
|
||||
(gnus-make-directory dir)
|
||||
(gnus-message 4 "Unpacking: %s" (format unpacker packet))
|
||||
(prog1
|
||||
(zerop (call-process
|
||||
shell-file-name nil nil nil shell-command-switch
|
||||
(format "cd %s ; %s" (expand-file-name dir)
|
||||
(format unpacker packet))))
|
||||
(gnus-message 4 "Unpacking...done")))
|
||||
|
||||
(defun gnus-soup-send-packet (packet)
|
||||
(gnus-soup-unpack-packet
|
||||
gnus-soup-replies-directory gnus-soup-unpacker packet)
|
||||
(let ((replies (gnus-soup-parse-replies
|
||||
(concat gnus-soup-replies-directory "REPLIES"))))
|
||||
(save-excursion
|
||||
(while replies
|
||||
(let* ((msg-file (concat gnus-soup-replies-directory
|
||||
(gnus-soup-reply-prefix (car replies))
|
||||
".MSG"))
|
||||
(msg-buf (and (file-exists-p msg-file)
|
||||
(nnheader-find-file-noselect msg-file)))
|
||||
(tmp-buf (get-buffer-create " *soup send*"))
|
||||
beg end)
|
||||
(cond
|
||||
((/= (gnus-soup-encoding-format
|
||||
(gnus-soup-reply-encoding (car replies)))
|
||||
?n)
|
||||
(error "Unsupported encoding"))
|
||||
((null msg-buf)
|
||||
t)
|
||||
(t
|
||||
(buffer-disable-undo msg-buf)
|
||||
(buffer-disable-undo tmp-buf)
|
||||
(set-buffer msg-buf)
|
||||
(goto-char (point-min))
|
||||
(while (not (eobp))
|
||||
(unless (looking-at "#! *rnews +\\([0-9]+\\)")
|
||||
(error "Bad header."))
|
||||
(forward-line 1)
|
||||
(setq beg (point)
|
||||
end (+ (point) (string-to-int
|
||||
(buffer-substring
|
||||
(match-beginning 1) (match-end 1)))))
|
||||
(switch-to-buffer tmp-buf)
|
||||
(erase-buffer)
|
||||
(insert-buffer-substring msg-buf beg end)
|
||||
(goto-char (point-min))
|
||||
(search-forward "\n\n")
|
||||
(forward-char -1)
|
||||
(insert mail-header-separator)
|
||||
(setq message-newsreader (setq message-mailer
|
||||
(gnus-extended-version)))
|
||||
(cond
|
||||
((string= (gnus-soup-reply-kind (car replies)) "news")
|
||||
(gnus-message 5 "Sending news message to %s..."
|
||||
(mail-fetch-field "newsgroups"))
|
||||
(sit-for 1)
|
||||
(let ((message-syntax-checks
|
||||
'dont-check-for-anything-just-trust-me))
|
||||
(funcall message-send-news-function)))
|
||||
((string= (gnus-soup-reply-kind (car replies)) "mail")
|
||||
(gnus-message 5 "Sending mail to %s..."
|
||||
(mail-fetch-field "to"))
|
||||
(sit-for 1)
|
||||
(message-send-mail))
|
||||
(t
|
||||
(error "Unknown reply kind")))
|
||||
(set-buffer msg-buf)
|
||||
(goto-char end))
|
||||
(delete-file (buffer-file-name))
|
||||
(kill-buffer msg-buf)
|
||||
(kill-buffer tmp-buf)
|
||||
(gnus-message 4 "Sent packet"))))
|
||||
(setq replies (cdr replies)))
|
||||
t)))
|
||||
|
||||
(provide 'gnus-soup)
|
||||
|
||||
;;; gnus-soup.el ends here
|
||||
528
lisp/gnus/gnus-spec.el
Normal file
528
lisp/gnus/gnus-spec.el
Normal file
|
|
@ -0,0 +1,528 @@
|
|||
;;; gnus-spec.el --- format spec functions for Gnus
|
||||
;; Copyright (C) 1996,97 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
|
||||
;; Keywords: news
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'gnus)
|
||||
|
||||
;;; Internal variables.
|
||||
|
||||
(defvar gnus-summary-mark-positions nil)
|
||||
(defvar gnus-group-mark-positions nil)
|
||||
(defvar gnus-group-indentation "")
|
||||
|
||||
;; Format specs. The chunks below are the machine-generated forms
|
||||
;; that are to be evaled as the result of the default format strings.
|
||||
;; We write them in here to get them byte-compiled. That way the
|
||||
;; default actions will be quite fast, while still retaining the full
|
||||
;; flexibility of the user-defined format specs.
|
||||
|
||||
;; First we have lots of dummy defvars to let the compiler know these
|
||||
;; are really dynamic variables.
|
||||
|
||||
(defvar gnus-tmp-unread)
|
||||
(defvar gnus-tmp-replied)
|
||||
(defvar gnus-tmp-score-char)
|
||||
(defvar gnus-tmp-indentation)
|
||||
(defvar gnus-tmp-opening-bracket)
|
||||
(defvar gnus-tmp-lines)
|
||||
(defvar gnus-tmp-name)
|
||||
(defvar gnus-tmp-closing-bracket)
|
||||
(defvar gnus-tmp-subject-or-nil)
|
||||
(defvar gnus-tmp-subject)
|
||||
(defvar gnus-tmp-marked)
|
||||
(defvar gnus-tmp-marked-mark)
|
||||
(defvar gnus-tmp-subscribed)
|
||||
(defvar gnus-tmp-process-marked)
|
||||
(defvar gnus-tmp-number-of-unread)
|
||||
(defvar gnus-tmp-group-name)
|
||||
(defvar gnus-tmp-group)
|
||||
(defvar gnus-tmp-article-number)
|
||||
(defvar gnus-tmp-unread-and-unselected)
|
||||
(defvar gnus-tmp-news-method)
|
||||
(defvar gnus-tmp-news-server)
|
||||
(defvar gnus-tmp-article-number)
|
||||
(defvar gnus-mouse-face)
|
||||
(defvar gnus-mouse-face-prop)
|
||||
|
||||
(defun gnus-summary-line-format-spec ()
|
||||
(insert gnus-tmp-unread gnus-tmp-replied
|
||||
gnus-tmp-score-char gnus-tmp-indentation)
|
||||
(gnus-put-text-property
|
||||
(point)
|
||||
(progn
|
||||
(insert
|
||||
gnus-tmp-opening-bracket
|
||||
(format "%4d: %-20s"
|
||||
gnus-tmp-lines
|
||||
(if (> (length gnus-tmp-name) 20)
|
||||
(substring gnus-tmp-name 0 20)
|
||||
gnus-tmp-name))
|
||||
gnus-tmp-closing-bracket)
|
||||
(point))
|
||||
gnus-mouse-face-prop gnus-mouse-face)
|
||||
(insert " " gnus-tmp-subject-or-nil "\n"))
|
||||
|
||||
(defvar gnus-summary-line-format-spec
|
||||
(gnus-byte-code 'gnus-summary-line-format-spec))
|
||||
|
||||
(defun gnus-summary-dummy-line-format-spec ()
|
||||
(insert "* ")
|
||||
(gnus-put-text-property
|
||||
(point)
|
||||
(progn
|
||||
(insert ": :")
|
||||
(point))
|
||||
gnus-mouse-face-prop gnus-mouse-face)
|
||||
(insert " " gnus-tmp-subject "\n"))
|
||||
|
||||
(defvar gnus-summary-dummy-line-format-spec
|
||||
(gnus-byte-code 'gnus-summary-dummy-line-format-spec))
|
||||
|
||||
(defun gnus-group-line-format-spec ()
|
||||
(insert gnus-tmp-marked-mark gnus-tmp-subscribed
|
||||
gnus-tmp-process-marked
|
||||
gnus-group-indentation
|
||||
(format "%5s: " gnus-tmp-number-of-unread))
|
||||
(gnus-put-text-property
|
||||
(point)
|
||||
(progn
|
||||
(insert gnus-tmp-group "\n")
|
||||
(1- (point)))
|
||||
gnus-mouse-face-prop gnus-mouse-face))
|
||||
(defvar gnus-group-line-format-spec
|
||||
(gnus-byte-code 'gnus-group-line-format-spec))
|
||||
|
||||
(defvar gnus-format-specs
|
||||
`((version . ,emacs-version)
|
||||
(group "%M\%S\%p\%P\%5y: %(%g%)%l\n" ,gnus-group-line-format-spec)
|
||||
(summary-dummy "* %(: :%) %S\n"
|
||||
,gnus-summary-dummy-line-format-spec)
|
||||
(summary "%U\%R\%z\%I\%(%[%4L: %-20,20n%]%) %s\n"
|
||||
,gnus-summary-line-format-spec))
|
||||
"Alist of format specs.")
|
||||
|
||||
(defvar gnus-article-mode-line-format-spec nil)
|
||||
(defvar gnus-summary-mode-line-format-spec nil)
|
||||
(defvar gnus-group-mode-line-format-spec nil)
|
||||
|
||||
;;; Phew. All that gruft is over, fortunately.
|
||||
|
||||
;;;###autoload
|
||||
(defun gnus-update-format (var)
|
||||
"Update the format specification near point."
|
||||
(interactive
|
||||
(list
|
||||
(save-excursion
|
||||
(eval-defun nil)
|
||||
;; Find the end of the current word.
|
||||
(re-search-forward "[ \t\n]" nil t)
|
||||
;; Search backward.
|
||||
(when (re-search-backward "\\(gnus-[-a-z]+-line-format\\)" nil t)
|
||||
(match-string 1)))))
|
||||
(let* ((type (intern (progn (string-match "gnus-\\([-a-z]+\\)-line" var)
|
||||
(match-string 1 var))))
|
||||
(entry (assq type gnus-format-specs))
|
||||
value spec)
|
||||
(when entry
|
||||
(setq gnus-format-specs (delq entry gnus-format-specs)))
|
||||
(set
|
||||
(intern (format "%s-spec" var))
|
||||
(gnus-parse-format (setq value (symbol-value (intern var)))
|
||||
(symbol-value (intern (format "%s-alist" var)))
|
||||
(not (string-match "mode" var))))
|
||||
(setq spec (symbol-value (intern (format "%s-spec" var))))
|
||||
(push (list type value spec) gnus-format-specs)
|
||||
|
||||
(pop-to-buffer "*Gnus Format*")
|
||||
(erase-buffer)
|
||||
(lisp-interaction-mode)
|
||||
(insert (pp-to-string spec))))
|
||||
|
||||
(defun gnus-update-format-specifications (&optional force &rest types)
|
||||
"Update all (necessary) format specifications."
|
||||
;; Make the indentation array.
|
||||
;; See whether all the stored info needs to be flushed.
|
||||
(when (or force
|
||||
(not (equal emacs-version
|
||||
(cdr (assq 'version gnus-format-specs)))))
|
||||
(setq gnus-format-specs nil))
|
||||
|
||||
;; Go through all the formats and see whether they need updating.
|
||||
(let (new-format entry type val)
|
||||
(while (setq type (pop types))
|
||||
;; Jump to the proper buffer to find out the value of
|
||||
;; the variable, if possible. (It may be buffer-local.)
|
||||
(save-excursion
|
||||
(let ((buffer (intern (format "gnus-%s-buffer" type)))
|
||||
val)
|
||||
(when (and (boundp buffer)
|
||||
(setq val (symbol-value buffer))
|
||||
(get-buffer val)
|
||||
(buffer-name (get-buffer val)))
|
||||
(set-buffer (get-buffer val)))
|
||||
(setq new-format (symbol-value
|
||||
(intern (format "gnus-%s-line-format" type)))))
|
||||
(setq entry (cdr (assq type gnus-format-specs)))
|
||||
(if (and (car entry)
|
||||
(equal (car entry) new-format))
|
||||
;; Use the old format.
|
||||
(set (intern (format "gnus-%s-line-format-spec" type))
|
||||
(cadr entry))
|
||||
;; This is a new format.
|
||||
(setq val
|
||||
(if (not (stringp new-format))
|
||||
;; This is a function call or something.
|
||||
new-format
|
||||
;; This is a "real" format.
|
||||
(gnus-parse-format
|
||||
new-format
|
||||
(symbol-value
|
||||
(intern (format "gnus-%s-line-format-alist"
|
||||
(if (eq type 'article-mode)
|
||||
'summary-mode type))))
|
||||
(not (string-match "mode$" (symbol-name type))))))
|
||||
;; Enter the new format spec into the list.
|
||||
(if entry
|
||||
(progn
|
||||
(setcar (cdr entry) val)
|
||||
(setcar entry new-format))
|
||||
(push (list type new-format val) gnus-format-specs))
|
||||
(set (intern (format "gnus-%s-line-format-spec" type)) val)))))
|
||||
|
||||
(unless (assq 'version gnus-format-specs)
|
||||
(push (cons 'version emacs-version) gnus-format-specs)))
|
||||
|
||||
(defvar gnus-mouse-face-0 'highlight)
|
||||
(defvar gnus-mouse-face-1 'highlight)
|
||||
(defvar gnus-mouse-face-2 'highlight)
|
||||
(defvar gnus-mouse-face-3 'highlight)
|
||||
(defvar gnus-mouse-face-4 'highlight)
|
||||
|
||||
(defun gnus-mouse-face-function (form type)
|
||||
`(gnus-put-text-property
|
||||
(point) (progn ,@form (point))
|
||||
gnus-mouse-face-prop
|
||||
,(if (equal type 0)
|
||||
'gnus-mouse-face
|
||||
`(quote ,(symbol-value (intern (format "gnus-mouse-face-%d" type)))))))
|
||||
|
||||
(defvar gnus-face-0 'bold)
|
||||
(defvar gnus-face-1 'italic)
|
||||
(defvar gnus-face-2 'bold-italic)
|
||||
(defvar gnus-face-3 'bold)
|
||||
(defvar gnus-face-4 'bold)
|
||||
|
||||
(defun gnus-face-face-function (form type)
|
||||
`(gnus-put-text-property
|
||||
(point) (progn ,@form (point))
|
||||
'face ',(symbol-value (intern (format "gnus-face-%d" type)))))
|
||||
|
||||
(defun gnus-tilde-max-form (el max-width)
|
||||
"Return a form that limits EL to MAX-WIDTH."
|
||||
(let ((max (abs max-width)))
|
||||
(if (symbolp el)
|
||||
`(if (> (length ,el) ,max)
|
||||
,(if (< max-width 0)
|
||||
`(substring ,el (- (length el) ,max))
|
||||
`(substring ,el 0 ,max))
|
||||
,el)
|
||||
`(let ((val (eval ,el)))
|
||||
(if (> (length val) ,max)
|
||||
,(if (< max-width 0)
|
||||
`(substring val (- (length val) ,max))
|
||||
`(substring val 0 ,max))
|
||||
val)))))
|
||||
|
||||
(defun gnus-tilde-cut-form (el cut-width)
|
||||
"Return a form that cuts CUT-WIDTH off of EL."
|
||||
(let ((cut (abs cut-width)))
|
||||
(if (symbolp el)
|
||||
`(if (> (length ,el) ,cut)
|
||||
,(if (< cut-width 0)
|
||||
`(substring ,el 0 (- (length el) ,cut))
|
||||
`(substring ,el ,cut))
|
||||
,el)
|
||||
`(let ((val (eval ,el)))
|
||||
(if (> (length val) ,cut)
|
||||
,(if (< cut-width 0)
|
||||
`(substring val 0 (- (length val) ,cut))
|
||||
`(substring val ,cut))
|
||||
val)))))
|
||||
|
||||
(defun gnus-tilde-ignore-form (el ignore-value)
|
||||
"Return a form that is blank when EL is IGNORE-VALUE."
|
||||
(if (symbolp el)
|
||||
`(if (equal ,el ,ignore-value)
|
||||
"" ,el)
|
||||
`(let ((val (eval ,el)))
|
||||
(if (equal val ,ignore-value)
|
||||
"" val))))
|
||||
|
||||
(defun gnus-parse-format (format spec-alist &optional insert)
|
||||
;; This function parses the FORMAT string with the help of the
|
||||
;; SPEC-ALIST and returns a list that can be eval'ed to return the
|
||||
;; string. If the FORMAT string contains the specifiers %( and %)
|
||||
;; the text between them will have the mouse-face text property.
|
||||
(if (string-match
|
||||
"\\`\\(.*\\)%[0-9]?[{(]\\(.*\\)%[0-9]?[})]\\(.*\n?\\)\\'"
|
||||
format)
|
||||
(gnus-parse-complex-format format spec-alist)
|
||||
;; This is a simple format.
|
||||
(gnus-parse-simple-format format spec-alist insert)))
|
||||
|
||||
(defun gnus-parse-complex-format (format spec-alist)
|
||||
(save-excursion
|
||||
(gnus-set-work-buffer)
|
||||
(insert format)
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward "\"" nil t)
|
||||
(replace-match "\\\"" nil t))
|
||||
(goto-char (point-min))
|
||||
(insert "(\"")
|
||||
(while (re-search-forward "%\\([0-9]+\\)?\\([{}()]\\)" nil t)
|
||||
(let ((number (if (match-beginning 1)
|
||||
(match-string 1) "0"))
|
||||
(delim (aref (match-string 2) 0)))
|
||||
(if (or (= delim ?\() (= delim ?\{))
|
||||
(replace-match (concat "\"(" (if (= delim ?\() "mouse" "face")
|
||||
" " number " \""))
|
||||
(replace-match "\")\""))))
|
||||
(goto-char (point-max))
|
||||
(insert "\")")
|
||||
(goto-char (point-min))
|
||||
(let ((form (read (current-buffer))))
|
||||
(cons 'progn (gnus-complex-form-to-spec form spec-alist)))))
|
||||
|
||||
(defun gnus-complex-form-to-spec (form spec-alist)
|
||||
(delq nil
|
||||
(mapcar
|
||||
(lambda (sform)
|
||||
(if (stringp sform)
|
||||
(gnus-parse-simple-format sform spec-alist t)
|
||||
(funcall (intern (format "gnus-%s-face-function" (car sform)))
|
||||
(gnus-complex-form-to-spec (cddr sform) spec-alist)
|
||||
(nth 1 sform))))
|
||||
form)))
|
||||
|
||||
(defun gnus-parse-simple-format (format spec-alist &optional insert)
|
||||
;; This function parses the FORMAT string with the help of the
|
||||
;; SPEC-ALIST and returns a list that can be eval'ed to return a
|
||||
;; string.
|
||||
(let ((max-width 0)
|
||||
spec flist fstring elem result dontinsert user-defined
|
||||
type value pad-width spec-beg cut-width ignore-value
|
||||
tilde-form tilde elem-type)
|
||||
(save-excursion
|
||||
(gnus-set-work-buffer)
|
||||
(insert format)
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward "%" nil t)
|
||||
(setq user-defined nil
|
||||
spec-beg nil
|
||||
pad-width nil
|
||||
max-width nil
|
||||
cut-width nil
|
||||
ignore-value nil
|
||||
tilde-form nil)
|
||||
(setq spec-beg (1- (point)))
|
||||
|
||||
;; Parse this spec fully.
|
||||
(while
|
||||
(cond
|
||||
((looking-at "\\([-.0-9]+\\)\\(,[-0-9]+\\)?")
|
||||
(setq pad-width (string-to-number (match-string 1)))
|
||||
(when (match-beginning 2)
|
||||
(setq max-width (string-to-number (buffer-substring
|
||||
(1+ (match-beginning 2))
|
||||
(match-end 2)))))
|
||||
(goto-char (match-end 0)))
|
||||
((looking-at "~")
|
||||
(forward-char 1)
|
||||
(setq tilde (read (current-buffer))
|
||||
type (car tilde)
|
||||
value (cadr tilde))
|
||||
(cond
|
||||
((memq type '(pad pad-left))
|
||||
(setq pad-width value))
|
||||
((eq type 'pad-right)
|
||||
(setq pad-width (- value)))
|
||||
((memq type '(max-right max))
|
||||
(setq max-width value))
|
||||
((eq type 'max-left)
|
||||
(setq max-width (- value)))
|
||||
((memq type '(cut cut-left))
|
||||
(setq cut-width value))
|
||||
((eq type 'cut-right)
|
||||
(setq cut-width (- value)))
|
||||
((eq type 'ignore)
|
||||
(setq ignore-value
|
||||
(if (stringp value) value (format "%s" value))))
|
||||
((eq type 'form)
|
||||
(setq tilde-form value))
|
||||
(t
|
||||
(error "Unknown tilde type: %s" tilde)))
|
||||
t)
|
||||
(t
|
||||
nil)))
|
||||
;; User-defined spec -- find the spec name.
|
||||
(when (= (setq spec (following-char)) ?u)
|
||||
(forward-char 1)
|
||||
(setq user-defined (following-char)))
|
||||
(forward-char 1)
|
||||
(delete-region spec-beg (point))
|
||||
|
||||
;; Now we have all the relevant data on this spec, so
|
||||
;; we start doing stuff.
|
||||
(insert "%")
|
||||
(if (eq spec ?%)
|
||||
;; "%%" just results in a "%".
|
||||
(insert "%")
|
||||
(cond
|
||||
;; Do tilde forms.
|
||||
((eq spec ?@)
|
||||
(setq elem (list tilde-form ?s)))
|
||||
;; Treat user defined format specifiers specially.
|
||||
(user-defined
|
||||
(setq elem
|
||||
(list
|
||||
(list (intern (format "gnus-user-format-function-%c"
|
||||
user-defined))
|
||||
'gnus-tmp-header)
|
||||
?s)))
|
||||
;; Find the specification from `spec-alist'.
|
||||
((setq elem (cdr (assq spec spec-alist))))
|
||||
(t
|
||||
(setq elem '("*" ?s))))
|
||||
(setq elem-type (cadr elem))
|
||||
;; Insert the new format elements.
|
||||
(when pad-width
|
||||
(insert (number-to-string pad-width)))
|
||||
;; Create the form to be evaled.
|
||||
(if (or max-width cut-width ignore-value)
|
||||
(progn
|
||||
(insert ?s)
|
||||
(let ((el (car elem)))
|
||||
(cond ((= (cadr elem) ?c)
|
||||
(setq el (list 'char-to-string el)))
|
||||
((= (cadr elem) ?d)
|
||||
(setq el (list 'int-to-string el))))
|
||||
(when ignore-value
|
||||
(setq el (gnus-tilde-ignore-form el ignore-value)))
|
||||
(when cut-width
|
||||
(setq el (gnus-tilde-cut-form el cut-width)))
|
||||
(when max-width
|
||||
(setq el (gnus-tilde-max-form el max-width)))
|
||||
(push el flist)))
|
||||
(insert elem-type)
|
||||
(push (car elem) flist))))
|
||||
(setq fstring (buffer-string)))
|
||||
|
||||
;; Do some postprocessing to increase efficiency.
|
||||
(setq
|
||||
result
|
||||
(cond
|
||||
;; Emptyness.
|
||||
((string= fstring "")
|
||||
nil)
|
||||
;; Not a format string.
|
||||
((not (string-match "%" fstring))
|
||||
(list fstring))
|
||||
;; A format string with just a single string spec.
|
||||
((string= fstring "%s")
|
||||
(list (car flist)))
|
||||
;; A single character.
|
||||
((string= fstring "%c")
|
||||
(list (car flist)))
|
||||
;; A single number.
|
||||
((string= fstring "%d")
|
||||
(setq dontinsert)
|
||||
(if insert
|
||||
(list `(princ ,(car flist)))
|
||||
(list `(int-to-string ,(car flist)))))
|
||||
;; Just lots of chars and strings.
|
||||
((string-match "\\`\\(%[cs]\\)+\\'" fstring)
|
||||
(nreverse flist))
|
||||
;; A single string spec at the beginning of the spec.
|
||||
((string-match "\\`%[sc][^%]+\\'" fstring)
|
||||
(list (car flist) (substring fstring 2)))
|
||||
;; A single string spec in the middle of the spec.
|
||||
((string-match "\\`\\([^%]+\\)%[sc]\\([^%]+\\)\\'" fstring)
|
||||
(list (match-string 1 fstring) (car flist) (match-string 2 fstring)))
|
||||
;; A single string spec in the end of the spec.
|
||||
((string-match "\\`\\([^%]+\\)%[sc]\\'" fstring)
|
||||
(list (match-string 1 fstring) (car flist)))
|
||||
;; A more complex spec.
|
||||
(t
|
||||
(list (cons 'format (cons fstring (nreverse flist)))))))
|
||||
|
||||
(if insert
|
||||
(when result
|
||||
(if dontinsert
|
||||
result
|
||||
(cons 'insert result)))
|
||||
(cond ((stringp result)
|
||||
result)
|
||||
((consp result)
|
||||
(cons 'concat result))
|
||||
(t "")))))
|
||||
|
||||
(defun gnus-eval-format (format &optional alist props)
|
||||
"Eval the format variable FORMAT, using ALIST.
|
||||
If PROPS, insert the result."
|
||||
(let ((form (gnus-parse-format format alist props)))
|
||||
(if props
|
||||
(gnus-add-text-properties (point) (progn (eval form) (point)) props)
|
||||
(eval form))))
|
||||
|
||||
(defun gnus-compile ()
|
||||
"Byte-compile the user-defined format specs."
|
||||
(interactive)
|
||||
(when gnus-xemacs
|
||||
(error "Can't compile specs under XEmacs"))
|
||||
(let ((entries gnus-format-specs)
|
||||
(byte-compile-warnings '(unresolved callargs redefine))
|
||||
entry gnus-tmp-func)
|
||||
(save-excursion
|
||||
(gnus-message 7 "Compiling format specs...")
|
||||
|
||||
(while entries
|
||||
(setq entry (pop entries))
|
||||
(if (eq (car entry) 'version)
|
||||
(setq gnus-format-specs (delq entry gnus-format-specs))
|
||||
(when (and (listp (caddr entry))
|
||||
(not (eq 'byte-code (caaddr entry))))
|
||||
(fset 'gnus-tmp-func `(lambda () ,(caddr entry)))
|
||||
(byte-compile 'gnus-tmp-func)
|
||||
(setcar (cddr entry) (gnus-byte-code 'gnus-tmp-func)))))
|
||||
|
||||
(push (cons 'version emacs-version) gnus-format-specs)
|
||||
;; Mark the .newsrc.eld file as "dirty".
|
||||
(gnus-dribble-enter " ")
|
||||
(gnus-message 7 "Compiling user specs...done"))))
|
||||
|
||||
(provide 'gnus-spec)
|
||||
|
||||
;;; gnus-spec.el ends here
|
||||
752
lisp/gnus/gnus-srvr.el
Normal file
752
lisp/gnus/gnus-srvr.el
Normal file
|
|
@ -0,0 +1,752 @@
|
|||
;;; gnus-srvr.el --- virtual server support for Gnus
|
||||
;; Copyright (C) 1995,96,97 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
|
||||
;; Keywords: news
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'gnus)
|
||||
(require 'gnus-spec)
|
||||
(require 'gnus-group)
|
||||
(require 'gnus-int)
|
||||
(require 'gnus-range)
|
||||
|
||||
(defvar gnus-server-mode-hook nil
|
||||
"Hook run in `gnus-server-mode' buffers.")
|
||||
|
||||
(defconst gnus-server-line-format " {%(%h:%w%)} %s\n"
|
||||
"Format of server lines.
|
||||
It works along the same lines as a normal formatting string,
|
||||
with some simple extensions.")
|
||||
|
||||
(defvar gnus-server-mode-line-format "Gnus List of servers"
|
||||
"The format specification for the server mode line.")
|
||||
|
||||
(defvar gnus-server-exit-hook nil
|
||||
"*Hook run when exiting the server buffer.")
|
||||
|
||||
;;; Internal variables.
|
||||
|
||||
(defvar gnus-inserted-opened-servers nil)
|
||||
|
||||
(defvar gnus-server-line-format-alist
|
||||
`((?h how ?s)
|
||||
(?n name ?s)
|
||||
(?w where ?s)
|
||||
(?s status ?s)))
|
||||
|
||||
(defvar gnus-server-mode-line-format-alist
|
||||
`((?S news-server ?s)
|
||||
(?M news-method ?s)
|
||||
(?u user-defined ?s)))
|
||||
|
||||
(defvar gnus-server-line-format-spec nil)
|
||||
(defvar gnus-server-mode-line-format-spec nil)
|
||||
(defvar gnus-server-killed-servers nil)
|
||||
|
||||
(defvar gnus-server-mode-map)
|
||||
|
||||
(defvar gnus-server-menu-hook nil
|
||||
"*Hook run after the creation of the server mode menu.")
|
||||
|
||||
(defun gnus-server-make-menu-bar ()
|
||||
(gnus-turn-off-edit-menu 'server)
|
||||
(unless (boundp 'gnus-server-server-menu)
|
||||
(easy-menu-define
|
||||
gnus-server-server-menu gnus-server-mode-map ""
|
||||
'("Server"
|
||||
["Add" gnus-server-add-server t]
|
||||
["Browse" gnus-server-read-server t]
|
||||
["Scan" gnus-server-scan-server t]
|
||||
["List" gnus-server-list-servers t]
|
||||
["Kill" gnus-server-kill-server t]
|
||||
["Yank" gnus-server-yank-server t]
|
||||
["Copy" gnus-server-copy-server t]
|
||||
["Edit" gnus-server-edit-server t]
|
||||
["Regenerate" gnus-server-regenerate-server t]
|
||||
["Exit" gnus-server-exit t]))
|
||||
|
||||
(easy-menu-define
|
||||
gnus-server-connections-menu gnus-server-mode-map ""
|
||||
'("Connections"
|
||||
["Open" gnus-server-open-server t]
|
||||
["Close" gnus-server-close-server t]
|
||||
["Deny" gnus-server-deny-server t]
|
||||
"---"
|
||||
["Open All" gnus-server-open-all-servers t]
|
||||
["Close All" gnus-server-close-all-servers t]
|
||||
["Reset All" gnus-server-remove-denials t]))
|
||||
|
||||
(run-hooks 'gnus-server-menu-hook)))
|
||||
|
||||
(defvar gnus-server-mode-map nil)
|
||||
(put 'gnus-server-mode 'mode-class 'special)
|
||||
|
||||
(unless gnus-server-mode-map
|
||||
(setq gnus-server-mode-map (make-sparse-keymap))
|
||||
(suppress-keymap gnus-server-mode-map)
|
||||
|
||||
(gnus-define-keys
|
||||
gnus-server-mode-map
|
||||
" " gnus-server-read-server
|
||||
"\r" gnus-server-read-server
|
||||
gnus-mouse-2 gnus-server-pick-server
|
||||
"q" gnus-server-exit
|
||||
"l" gnus-server-list-servers
|
||||
"k" gnus-server-kill-server
|
||||
"y" gnus-server-yank-server
|
||||
"c" gnus-server-copy-server
|
||||
"a" gnus-server-add-server
|
||||
"e" gnus-server-edit-server
|
||||
"s" gnus-server-scan-server
|
||||
|
||||
"O" gnus-server-open-server
|
||||
"\M-o" gnus-server-open-all-servers
|
||||
"C" gnus-server-close-server
|
||||
"\M-c" gnus-server-close-all-servers
|
||||
"D" gnus-server-deny-server
|
||||
"R" gnus-server-remove-denials
|
||||
|
||||
"g" gnus-server-regenerate-server
|
||||
|
||||
"\C-c\C-i" gnus-info-find-node
|
||||
"\C-c\C-b" gnus-bug))
|
||||
|
||||
(defun gnus-server-mode ()
|
||||
"Major mode for listing and editing servers.
|
||||
|
||||
All normal editing commands are switched off.
|
||||
\\<gnus-server-mode-map>
|
||||
For more in-depth information on this mode, read the manual
|
||||
(`\\[gnus-info-find-node]').
|
||||
|
||||
The following commands are available:
|
||||
|
||||
\\{gnus-server-mode-map}"
|
||||
(interactive)
|
||||
(when (gnus-visual-p 'server-menu 'menu)
|
||||
(gnus-server-make-menu-bar))
|
||||
(kill-all-local-variables)
|
||||
(gnus-simplify-mode-line)
|
||||
(setq major-mode 'gnus-server-mode)
|
||||
(setq mode-name "Server")
|
||||
(gnus-set-default-directory)
|
||||
(setq mode-line-process nil)
|
||||
(use-local-map gnus-server-mode-map)
|
||||
(buffer-disable-undo (current-buffer))
|
||||
(setq truncate-lines t)
|
||||
(setq buffer-read-only t)
|
||||
(run-hooks 'gnus-server-mode-hook))
|
||||
|
||||
(defun gnus-server-insert-server-line (name method)
|
||||
(let* ((how (car method))
|
||||
(where (nth 1 method))
|
||||
(elem (assoc method gnus-opened-servers))
|
||||
(status (cond ((eq (nth 1 elem) 'denied)
|
||||
"(denied)")
|
||||
((or (gnus-server-opened method)
|
||||
(eq (nth 1 elem) 'ok))
|
||||
"(opened)")
|
||||
(t
|
||||
"(closed)"))))
|
||||
(beginning-of-line)
|
||||
(gnus-add-text-properties
|
||||
(point)
|
||||
(prog1 (1+ (point))
|
||||
;; Insert the text.
|
||||
(eval gnus-server-line-format-spec))
|
||||
(list 'gnus-server (intern name)))))
|
||||
|
||||
(defun gnus-enter-server-buffer ()
|
||||
"Set up the server buffer."
|
||||
(gnus-server-setup-buffer)
|
||||
(gnus-configure-windows 'server)
|
||||
(gnus-server-prepare))
|
||||
|
||||
(defun gnus-server-setup-buffer ()
|
||||
"Initialize the server buffer."
|
||||
(unless (get-buffer gnus-server-buffer)
|
||||
(save-excursion
|
||||
(set-buffer (get-buffer-create gnus-server-buffer))
|
||||
(gnus-server-mode)
|
||||
(when gnus-carpal
|
||||
(gnus-carpal-setup-buffer 'server)))))
|
||||
|
||||
(defun gnus-server-prepare ()
|
||||
(setq gnus-server-mode-line-format-spec
|
||||
(gnus-parse-format gnus-server-mode-line-format
|
||||
gnus-server-mode-line-format-alist))
|
||||
(setq gnus-server-line-format-spec
|
||||
(gnus-parse-format gnus-server-line-format
|
||||
gnus-server-line-format-alist t))
|
||||
(let ((alist gnus-server-alist)
|
||||
(buffer-read-only nil)
|
||||
(opened gnus-opened-servers)
|
||||
done server op-ser)
|
||||
(erase-buffer)
|
||||
(setq gnus-inserted-opened-servers nil)
|
||||
;; First we do the real list of servers.
|
||||
(while alist
|
||||
(unless (member (cdar alist) done)
|
||||
(push (cdar alist) done)
|
||||
(cdr (setq server (pop alist)))
|
||||
(when (and server (car server) (cdr server))
|
||||
(gnus-server-insert-server-line (car server) (cdr server))))
|
||||
(when (member (cdar alist) done)
|
||||
(pop alist)))
|
||||
;; Then we insert the list of servers that have been opened in
|
||||
;; this session.
|
||||
(while opened
|
||||
(unless (member (caar opened) done)
|
||||
(push (caar opened) done)
|
||||
(gnus-server-insert-server-line
|
||||
(setq op-ser (format "%s:%s" (caaar opened) (nth 1 (caar opened))))
|
||||
(caar opened))
|
||||
(push (list op-ser (caar opened)) gnus-inserted-opened-servers))
|
||||
(setq opened (cdr opened))))
|
||||
(goto-char (point-min))
|
||||
(gnus-server-position-point))
|
||||
|
||||
(defun gnus-server-server-name ()
|
||||
(let ((server (get-text-property (gnus-point-at-bol) 'gnus-server)))
|
||||
(and server (symbol-name server))))
|
||||
|
||||
(defalias 'gnus-server-position-point 'gnus-goto-colon)
|
||||
|
||||
(defconst gnus-server-edit-buffer "*Gnus edit server*")
|
||||
|
||||
(defun gnus-server-update-server (server)
|
||||
(save-excursion
|
||||
(set-buffer gnus-server-buffer)
|
||||
(let* ((buffer-read-only nil)
|
||||
(entry (assoc server gnus-server-alist))
|
||||
(oentry (assoc (gnus-server-to-method server)
|
||||
gnus-opened-servers)))
|
||||
(when entry
|
||||
(gnus-dribble-enter
|
||||
(concat "(gnus-server-set-info \"" server "\" '"
|
||||
(prin1-to-string (cdr entry)) ")\n")))
|
||||
(when (or entry oentry)
|
||||
;; Buffer may be narrowed.
|
||||
(save-restriction
|
||||
(widen)
|
||||
(when (gnus-server-goto-server server)
|
||||
(gnus-delete-line))
|
||||
(if entry
|
||||
(gnus-server-insert-server-line (car entry) (cdr entry))
|
||||
(gnus-server-insert-server-line
|
||||
(format "%s:%s" (caar oentry) (nth 1 (car oentry)))
|
||||
(car oentry)))
|
||||
(gnus-server-position-point))))))
|
||||
|
||||
(defun gnus-server-set-info (server info)
|
||||
;; Enter a select method into the virtual server alist.
|
||||
(when (and server info)
|
||||
(gnus-dribble-enter
|
||||
(concat "(gnus-server-set-info \"" server "\" '"
|
||||
(prin1-to-string info) ")"))
|
||||
(let* ((server (nth 1 info))
|
||||
(entry (assoc server gnus-server-alist)))
|
||||
(if entry (setcdr entry info)
|
||||
(setq gnus-server-alist
|
||||
(nconc gnus-server-alist (list (cons server info))))))))
|
||||
|
||||
;;; Interactive server functions.
|
||||
|
||||
(defun gnus-server-kill-server (server)
|
||||
"Kill the server on the current line."
|
||||
(interactive (list (gnus-server-server-name)))
|
||||
(unless (gnus-server-goto-server server)
|
||||
(if server (error "No such server: %s" server)
|
||||
(error "No server on the current line")))
|
||||
(unless (assoc server gnus-server-alist)
|
||||
(error "Read-only server %s" server))
|
||||
(gnus-dribble-enter "")
|
||||
(let ((buffer-read-only nil))
|
||||
(gnus-delete-line))
|
||||
(push (assoc server gnus-server-alist) gnus-server-killed-servers)
|
||||
(setq gnus-server-alist (delq (car gnus-server-killed-servers)
|
||||
gnus-server-alist))
|
||||
(gnus-server-position-point))
|
||||
|
||||
(defun gnus-server-yank-server ()
|
||||
"Yank the previously killed server."
|
||||
(interactive)
|
||||
(unless gnus-server-killed-servers
|
||||
(error "No killed servers to be yanked"))
|
||||
(let ((alist gnus-server-alist)
|
||||
(server (gnus-server-server-name))
|
||||
(killed (car gnus-server-killed-servers)))
|
||||
(if (not server)
|
||||
(setq gnus-server-alist (nconc gnus-server-alist (list killed)))
|
||||
(if (string= server (caar gnus-server-alist))
|
||||
(push killed gnus-server-alist)
|
||||
(while (and (cdr alist)
|
||||
(not (string= server (caadr alist))))
|
||||
(setq alist (cdr alist)))
|
||||
(if alist
|
||||
(setcdr alist (cons killed (cdr alist)))
|
||||
(setq gnus-server-alist (list killed)))))
|
||||
(gnus-server-update-server (car killed))
|
||||
(setq gnus-server-killed-servers (cdr gnus-server-killed-servers))
|
||||
(gnus-server-position-point)))
|
||||
|
||||
(defun gnus-server-exit ()
|
||||
"Return to the group buffer."
|
||||
(interactive)
|
||||
(run-hooks 'gnus-server-exit-hook)
|
||||
(kill-buffer (current-buffer))
|
||||
(gnus-configure-windows 'group t))
|
||||
|
||||
(defun gnus-server-list-servers ()
|
||||
"List all available servers."
|
||||
(interactive)
|
||||
(let ((cur (gnus-server-server-name)))
|
||||
(gnus-server-prepare)
|
||||
(if cur (gnus-server-goto-server cur)
|
||||
(goto-char (point-max))
|
||||
(forward-line -1))
|
||||
(gnus-server-position-point)))
|
||||
|
||||
(defun gnus-server-set-status (method status)
|
||||
"Make METHOD have STATUS."
|
||||
(let ((entry (assoc method gnus-opened-servers)))
|
||||
(if entry
|
||||
(setcar (cdr entry) status)
|
||||
(push (list method status) gnus-opened-servers))))
|
||||
|
||||
(defun gnus-opened-servers-remove (method)
|
||||
"Remove METHOD from the list of opened servers."
|
||||
(setq gnus-opened-servers (delq (assoc method gnus-opened-servers)
|
||||
gnus-opened-servers)))
|
||||
|
||||
(defun gnus-server-open-server (server)
|
||||
"Force an open of SERVER."
|
||||
(interactive (list (gnus-server-server-name)))
|
||||
(let ((method (gnus-server-to-method server)))
|
||||
(unless method
|
||||
(error "No such server: %s" server))
|
||||
(gnus-server-set-status method 'ok)
|
||||
(prog1
|
||||
(or (gnus-open-server method)
|
||||
(progn (message "Couldn't open %s" server) nil))
|
||||
(gnus-server-update-server server)
|
||||
(gnus-server-position-point))))
|
||||
|
||||
(defun gnus-server-open-all-servers ()
|
||||
"Open all servers."
|
||||
(interactive)
|
||||
(let ((servers gnus-inserted-opened-servers))
|
||||
(while servers
|
||||
(gnus-server-open-server (car (pop servers))))))
|
||||
|
||||
(defun gnus-server-close-server (server)
|
||||
"Close SERVER."
|
||||
(interactive (list (gnus-server-server-name)))
|
||||
(let ((method (gnus-server-to-method server)))
|
||||
(unless method
|
||||
(error "No such server: %s" server))
|
||||
(gnus-server-set-status method 'closed)
|
||||
(prog1
|
||||
(gnus-close-server method)
|
||||
(gnus-server-update-server server)
|
||||
(gnus-server-position-point))))
|
||||
|
||||
(defun gnus-server-close-all-servers ()
|
||||
"Close all servers."
|
||||
(interactive)
|
||||
(let ((servers gnus-inserted-opened-servers))
|
||||
(while servers
|
||||
(gnus-server-close-server (car (pop servers))))))
|
||||
|
||||
(defun gnus-server-deny-server (server)
|
||||
"Make sure SERVER will never be attempted opened."
|
||||
(interactive (list (gnus-server-server-name)))
|
||||
(let ((method (gnus-server-to-method server)))
|
||||
(unless method
|
||||
(error "No such server: %s" server))
|
||||
(gnus-server-set-status method 'denied))
|
||||
(gnus-server-update-server server)
|
||||
(gnus-server-position-point)
|
||||
t)
|
||||
|
||||
(defun gnus-server-remove-denials ()
|
||||
"Make all denied servers into closed servers."
|
||||
(interactive)
|
||||
(let ((servers gnus-opened-servers))
|
||||
(while servers
|
||||
(when (eq (nth 1 (car servers)) 'denied)
|
||||
(setcar (nthcdr 1 (car servers)) 'closed))
|
||||
(setq servers (cdr servers))))
|
||||
(gnus-server-list-servers))
|
||||
|
||||
(defun gnus-server-copy-server (from to)
|
||||
(interactive
|
||||
(list
|
||||
(or (gnus-server-server-name)
|
||||
(error "No server on the current line"))
|
||||
(read-string "Copy to: ")))
|
||||
(unless from
|
||||
(error "No server on current line"))
|
||||
(unless (and to (not (string= to "")))
|
||||
(error "No name to copy to"))
|
||||
(when (assoc to gnus-server-alist)
|
||||
(error "%s already exists" to))
|
||||
(unless (gnus-server-to-method from)
|
||||
(error "%s: no such server" from))
|
||||
(let ((to-entry (cons from (gnus-copy-sequence
|
||||
(gnus-server-to-method from)))))
|
||||
(setcar to-entry to)
|
||||
(setcar (nthcdr 2 to-entry) to)
|
||||
(push to-entry gnus-server-killed-servers)
|
||||
(gnus-server-yank-server)))
|
||||
|
||||
(defun gnus-server-add-server (how where)
|
||||
(interactive
|
||||
(list (intern (completing-read "Server method: "
|
||||
gnus-valid-select-methods nil t))
|
||||
(read-string "Server name: ")))
|
||||
(when (assq where gnus-server-alist)
|
||||
(error "Server with that name already defined"))
|
||||
(push (list where how where) gnus-server-killed-servers)
|
||||
(gnus-server-yank-server))
|
||||
|
||||
(defun gnus-server-goto-server (server)
|
||||
"Jump to a server line."
|
||||
(interactive
|
||||
(list (completing-read "Goto server: " gnus-server-alist nil t)))
|
||||
(let ((to (text-property-any (point-min) (point-max)
|
||||
'gnus-server (intern server))))
|
||||
(when to
|
||||
(goto-char to)
|
||||
(gnus-server-position-point))))
|
||||
|
||||
(defun gnus-server-edit-server (server)
|
||||
"Edit the server on the current line."
|
||||
(interactive (list (gnus-server-server-name)))
|
||||
(unless server
|
||||
(error "No server on current line"))
|
||||
(unless (assoc server gnus-server-alist)
|
||||
(error "This server can't be edited"))
|
||||
(let ((info (cdr (assoc server gnus-server-alist))))
|
||||
(gnus-close-server info)
|
||||
(gnus-edit-form
|
||||
info "Editing the server."
|
||||
`(lambda (form)
|
||||
(gnus-server-set-info ,server form)
|
||||
(gnus-server-list-servers)
|
||||
(gnus-server-position-point)))))
|
||||
|
||||
(defun gnus-server-scan-server (server)
|
||||
"Request a scan from the current server."
|
||||
(interactive (list (gnus-server-server-name)))
|
||||
(gnus-message 3 "Scanning %s...done" server)
|
||||
(gnus-request-scan nil (gnus-server-to-method server))
|
||||
(gnus-message 3 "Scanning %s...done" server))
|
||||
|
||||
(defun gnus-server-read-server (server)
|
||||
"Browse a server."
|
||||
(interactive (list (gnus-server-server-name)))
|
||||
(let ((buf (current-buffer)))
|
||||
(prog1
|
||||
(gnus-browse-foreign-server (gnus-server-to-method server) buf)
|
||||
(save-excursion
|
||||
(set-buffer buf)
|
||||
(gnus-server-update-server (gnus-server-server-name))
|
||||
(gnus-server-position-point)))))
|
||||
|
||||
(defun gnus-server-pick-server (e)
|
||||
(interactive "e")
|
||||
(mouse-set-point e)
|
||||
(gnus-server-read-server (gnus-server-server-name)))
|
||||
|
||||
|
||||
;;;
|
||||
;;; Browse Server Mode
|
||||
;;;
|
||||
|
||||
(defvar gnus-browse-menu-hook nil
|
||||
"*Hook run after the creation of the browse mode menu.")
|
||||
|
||||
(defvar gnus-browse-mode-hook nil)
|
||||
(defvar gnus-browse-mode-map nil)
|
||||
(put 'gnus-browse-mode 'mode-class 'special)
|
||||
|
||||
(unless gnus-browse-mode-map
|
||||
(setq gnus-browse-mode-map (make-keymap))
|
||||
(suppress-keymap gnus-browse-mode-map)
|
||||
|
||||
(gnus-define-keys
|
||||
gnus-browse-mode-map
|
||||
" " gnus-browse-read-group
|
||||
"=" gnus-browse-select-group
|
||||
"n" gnus-browse-next-group
|
||||
"p" gnus-browse-prev-group
|
||||
"\177" gnus-browse-prev-group
|
||||
"N" gnus-browse-next-group
|
||||
"P" gnus-browse-prev-group
|
||||
"\M-n" gnus-browse-next-group
|
||||
"\M-p" gnus-browse-prev-group
|
||||
"\r" gnus-browse-select-group
|
||||
"u" gnus-browse-unsubscribe-current-group
|
||||
"l" gnus-browse-exit
|
||||
"L" gnus-browse-exit
|
||||
"q" gnus-browse-exit
|
||||
"Q" gnus-browse-exit
|
||||
"\C-c\C-c" gnus-browse-exit
|
||||
"?" gnus-browse-describe-briefly
|
||||
|
||||
"\C-c\C-i" gnus-info-find-node
|
||||
"\C-c\C-b" gnus-bug))
|
||||
|
||||
(defun gnus-browse-make-menu-bar ()
|
||||
(gnus-turn-off-edit-menu 'browse)
|
||||
(unless (boundp 'gnus-browse-menu)
|
||||
(easy-menu-define
|
||||
gnus-browse-menu gnus-browse-mode-map ""
|
||||
'("Browse"
|
||||
["Subscribe" gnus-browse-unsubscribe-current-group t]
|
||||
["Read" gnus-browse-read-group t]
|
||||
["Select" gnus-browse-read-group t]
|
||||
["Next" gnus-browse-next-group t]
|
||||
["Prev" gnus-browse-next-group t]
|
||||
["Exit" gnus-browse-exit t]))
|
||||
(run-hooks 'gnus-browse-menu-hook)))
|
||||
|
||||
(defvar gnus-browse-current-method nil)
|
||||
(defvar gnus-browse-return-buffer nil)
|
||||
|
||||
(defvar gnus-browse-buffer "*Gnus Browse Server*")
|
||||
|
||||
(defun gnus-browse-foreign-server (method &optional return-buffer)
|
||||
"Browse the server METHOD."
|
||||
(setq gnus-browse-current-method method)
|
||||
(setq gnus-browse-return-buffer return-buffer)
|
||||
(when (stringp method)
|
||||
(setq method (gnus-server-to-method method)))
|
||||
(let ((gnus-select-method method)
|
||||
groups group)
|
||||
(gnus-message 5 "Connecting to %s..." (nth 1 method))
|
||||
(cond
|
||||
((not (gnus-check-server method))
|
||||
(gnus-message
|
||||
1 "Unable to contact server: %s" (gnus-status-message method))
|
||||
nil)
|
||||
((not
|
||||
(prog2
|
||||
(gnus-message 6 "Reading active file...")
|
||||
(gnus-request-list method)
|
||||
(gnus-message 6 "Reading active file...done")))
|
||||
(gnus-message
|
||||
1 "Couldn't request list: %s" (gnus-status-message method))
|
||||
nil)
|
||||
(t
|
||||
(get-buffer-create gnus-browse-buffer)
|
||||
(gnus-add-current-to-buffer-list)
|
||||
(when gnus-carpal
|
||||
(gnus-carpal-setup-buffer 'browse))
|
||||
(gnus-configure-windows 'browse)
|
||||
(buffer-disable-undo (current-buffer))
|
||||
(let ((buffer-read-only nil))
|
||||
(erase-buffer))
|
||||
(gnus-browse-mode)
|
||||
(setq mode-line-buffer-identification
|
||||
(list
|
||||
(format
|
||||
"Gnus: %%b {%s:%s}" (car method) (cadr method))))
|
||||
(save-excursion
|
||||
(set-buffer nntp-server-buffer)
|
||||
(let ((cur (current-buffer)))
|
||||
(goto-char (point-min))
|
||||
(unless (string= gnus-ignored-newsgroups "")
|
||||
(delete-matching-lines gnus-ignored-newsgroups))
|
||||
(while (re-search-forward
|
||||
"\\(^[^ \t]+\\)[ \t]+[0-9]+[ \t]+[0-9]+" nil t)
|
||||
(goto-char (match-end 1))
|
||||
(push (cons (match-string 1)
|
||||
(max 0 (- (1+ (read cur)) (read cur))))
|
||||
groups))))
|
||||
(setq groups (sort groups
|
||||
(lambda (l1 l2)
|
||||
(string< (car l1) (car l2)))))
|
||||
(let ((buffer-read-only nil))
|
||||
(while groups
|
||||
(setq group (car groups))
|
||||
(insert
|
||||
(format "K%7d: %s\n" (cdr group) (car group)))
|
||||
(setq groups (cdr groups))))
|
||||
(switch-to-buffer (current-buffer))
|
||||
(goto-char (point-min))
|
||||
(gnus-group-position-point)
|
||||
(gnus-message 5 "Connecting to %s...done" (nth 1 method))
|
||||
t))))
|
||||
|
||||
(defun gnus-browse-mode ()
|
||||
"Major mode for browsing a foreign server.
|
||||
|
||||
All normal editing commands are switched off.
|
||||
|
||||
\\<gnus-browse-mode-map>
|
||||
The only things you can do in this buffer is
|
||||
|
||||
1) `\\[gnus-browse-unsubscribe-current-group]' to subscribe to a group.
|
||||
The group will be inserted into the group buffer upon exit from this
|
||||
buffer.
|
||||
|
||||
2) `\\[gnus-browse-read-group]' to read a group ephemerally.
|
||||
|
||||
3) `\\[gnus-browse-exit]' to return to the group buffer."
|
||||
(interactive)
|
||||
(kill-all-local-variables)
|
||||
(when (gnus-visual-p 'browse-menu 'menu)
|
||||
(gnus-browse-make-menu-bar))
|
||||
(gnus-simplify-mode-line)
|
||||
(setq major-mode 'gnus-browse-mode)
|
||||
(setq mode-name "Browse Server")
|
||||
(setq mode-line-process nil)
|
||||
(use-local-map gnus-browse-mode-map)
|
||||
(buffer-disable-undo (current-buffer))
|
||||
(setq truncate-lines t)
|
||||
(gnus-set-default-directory)
|
||||
(setq buffer-read-only t)
|
||||
(run-hooks 'gnus-browse-mode-hook))
|
||||
|
||||
(defun gnus-browse-read-group (&optional no-article)
|
||||
"Enter the group at the current line."
|
||||
(interactive)
|
||||
(let ((group (gnus-group-real-name (gnus-browse-group-name))))
|
||||
(unless (gnus-group-read-ephemeral-group
|
||||
group gnus-browse-current-method nil
|
||||
(cons (current-buffer) 'browse))
|
||||
(error "Couldn't enter %s" group))))
|
||||
|
||||
(defun gnus-browse-select-group ()
|
||||
"Select the current group."
|
||||
(interactive)
|
||||
(gnus-browse-read-group 'no))
|
||||
|
||||
(defun gnus-browse-next-group (n)
|
||||
"Go to the next group."
|
||||
(interactive "p")
|
||||
(prog1
|
||||
(forward-line n)
|
||||
(gnus-group-position-point)))
|
||||
|
||||
(defun gnus-browse-prev-group (n)
|
||||
"Go to the next group."
|
||||
(interactive "p")
|
||||
(gnus-browse-next-group (- n)))
|
||||
|
||||
(defun gnus-browse-unsubscribe-current-group (arg)
|
||||
"(Un)subscribe to the next ARG groups."
|
||||
(interactive "p")
|
||||
(when (eobp)
|
||||
(error "No group at current line."))
|
||||
(let ((ward (if (< arg 0) -1 1))
|
||||
(arg (abs arg)))
|
||||
(while (and (> arg 0)
|
||||
(not (eobp))
|
||||
(gnus-browse-unsubscribe-group)
|
||||
(zerop (gnus-browse-next-group ward)))
|
||||
(decf arg))
|
||||
(gnus-group-position-point)
|
||||
(when (/= 0 arg)
|
||||
(gnus-message 7 "No more newsgroups"))
|
||||
arg))
|
||||
|
||||
(defun gnus-browse-group-name ()
|
||||
(save-excursion
|
||||
(beginning-of-line)
|
||||
(when (re-search-forward ": \\(.*\\)$" (gnus-point-at-eol) t)
|
||||
(gnus-group-prefixed-name
|
||||
;; Remove text props.
|
||||
(format "%s" (match-string 1))
|
||||
gnus-browse-current-method))))
|
||||
|
||||
(defun gnus-browse-unsubscribe-group ()
|
||||
"Toggle subscription of the current group in the browse buffer."
|
||||
(let ((sub nil)
|
||||
(buffer-read-only nil)
|
||||
group)
|
||||
(save-excursion
|
||||
(beginning-of-line)
|
||||
;; If this group it killed, then we want to subscribe it.
|
||||
(when (= (following-char) ?K)
|
||||
(setq sub t))
|
||||
(setq group (gnus-browse-group-name))
|
||||
;; Make sure the group has been properly removed before we
|
||||
;; subscribe to it.
|
||||
(gnus-kill-ephemeral-group group)
|
||||
(delete-char 1)
|
||||
(if sub
|
||||
(progn
|
||||
(gnus-group-change-level
|
||||
(list t group gnus-level-default-subscribed
|
||||
nil nil gnus-browse-current-method)
|
||||
gnus-level-default-subscribed gnus-level-killed
|
||||
(and (car (nth 1 gnus-newsrc-alist))
|
||||
(gnus-gethash (car (nth 1 gnus-newsrc-alist))
|
||||
gnus-newsrc-hashtb))
|
||||
t)
|
||||
(insert ? ))
|
||||
(gnus-group-change-level
|
||||
group gnus-level-killed gnus-level-default-subscribed)
|
||||
(insert ?K)))
|
||||
t))
|
||||
|
||||
(defun gnus-browse-exit ()
|
||||
"Quit browsing and return to the group buffer."
|
||||
(interactive)
|
||||
(when (eq major-mode 'gnus-browse-mode)
|
||||
(kill-buffer (current-buffer)))
|
||||
;; Insert the newly subscribed groups in the group buffer.
|
||||
(save-excursion
|
||||
(set-buffer gnus-group-buffer)
|
||||
(gnus-group-list-groups nil))
|
||||
(if gnus-browse-return-buffer
|
||||
(gnus-configure-windows 'server 'force)
|
||||
(gnus-configure-windows 'group 'force)))
|
||||
|
||||
(defun gnus-browse-describe-briefly ()
|
||||
"Give a one line description of the group mode commands."
|
||||
(interactive)
|
||||
(gnus-message 6
|
||||
(substitute-command-keys "\\<gnus-browse-mode-map>\\[gnus-group-next-group]:Forward \\[gnus-group-prev-group]:Backward \\[gnus-browse-exit]:Exit \\[gnus-info-find-node]:Run Info \\[gnus-browse-describe-briefly]:This help")))
|
||||
|
||||
(defun gnus-server-regenerate-server ()
|
||||
"Issue a command to the server to regenerate all its data structures."
|
||||
(interactive)
|
||||
(let ((server (gnus-server-server-name)))
|
||||
(unless server
|
||||
(error "No server on the current line"))
|
||||
(if (not (gnus-check-backend-function
|
||||
'request-regenerate (car (gnus-server-to-method server))))
|
||||
(error "This backend doesn't support regeneration")
|
||||
(gnus-message 5 "Requesting regeneration of %s..." server)
|
||||
(if (gnus-request-regenerate server)
|
||||
(gnus-message 5 "Requesting regeneration of %s...done" server)
|
||||
(gnus-message 5 "Couldn't regenerate %s" server)))))
|
||||
|
||||
(provide 'gnus-srvr)
|
||||
|
||||
;;; gnus-srvr.el ends here.
|
||||
2461
lisp/gnus/gnus-start.el
Normal file
2461
lisp/gnus/gnus-start.el
Normal file
File diff suppressed because it is too large
Load diff
8686
lisp/gnus/gnus-sum.el
Normal file
8686
lisp/gnus/gnus-sum.el
Normal file
File diff suppressed because it is too large
Load diff
1397
lisp/gnus/gnus-topic.el
Normal file
1397
lisp/gnus/gnus-topic.el
Normal file
File diff suppressed because it is too large
Load diff
173
lisp/gnus/gnus-undo.el
Normal file
173
lisp/gnus/gnus-undo.el
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
;;; gnus-undo.el --- minor mode for undoing in Gnus
|
||||
;; Copyright (C) 1996,97 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
|
||||
;; Keywords: news
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; This package allows arbitrary undoing in Gnus buffers. As all the
|
||||
;; Gnus buffers aren't very text-oriented (what is in the buffers is
|
||||
;; just some random representation of the actual data), normal Emacs
|
||||
;; undoing doesn't work at all for Gnus.
|
||||
;;
|
||||
;; This package works by letting Gnus register functions for reversing
|
||||
;; actions, and then calling these functions when the user pushes the
|
||||
;; `undo' key. As with normal `undo', there it is possible to set
|
||||
;; undo boundaries and so on.
|
||||
;;
|
||||
;; Internally, the undo sequence is represented by the
|
||||
;; `gnus-undo-actions' list, where each element is a list of functions
|
||||
;; to be called, in sequence, to undo some action. (An "action" is a
|
||||
;; collection of functions.)
|
||||
;;
|
||||
;; For instance, a function for killing a group will call
|
||||
;; `gnus-undo-register' with a function that un-kills the group. This
|
||||
;; package will put that function into an action.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'gnus-util)
|
||||
(require 'gnus)
|
||||
|
||||
(defvar gnus-undo-mode nil
|
||||
"Minor mode for undoing in Gnus buffers.")
|
||||
|
||||
(defvar gnus-undo-mode-hook nil
|
||||
"Hook called in all `gnus-undo-mode' buffers.")
|
||||
|
||||
;;; Internal variables.
|
||||
|
||||
(defvar gnus-undo-actions nil)
|
||||
(defvar gnus-undo-boundary t)
|
||||
(defvar gnus-undo-last nil)
|
||||
(defvar gnus-undo-boundary-inhibit nil)
|
||||
|
||||
;;; Minor mode definition.
|
||||
|
||||
(defvar gnus-undo-mode-map nil)
|
||||
|
||||
(unless gnus-undo-mode-map
|
||||
(setq gnus-undo-mode-map (make-sparse-keymap))
|
||||
|
||||
(gnus-define-keys gnus-undo-mode-map
|
||||
"\M-\C-_" gnus-undo
|
||||
"\C-_" gnus-undo
|
||||
"\C-xu" gnus-undo
|
||||
[(control /)] gnus-undo ; many people are used to type `C-/' on
|
||||
; X terminals and get `C-_'.
|
||||
))
|
||||
|
||||
(defun gnus-undo-make-menu-bar ()
|
||||
(when nil
|
||||
(define-key-after (current-local-map) [menu-bar file gnus-undo]
|
||||
(cons "Undo" 'gnus-undo-actions)
|
||||
[menu-bar file whatever])))
|
||||
|
||||
(defun gnus-undo-mode (&optional arg)
|
||||
"Minor mode for providing `undo' in Gnus buffers.
|
||||
|
||||
\\{gnus-undo-mode-map}"
|
||||
(interactive "P")
|
||||
(set (make-local-variable 'gnus-undo-mode)
|
||||
(if (null arg) (not gnus-undo-mode)
|
||||
(> (prefix-numeric-value arg) 0)))
|
||||
(set (make-local-variable 'gnus-undo-actions) nil)
|
||||
(set (make-local-variable 'gnus-undo-boundary) t)
|
||||
(when gnus-undo-mode
|
||||
;; Set up the menu.
|
||||
(when (gnus-visual-p 'undo-menu 'menu)
|
||||
(gnus-undo-make-menu-bar))
|
||||
;; Don't display anything in the mode line -- too annoying.
|
||||
;;(unless (assq 'gnus-undo-mode minor-mode-alist)
|
||||
;; (push '(gnus-undo-mode " Undo") minor-mode-alist))
|
||||
(unless (assq 'gnus-undo-mode minor-mode-map-alist)
|
||||
(push (cons 'gnus-undo-mode gnus-undo-mode-map)
|
||||
minor-mode-map-alist))
|
||||
(make-local-hook 'post-command-hook)
|
||||
(add-hook 'post-command-hook 'gnus-undo-boundary nil t)
|
||||
(add-hook 'gnus-summary-exit-hook 'gnus-undo-boundary)
|
||||
(run-hooks 'gnus-undo-mode-hook)))
|
||||
|
||||
;;; Interface functions.
|
||||
|
||||
(defun gnus-disable-undo (&optional buffer)
|
||||
"Disable undoing in the current buffer."
|
||||
(interactive)
|
||||
(save-excursion
|
||||
(when buffer
|
||||
(set-buffer buffer))
|
||||
(gnus-undo-mode -1)))
|
||||
|
||||
(defun gnus-undo-boundary ()
|
||||
"Set Gnus undo boundary."
|
||||
(if gnus-undo-boundary-inhibit
|
||||
(setq gnus-undo-boundary-inhibit nil)
|
||||
(setq gnus-undo-boundary t)))
|
||||
|
||||
(defun gnus-undo-register (form)
|
||||
"Register FORMS as something to be performed to undo a change.
|
||||
FORMS may use backtick quote syntax."
|
||||
(when gnus-undo-mode
|
||||
(gnus-undo-register-1
|
||||
`(lambda ()
|
||||
,form))))
|
||||
|
||||
(put 'gnus-undo-register 'lisp-indent-function 0)
|
||||
(put 'gnus-undo-register 'edebug-form-spec '(body))
|
||||
|
||||
(defun gnus-undo-register-1 (function)
|
||||
"Register FUNCTION as something to be performed to undo a change."
|
||||
(when gnus-undo-mode
|
||||
(cond
|
||||
;; We are on a boundary, so we create a new action.
|
||||
(gnus-undo-boundary
|
||||
(push (list function) gnus-undo-actions)
|
||||
(setq gnus-undo-boundary nil))
|
||||
;; Prepend the function to an old action.
|
||||
(gnus-undo-actions
|
||||
(setcar gnus-undo-actions (cons function (car gnus-undo-actions))))
|
||||
;; Initialize list.
|
||||
(t
|
||||
(setq gnus-undo-actions (list (list function)))))
|
||||
(setq gnus-undo-boundary-inhibit t)))
|
||||
|
||||
(defun gnus-undo (n)
|
||||
"Undo some previous changes in Gnus buffers.
|
||||
Repeat this command to undo more changes.
|
||||
A numeric argument serves as a repeat count."
|
||||
(interactive "p")
|
||||
(unless gnus-undo-mode
|
||||
(error "Undoing is not enabled in this buffer"))
|
||||
(message "%s" last-command)
|
||||
(when (or (not (eq last-command 'gnus-undo))
|
||||
(not gnus-undo-last))
|
||||
(setq gnus-undo-last gnus-undo-actions))
|
||||
(let ((action (pop gnus-undo-last)))
|
||||
(unless action
|
||||
(error "Nothing further to undo"))
|
||||
(setq gnus-undo-actions (delq action gnus-undo-actions))
|
||||
(setq gnus-undo-boundary t)
|
||||
(while action
|
||||
(funcall (pop action)))))
|
||||
|
||||
(provide 'gnus-undo)
|
||||
|
||||
;;; gnus-undo.el ends here
|
||||
829
lisp/gnus/gnus-util.el
Normal file
829
lisp/gnus/gnus-util.el
Normal file
|
|
@ -0,0 +1,829 @@
|
|||
;;; gnus-util.el --- utility functions for Gnus
|
||||
;; Copyright (C) 1996,97 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
|
||||
;; Keywords: news
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; Nothing in this file depends on any other parts of Gnus -- all
|
||||
;; functions and macros in this file are utility functions that are
|
||||
;; used by Gnus and may be used by any other package without loading
|
||||
;; Gnus first.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'custom)
|
||||
(require 'cl)
|
||||
(require 'nnheader)
|
||||
(require 'timezone)
|
||||
(require 'message)
|
||||
|
||||
(eval-and-compile
|
||||
(autoload 'nnmail-date-to-time "nnmail"))
|
||||
|
||||
(defun gnus-boundp (variable)
|
||||
"Return non-nil if VARIABLE is bound and non-nil."
|
||||
(and (boundp variable)
|
||||
(symbol-value variable)))
|
||||
|
||||
(defmacro gnus-eval-in-buffer-window (buffer &rest forms)
|
||||
"Pop to BUFFER, evaluate FORMS, and then return to the original window."
|
||||
(let ((tempvar (make-symbol "GnusStartBufferWindow"))
|
||||
(w (make-symbol "w"))
|
||||
(buf (make-symbol "buf")))
|
||||
`(let* ((,tempvar (selected-window))
|
||||
(,buf ,buffer)
|
||||
(,w (get-buffer-window ,buf 'visible)))
|
||||
(unwind-protect
|
||||
(progn
|
||||
(if ,w
|
||||
(progn
|
||||
(select-window ,w)
|
||||
(set-buffer (window-buffer ,w)))
|
||||
(pop-to-buffer ,buf))
|
||||
,@forms)
|
||||
(select-window ,tempvar)))))
|
||||
|
||||
(put 'gnus-eval-in-buffer-window 'lisp-indent-function 1)
|
||||
(put 'gnus-eval-in-buffer-window 'edebug-form-spec '(form body))
|
||||
|
||||
(defmacro gnus-intern-safe (string hashtable)
|
||||
"Set hash value. Arguments are STRING, VALUE, and HASHTABLE."
|
||||
`(let ((symbol (intern ,string ,hashtable)))
|
||||
(or (boundp symbol)
|
||||
(set symbol nil))
|
||||
symbol))
|
||||
|
||||
;; modified by MORIOKA Tomohiko <morioka@jaist.ac.jp>
|
||||
;; function `substring' might cut on a middle of multi-octet
|
||||
;; character.
|
||||
(defun gnus-truncate-string (str width)
|
||||
(substring str 0 width))
|
||||
|
||||
;; Added by Geoffrey T. Dairiki <dairiki@u.washington.edu>. A safe way
|
||||
;; to limit the length of a string. This function is necessary since
|
||||
;; `(substr "abc" 0 30)' pukes with "Args out of range".
|
||||
(defsubst gnus-limit-string (str width)
|
||||
(if (> (length str) width)
|
||||
(substring str 0 width)
|
||||
str))
|
||||
|
||||
(defsubst gnus-functionp (form)
|
||||
"Return non-nil if FORM is funcallable."
|
||||
(or (and (symbolp form) (fboundp form))
|
||||
(and (listp form) (eq (car form) 'lambda))
|
||||
(compiled-function-p form)))
|
||||
|
||||
(defsubst gnus-goto-char (point)
|
||||
(and point (goto-char point)))
|
||||
|
||||
(defmacro gnus-buffer-exists-p (buffer)
|
||||
`(let ((buffer ,buffer))
|
||||
(when buffer
|
||||
(funcall (if (stringp buffer) 'get-buffer 'buffer-name)
|
||||
buffer))))
|
||||
|
||||
(defmacro gnus-kill-buffer (buffer)
|
||||
`(let ((buf ,buffer))
|
||||
(when (gnus-buffer-exists-p buf)
|
||||
(kill-buffer buf))))
|
||||
|
||||
(if (fboundp 'point-at-bol)
|
||||
(fset 'gnus-point-at-bol 'point-at-bol)
|
||||
(defun gnus-point-at-bol ()
|
||||
"Return point at the beginning of the line."
|
||||
(let ((p (point)))
|
||||
(beginning-of-line)
|
||||
(prog1
|
||||
(point)
|
||||
(goto-char p)))))
|
||||
|
||||
(if (fboundp 'point-at-eol)
|
||||
(fset 'gnus-point-at-eol 'point-at-eol)
|
||||
(defun gnus-point-at-eol ()
|
||||
"Return point at the end of the line."
|
||||
(let ((p (point)))
|
||||
(end-of-line)
|
||||
(prog1
|
||||
(point)
|
||||
(goto-char p)))))
|
||||
|
||||
(defun gnus-delete-first (elt list)
|
||||
"Delete by side effect the first occurrence of ELT as a member of LIST."
|
||||
(if (equal (car list) elt)
|
||||
(cdr list)
|
||||
(let ((total list))
|
||||
(while (and (cdr list)
|
||||
(not (equal (cadr list) elt)))
|
||||
(setq list (cdr list)))
|
||||
(when (cdr list)
|
||||
(setcdr list (cddr list)))
|
||||
total)))
|
||||
|
||||
;; Delete the current line (and the next N lines).
|
||||
(defmacro gnus-delete-line (&optional n)
|
||||
`(delete-region (progn (beginning-of-line) (point))
|
||||
(progn (forward-line ,(or n 1)) (point))))
|
||||
|
||||
(defun gnus-byte-code (func)
|
||||
"Return a form that can be `eval'ed based on FUNC."
|
||||
(let ((fval (symbol-function func)))
|
||||
(if (compiled-function-p fval)
|
||||
(let ((flist (append fval nil)))
|
||||
(setcar flist 'byte-code)
|
||||
flist)
|
||||
(cons 'progn (cddr fval)))))
|
||||
|
||||
(defun gnus-extract-address-components (from)
|
||||
(let (name address)
|
||||
;; First find the address - the thing with the @ in it. This may
|
||||
;; not be accurate in mail addresses, but does the trick most of
|
||||
;; the time in news messages.
|
||||
(when (string-match "\\b[^@ \t<>]+[!@][^@ \t<>]+\\b" from)
|
||||
(setq address (substring from (match-beginning 0) (match-end 0))))
|
||||
;; Then we check whether the "name <address>" format is used.
|
||||
(and address
|
||||
;; Fix by MORIOKA Tomohiko <morioka@jaist.ac.jp>
|
||||
;; Linear white space is not required.
|
||||
(string-match (concat "[ \t]*<" (regexp-quote address) ">") from)
|
||||
(and (setq name (substring from 0 (match-beginning 0)))
|
||||
;; Strip any quotes from the name.
|
||||
(string-match "\".*\"" name)
|
||||
(setq name (substring name 1 (1- (match-end 0))))))
|
||||
;; If not, then "address (name)" is used.
|
||||
(or name
|
||||
(and (string-match "(.+)" from)
|
||||
(setq name (substring from (1+ (match-beginning 0))
|
||||
(1- (match-end 0)))))
|
||||
(and (string-match "()" from)
|
||||
(setq name address))
|
||||
;; Fix by MORIOKA Tomohiko <morioka@jaist.ac.jp>.
|
||||
;; XOVER might not support folded From headers.
|
||||
(and (string-match "(.*" from)
|
||||
(setq name (substring from (1+ (match-beginning 0))
|
||||
(match-end 0)))))
|
||||
;; Fix by Hallvard B Furuseth <h.b.furuseth@usit.uio.no>.
|
||||
(list (or name from) (or address from))))
|
||||
|
||||
(defun gnus-fetch-field (field)
|
||||
"Return the value of the header FIELD of current article."
|
||||
(save-excursion
|
||||
(save-restriction
|
||||
(let ((case-fold-search t)
|
||||
(inhibit-point-motion-hooks t))
|
||||
(nnheader-narrow-to-headers)
|
||||
(message-fetch-field field)))))
|
||||
|
||||
(defun gnus-goto-colon ()
|
||||
(beginning-of-line)
|
||||
(search-forward ":" (gnus-point-at-eol) t))
|
||||
|
||||
(defun gnus-remove-text-with-property (prop)
|
||||
"Delete all text in the current buffer with text property PROP."
|
||||
(save-excursion
|
||||
(goto-char (point-min))
|
||||
(while (not (eobp))
|
||||
(while (get-text-property (point) prop)
|
||||
(delete-char 1))
|
||||
(goto-char (next-single-property-change (point) prop nil (point-max))))))
|
||||
|
||||
(defun gnus-newsgroup-directory-form (newsgroup)
|
||||
"Make hierarchical directory name from NEWSGROUP name."
|
||||
(let ((newsgroup (gnus-newsgroup-savable-name newsgroup))
|
||||
(len (length newsgroup))
|
||||
idx)
|
||||
;; If this is a foreign group, we don't want to translate the
|
||||
;; entire name.
|
||||
(if (setq idx (string-match ":" newsgroup))
|
||||
(aset newsgroup idx ?/)
|
||||
(setq idx 0))
|
||||
;; Replace all occurrences of `.' with `/'.
|
||||
(while (< idx len)
|
||||
(when (= (aref newsgroup idx) ?.)
|
||||
(aset newsgroup idx ?/))
|
||||
(setq idx (1+ idx)))
|
||||
newsgroup))
|
||||
|
||||
(defun gnus-newsgroup-savable-name (group)
|
||||
;; Replace any slashes in a group name (eg. an ange-ftp nndoc group)
|
||||
;; with dots.
|
||||
(nnheader-replace-chars-in-string group ?/ ?.))
|
||||
|
||||
(defun gnus-string> (s1 s2)
|
||||
(not (or (string< s1 s2)
|
||||
(string= s1 s2))))
|
||||
|
||||
;;; Time functions.
|
||||
|
||||
(defun gnus-days-between (date1 date2)
|
||||
;; Return the number of days between date1 and date2.
|
||||
(- (gnus-day-number date1) (gnus-day-number date2)))
|
||||
|
||||
(defun gnus-day-number (date)
|
||||
(let ((dat (mapcar (lambda (s) (and s (string-to-int s)) )
|
||||
(timezone-parse-date date))))
|
||||
(timezone-absolute-from-gregorian
|
||||
(nth 1 dat) (nth 2 dat) (car dat))))
|
||||
|
||||
(defun gnus-time-to-day (time)
|
||||
"Convert TIME to day number."
|
||||
(let ((tim (decode-time time)))
|
||||
(timezone-absolute-from-gregorian
|
||||
(nth 4 tim) (nth 3 tim) (nth 5 tim))))
|
||||
|
||||
(defun gnus-encode-date (date)
|
||||
"Convert DATE to internal time."
|
||||
(let* ((parse (timezone-parse-date date))
|
||||
(date (mapcar (lambda (d) (and d (string-to-int d))) parse))
|
||||
(time (mapcar 'string-to-int (timezone-parse-time (aref parse 3)))))
|
||||
(encode-time (caddr time) (cadr time) (car time)
|
||||
(caddr date) (cadr date) (car date) (nth 4 date))))
|
||||
|
||||
(defun gnus-time-minus (t1 t2)
|
||||
"Subtract two internal times."
|
||||
(let ((borrow (< (cadr t1) (cadr t2))))
|
||||
(list (- (car t1) (car t2) (if borrow 1 0))
|
||||
(- (+ (if borrow 65536 0) (cadr t1)) (cadr t2)))))
|
||||
|
||||
(defun gnus-time-less (t1 t2)
|
||||
"Say whether time T1 is less than time T2."
|
||||
(or (< (car t1) (car t2))
|
||||
(and (= (car t1) (car t2))
|
||||
(< (nth 1 t1) (nth 1 t2)))))
|
||||
|
||||
(defun gnus-file-newer-than (file date)
|
||||
(let ((fdate (nth 5 (file-attributes file))))
|
||||
(or (> (car fdate) (car date))
|
||||
(and (= (car fdate) (car date))
|
||||
(> (nth 1 fdate) (nth 1 date))))))
|
||||
|
||||
;;; Keymap macros.
|
||||
|
||||
(defmacro gnus-local-set-keys (&rest plist)
|
||||
"Set the keys in PLIST in the current keymap."
|
||||
`(gnus-define-keys-1 (current-local-map) ',plist))
|
||||
|
||||
(defmacro gnus-define-keys (keymap &rest plist)
|
||||
"Define all keys in PLIST in KEYMAP."
|
||||
`(gnus-define-keys-1 (quote ,keymap) (quote ,plist)))
|
||||
|
||||
(defmacro gnus-define-keys-safe (keymap &rest plist)
|
||||
"Define all keys in PLIST in KEYMAP without overwriting previous definitions."
|
||||
`(gnus-define-keys-1 (quote ,keymap) (quote ,plist) t))
|
||||
|
||||
(put 'gnus-define-keys 'lisp-indent-function 1)
|
||||
(put 'gnus-define-keys-safe 'lisp-indent-function 1)
|
||||
(put 'gnus-local-set-keys 'lisp-indent-function 1)
|
||||
|
||||
(defmacro gnus-define-keymap (keymap &rest plist)
|
||||
"Define all keys in PLIST in KEYMAP."
|
||||
`(gnus-define-keys-1 ,keymap (quote ,plist)))
|
||||
|
||||
(put 'gnus-define-keymap 'lisp-indent-function 1)
|
||||
|
||||
(defun gnus-define-keys-1 (keymap plist &optional safe)
|
||||
(when (null keymap)
|
||||
(error "Can't set keys in a null keymap"))
|
||||
(cond ((symbolp keymap)
|
||||
(setq keymap (symbol-value keymap)))
|
||||
((keymapp keymap))
|
||||
((listp keymap)
|
||||
(set (car keymap) nil)
|
||||
(define-prefix-command (car keymap))
|
||||
(define-key (symbol-value (caddr keymap)) (cadr keymap) (car keymap))
|
||||
(setq keymap (symbol-value (car keymap)))))
|
||||
(let (key)
|
||||
(while plist
|
||||
(when (symbolp (setq key (pop plist)))
|
||||
(setq key (symbol-value key)))
|
||||
(if (or (not safe)
|
||||
(eq (lookup-key keymap key) 'undefined))
|
||||
(define-key keymap key (pop plist))
|
||||
(pop plist)))))
|
||||
|
||||
(defun gnus-completing-read (default prompt &rest args)
|
||||
;; Like `completing-read', except that DEFAULT is the default argument.
|
||||
(let* ((prompt (if default
|
||||
(concat prompt " (default " default ") ")
|
||||
(concat prompt " ")))
|
||||
(answer (apply 'completing-read prompt args)))
|
||||
(if (or (null answer) (zerop (length answer)))
|
||||
default
|
||||
answer)))
|
||||
|
||||
;; Two silly functions to ensure that all `y-or-n-p' questions clear
|
||||
;; the echo area.
|
||||
(defun gnus-y-or-n-p (prompt)
|
||||
(prog1
|
||||
(y-or-n-p prompt)
|
||||
(message "")))
|
||||
|
||||
(defun gnus-yes-or-no-p (prompt)
|
||||
(prog1
|
||||
(yes-or-no-p prompt)
|
||||
(message "")))
|
||||
|
||||
;; I suspect there's a better way, but I haven't taken the time to do
|
||||
;; it yet. -erik selberg@cs.washington.edu
|
||||
(defun gnus-dd-mmm (messy-date)
|
||||
"Return a string like DD-MMM from a big messy string"
|
||||
(let ((datevec (ignore-errors (timezone-parse-date messy-date))))
|
||||
(if (not datevec)
|
||||
"??-???"
|
||||
(format "%2s-%s"
|
||||
(condition-case ()
|
||||
;; Make sure leading zeroes are stripped.
|
||||
(number-to-string (string-to-number (aref datevec 2)))
|
||||
(error "??"))
|
||||
(capitalize
|
||||
(or (car
|
||||
(nth (1- (string-to-number (aref datevec 1)))
|
||||
timezone-months-assoc))
|
||||
"???"))))))
|
||||
|
||||
(defmacro gnus-date-get-time (date)
|
||||
"Convert DATE string to Emacs time.
|
||||
Cache the result as a text property stored in DATE."
|
||||
;; Either return the cached value...
|
||||
`(let ((d ,date))
|
||||
(if (equal "" d)
|
||||
'(0 0)
|
||||
(or (get-text-property 0 'gnus-time d)
|
||||
;; or compute the value...
|
||||
(let ((time (nnmail-date-to-time d)))
|
||||
;; and store it back in the string.
|
||||
(put-text-property 0 1 'gnus-time time d)
|
||||
time)))))
|
||||
|
||||
(defsubst gnus-time-iso8601 (time)
|
||||
"Return a string of TIME in YYMMDDTHHMMSS format."
|
||||
(format-time-string "%Y%m%dT%H%M%S" time))
|
||||
|
||||
(defun gnus-date-iso8601 (header)
|
||||
"Convert the date field in HEADER to YYMMDDTHHMMSS"
|
||||
(condition-case ()
|
||||
(gnus-time-iso8601 (gnus-date-get-time (mail-header-date header)))
|
||||
(error "")))
|
||||
|
||||
(defun gnus-mode-string-quote (string)
|
||||
"Quote all \"%\"'s in STRING."
|
||||
(save-excursion
|
||||
(gnus-set-work-buffer)
|
||||
(insert string)
|
||||
(goto-char (point-min))
|
||||
(while (search-forward "%" nil t)
|
||||
(insert "%"))
|
||||
(buffer-string)))
|
||||
|
||||
;; Make a hash table (default and minimum size is 256).
|
||||
;; Optional argument HASHSIZE specifies the table size.
|
||||
(defun gnus-make-hashtable (&optional hashsize)
|
||||
(make-vector (if hashsize (max (gnus-create-hash-size hashsize) 256) 256) 0))
|
||||
|
||||
;; Make a number that is suitable for hashing; bigger than MIN and
|
||||
;; equal to some 2^x. Many machines (such as sparcs) do not have a
|
||||
;; hardware modulo operation, so they implement it in software. On
|
||||
;; many sparcs over 50% of the time to intern is spent in the modulo.
|
||||
;; Yes, it's slower than actually computing the hash from the string!
|
||||
;; So we use powers of 2 so people can optimize the modulo to a mask.
|
||||
(defun gnus-create-hash-size (min)
|
||||
(let ((i 1))
|
||||
(while (< i min)
|
||||
(setq i (* 2 i)))
|
||||
i))
|
||||
|
||||
(defcustom gnus-verbose 7
|
||||
"*Integer that says how verbose Gnus should be.
|
||||
The higher the number, the more messages Gnus will flash to say what
|
||||
it's doing. At zero, Gnus will be totally mute; at five, Gnus will
|
||||
display most important messages; and at ten, Gnus will keep on
|
||||
jabbering all the time."
|
||||
:group 'gnus-start
|
||||
:type 'integer)
|
||||
|
||||
;; Show message if message has a lower level than `gnus-verbose'.
|
||||
;; Guideline for numbers:
|
||||
;; 1 - error messages, 3 - non-serious error messages, 5 - messages
|
||||
;; for things that take a long time, 7 - not very important messages
|
||||
;; on stuff, 9 - messages inside loops.
|
||||
(defun gnus-message (level &rest args)
|
||||
(if (<= level gnus-verbose)
|
||||
(apply 'message args)
|
||||
;; We have to do this format thingy here even if the result isn't
|
||||
;; shown - the return value has to be the same as the return value
|
||||
;; from `message'.
|
||||
(apply 'format args)))
|
||||
|
||||
(defun gnus-error (level &rest args)
|
||||
"Beep an error if LEVEL is equal to or less than `gnus-verbose'."
|
||||
(when (<= (floor level) gnus-verbose)
|
||||
(apply 'message args)
|
||||
(ding)
|
||||
(let (duration)
|
||||
(when (and (floatp level)
|
||||
(not (zerop (setq duration (* 10 (- level (floor level)))))))
|
||||
(sit-for duration))))
|
||||
nil)
|
||||
|
||||
(defun gnus-split-references (references)
|
||||
"Return a list of Message-IDs in REFERENCES."
|
||||
(let ((beg 0)
|
||||
ids)
|
||||
(while (string-match "<[^>]+>" references beg)
|
||||
(push (substring references (match-beginning 0) (setq beg (match-end 0)))
|
||||
ids))
|
||||
(nreverse ids)))
|
||||
|
||||
(defun gnus-parent-id (references &optional n)
|
||||
"Return the last Message-ID in REFERENCES.
|
||||
If N, return the Nth ancestor instead."
|
||||
(when references
|
||||
(let ((ids (inline (gnus-split-references references))))
|
||||
(car (last ids (or n 1))))))
|
||||
|
||||
(defsubst gnus-buffer-live-p (buffer)
|
||||
"Say whether BUFFER is alive or not."
|
||||
(and buffer
|
||||
(get-buffer buffer)
|
||||
(buffer-name (get-buffer buffer))))
|
||||
|
||||
(defun gnus-horizontal-recenter ()
|
||||
"Recenter the current buffer horizontally."
|
||||
(if (< (current-column) (/ (window-width) 2))
|
||||
(set-window-hscroll (get-buffer-window (current-buffer) t) 0)
|
||||
(let* ((orig (point))
|
||||
(end (window-end (get-buffer-window (current-buffer) t)))
|
||||
(max 0))
|
||||
;; Find the longest line currently displayed in the window.
|
||||
(goto-char (window-start))
|
||||
(while (and (not (eobp))
|
||||
(< (point) end))
|
||||
(end-of-line)
|
||||
(setq max (max max (current-column)))
|
||||
(forward-line 1))
|
||||
(goto-char orig)
|
||||
;; Scroll horizontally to center (sort of) the point.
|
||||
(if (> max (window-width))
|
||||
(set-window-hscroll
|
||||
(get-buffer-window (current-buffer) t)
|
||||
(min (- (current-column) (/ (window-width) 3))
|
||||
(+ 2 (- max (window-width)))))
|
||||
(set-window-hscroll (get-buffer-window (current-buffer) t) 0))
|
||||
max)))
|
||||
|
||||
(defun gnus-read-event-char ()
|
||||
"Get the next event."
|
||||
(let ((event (read-event)))
|
||||
;; should be gnus-characterp, but this can't be called in XEmacs anyway
|
||||
(cons (and (numberp event) event) event)))
|
||||
|
||||
(defun gnus-sortable-date (date)
|
||||
"Make sortable string by string-lessp from DATE.
|
||||
Timezone package is used."
|
||||
(condition-case ()
|
||||
(progn
|
||||
(setq date (inline (timezone-fix-time
|
||||
date nil
|
||||
(aref (inline (timezone-parse-date date)) 4))))
|
||||
(inline
|
||||
(timezone-make-sortable-date
|
||||
(aref date 0) (aref date 1) (aref date 2)
|
||||
(inline
|
||||
(timezone-make-time-string
|
||||
(aref date 3) (aref date 4) (aref date 5))))))
|
||||
(error "")))
|
||||
|
||||
(defun gnus-copy-file (file &optional to)
|
||||
"Copy FILE to TO."
|
||||
(interactive
|
||||
(list (read-file-name "Copy file: " default-directory)
|
||||
(read-file-name "Copy file to: " default-directory)))
|
||||
(unless to
|
||||
(setq to (read-file-name "Copy file to: " default-directory)))
|
||||
(when (file-directory-p to)
|
||||
(setq to (concat (file-name-as-directory to)
|
||||
(file-name-nondirectory file))))
|
||||
(copy-file file to))
|
||||
|
||||
(defun gnus-kill-all-overlays ()
|
||||
"Delete all overlays in the current buffer."
|
||||
(unless gnus-xemacs
|
||||
(let* ((overlayss (overlay-lists))
|
||||
(buffer-read-only nil)
|
||||
(overlays (nconc (car overlayss) (cdr overlayss))))
|
||||
(while overlays
|
||||
(delete-overlay (pop overlays))))))
|
||||
|
||||
(defvar gnus-work-buffer " *gnus work*")
|
||||
|
||||
(defun gnus-set-work-buffer ()
|
||||
"Put point in the empty Gnus work buffer."
|
||||
(if (get-buffer gnus-work-buffer)
|
||||
(progn
|
||||
(set-buffer gnus-work-buffer)
|
||||
(erase-buffer))
|
||||
(set-buffer (get-buffer-create gnus-work-buffer))
|
||||
(kill-all-local-variables)
|
||||
(buffer-disable-undo (current-buffer))))
|
||||
|
||||
(defmacro gnus-group-real-name (group)
|
||||
"Find the real name of a foreign newsgroup."
|
||||
`(let ((gname ,group))
|
||||
(if (string-match "^[^:]+:" gname)
|
||||
(substring gname (match-end 0))
|
||||
gname)))
|
||||
|
||||
(defun gnus-make-sort-function (funs)
|
||||
"Return a composite sort condition based on the functions in FUNC."
|
||||
(cond
|
||||
((not (listp funs)) funs)
|
||||
((null funs) funs)
|
||||
((cdr funs)
|
||||
`(lambda (t1 t2)
|
||||
,(gnus-make-sort-function-1 (reverse funs))))
|
||||
(t
|
||||
(car funs))))
|
||||
|
||||
(defun gnus-make-sort-function-1 (funs)
|
||||
"Return a composite sort condition based on the functions in FUNC."
|
||||
(if (cdr funs)
|
||||
`(or (,(car funs) t1 t2)
|
||||
(and (not (,(car funs) t2 t1))
|
||||
,(gnus-make-sort-function-1 (cdr funs))))
|
||||
`(,(car funs) t1 t2)))
|
||||
|
||||
(defun gnus-turn-off-edit-menu (type)
|
||||
"Turn off edit menu in `gnus-TYPE-mode-map'."
|
||||
(define-key (symbol-value (intern (format "gnus-%s-mode-map" type)))
|
||||
[menu-bar edit] 'undefined))
|
||||
|
||||
(defun gnus-prin1 (form)
|
||||
"Use `prin1' on FORM in the current buffer.
|
||||
Bind `print-quoted' to t while printing."
|
||||
(let ((print-quoted t)
|
||||
print-level print-length)
|
||||
(prin1 form (current-buffer))))
|
||||
|
||||
(defun gnus-prin1-to-string (form)
|
||||
"The same as `prin1', but but `print-quoted' to t."
|
||||
(let ((print-quoted t))
|
||||
(prin1-to-string form)))
|
||||
|
||||
(defun gnus-make-directory (directory)
|
||||
"Make DIRECTORY (and all its parents) if it doesn't exist."
|
||||
(when (and directory
|
||||
(not (file-exists-p directory)))
|
||||
(make-directory directory t))
|
||||
t)
|
||||
|
||||
(defun gnus-write-buffer (file)
|
||||
"Write the current buffer's contents to FILE."
|
||||
;; Make sure the directory exists.
|
||||
(gnus-make-directory (file-name-directory file))
|
||||
;; Write the buffer.
|
||||
(write-region (point-min) (point-max) file nil 'quietly))
|
||||
|
||||
(defmacro gnus-delete-assq (key list)
|
||||
`(let ((listval (eval ,list)))
|
||||
(setq ,list (delq (assq ,key listval) listval))))
|
||||
|
||||
(defmacro gnus-delete-assoc (key list)
|
||||
`(let ((listval ,list))
|
||||
(setq ,list (delq (assoc ,key listval) listval))))
|
||||
|
||||
(defun gnus-delete-file (file)
|
||||
"Delete FILE if it exists."
|
||||
(when (file-exists-p file)
|
||||
(delete-file file)))
|
||||
|
||||
(defun gnus-strip-whitespace (string)
|
||||
"Return STRING stripped of all whitespace."
|
||||
(while (string-match "[\r\n\t ]+" string)
|
||||
(setq string (replace-match "" t t string)))
|
||||
string)
|
||||
|
||||
(defun gnus-put-text-property-excluding-newlines (beg end prop val)
|
||||
"The same as `put-text-property', but don't put this prop on any newlines in the region."
|
||||
(save-match-data
|
||||
(save-excursion
|
||||
(save-restriction
|
||||
(goto-char beg)
|
||||
(while (re-search-forward "[ \t]*\n" end 'move)
|
||||
(put-text-property beg (match-beginning 0) prop val)
|
||||
(setq beg (point)))
|
||||
(put-text-property beg (point) prop val)))))
|
||||
|
||||
;;; Protected and atomic operations. dmoore@ucsd.edu 21.11.1996
|
||||
;;; The primary idea here is to try to protect internal datastructures
|
||||
;;; from becoming corrupted when the user hits C-g, or if a hook or
|
||||
;;; similar blows up. Often in Gnus multiple tables/lists need to be
|
||||
;;; updated at the same time, or information can be lost.
|
||||
|
||||
(defvar gnus-atomic-be-safe t
|
||||
"If t, certain operations will be protected from interruption by C-g.")
|
||||
|
||||
(defmacro gnus-atomic-progn (&rest forms)
|
||||
"Evaluate FORMS atomically, which means to protect the evaluation
|
||||
from being interrupted by the user. An error from the forms themselves
|
||||
will return without finishing the operation. Since interrupts from
|
||||
the user are disabled, it is recommended that only the most minimal
|
||||
operations are performed by FORMS. If you wish to assign many
|
||||
complicated values atomically, compute the results into temporary
|
||||
variables and then do only the assignment atomically."
|
||||
`(let ((inhibit-quit gnus-atomic-be-safe))
|
||||
,@forms))
|
||||
|
||||
(put 'gnus-atomic-progn 'lisp-indent-function 0)
|
||||
|
||||
(defmacro gnus-atomic-progn-assign (protect &rest forms)
|
||||
"Evaluate FORMS, but insure that the variables listed in PROTECT
|
||||
are not changed if anything in FORMS signals an error or otherwise
|
||||
non-locally exits. The variables listed in PROTECT are updated atomically.
|
||||
It is safe to use gnus-atomic-progn-assign with long computations.
|
||||
|
||||
Note that if any of the symbols in PROTECT were unbound, they will be
|
||||
set to nil on a sucessful assignment. In case of an error or other
|
||||
non-local exit, it will still be unbound."
|
||||
(let* ((temp-sym-map (mapcar (lambda (x) (list (make-symbol
|
||||
(concat (symbol-name x)
|
||||
"-tmp"))
|
||||
x))
|
||||
protect))
|
||||
(sym-temp-map (mapcar (lambda (x) (list (cadr x) (car x)))
|
||||
temp-sym-map))
|
||||
(temp-sym-let (mapcar (lambda (x) (list (car x)
|
||||
`(and (boundp ',(cadr x))
|
||||
,(cadr x))))
|
||||
temp-sym-map))
|
||||
(sym-temp-let sym-temp-map)
|
||||
(temp-sym-assign (apply 'append temp-sym-map))
|
||||
(sym-temp-assign (apply 'append sym-temp-map))
|
||||
(result (make-symbol "result-tmp")))
|
||||
`(let (,@temp-sym-let
|
||||
,result)
|
||||
(let ,sym-temp-let
|
||||
(setq ,result (progn ,@forms))
|
||||
(setq ,@temp-sym-assign))
|
||||
(let ((inhibit-quit gnus-atomic-be-safe))
|
||||
(setq ,@sym-temp-assign))
|
||||
,result)))
|
||||
|
||||
(put 'gnus-atomic-progn-assign 'lisp-indent-function 1)
|
||||
;(put 'gnus-atomic-progn-assign 'edebug-form-spec '(sexp body))
|
||||
|
||||
(defmacro gnus-atomic-setq (&rest pairs)
|
||||
"Similar to setq, except that the real symbols are only assigned when
|
||||
there are no errors. And when the real symbols are assigned, they are
|
||||
done so atomically. If other variables might be changed via side-effect,
|
||||
see gnus-atomic-progn-assign. It is safe to use gnus-atomic-setq
|
||||
with potentially long computations."
|
||||
(let ((tpairs pairs)
|
||||
syms)
|
||||
(while tpairs
|
||||
(push (car tpairs) syms)
|
||||
(setq tpairs (cddr tpairs)))
|
||||
`(gnus-atomic-progn-assign ,syms
|
||||
(setq ,@pairs))))
|
||||
|
||||
;(put 'gnus-atomic-setq 'edebug-form-spec '(body))
|
||||
|
||||
|
||||
;;; Functions for saving to babyl/mail files.
|
||||
|
||||
(defvar rmail-default-rmail-file)
|
||||
(defun gnus-output-to-rmail (filename &optional ask)
|
||||
"Append the current article to an Rmail file named FILENAME."
|
||||
(require 'rmail)
|
||||
;; Most of these codes are borrowed from rmailout.el.
|
||||
(setq filename (expand-file-name filename))
|
||||
(setq rmail-default-rmail-file filename)
|
||||
(let ((artbuf (current-buffer))
|
||||
(tmpbuf (get-buffer-create " *Gnus-output*")))
|
||||
(save-excursion
|
||||
(or (get-file-buffer filename)
|
||||
(file-exists-p filename)
|
||||
(if (or (not ask)
|
||||
(gnus-yes-or-no-p
|
||||
(concat "\"" filename "\" does not exist, create it? ")))
|
||||
(let ((file-buffer (create-file-buffer filename)))
|
||||
(save-excursion
|
||||
(set-buffer file-buffer)
|
||||
(rmail-insert-rmail-file-header)
|
||||
(let ((require-final-newline nil))
|
||||
(gnus-write-buffer filename)))
|
||||
(kill-buffer file-buffer))
|
||||
(error "Output file does not exist")))
|
||||
(set-buffer tmpbuf)
|
||||
(erase-buffer)
|
||||
(insert-buffer-substring artbuf)
|
||||
(gnus-convert-article-to-rmail)
|
||||
;; Decide whether to append to a file or to an Emacs buffer.
|
||||
(let ((outbuf (get-file-buffer filename)))
|
||||
(if (not outbuf)
|
||||
(append-to-file (point-min) (point-max) filename)
|
||||
;; File has been visited, in buffer OUTBUF.
|
||||
(set-buffer outbuf)
|
||||
(let ((buffer-read-only nil)
|
||||
(msg (and (boundp 'rmail-current-message)
|
||||
(symbol-value 'rmail-current-message))))
|
||||
;; If MSG is non-nil, buffer is in RMAIL mode.
|
||||
(when msg
|
||||
(widen)
|
||||
(narrow-to-region (point-max) (point-max)))
|
||||
(insert-buffer-substring tmpbuf)
|
||||
(when msg
|
||||
(goto-char (point-min))
|
||||
(widen)
|
||||
(search-backward "\^_")
|
||||
(narrow-to-region (point) (point-max))
|
||||
(goto-char (1+ (point-min)))
|
||||
(rmail-count-new-messages t)
|
||||
(rmail-show-message msg))))))
|
||||
(kill-buffer tmpbuf)))
|
||||
|
||||
(defun gnus-output-to-mail (filename &optional ask)
|
||||
"Append the current article to a mail file named FILENAME."
|
||||
(setq filename (expand-file-name filename))
|
||||
(let ((artbuf (current-buffer))
|
||||
(tmpbuf (get-buffer-create " *Gnus-output*")))
|
||||
(save-excursion
|
||||
;; Create the file, if it doesn't exist.
|
||||
(when (and (not (get-file-buffer filename))
|
||||
(not (file-exists-p filename)))
|
||||
(if (or (not ask)
|
||||
(gnus-y-or-n-p
|
||||
(concat "\"" filename "\" does not exist, create it? ")))
|
||||
(let ((file-buffer (create-file-buffer filename)))
|
||||
(save-excursion
|
||||
(set-buffer file-buffer)
|
||||
(let ((require-final-newline nil))
|
||||
(gnus-write-buffer filename)))
|
||||
(kill-buffer file-buffer))
|
||||
(error "Output file does not exist")))
|
||||
(set-buffer tmpbuf)
|
||||
(erase-buffer)
|
||||
(insert-buffer-substring artbuf)
|
||||
(goto-char (point-min))
|
||||
(if (looking-at "From ")
|
||||
(forward-line 1)
|
||||
(insert "From nobody " (current-time-string) "\n"))
|
||||
(let (case-fold-search)
|
||||
(while (re-search-forward "^From " nil t)
|
||||
(beginning-of-line)
|
||||
(insert ">")))
|
||||
;; Decide whether to append to a file or to an Emacs buffer.
|
||||
(let ((outbuf (get-file-buffer filename)))
|
||||
(if (not outbuf)
|
||||
(let ((buffer-read-only nil))
|
||||
(save-excursion
|
||||
(goto-char (point-max))
|
||||
(forward-char -2)
|
||||
(unless (looking-at "\n\n")
|
||||
(goto-char (point-max))
|
||||
(unless (bolp)
|
||||
(insert "\n"))
|
||||
(insert "\n"))
|
||||
(goto-char (point-max))
|
||||
(append-to-file (point-min) (point-max) filename)))
|
||||
;; File has been visited, in buffer OUTBUF.
|
||||
(set-buffer outbuf)
|
||||
(let ((buffer-read-only nil))
|
||||
(goto-char (point-max))
|
||||
(unless (eobp)
|
||||
(insert "\n"))
|
||||
(insert "\n")
|
||||
(insert-buffer-substring tmpbuf)))))
|
||||
(kill-buffer tmpbuf)))
|
||||
|
||||
(defun gnus-convert-article-to-rmail ()
|
||||
"Convert article in current buffer to Rmail message format."
|
||||
(let ((buffer-read-only nil))
|
||||
;; Convert article directly into Babyl format.
|
||||
(goto-char (point-min))
|
||||
(insert "\^L\n0, unseen,,\n*** EOOH ***\n")
|
||||
(while (search-forward "\n\^_" nil t) ;single char
|
||||
(replace-match "\n^_" t t)) ;2 chars: "^" and "_"
|
||||
(goto-char (point-max))
|
||||
(insert "\^_")))
|
||||
|
||||
(provide 'gnus-util)
|
||||
|
||||
;;; gnus-util.el ends here
|
||||
2063
lisp/gnus/gnus-uu.el
Normal file
2063
lisp/gnus/gnus-uu.el
Normal file
File diff suppressed because it is too large
Load diff
107
lisp/gnus/gnus-vm.el
Normal file
107
lisp/gnus/gnus-vm.el
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
;;; gnus-vm.el --- vm interface for Gnus
|
||||
;; Copyright (C) 1994,95,96,97 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Per Persson <pp@gnu.ai.mit.edu>
|
||||
;; Keywords: news, mail
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; Major contributors:
|
||||
;; Christian Limpach <Christian.Limpach@nice.ch>
|
||||
;; Some code stolen from:
|
||||
;; Rick Sladkey <jrs@world.std.com>
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'sendmail)
|
||||
(require 'message)
|
||||
(require 'gnus)
|
||||
(require 'gnus-msg)
|
||||
|
||||
(eval-when-compile
|
||||
(autoload 'vm-mode "vm")
|
||||
(autoload 'vm-save-message "vm")
|
||||
(autoload 'vm-forward-message "vm")
|
||||
(autoload 'vm-reply "vm")
|
||||
(autoload 'vm-mail "vm"))
|
||||
|
||||
(defvar gnus-vm-inhibit-window-system nil
|
||||
"Inhibit loading `win-vm' if using a window-system.
|
||||
Has to be set before gnus-vm is loaded.")
|
||||
|
||||
(or gnus-vm-inhibit-window-system
|
||||
(condition-case nil
|
||||
(when window-system
|
||||
(require 'win-vm))
|
||||
(error nil)))
|
||||
|
||||
(when (not (featurep 'vm))
|
||||
(load "vm"))
|
||||
|
||||
(defun gnus-vm-make-folder (&optional buffer)
|
||||
(let ((article (or buffer (current-buffer)))
|
||||
(tmp-folder (generate-new-buffer " *tmp-folder*"))
|
||||
(start (point-min))
|
||||
(end (point-max)))
|
||||
(set-buffer tmp-folder)
|
||||
(insert-buffer-substring article start end)
|
||||
(goto-char (point-min))
|
||||
(if (looking-at "^\\(From [^ ]+ \\).*$")
|
||||
(replace-match (concat "\\1" (current-time-string)))
|
||||
(insert "From " gnus-newsgroup-name " "
|
||||
(current-time-string) "\n"))
|
||||
(while (re-search-forward "\n\nFrom " nil t)
|
||||
(replace-match "\n\n>From "))
|
||||
;; insert a newline, otherwise the last line gets lost
|
||||
(goto-char (point-max))
|
||||
(insert "\n")
|
||||
(vm-mode)
|
||||
tmp-folder))
|
||||
|
||||
(defun gnus-summary-save-article-vm (&optional arg)
|
||||
"Append the current article to a vm folder.
|
||||
If N is a positive number, save the N next articles.
|
||||
If N is a negative number, save the N previous articles.
|
||||
If N is nil and any articles have been marked with the process mark,
|
||||
save those articles instead."
|
||||
(interactive "P")
|
||||
(let ((gnus-default-article-saver 'gnus-summary-save-in-vm))
|
||||
(gnus-summary-save-article arg)))
|
||||
|
||||
(defun gnus-summary-save-in-vm (&optional folder)
|
||||
(interactive)
|
||||
(setq folder
|
||||
(cond ((eq folder 'default) default-name)
|
||||
(folder folder)
|
||||
(t (gnus-read-save-file-name
|
||||
"Save %s in VM folder:" folder
|
||||
gnus-mail-save-name gnus-newsgroup-name
|
||||
gnus-current-headers 'gnus-newsgroup-last-mail))))
|
||||
(gnus-eval-in-buffer-window gnus-original-article-buffer
|
||||
(save-excursion
|
||||
(save-restriction
|
||||
(widen)
|
||||
(let ((vm-folder (gnus-vm-make-folder)))
|
||||
(vm-save-message folder)
|
||||
(kill-buffer vm-folder))))))
|
||||
|
||||
(provide 'gnus-vm)
|
||||
|
||||
;;; gnus-vm.el ends here.
|
||||
550
lisp/gnus/gnus-win.el
Normal file
550
lisp/gnus/gnus-win.el
Normal file
|
|
@ -0,0 +1,550 @@
|
|||
;;; gnus-win.el --- window configuration functions for Gnus
|
||||
;; Copyright (C) 1996,97 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
|
||||
;; Keywords: news
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'gnus)
|
||||
|
||||
(defgroup gnus-windows nil
|
||||
"Window configuration."
|
||||
:group 'gnus)
|
||||
|
||||
(defcustom gnus-use-full-window t
|
||||
"*If non-nil, use the entire Emacs screen."
|
||||
:group 'gnus-windows
|
||||
:type 'boolean)
|
||||
|
||||
(defvar gnus-window-configuration nil
|
||||
"Obsolete variable. See `gnus-buffer-configuration'.")
|
||||
|
||||
(defcustom gnus-window-min-width 2
|
||||
"*Minimum width of Gnus buffers."
|
||||
:group 'gnus-windows
|
||||
:type 'integer)
|
||||
|
||||
(defcustom gnus-window-min-height 1
|
||||
"*Minimum height of Gnus buffers."
|
||||
:group 'gnus-windows
|
||||
:type 'integer)
|
||||
|
||||
(defcustom gnus-always-force-window-configuration nil
|
||||
"*If non-nil, always force the Gnus window configurations."
|
||||
:group 'gnus-windows
|
||||
:type 'boolean)
|
||||
|
||||
(defvar gnus-buffer-configuration
|
||||
'((group
|
||||
(vertical 1.0
|
||||
(group 1.0 point)
|
||||
(if gnus-carpal '(group-carpal 4))))
|
||||
(summary
|
||||
(vertical 1.0
|
||||
(summary 1.0 point)
|
||||
(if gnus-carpal '(summary-carpal 4))))
|
||||
(article
|
||||
(cond
|
||||
((and gnus-use-picons
|
||||
(eq gnus-picons-display-where 'picons))
|
||||
'(frame 1.0
|
||||
(vertical 1.0
|
||||
(summary 0.25 point)
|
||||
(if gnus-carpal '(summary-carpal 4))
|
||||
(article 1.0))
|
||||
(vertical ((height . 5) (width . 15)
|
||||
(user-position . t)
|
||||
(left . -1) (top . 1))
|
||||
(picons 1.0))))
|
||||
(gnus-use-trees
|
||||
'(vertical 1.0
|
||||
(summary 0.25 point)
|
||||
(tree 0.25)
|
||||
(article 1.0)))
|
||||
(t
|
||||
'(vertical 1.0
|
||||
(summary 0.25 point)
|
||||
(if gnus-carpal '(summary-carpal 4))
|
||||
(article 1.0)))))
|
||||
(server
|
||||
(vertical 1.0
|
||||
(server 1.0 point)
|
||||
(if gnus-carpal '(server-carpal 2))))
|
||||
(browse
|
||||
(vertical 1.0
|
||||
(browse 1.0 point)
|
||||
(if gnus-carpal '(browse-carpal 2))))
|
||||
(message
|
||||
(vertical 1.0
|
||||
(message 1.0 point)))
|
||||
(pick
|
||||
(vertical 1.0
|
||||
(article 1.0 point)))
|
||||
(info
|
||||
(vertical 1.0
|
||||
(info 1.0 point)))
|
||||
(summary-faq
|
||||
(vertical 1.0
|
||||
(summary 0.25)
|
||||
(faq 1.0 point)))
|
||||
(edit-article
|
||||
(vertical 1.0
|
||||
(article 1.0 point)))
|
||||
(edit-form
|
||||
(vertical 1.0
|
||||
(group 0.5)
|
||||
(edit-form 1.0 point)))
|
||||
(edit-score
|
||||
(vertical 1.0
|
||||
(summary 0.25)
|
||||
(edit-score 1.0 point)))
|
||||
(post
|
||||
(vertical 1.0
|
||||
(post 1.0 point)))
|
||||
(reply
|
||||
(vertical 1.0
|
||||
(article-copy 0.5)
|
||||
(message 1.0 point)))
|
||||
(forward
|
||||
(vertical 1.0
|
||||
(message 1.0 point)))
|
||||
(reply-yank
|
||||
(vertical 1.0
|
||||
(message 1.0 point)))
|
||||
(mail-bounce
|
||||
(vertical 1.0
|
||||
(article 0.5)
|
||||
(message 1.0 point)))
|
||||
(draft
|
||||
(vertical 1.0
|
||||
(draft 1.0 point)))
|
||||
(pipe
|
||||
(vertical 1.0
|
||||
(summary 0.25 point)
|
||||
(if gnus-carpal '(summary-carpal 4))
|
||||
("*Shell Command Output*" 1.0)))
|
||||
(bug
|
||||
(vertical 1.0
|
||||
("*Gnus Help Bug*" 0.5)
|
||||
("*Gnus Bug*" 1.0 point)))
|
||||
(score-trace
|
||||
(vertical 1.0
|
||||
(summary 0.5 point)
|
||||
("*Score Trace*" 1.0)))
|
||||
(score-words
|
||||
(vertical 1.0
|
||||
(summary 0.5 point)
|
||||
("*Score Words*" 1.0)))
|
||||
(compose-bounce
|
||||
(vertical 1.0
|
||||
(article 0.5)
|
||||
(message 1.0 point))))
|
||||
"Window configuration for all possible Gnus buffers.
|
||||
See the Gnus manual for an explanation of the syntax used.")
|
||||
|
||||
(defvar gnus-window-to-buffer
|
||||
'((group . gnus-group-buffer)
|
||||
(summary . gnus-summary-buffer)
|
||||
(article . gnus-article-buffer)
|
||||
(server . gnus-server-buffer)
|
||||
(browse . "*Gnus Browse Server*")
|
||||
(edit-group . gnus-group-edit-buffer)
|
||||
(edit-form . gnus-edit-form-buffer)
|
||||
(edit-server . gnus-server-edit-buffer)
|
||||
(group-carpal . gnus-carpal-group-buffer)
|
||||
(summary-carpal . gnus-carpal-summary-buffer)
|
||||
(server-carpal . gnus-carpal-server-buffer)
|
||||
(browse-carpal . gnus-carpal-browse-buffer)
|
||||
(edit-score . gnus-score-edit-buffer)
|
||||
(message . gnus-message-buffer)
|
||||
(mail . gnus-message-buffer)
|
||||
(post-news . gnus-message-buffer)
|
||||
(faq . gnus-faq-buffer)
|
||||
(picons . "*Picons*")
|
||||
(tree . gnus-tree-buffer)
|
||||
(info . gnus-info-buffer)
|
||||
(article-copy . gnus-article-copy)
|
||||
(draft . gnus-draft-buffer))
|
||||
"Mapping from short symbols to buffer names or buffer variables.")
|
||||
|
||||
;;; Internal variables.
|
||||
|
||||
(defvar gnus-current-window-configuration nil
|
||||
"The most recently set window configuration.")
|
||||
|
||||
(defvar gnus-created-frames nil)
|
||||
|
||||
(defun gnus-kill-gnus-frames ()
|
||||
"Kill all frames Gnus has created."
|
||||
(while gnus-created-frames
|
||||
(when (frame-live-p (car gnus-created-frames))
|
||||
;; We slap a condition-case around this `delete-frame' to ensure
|
||||
;; against errors if we try do delete the single frame that's left.
|
||||
(ignore-errors
|
||||
(delete-frame (car gnus-created-frames))))
|
||||
(pop gnus-created-frames)))
|
||||
|
||||
(defun gnus-window-configuration-element (list)
|
||||
(while (and list
|
||||
(not (assq (car list) gnus-window-configuration)))
|
||||
(pop list))
|
||||
(cadr (assq (car list) gnus-window-configuration)))
|
||||
|
||||
(defun gnus-windows-old-to-new (setting)
|
||||
;; First we take care of the really, really old Gnus 3 actions.
|
||||
(when (symbolp setting)
|
||||
(setq setting
|
||||
;; Take care of ooold GNUS 3.x values.
|
||||
(cond ((eq setting 'SelectArticle) 'article)
|
||||
((memq setting '(SelectNewsgroup SelectSubject ExpandSubject))
|
||||
'summary)
|
||||
((memq setting '(ExitNewsgroup)) 'group)
|
||||
(t setting))))
|
||||
(if (or (listp setting)
|
||||
(not (and gnus-window-configuration
|
||||
(memq setting '(group summary article)))))
|
||||
setting
|
||||
(let* ((elem
|
||||
(cond
|
||||
((eq setting 'group)
|
||||
(gnus-window-configuration-element
|
||||
'(group newsgroups ExitNewsgroup)))
|
||||
((eq setting 'summary)
|
||||
(gnus-window-configuration-element
|
||||
'(summary SelectNewsgroup SelectSubject ExpandSubject)))
|
||||
((eq setting 'article)
|
||||
(gnus-window-configuration-element
|
||||
'(article SelectArticle)))))
|
||||
(total (apply '+ elem))
|
||||
(types '(group summary article))
|
||||
(pbuf (if (eq setting 'newsgroups) 'group 'summary))
|
||||
(i 0)
|
||||
perc out)
|
||||
(while (< i 3)
|
||||
(or (not (numberp (nth i elem)))
|
||||
(zerop (nth i elem))
|
||||
(progn
|
||||
(setq perc (if (= i 2)
|
||||
1.0
|
||||
(/ (float (nth i elem)) total)))
|
||||
(push (if (eq pbuf (nth i types))
|
||||
(list (nth i types) perc 'point)
|
||||
(list (nth i types) perc))
|
||||
out)))
|
||||
(incf i))
|
||||
`(vertical 1.0 ,@(nreverse out)))))
|
||||
|
||||
;;;###autoload
|
||||
(defun gnus-add-configuration (conf)
|
||||
"Add the window configuration CONF to `gnus-buffer-configuration'."
|
||||
(setq gnus-buffer-configuration
|
||||
(cons conf (delq (assq (car conf) gnus-buffer-configuration)
|
||||
gnus-buffer-configuration))))
|
||||
|
||||
(defvar gnus-frame-list nil)
|
||||
|
||||
(defun gnus-configure-frame (split &optional window)
|
||||
"Split WINDOW according to SPLIT."
|
||||
(unless window
|
||||
(setq window (get-buffer-window (current-buffer))))
|
||||
(select-window window)
|
||||
;; This might be an old-stylee buffer config.
|
||||
(when (vectorp split)
|
||||
(setq split (append split nil)))
|
||||
(when (or (consp (car split))
|
||||
(vectorp (car split)))
|
||||
(push 1.0 split)
|
||||
(push 'vertical split))
|
||||
;; The SPLIT might be something that is to be evaled to
|
||||
;; return a new SPLIT.
|
||||
(while (and (not (assq (car split) gnus-window-to-buffer))
|
||||
(gnus-functionp (car split)))
|
||||
(setq split (eval split)))
|
||||
(let* ((type (car split))
|
||||
(subs (cddr split))
|
||||
(len (if (eq type 'horizontal) (window-width) (window-height)))
|
||||
(total 0)
|
||||
(window-min-width (or gnus-window-min-width window-min-width))
|
||||
(window-min-height (or gnus-window-min-height window-min-height))
|
||||
s result new-win rest comp-subs size sub)
|
||||
(cond
|
||||
;; Nothing to do here.
|
||||
((null split))
|
||||
;; Don't switch buffers.
|
||||
((null type)
|
||||
(and (memq 'point split) window))
|
||||
;; This is a buffer to be selected.
|
||||
((not (memq type '(frame horizontal vertical)))
|
||||
(let ((buffer (cond ((stringp type) type)
|
||||
(t (cdr (assq type gnus-window-to-buffer)))))
|
||||
buf)
|
||||
(unless buffer
|
||||
(error "Illegal buffer type: %s" type))
|
||||
(unless (setq buf (get-buffer (if (symbolp buffer)
|
||||
(symbol-value buffer) buffer)))
|
||||
(setq buf (get-buffer-create (if (symbolp buffer)
|
||||
(symbol-value buffer) buffer))))
|
||||
(switch-to-buffer buf)
|
||||
;; We return the window if it has the `point' spec.
|
||||
(and (memq 'point split) window)))
|
||||
;; This is a frame split.
|
||||
((eq type 'frame)
|
||||
(unless gnus-frame-list
|
||||
(setq gnus-frame-list (list (window-frame
|
||||
(get-buffer-window (current-buffer))))))
|
||||
(let ((i 0)
|
||||
params frame fresult)
|
||||
(while (< i (length subs))
|
||||
;; Frame parameter is gotten from the sub-split.
|
||||
(setq params (cadr (elt subs i)))
|
||||
;; It should be a list.
|
||||
(unless (listp params)
|
||||
(setq params nil))
|
||||
;; Create a new frame?
|
||||
(unless (setq frame (elt gnus-frame-list i))
|
||||
(nconc gnus-frame-list (list (setq frame (make-frame params))))
|
||||
(push frame gnus-created-frames))
|
||||
;; Is the old frame still alive?
|
||||
(unless (frame-live-p frame)
|
||||
(setcar (nthcdr i gnus-frame-list)
|
||||
(setq frame (make-frame params))))
|
||||
;; Select the frame in question and do more splits there.
|
||||
(select-frame frame)
|
||||
(setq fresult (or (gnus-configure-frame (elt subs i)) fresult))
|
||||
(incf i))
|
||||
;; Select the frame that has the selected buffer.
|
||||
(when fresult
|
||||
(select-frame (window-frame fresult)))))
|
||||
;; This is a normal split.
|
||||
(t
|
||||
(when (> (length subs) 0)
|
||||
;; First we have to compute the sizes of all new windows.
|
||||
(while subs
|
||||
(setq sub (append (pop subs) nil))
|
||||
(while (and (not (assq (car sub) gnus-window-to-buffer))
|
||||
(gnus-functionp (car sub)))
|
||||
(setq sub (eval sub)))
|
||||
(when sub
|
||||
(push sub comp-subs)
|
||||
(setq size (cadar comp-subs))
|
||||
(cond ((equal size 1.0)
|
||||
(setq rest (car comp-subs))
|
||||
(setq s 0))
|
||||
((floatp size)
|
||||
(setq s (floor (* size len))))
|
||||
((integerp size)
|
||||
(setq s size))
|
||||
(t
|
||||
(error "Illegal size: %s" size)))
|
||||
;; Try to make sure that we are inside the safe limits.
|
||||
(cond ((zerop s))
|
||||
((eq type 'horizontal)
|
||||
(setq s (max s window-min-width)))
|
||||
((eq type 'vertical)
|
||||
(setq s (max s window-min-height))))
|
||||
(setcar (cdar comp-subs) s)
|
||||
(incf total s)))
|
||||
;; Take care of the "1.0" spec.
|
||||
(if rest
|
||||
(setcar (cdr rest) (- len total))
|
||||
(error "No 1.0 specs in %s" split))
|
||||
;; The we do the actual splitting in a nice recursive
|
||||
;; fashion.
|
||||
(setq comp-subs (nreverse comp-subs))
|
||||
(while comp-subs
|
||||
(if (null (cdr comp-subs))
|
||||
(setq new-win window)
|
||||
(setq new-win
|
||||
(split-window window (cadar comp-subs)
|
||||
(eq type 'horizontal))))
|
||||
(setq result (or (gnus-configure-frame
|
||||
(car comp-subs) window)
|
||||
result))
|
||||
(select-window new-win)
|
||||
(setq window new-win)
|
||||
(setq comp-subs (cdr comp-subs))))
|
||||
;; Return the proper window, if any.
|
||||
(when result
|
||||
(select-window result))))))
|
||||
|
||||
(defvar gnus-frame-split-p nil)
|
||||
|
||||
(defun gnus-configure-windows (setting &optional force)
|
||||
(setq gnus-current-window-configuration setting)
|
||||
(setq force (or force gnus-always-force-window-configuration))
|
||||
(setq setting (gnus-windows-old-to-new setting))
|
||||
(let ((split (if (symbolp setting)
|
||||
(cadr (assq setting gnus-buffer-configuration))
|
||||
setting))
|
||||
all-visible)
|
||||
|
||||
(setq gnus-frame-split-p nil)
|
||||
|
||||
(unless split
|
||||
(error "No such setting: %s" setting))
|
||||
|
||||
(if (and (setq all-visible (gnus-all-windows-visible-p split))
|
||||
(not force))
|
||||
;; All the windows mentioned are already visible, so we just
|
||||
;; put point in the assigned buffer, and do not touch the
|
||||
;; winconf.
|
||||
(select-window all-visible)
|
||||
|
||||
;; Either remove all windows or just remove all Gnus windows.
|
||||
(let ((frame (selected-frame)))
|
||||
(unwind-protect
|
||||
(if gnus-use-full-window
|
||||
;; We want to remove all other windows.
|
||||
(if (not gnus-frame-split-p)
|
||||
;; This is not a `frame' split, so we ignore the
|
||||
;; other frames.
|
||||
(delete-other-windows)
|
||||
;; This is a `frame' split, so we delete all windows
|
||||
;; on all frames.
|
||||
(gnus-delete-windows-in-gnusey-frames))
|
||||
;; Just remove some windows.
|
||||
(gnus-remove-some-windows)
|
||||
(switch-to-buffer nntp-server-buffer))
|
||||
(select-frame frame)))
|
||||
|
||||
(switch-to-buffer nntp-server-buffer)
|
||||
(gnus-configure-frame split (get-buffer-window (current-buffer))))))
|
||||
|
||||
(defun gnus-delete-windows-in-gnusey-frames ()
|
||||
"Do a `delete-other-windows' in all frames that have Gnus windows."
|
||||
(let ((buffers
|
||||
(mapcar
|
||||
(lambda (elem)
|
||||
(if (symbolp (cdr elem))
|
||||
(when (and (boundp (cdr elem))
|
||||
(symbol-value (cdr elem)))
|
||||
(get-buffer (symbol-value (cdr elem))))
|
||||
(when (cdr elem)
|
||||
(get-buffer (cdr elem)))))
|
||||
gnus-window-to-buffer)))
|
||||
(mapcar
|
||||
(lambda (frame)
|
||||
(unless (eq (cdr (assq 'minibuffer
|
||||
(frame-parameters frame)))
|
||||
'only)
|
||||
(select-frame frame)
|
||||
(let (do-delete)
|
||||
(walk-windows
|
||||
(lambda (window)
|
||||
(when (memq (window-buffer window) buffers)
|
||||
(setq do-delete t))))
|
||||
(when do-delete
|
||||
(delete-other-windows)))))
|
||||
(frame-list))))
|
||||
|
||||
(defun gnus-all-windows-visible-p (split)
|
||||
"Say whether all buffers in SPLIT are currently visible.
|
||||
In particular, the value returned will be the window that
|
||||
should have point."
|
||||
(let ((stack (list split))
|
||||
(all-visible t)
|
||||
type buffer win buf)
|
||||
(while (and (setq split (pop stack))
|
||||
all-visible)
|
||||
;; Be backwards compatible.
|
||||
(when (vectorp split)
|
||||
(setq split (append split nil)))
|
||||
(when (or (consp (car split))
|
||||
(vectorp (car split)))
|
||||
(push 1.0 split)
|
||||
(push 'vertical split))
|
||||
;; The SPLIT might be something that is to be evaled to
|
||||
;; return a new SPLIT.
|
||||
(while (and (not (assq (car split) gnus-window-to-buffer))
|
||||
(gnus-functionp (car split)))
|
||||
(setq split (eval split)))
|
||||
|
||||
(setq type (elt split 0))
|
||||
(cond
|
||||
;; Nothing here.
|
||||
((null split) t)
|
||||
;; A buffer.
|
||||
((not (memq type '(horizontal vertical frame)))
|
||||
(setq buffer (cond ((stringp type) type)
|
||||
(t (cdr (assq type gnus-window-to-buffer)))))
|
||||
(unless buffer
|
||||
(error "Illegal buffer type: %s" type))
|
||||
(when (setq buf (get-buffer (if (symbolp buffer)
|
||||
(symbol-value buffer)
|
||||
buffer)))
|
||||
(setq win (get-buffer-window buf t)))
|
||||
(if win
|
||||
(when (memq 'point split)
|
||||
(setq all-visible win))
|
||||
(setq all-visible nil)))
|
||||
(t
|
||||
(when (eq type 'frame)
|
||||
(setq gnus-frame-split-p t))
|
||||
(setq stack (append (cddr split) stack)))))
|
||||
(unless (eq all-visible t)
|
||||
all-visible)))
|
||||
|
||||
(defun gnus-window-top-edge (&optional window)
|
||||
(nth 1 (window-edges window)))
|
||||
|
||||
(defun gnus-remove-some-windows ()
|
||||
(let ((buffers gnus-window-to-buffer)
|
||||
buf bufs lowest-buf lowest)
|
||||
(save-excursion
|
||||
;; Remove windows on all known Gnus buffers.
|
||||
(while buffers
|
||||
(setq buf (cdar buffers))
|
||||
(when (symbolp buf)
|
||||
(setq buf (and (boundp buf) (symbol-value buf))))
|
||||
(and buf
|
||||
(get-buffer-window buf)
|
||||
(progn
|
||||
(push buf bufs)
|
||||
(pop-to-buffer buf)
|
||||
(when (or (not lowest)
|
||||
(< (gnus-window-top-edge) lowest))
|
||||
(setq lowest (gnus-window-top-edge))
|
||||
(setq lowest-buf buf))))
|
||||
(setq buffers (cdr buffers)))
|
||||
;; Remove windows on *all* summary buffers.
|
||||
(walk-windows
|
||||
(lambda (win)
|
||||
(let ((buf (window-buffer win)))
|
||||
(when (string-match "^\\*Summary" (buffer-name buf))
|
||||
(push buf bufs)
|
||||
(pop-to-buffer buf)
|
||||
(when (or (not lowest)
|
||||
(< (gnus-window-top-edge) lowest))
|
||||
(setq lowest-buf buf)
|
||||
(setq lowest (gnus-window-top-edge)))))))
|
||||
(when lowest-buf
|
||||
(pop-to-buffer lowest-buf)
|
||||
(switch-to-buffer nntp-server-buffer))
|
||||
(while bufs
|
||||
(when (not (eq (car bufs) lowest-buf))
|
||||
(delete-windows-on (car bufs)))
|
||||
(setq bufs (cdr bufs))))))
|
||||
|
||||
(provide 'gnus-win)
|
||||
|
||||
;;; gnus-win.el ends here
|
||||
2569
lisp/gnus/gnus.el
Normal file
2569
lisp/gnus/gnus.el
Normal file
File diff suppressed because it is too large
Load diff
409
lisp/gnus/md5.el
Normal file
409
lisp/gnus/md5.el
Normal file
|
|
@ -0,0 +1,409 @@
|
|||
;;; md5.el -- MD5 Message Digest Algorithm
|
||||
;;; Gareth Rees <gdr11@cl.cam.ac.uk>
|
||||
|
||||
;; LCD Archive Entry:
|
||||
;; md5|Gareth Rees|gdr11@cl.cam.ac.uk|
|
||||
;; MD5 cryptographic message digest algorithm|
|
||||
;; 13-Nov-95|1.0|~/misc/md5.el.Z|
|
||||
|
||||
;;; Details: ------------------------------------------------------------------
|
||||
|
||||
;; This is a direct translation into Emacs LISP of the reference C
|
||||
;; implementation of the MD5 Message-Digest Algorithm written by RSA
|
||||
;; Data Security, Inc.
|
||||
;;
|
||||
;; The algorithm takes a message (that is, a string of bytes) and
|
||||
;; computes a 16-byte checksum or "digest" for the message. This digest
|
||||
;; is supposed to be cryptographically strong in the sense that if you
|
||||
;; are given a 16-byte digest D, then there is no easier way to
|
||||
;; construct a message whose digest is D than to exhaustively search the
|
||||
;; space of messages. However, the robustness of the algorithm has not
|
||||
;; been proven, and a similar algorithm (MD4) was shown to be unsound,
|
||||
;; so treat with caution!
|
||||
;;
|
||||
;; The C algorithm uses 32-bit integers; because GNU Emacs
|
||||
;; implementations provide 28-bit integers (with 24-bit integers on
|
||||
;; versions prior to 19.29), the code represents a 32-bit integer as the
|
||||
;; cons of two 16-bit integers. The most significant word is stored in
|
||||
;; the car and the least significant in the cdr. The algorithm requires
|
||||
;; at least 17 bits of integer representation in order to represent the
|
||||
;; carry from a 16-bit addition.
|
||||
|
||||
;;; Usage: --------------------------------------------------------------------
|
||||
|
||||
;; To compute the MD5 Message Digest for a message M (represented as a
|
||||
;; string or as a vector of bytes), call
|
||||
;;
|
||||
;; (md5-encode M)
|
||||
;;
|
||||
;; which returns the message digest as a vector of 16 bytes. If you
|
||||
;; need to supply the message in pieces M1, M2, ... Mn, then call
|
||||
;;
|
||||
;; (md5-init)
|
||||
;; (md5-update M1)
|
||||
;; (md5-update M2)
|
||||
;; ...
|
||||
;; (md5-update Mn)
|
||||
;; (md5-final)
|
||||
|
||||
;;; Copyright and licence: ----------------------------------------------------
|
||||
|
||||
;; Copyright (C) 1995 by Gareth Rees
|
||||
;; Derived from the RSA Data Security, Inc. MD5 Message-Digest Algorithm
|
||||
;;
|
||||
;; md5.el is free software; you can redistribute it and/or modify it
|
||||
;; under the terms of the GNU General Public License as published by the
|
||||
;; Free Software Foundation; either version 2, or (at your option) any
|
||||
;; later version.
|
||||
;;
|
||||
;; md5.el is distributed in the hope that it will be useful, but WITHOUT
|
||||
;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
;; FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
;; for more details.
|
||||
;;
|
||||
;; The original copyright notice is given below, as required by the
|
||||
;; licence for the original code. This code is distributed under *both*
|
||||
;; RSA's original licence and the GNU General Public Licence. (There
|
||||
;; should be no problems, as the former is more liberal than the
|
||||
;; latter).
|
||||
|
||||
;;; Original copyright notice: ------------------------------------------------
|
||||
|
||||
;; Copyright (C) 1990, RSA Data Security, Inc. All rights reserved.
|
||||
;;
|
||||
;; License to copy and use this software is granted provided that it is
|
||||
;; identified as the "RSA Data Security, Inc. MD5 Message- Digest
|
||||
;; Algorithm" in all material mentioning or referencing this software or
|
||||
;; this function.
|
||||
;;
|
||||
;; License is also granted to make and use derivative works provided
|
||||
;; that such works are identified as "derived from the RSA Data
|
||||
;; Security, Inc. MD5 Message-Digest Algorithm" in all material
|
||||
;; mentioning or referencing the derived work.
|
||||
;;
|
||||
;; RSA Data Security, Inc. makes no representations concerning either
|
||||
;; the merchantability of this software or the suitability of this
|
||||
;; software for any particular purpose. It is provided "as is" without
|
||||
;; express or implied warranty of any kind.
|
||||
;;
|
||||
;; These notices must be retained in any copies of any part of this
|
||||
;; documentation and/or software.
|
||||
|
||||
;;; Code: ---------------------------------------------------------------------
|
||||
|
||||
(defvar md5-program "md5"
|
||||
"*Program that reads a message on its standard input and writes an
|
||||
MD5 digest on its output.")
|
||||
|
||||
(defvar md5-maximum-internal-length 4096
|
||||
"*The maximum size of a piece of data that should use the MD5 routines
|
||||
written in lisp. If a message exceeds this, it will be run through an
|
||||
external filter for processing. Also see the `md5-program' variable.
|
||||
This variable has no effect if you call the md5-init|update|final
|
||||
functions - only used by the `md5' function's simpler interface.")
|
||||
|
||||
(defvar md5-bits (make-vector 4 0)
|
||||
"Number of bits handled, modulo 2^64.
|
||||
Represented as four 16-bit numbers, least significant first.")
|
||||
(defvar md5-buffer (make-vector 4 '(0 . 0))
|
||||
"Scratch buffer (four 32-bit integers).")
|
||||
(defvar md5-input (make-vector 64 0)
|
||||
"Input buffer (64 bytes).")
|
||||
|
||||
(defun md5-unhex (x)
|
||||
(if (> x ?9)
|
||||
(if (>= x ?a)
|
||||
(+ 10 (- x ?a))
|
||||
(+ 10 (- x ?A)))
|
||||
(- x ?0)))
|
||||
|
||||
(defun md5-encode (message)
|
||||
"Encodes MESSAGE using the MD5 message digest algorithm.
|
||||
MESSAGE must be a string or an array of bytes.
|
||||
Returns a vector of 16 bytes containing the message digest."
|
||||
(if (<= (length message) md5-maximum-internal-length)
|
||||
(progn
|
||||
(md5-init)
|
||||
(md5-update message)
|
||||
(md5-final))
|
||||
(save-excursion
|
||||
(set-buffer (get-buffer-create " *md5-work*"))
|
||||
(erase-buffer)
|
||||
(insert message)
|
||||
(call-process-region (point-min) (point-max)
|
||||
(or shell-file-name "/bin/sh")
|
||||
t (current-buffer) nil
|
||||
"-c" md5-program)
|
||||
;; MD5 digest is 32 chars long
|
||||
;; mddriver adds a newline to make neaten output for tty
|
||||
;; viewing, make sure we leave it behind.
|
||||
(let ((data (buffer-substring (point-min) (+ (point-min) 32)))
|
||||
(vec (make-vector 16 0))
|
||||
(ctr 0))
|
||||
(while (< ctr 16)
|
||||
(aset vec ctr (+ (* 16 (md5-unhex (aref data (* ctr 2))))
|
||||
(md5-unhex (aref data (1+ (* ctr 2))))))
|
||||
(setq ctr (1+ ctr)))))))
|
||||
|
||||
(defsubst md5-add (x y)
|
||||
"Return 32-bit sum of 32-bit integers X and Y."
|
||||
(let ((m (+ (car x) (car y)))
|
||||
(l (+ (cdr x) (cdr y))))
|
||||
(cons (logand 65535 (+ m (lsh l -16))) (logand l 65535))))
|
||||
|
||||
;; FF, GG, HH and II are basic MD5 functions, providing transformations
|
||||
;; for rounds 1, 2, 3 and 4 respectively. Each function follows this
|
||||
;; pattern of computation (where ROTATE(x,y) means rotate 32-bit value x
|
||||
;; by y bits to the left):
|
||||
;;
|
||||
;; FF(a,b,c,d,x,s,ac) = ROTATE(a + F(b,c,d) + x + ac,s) + b
|
||||
;;
|
||||
;; so we use the macro `md5-make-step' to construct each one. The
|
||||
;; helper functions F, G, H and I operate on 16-bit numbers; the full
|
||||
;; operation splits its inputs, operates on the halves separately and
|
||||
;; then puts the results together.
|
||||
|
||||
(defsubst md5-F (x y z) (logior (logand x y) (logand (lognot x) z)))
|
||||
(defsubst md5-G (x y z) (logior (logand x z) (logand y (lognot z))))
|
||||
(defsubst md5-H (x y z) (logxor x y z))
|
||||
(defsubst md5-I (x y z) (logxor y (logior x (logand 65535 (lognot z)))))
|
||||
|
||||
(defmacro md5-make-step (name func)
|
||||
(`
|
||||
(defun (, name) (a b c d x s ac)
|
||||
(let*
|
||||
((m1 (+ (car a) ((, func) (car b) (car c) (car d)) (car x) (car ac)))
|
||||
(l1 (+ (cdr a) ((, func) (cdr b) (cdr c) (cdr d)) (cdr x) (cdr ac)))
|
||||
(m2 (logand 65535 (+ m1 (lsh l1 -16))))
|
||||
(l2 (logand 65535 l1))
|
||||
(m3 (logand 65535 (if (> s 15)
|
||||
(+ (lsh m2 (- s 32)) (lsh l2 (- s 16)))
|
||||
(+ (lsh m2 s) (lsh l2 (- s 16))))))
|
||||
(l3 (logand 65535 (if (> s 15)
|
||||
(+ (lsh l2 (- s 32)) (lsh m2 (- s 16)))
|
||||
(+ (lsh l2 s) (lsh m2 (- s 16)))))))
|
||||
(md5-add (cons m3 l3) b)))))
|
||||
|
||||
(md5-make-step md5-FF md5-F)
|
||||
(md5-make-step md5-GG md5-G)
|
||||
(md5-make-step md5-HH md5-H)
|
||||
(md5-make-step md5-II md5-I)
|
||||
|
||||
(defun md5-init ()
|
||||
"Initialise the state of the message-digest routines."
|
||||
(aset md5-bits 0 0)
|
||||
(aset md5-bits 1 0)
|
||||
(aset md5-bits 2 0)
|
||||
(aset md5-bits 3 0)
|
||||
(aset md5-buffer 0 '(26437 . 8961))
|
||||
(aset md5-buffer 1 '(61389 . 43913))
|
||||
(aset md5-buffer 2 '(39098 . 56574))
|
||||
(aset md5-buffer 3 '( 4146 . 21622)))
|
||||
|
||||
(defun md5-update (string)
|
||||
"Update the current MD5 state with STRING (an array of bytes)."
|
||||
(let ((len (length string))
|
||||
(i 0)
|
||||
(j 0))
|
||||
(while (< i len)
|
||||
;; Compute number of bytes modulo 64
|
||||
(setq j (% (/ (aref md5-bits 0) 8) 64))
|
||||
|
||||
;; Store this byte (truncating to 8 bits to be sure)
|
||||
(aset md5-input j (logand 255 (aref string i)))
|
||||
|
||||
;; Update number of bits by 8 (modulo 2^64)
|
||||
(let ((c 8) (k 0))
|
||||
(while (and (> c 0) (< k 4))
|
||||
(let ((b (aref md5-bits k)))
|
||||
(aset md5-bits k (logand 65535 (+ b c)))
|
||||
(setq c (if (> b (- 65535 c)) 1 0)
|
||||
k (1+ k)))))
|
||||
|
||||
;; Increment number of bytes processed
|
||||
(setq i (1+ i))
|
||||
|
||||
;; When 64 bytes accumulated, pack them into sixteen 32-bit
|
||||
;; integers in the array `in' and then tranform them.
|
||||
(if (= j 63)
|
||||
(let ((in (make-vector 16 (cons 0 0)))
|
||||
(k 0)
|
||||
(kk 0))
|
||||
(while (< k 16)
|
||||
(aset in k (md5-pack md5-input kk))
|
||||
(setq k (+ k 1) kk (+ kk 4)))
|
||||
(md5-transform in))))))
|
||||
|
||||
(defun md5-pack (array i)
|
||||
"Pack the four bytes at ARRAY reference I to I+3 into a 32-bit integer."
|
||||
(cons (+ (lsh (aref array (+ i 3)) 8) (aref array (+ i 2)))
|
||||
(+ (lsh (aref array (+ i 1)) 8) (aref array (+ i 0)))))
|
||||
|
||||
(defun md5-byte (array n b)
|
||||
"Unpack byte B (0 to 3) from Nth member of ARRAY of 32-bit integers."
|
||||
(let ((e (aref array n)))
|
||||
(cond ((eq b 0) (logand 255 (cdr e)))
|
||||
((eq b 1) (lsh (cdr e) -8))
|
||||
((eq b 2) (logand 255 (car e)))
|
||||
((eq b 3) (lsh (car e) -8)))))
|
||||
|
||||
(defun md5-final ()
|
||||
(let ((in (make-vector 16 (cons 0 0)))
|
||||
(j 0)
|
||||
(digest (make-vector 16 0))
|
||||
(padding))
|
||||
|
||||
;; Save the number of bits in the message
|
||||
(aset in 14 (cons (aref md5-bits 1) (aref md5-bits 0)))
|
||||
(aset in 15 (cons (aref md5-bits 3) (aref md5-bits 2)))
|
||||
|
||||
;; Compute number of bytes modulo 64
|
||||
(setq j (% (/ (aref md5-bits 0) 8) 64))
|
||||
|
||||
;; Pad out computation to 56 bytes modulo 64
|
||||
(setq padding (make-vector (if (< j 56) (- 56 j) (- 120 j)) 0))
|
||||
(aset padding 0 128)
|
||||
(md5-update padding)
|
||||
|
||||
;; Append length in bits and transform
|
||||
(let ((k 0) (kk 0))
|
||||
(while (< k 14)
|
||||
(aset in k (md5-pack md5-input kk))
|
||||
(setq k (+ k 1) kk (+ kk 4))))
|
||||
(md5-transform in)
|
||||
|
||||
;; Store the results in the digest
|
||||
(let ((k 0) (kk 0))
|
||||
(while (< k 4)
|
||||
(aset digest (+ kk 0) (md5-byte md5-buffer k 0))
|
||||
(aset digest (+ kk 1) (md5-byte md5-buffer k 1))
|
||||
(aset digest (+ kk 2) (md5-byte md5-buffer k 2))
|
||||
(aset digest (+ kk 3) (md5-byte md5-buffer k 3))
|
||||
(setq k (+ k 1) kk (+ kk 4))))
|
||||
|
||||
;; Return digest
|
||||
digest))
|
||||
|
||||
;; It says in the RSA source, "Note that if the Mysterious Constants are
|
||||
;; arranged backwards in little-endian order and decrypted with the DES
|
||||
;; they produce OCCULT MESSAGES!" Security through obscurity?
|
||||
|
||||
(defun md5-transform (in)
|
||||
"Basic MD5 step. Transform md5-buffer based on array IN."
|
||||
(let ((a (aref md5-buffer 0))
|
||||
(b (aref md5-buffer 1))
|
||||
(c (aref md5-buffer 2))
|
||||
(d (aref md5-buffer 3)))
|
||||
(setq
|
||||
a (md5-FF a b c d (aref in 0) 7 '(55146 . 42104))
|
||||
d (md5-FF d a b c (aref in 1) 12 '(59591 . 46934))
|
||||
c (md5-FF c d a b (aref in 2) 17 '( 9248 . 28891))
|
||||
b (md5-FF b c d a (aref in 3) 22 '(49597 . 52974))
|
||||
a (md5-FF a b c d (aref in 4) 7 '(62844 . 4015))
|
||||
d (md5-FF d a b c (aref in 5) 12 '(18311 . 50730))
|
||||
c (md5-FF c d a b (aref in 6) 17 '(43056 . 17939))
|
||||
b (md5-FF b c d a (aref in 7) 22 '(64838 . 38145))
|
||||
a (md5-FF a b c d (aref in 8) 7 '(27008 . 39128))
|
||||
d (md5-FF d a b c (aref in 9) 12 '(35652 . 63407))
|
||||
c (md5-FF c d a b (aref in 10) 17 '(65535 . 23473))
|
||||
b (md5-FF b c d a (aref in 11) 22 '(35164 . 55230))
|
||||
a (md5-FF a b c d (aref in 12) 7 '(27536 . 4386))
|
||||
d (md5-FF d a b c (aref in 13) 12 '(64920 . 29075))
|
||||
c (md5-FF c d a b (aref in 14) 17 '(42617 . 17294))
|
||||
b (md5-FF b c d a (aref in 15) 22 '(18868 . 2081))
|
||||
a (md5-GG a b c d (aref in 1) 5 '(63006 . 9570))
|
||||
d (md5-GG d a b c (aref in 6) 9 '(49216 . 45888))
|
||||
c (md5-GG c d a b (aref in 11) 14 '( 9822 . 23121))
|
||||
b (md5-GG b c d a (aref in 0) 20 '(59830 . 51114))
|
||||
a (md5-GG a b c d (aref in 5) 5 '(54831 . 4189))
|
||||
d (md5-GG d a b c (aref in 10) 9 '( 580 . 5203))
|
||||
c (md5-GG c d a b (aref in 15) 14 '(55457 . 59009))
|
||||
b (md5-GG b c d a (aref in 4) 20 '(59347 . 64456))
|
||||
a (md5-GG a b c d (aref in 9) 5 '( 8673 . 52710))
|
||||
d (md5-GG d a b c (aref in 14) 9 '(49975 . 2006))
|
||||
c (md5-GG c d a b (aref in 3) 14 '(62677 . 3463))
|
||||
b (md5-GG b c d a (aref in 8) 20 '(17754 . 5357))
|
||||
a (md5-GG a b c d (aref in 13) 5 '(43491 . 59653))
|
||||
d (md5-GG d a b c (aref in 2) 9 '(64751 . 41976))
|
||||
c (md5-GG c d a b (aref in 7) 14 '(26479 . 729))
|
||||
b (md5-GG b c d a (aref in 12) 20 '(36138 . 19594))
|
||||
a (md5-HH a b c d (aref in 5) 4 '(65530 . 14658))
|
||||
d (md5-HH d a b c (aref in 8) 11 '(34673 . 63105))
|
||||
c (md5-HH c d a b (aref in 11) 16 '(28061 . 24866))
|
||||
b (md5-HH b c d a (aref in 14) 23 '(64997 . 14348))
|
||||
a (md5-HH a b c d (aref in 1) 4 '(42174 . 59972))
|
||||
d (md5-HH d a b c (aref in 4) 11 '(19422 . 53161))
|
||||
c (md5-HH c d a b (aref in 7) 16 '(63163 . 19296))
|
||||
b (md5-HH b c d a (aref in 10) 23 '(48831 . 48240))
|
||||
a (md5-HH a b c d (aref in 13) 4 '(10395 . 32454))
|
||||
d (md5-HH d a b c (aref in 0) 11 '(60065 . 10234))
|
||||
c (md5-HH c d a b (aref in 3) 16 '(54511 . 12421))
|
||||
b (md5-HH b c d a (aref in 6) 23 '( 1160 . 7429))
|
||||
a (md5-HH a b c d (aref in 9) 4 '(55764 . 53305))
|
||||
d (md5-HH d a b c (aref in 12) 11 '(59099 . 39397))
|
||||
c (md5-HH c d a b (aref in 15) 16 '( 8098 . 31992))
|
||||
b (md5-HH b c d a (aref in 2) 23 '(50348 . 22117))
|
||||
a (md5-II a b c d (aref in 0) 6 '(62505 . 8772))
|
||||
d (md5-II d a b c (aref in 7) 10 '(17194 . 65431))
|
||||
c (md5-II c d a b (aref in 14) 15 '(43924 . 9127))
|
||||
b (md5-II b c d a (aref in 5) 21 '(64659 . 41017))
|
||||
a (md5-II a b c d (aref in 12) 6 '(25947 . 22979))
|
||||
d (md5-II d a b c (aref in 3) 10 '(36620 . 52370))
|
||||
c (md5-II c d a b (aref in 10) 15 '(65519 . 62589))
|
||||
b (md5-II b c d a (aref in 1) 21 '(34180 . 24017))
|
||||
a (md5-II a b c d (aref in 8) 6 '(28584 . 32335))
|
||||
d (md5-II d a b c (aref in 15) 10 '(65068 . 59104))
|
||||
c (md5-II c d a b (aref in 6) 15 '(41729 . 17172))
|
||||
b (md5-II b c d a (aref in 13) 21 '(19976 . 4513))
|
||||
a (md5-II a b c d (aref in 4) 6 '(63315 . 32386))
|
||||
d (md5-II d a b c (aref in 11) 10 '(48442 . 62005))
|
||||
c (md5-II c d a b (aref in 2) 15 '(10967 . 53947))
|
||||
b (md5-II b c d a (aref in 9) 21 '(60294 . 54161)))
|
||||
|
||||
(aset md5-buffer 0 (md5-add (aref md5-buffer 0) a))
|
||||
(aset md5-buffer 1 (md5-add (aref md5-buffer 1) b))
|
||||
(aset md5-buffer 2 (md5-add (aref md5-buffer 2) c))
|
||||
(aset md5-buffer 3 (md5-add (aref md5-buffer 3) d))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;; Here begins the merger with the XEmacs API and the md5.el from the URL
|
||||
;;; package. Courtesy wmperry@spry.com
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
(defun md5 (object &optional start end)
|
||||
"Return the MD5 (a secure message digest algorithm) of an object.
|
||||
OBJECT is either a string or a buffer.
|
||||
Optional arguments START and END denote buffer positions for computing the
|
||||
hash of a portion of OBJECT."
|
||||
(let ((buffer nil))
|
||||
(unwind-protect
|
||||
(save-excursion
|
||||
(setq buffer (generate-new-buffer " *md5-work*"))
|
||||
(set-buffer buffer)
|
||||
(cond
|
||||
((bufferp object)
|
||||
(insert-buffer-substring object start end))
|
||||
((stringp object)
|
||||
(insert (if (or start end)
|
||||
(substring object start end)
|
||||
object)))
|
||||
(t nil))
|
||||
(prog1
|
||||
(if (<= (point-max) md5-maximum-internal-length)
|
||||
(mapconcat
|
||||
(function (lambda (node) (format "%02x" node)))
|
||||
(md5-encode (buffer-string))
|
||||
"")
|
||||
(call-process-region (point-min) (point-max)
|
||||
(or shell-file-name "/bin/sh")
|
||||
t buffer nil
|
||||
"-c" md5-program)
|
||||
;; MD5 digest is 32 chars long
|
||||
;; mddriver adds a newline to make neaten output for tty
|
||||
;; viewing, make sure we leave it behind.
|
||||
(buffer-substring (point-min) (+ (point-min) 32)))
|
||||
(kill-buffer buffer)))
|
||||
(and buffer (kill-buffer buffer) nil))))
|
||||
|
||||
(provide 'md5)
|
||||
|
||||
;;; md5.el ends here ----------------------------------------------------------
|
||||
3615
lisp/gnus/message.el
Normal file
3615
lisp/gnus/message.el
Normal file
File diff suppressed because it is too large
Load diff
86
lisp/gnus/messcompat.el
Normal file
86
lisp/gnus/messcompat.el
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
;;; messcompat.el --- making message mode compatible with mail mode
|
||||
;; Copyright (C) 1996,97 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
|
||||
;; Keywords: mail, news
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; This file tries to provide backward compatability with sendmail.el
|
||||
;; for Message mode. It should be used by simply adding
|
||||
;;
|
||||
;; (require 'messcompat)
|
||||
;;
|
||||
;; to the .emacs file. Loading it after Message mode has been
|
||||
;; loaded will have no effect.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'sendmail)
|
||||
|
||||
(defvar message-from-style mail-from-style
|
||||
"*Specifies how \"From\" headers look.
|
||||
|
||||
If `nil', they contain just the return address like:
|
||||
king@grassland.com
|
||||
If `parens', they look like:
|
||||
king@grassland.com (Elvis Parsley)
|
||||
If `angles', they look like:
|
||||
Elvis Parsley <king@grassland.com>
|
||||
|
||||
Otherwise, most addresses look like `angles', but they look like
|
||||
`parens' if `angles' would need quoting and `parens' would not.")
|
||||
|
||||
(defvar message-interactive mail-interactive
|
||||
"Non-nil means when sending a message wait for and display errors.
|
||||
nil means let mailer mail back a message to report errors.")
|
||||
|
||||
(defvar message-setup-hook mail-setup-hook
|
||||
"Normal hook, run each time a new outgoing message is initialized.
|
||||
The function `message-setup' runs this hook.")
|
||||
|
||||
(defvar message-mode-hook mail-mode-hook
|
||||
"Hook run in message mode buffers.")
|
||||
|
||||
(defvar message-indentation-spaces mail-indentation-spaces
|
||||
"*Number of spaces to insert at the beginning of each cited line.
|
||||
Used by `message-yank-original' via `message-yank-cite'.")
|
||||
|
||||
(defvar message-signature mail-signature
|
||||
"*String to be inserted at the end of the message buffer.
|
||||
If t, the `message-signature-file' file will be inserted instead.
|
||||
If a function, the result from the function will be used instead.
|
||||
If a form, the result from the form will be used instead.")
|
||||
|
||||
;;;###autoload
|
||||
(defvar message-signature-file mail-signature-file
|
||||
"*File containing the text inserted at end of message. buffer.")
|
||||
|
||||
(defvar message-default-headers mail-default-headers
|
||||
"*A string containing header lines to be inserted in outgoing messages.
|
||||
It is inserted before you edit the message, so you can edit or delete
|
||||
these lines.")
|
||||
|
||||
(defvar message-send-hook mail-send-hook
|
||||
"Hook run before sending messages.")
|
||||
|
||||
(provide 'messcompat)
|
||||
|
||||
;;; messcompat.el ends here
|
||||
650
lisp/gnus/nnbabyl.el
Normal file
650
lisp/gnus/nnbabyl.el
Normal file
|
|
@ -0,0 +1,650 @@
|
|||
;;; nnbabyl.el --- rmail mbox access for Gnus
|
||||
;; Copyright (C) 1995,96,97 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
|
||||
;; Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
|
||||
;; Keywords: news, mail
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; For an overview of what the interface functions do, please see the
|
||||
;; Gnus sources.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'nnheader)
|
||||
(require 'rmail)
|
||||
(require 'nnmail)
|
||||
(require 'nnoo)
|
||||
(eval-when-compile (require 'cl))
|
||||
|
||||
(nnoo-declare nnbabyl)
|
||||
|
||||
(defvoo nnbabyl-mbox-file (expand-file-name "~/RMAIL")
|
||||
"The name of the rmail box file in the users home directory.")
|
||||
|
||||
(defvoo nnbabyl-active-file (expand-file-name "~/.rmail-active")
|
||||
"The name of the active file for the rmail box.")
|
||||
|
||||
(defvoo nnbabyl-get-new-mail t
|
||||
"If non-nil, nnbabyl will check the incoming mail file and split the mail.")
|
||||
|
||||
(defvoo nnbabyl-prepare-save-mail-hook nil
|
||||
"Hook run narrowed to an article before saving.")
|
||||
|
||||
|
||||
|
||||
(defvar nnbabyl-mail-delimiter "\^_")
|
||||
|
||||
(defconst nnbabyl-version "nnbabyl 1.0"
|
||||
"nnbabyl version.")
|
||||
|
||||
(defvoo nnbabyl-mbox-buffer nil)
|
||||
(defvoo nnbabyl-current-group nil)
|
||||
(defvoo nnbabyl-status-string "")
|
||||
(defvoo nnbabyl-group-alist nil)
|
||||
(defvoo nnbabyl-active-timestamp nil)
|
||||
|
||||
(defvoo nnbabyl-previous-buffer-mode nil)
|
||||
|
||||
(eval-and-compile
|
||||
(autoload 'gnus-set-text-properties "gnus-ems"))
|
||||
|
||||
|
||||
|
||||
;;; Interface functions
|
||||
|
||||
(nnoo-define-basics nnbabyl)
|
||||
|
||||
(deffoo nnbabyl-retrieve-headers (articles &optional group server fetch-old)
|
||||
(save-excursion
|
||||
(set-buffer nntp-server-buffer)
|
||||
(erase-buffer)
|
||||
(let ((number (length articles))
|
||||
(count 0)
|
||||
(delim (concat "^" nnbabyl-mail-delimiter))
|
||||
article art-string start stop)
|
||||
(nnbabyl-possibly-change-newsgroup group server)
|
||||
(while (setq article (pop articles))
|
||||
(setq art-string (nnbabyl-article-string article))
|
||||
(set-buffer nnbabyl-mbox-buffer)
|
||||
(end-of-line)
|
||||
(when (or (search-forward art-string nil t)
|
||||
(search-backward art-string nil t))
|
||||
(unless (re-search-backward delim nil t)
|
||||
(goto-char (point-min)))
|
||||
(while (and (not (looking-at ".+:"))
|
||||
(zerop (forward-line 1))))
|
||||
(setq start (point))
|
||||
(search-forward "\n\n" nil t)
|
||||
(setq stop (1- (point)))
|
||||
(set-buffer nntp-server-buffer)
|
||||
(insert "221 ")
|
||||
(princ article (current-buffer))
|
||||
(insert " Article retrieved.\n")
|
||||
(insert-buffer-substring nnbabyl-mbox-buffer start stop)
|
||||
(goto-char (point-max))
|
||||
(insert ".\n"))
|
||||
(and (numberp nnmail-large-newsgroup)
|
||||
(> number nnmail-large-newsgroup)
|
||||
(zerop (% (incf count) 20))
|
||||
(nnheader-message 5 "nnbabyl: Receiving headers... %d%%"
|
||||
(/ (* count 100) number))))
|
||||
|
||||
(and (numberp nnmail-large-newsgroup)
|
||||
(> number nnmail-large-newsgroup)
|
||||
(nnheader-message 5 "nnbabyl: Receiving headers...done"))
|
||||
|
||||
(set-buffer nntp-server-buffer)
|
||||
(nnheader-fold-continuation-lines)
|
||||
'headers)))
|
||||
|
||||
(deffoo nnbabyl-open-server (server &optional defs)
|
||||
(nnoo-change-server 'nnbabyl server defs)
|
||||
(nnbabyl-create-mbox)
|
||||
(cond
|
||||
((not (file-exists-p nnbabyl-mbox-file))
|
||||
(nnbabyl-close-server)
|
||||
(nnheader-report 'nnbabyl "No such file: %s" nnbabyl-mbox-file))
|
||||
((file-directory-p nnbabyl-mbox-file)
|
||||
(nnbabyl-close-server)
|
||||
(nnheader-report 'nnbabyl "Not a regular file: %s" nnbabyl-mbox-file))
|
||||
(t
|
||||
(nnheader-report 'nnbabyl "Opened server %s using mbox %s" server
|
||||
nnbabyl-mbox-file)
|
||||
t)))
|
||||
|
||||
(deffoo nnbabyl-close-server (&optional server)
|
||||
;; Restore buffer mode.
|
||||
(when (and (nnbabyl-server-opened)
|
||||
nnbabyl-previous-buffer-mode)
|
||||
(save-excursion
|
||||
(set-buffer nnbabyl-mbox-buffer)
|
||||
(narrow-to-region
|
||||
(caar nnbabyl-previous-buffer-mode)
|
||||
(cdar nnbabyl-previous-buffer-mode))
|
||||
(funcall (cdr nnbabyl-previous-buffer-mode))))
|
||||
(nnoo-close-server 'nnbabyl server)
|
||||
(setq nnbabyl-mbox-buffer nil)
|
||||
t)
|
||||
|
||||
(deffoo nnbabyl-server-opened (&optional server)
|
||||
(and (nnoo-current-server-p 'nnbabyl server)
|
||||
nnbabyl-mbox-buffer
|
||||
(buffer-name nnbabyl-mbox-buffer)
|
||||
nntp-server-buffer
|
||||
(buffer-name nntp-server-buffer)))
|
||||
|
||||
(deffoo nnbabyl-request-article (article &optional newsgroup server buffer)
|
||||
(nnbabyl-possibly-change-newsgroup newsgroup server)
|
||||
(save-excursion
|
||||
(set-buffer nnbabyl-mbox-buffer)
|
||||
(goto-char (point-min))
|
||||
(when (search-forward (nnbabyl-article-string article) nil t)
|
||||
(let (start stop summary-line)
|
||||
(unless (re-search-backward (concat "^" nnbabyl-mail-delimiter) nil t)
|
||||
(goto-char (point-min))
|
||||
(end-of-line))
|
||||
(while (and (not (looking-at ".+:"))
|
||||
(zerop (forward-line 1))))
|
||||
(setq start (point))
|
||||
(or (when (re-search-forward
|
||||
(concat "^" nnbabyl-mail-delimiter) nil t)
|
||||
(beginning-of-line)
|
||||
t)
|
||||
(goto-char (point-max)))
|
||||
(setq stop (point))
|
||||
(let ((nntp-server-buffer (or buffer nntp-server-buffer)))
|
||||
(set-buffer nntp-server-buffer)
|
||||
(erase-buffer)
|
||||
(insert-buffer-substring nnbabyl-mbox-buffer start stop)
|
||||
(goto-char (point-min))
|
||||
;; If there is an EOOH header, then we have to remove some
|
||||
;; duplicated headers.
|
||||
(setq summary-line (looking-at "Summary-line:"))
|
||||
(when (search-forward "\n*** EOOH ***" nil t)
|
||||
(if summary-line
|
||||
;; The headers to be deleted are located before the
|
||||
;; EOOH line...
|
||||
(delete-region (point-min) (progn (forward-line 1)
|
||||
(point)))
|
||||
;; ...or after.
|
||||
(delete-region (progn (beginning-of-line) (point))
|
||||
(or (search-forward "\n\n" nil t)
|
||||
(point)))))
|
||||
(if (numberp article)
|
||||
(cons nnbabyl-current-group article)
|
||||
(nnbabyl-article-group-number)))))))
|
||||
|
||||
(deffoo nnbabyl-request-group (group &optional server dont-check)
|
||||
(let ((active (cadr (assoc group nnbabyl-group-alist))))
|
||||
(save-excursion
|
||||
(cond
|
||||
((or (null active)
|
||||
(null (nnbabyl-possibly-change-newsgroup group server)))
|
||||
(nnheader-report 'nnbabyl "No such group: %s" group))
|
||||
(dont-check
|
||||
(nnheader-report 'nnbabyl "Selected group %s" group)
|
||||
(nnheader-insert ""))
|
||||
(t
|
||||
(nnheader-report 'nnbabyl "Selected group %s" group)
|
||||
(nnheader-insert "211 %d %d %d %s\n"
|
||||
(1+ (- (cdr active) (car active)))
|
||||
(car active) (cdr active) group))))))
|
||||
|
||||
(deffoo nnbabyl-request-scan (&optional group server)
|
||||
(nnbabyl-possibly-change-newsgroup group server)
|
||||
(nnbabyl-read-mbox)
|
||||
(nnmail-get-new-mail
|
||||
'nnbabyl
|
||||
(lambda ()
|
||||
(save-excursion
|
||||
(set-buffer nnbabyl-mbox-buffer)
|
||||
(save-buffer)))
|
||||
(file-name-directory nnbabyl-mbox-file)
|
||||
group
|
||||
(lambda ()
|
||||
(save-excursion
|
||||
(let ((in-buf (current-buffer)))
|
||||
(goto-char (point-min))
|
||||
(while (search-forward "\n\^_\n" nil t)
|
||||
(delete-char -1))
|
||||
(set-buffer nnbabyl-mbox-buffer)
|
||||
(goto-char (point-max))
|
||||
(search-backward "\n\^_" nil t)
|
||||
(goto-char (match-end 0))
|
||||
(insert-buffer-substring in-buf)))
|
||||
(nnmail-save-active nnbabyl-group-alist nnbabyl-active-file))))
|
||||
|
||||
(deffoo nnbabyl-close-group (group &optional server)
|
||||
t)
|
||||
|
||||
(deffoo nnbabyl-request-create-group (group &optional server args)
|
||||
(nnmail-activate 'nnbabyl)
|
||||
(unless (assoc group nnbabyl-group-alist)
|
||||
(push (list group (cons 1 0))
|
||||
nnbabyl-group-alist)
|
||||
(nnmail-save-active nnbabyl-group-alist nnbabyl-active-file))
|
||||
t)
|
||||
|
||||
(deffoo nnbabyl-request-list (&optional server)
|
||||
(save-excursion
|
||||
(nnmail-find-file nnbabyl-active-file)
|
||||
(setq nnbabyl-group-alist (nnmail-get-active))
|
||||
t))
|
||||
|
||||
(deffoo nnbabyl-request-newgroups (date &optional server)
|
||||
(nnbabyl-request-list server))
|
||||
|
||||
(deffoo nnbabyl-request-list-newsgroups (&optional server)
|
||||
(nnheader-report 'nnbabyl "nnbabyl: LIST NEWSGROUPS is not implemented."))
|
||||
|
||||
(deffoo nnbabyl-request-expire-articles
|
||||
(articles newsgroup &optional server force)
|
||||
(nnbabyl-possibly-change-newsgroup newsgroup server)
|
||||
(let* ((is-old t)
|
||||
rest)
|
||||
(nnmail-activate 'nnbabyl)
|
||||
|
||||
(save-excursion
|
||||
(set-buffer nnbabyl-mbox-buffer)
|
||||
(gnus-set-text-properties (point-min) (point-max) nil)
|
||||
(while (and articles is-old)
|
||||
(goto-char (point-min))
|
||||
(when (search-forward (nnbabyl-article-string (car articles)) nil t)
|
||||
(if (setq is-old
|
||||
(nnmail-expired-article-p
|
||||
newsgroup
|
||||
(buffer-substring
|
||||
(point) (progn (end-of-line) (point))) force))
|
||||
(progn
|
||||
(nnheader-message 5 "Deleting article %d in %s..."
|
||||
(car articles) newsgroup)
|
||||
(nnbabyl-delete-mail))
|
||||
(push (car articles) rest)))
|
||||
(setq articles (cdr articles)))
|
||||
(save-buffer)
|
||||
;; Find the lowest active article in this group.
|
||||
(let ((active (nth 1 (assoc newsgroup nnbabyl-group-alist))))
|
||||
(goto-char (point-min))
|
||||
(while (and (not (search-forward
|
||||
(nnbabyl-article-string (car active)) nil t))
|
||||
(<= (car active) (cdr active)))
|
||||
(setcar active (1+ (car active)))
|
||||
(goto-char (point-min))))
|
||||
(nnmail-save-active nnbabyl-group-alist nnbabyl-active-file)
|
||||
(nconc rest articles))))
|
||||
|
||||
(deffoo nnbabyl-request-move-article
|
||||
(article group server accept-form &optional last)
|
||||
(let ((buf (get-buffer-create " *nnbabyl move*"))
|
||||
result)
|
||||
(and
|
||||
(nnbabyl-request-article article group server)
|
||||
(save-excursion
|
||||
(set-buffer buf)
|
||||
(insert-buffer-substring nntp-server-buffer)
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward
|
||||
"^X-Gnus-Newsgroup:"
|
||||
(save-excursion (search-forward "\n\n" nil t) (point)) t)
|
||||
(delete-region (progn (beginning-of-line) (point))
|
||||
(progn (forward-line 1) (point))))
|
||||
(setq result (eval accept-form))
|
||||
(kill-buffer (current-buffer))
|
||||
result)
|
||||
(save-excursion
|
||||
(nnbabyl-possibly-change-newsgroup group server)
|
||||
(set-buffer nnbabyl-mbox-buffer)
|
||||
(goto-char (point-min))
|
||||
(if (search-forward (nnbabyl-article-string article) nil t)
|
||||
(nnbabyl-delete-mail))
|
||||
(and last (save-buffer))))
|
||||
result))
|
||||
|
||||
(deffoo nnbabyl-request-accept-article (group &optional server last)
|
||||
(nnbabyl-possibly-change-newsgroup group server)
|
||||
(nnmail-check-syntax)
|
||||
(let ((buf (current-buffer))
|
||||
result beg)
|
||||
(and
|
||||
(nnmail-activate 'nnbabyl)
|
||||
(save-excursion
|
||||
(goto-char (point-min))
|
||||
(search-forward "\n\n" nil t)
|
||||
(forward-line -1)
|
||||
(save-excursion
|
||||
(while (re-search-backward "^X-Gnus-Newsgroup: " beg t)
|
||||
(delete-region (point) (progn (forward-line 1) (point)))))
|
||||
(when nnmail-cache-accepted-message-ids
|
||||
(nnmail-cache-insert (nnmail-fetch-field "message-id")))
|
||||
(setq result
|
||||
(if (stringp group)
|
||||
(list (cons group (nnbabyl-active-number group)))
|
||||
(nnmail-article-group 'nnbabyl-active-number)))
|
||||
(if (and (null result)
|
||||
(yes-or-no-p "Moved to `junk' group; delete article? "))
|
||||
(setq result 'junk)
|
||||
(setq result (car (nnbabyl-save-mail result))))
|
||||
(set-buffer nnbabyl-mbox-buffer)
|
||||
(goto-char (point-max))
|
||||
(search-backward "\n\^_")
|
||||
(goto-char (match-end 0))
|
||||
(insert-buffer-substring buf)
|
||||
(when last
|
||||
(when nnmail-cache-accepted-message-ids
|
||||
(nnmail-cache-insert (nnmail-fetch-field "message-id")))
|
||||
(save-buffer)
|
||||
(nnmail-save-active nnbabyl-group-alist nnbabyl-active-file))
|
||||
result))))
|
||||
|
||||
(deffoo nnbabyl-request-replace-article (article group buffer)
|
||||
(nnbabyl-possibly-change-newsgroup group)
|
||||
(save-excursion
|
||||
(set-buffer nnbabyl-mbox-buffer)
|
||||
(goto-char (point-min))
|
||||
(if (not (search-forward (nnbabyl-article-string article) nil t))
|
||||
nil
|
||||
(nnbabyl-delete-mail t t)
|
||||
(insert-buffer-substring buffer)
|
||||
(save-buffer)
|
||||
t)))
|
||||
|
||||
(deffoo nnbabyl-request-delete-group (group &optional force server)
|
||||
(nnbabyl-possibly-change-newsgroup group server)
|
||||
;; Delete all articles in GROUP.
|
||||
(if (not force)
|
||||
() ; Don't delete the articles.
|
||||
(save-excursion
|
||||
(set-buffer nnbabyl-mbox-buffer)
|
||||
(goto-char (point-min))
|
||||
;; Delete all articles in this group.
|
||||
(let ((ident (concat "\nX-Gnus-Newsgroup: " nnbabyl-current-group ":"))
|
||||
found)
|
||||
(while (search-forward ident nil t)
|
||||
(setq found t)
|
||||
(nnbabyl-delete-mail))
|
||||
(when found
|
||||
(save-buffer)))))
|
||||
;; Remove the group from all structures.
|
||||
(setq nnbabyl-group-alist
|
||||
(delq (assoc group nnbabyl-group-alist) nnbabyl-group-alist)
|
||||
nnbabyl-current-group nil)
|
||||
;; Save the active file.
|
||||
(nnmail-save-active nnbabyl-group-alist nnbabyl-active-file)
|
||||
t)
|
||||
|
||||
(deffoo nnbabyl-request-rename-group (group new-name &optional server)
|
||||
(nnbabyl-possibly-change-newsgroup group server)
|
||||
(save-excursion
|
||||
(set-buffer nnbabyl-mbox-buffer)
|
||||
(goto-char (point-min))
|
||||
(let ((ident (concat "\nX-Gnus-Newsgroup: " nnbabyl-current-group ":"))
|
||||
(new-ident (concat "\nX-Gnus-Newsgroup: " new-name ":"))
|
||||
found)
|
||||
(while (search-forward ident nil t)
|
||||
(replace-match new-ident t t)
|
||||
(setq found t))
|
||||
(when found
|
||||
(save-buffer))))
|
||||
(let ((entry (assoc group nnbabyl-group-alist)))
|
||||
(and entry (setcar entry new-name))
|
||||
(setq nnbabyl-current-group nil)
|
||||
;; Save the new group alist.
|
||||
(nnmail-save-active nnbabyl-group-alist nnbabyl-active-file)
|
||||
t))
|
||||
|
||||
|
||||
;;; Internal functions.
|
||||
|
||||
;; If FORCE, delete article no matter how many X-Gnus-Newsgroup
|
||||
;; headers there are. If LEAVE-DELIM, don't delete the Unix mbox
|
||||
;; delimiter line.
|
||||
(defun nnbabyl-delete-mail (&optional force leave-delim)
|
||||
;; Delete the current X-Gnus-Newsgroup line.
|
||||
(unless force
|
||||
(delete-region
|
||||
(progn (beginning-of-line) (point))
|
||||
(progn (forward-line 1) (point))))
|
||||
;; Beginning of the article.
|
||||
(save-excursion
|
||||
(save-restriction
|
||||
(widen)
|
||||
(narrow-to-region
|
||||
(save-excursion
|
||||
(unless (re-search-backward (concat "^" nnbabyl-mail-delimiter) nil t)
|
||||
(goto-char (point-min))
|
||||
(end-of-line))
|
||||
(if leave-delim (progn (forward-line 1) (point))
|
||||
(match-beginning 0)))
|
||||
(progn
|
||||
(forward-line 1)
|
||||
(or (and (re-search-forward (concat "^" nnbabyl-mail-delimiter)
|
||||
nil t)
|
||||
(match-beginning 0))
|
||||
(point-max))))
|
||||
(goto-char (point-min))
|
||||
;; Only delete the article if no other groups owns it as well.
|
||||
(when (or force (not (re-search-forward "^X-Gnus-Newsgroup: " nil t)))
|
||||
(delete-region (point-min) (point-max))))))
|
||||
|
||||
(defun nnbabyl-possibly-change-newsgroup (newsgroup &optional server)
|
||||
(when (and server
|
||||
(not (nnbabyl-server-opened server)))
|
||||
(nnbabyl-open-server server))
|
||||
(when (or (not nnbabyl-mbox-buffer)
|
||||
(not (buffer-name nnbabyl-mbox-buffer)))
|
||||
(save-excursion (nnbabyl-read-mbox)))
|
||||
(unless nnbabyl-group-alist
|
||||
(nnmail-activate 'nnbabyl))
|
||||
(if newsgroup
|
||||
(if (assoc newsgroup nnbabyl-group-alist)
|
||||
(setq nnbabyl-current-group newsgroup)
|
||||
(nnheader-report 'nnbabyl "No such group in file"))
|
||||
t))
|
||||
|
||||
(defun nnbabyl-article-string (article)
|
||||
(if (numberp article)
|
||||
(concat "\nX-Gnus-Newsgroup: " nnbabyl-current-group ":"
|
||||
(int-to-string article) " ")
|
||||
(concat "\nMessage-ID: " article)))
|
||||
|
||||
(defun nnbabyl-article-group-number ()
|
||||
(save-excursion
|
||||
(goto-char (point-min))
|
||||
(when (re-search-forward "^X-Gnus-Newsgroup: +\\([^:]+\\):\\([0-9]+\\) "
|
||||
nil t)
|
||||
(cons (buffer-substring (match-beginning 1) (match-end 1))
|
||||
(string-to-int
|
||||
(buffer-substring (match-beginning 2) (match-end 2)))))))
|
||||
|
||||
(defun nnbabyl-insert-lines ()
|
||||
"Insert how many lines and chars there are in the body of the mail."
|
||||
(let (lines chars)
|
||||
(save-excursion
|
||||
(goto-char (point-min))
|
||||
(when (search-forward "\n\n" nil t)
|
||||
;; There may be an EOOH line here...
|
||||
(when (looking-at "\\*\\*\\* EOOH \\*\\*\\*")
|
||||
(search-forward "\n\n" nil t))
|
||||
(setq chars (- (point-max) (point))
|
||||
lines (max (- (count-lines (point) (point-max)) 1) 0))
|
||||
;; Move back to the end of the headers.
|
||||
(goto-char (point-min))
|
||||
(search-forward "\n\n" nil t)
|
||||
(forward-char -1)
|
||||
(save-excursion
|
||||
(when (re-search-backward "^Lines: " nil t)
|
||||
(delete-region (point) (progn (forward-line 1) (point)))))
|
||||
(insert (format "Lines: %d\n" lines))
|
||||
chars))))
|
||||
|
||||
(defun nnbabyl-save-mail (group-art)
|
||||
;; Called narrowed to an article.
|
||||
(nnbabyl-insert-lines)
|
||||
(nnmail-insert-xref group-art)
|
||||
(nnbabyl-insert-newsgroup-line group-art)
|
||||
(run-hooks 'nnbabyl-prepare-save-mail-hook)
|
||||
group-art)
|
||||
|
||||
(defun nnbabyl-insert-newsgroup-line (group-art)
|
||||
(save-excursion
|
||||
(goto-char (point-min))
|
||||
(while (looking-at "From ")
|
||||
(replace-match "Mail-from: From " t t)
|
||||
(forward-line 1))
|
||||
;; If there is a C-l at the beginning of the narrowed region, this
|
||||
;; isn't really a "save", but rather a "scan".
|
||||
(goto-char (point-min))
|
||||
(unless (looking-at "\^L")
|
||||
(save-excursion
|
||||
(insert "\^L\n0, unseen,,\n*** EOOH ***\n")
|
||||
(goto-char (point-max))
|
||||
(insert "\^_\n")))
|
||||
(when (search-forward "\n\n" nil t)
|
||||
(forward-char -1)
|
||||
(while group-art
|
||||
(insert (format "X-Gnus-Newsgroup: %s:%d %s\n"
|
||||
(caar group-art) (cdar group-art)
|
||||
(current-time-string)))
|
||||
(setq group-art (cdr group-art))))
|
||||
t))
|
||||
|
||||
(defun nnbabyl-active-number (group)
|
||||
;; Find the next article number in GROUP.
|
||||
(let ((active (cadr (assoc group nnbabyl-group-alist))))
|
||||
(if active
|
||||
(setcdr active (1+ (cdr active)))
|
||||
;; This group is new, so we create a new entry for it.
|
||||
;; This might be a bit naughty... creating groups on the drop of
|
||||
;; a hat, but I don't know...
|
||||
(push (list group (setq active (cons 1 1)))
|
||||
nnbabyl-group-alist))
|
||||
(cdr active)))
|
||||
|
||||
(defun nnbabyl-create-mbox ()
|
||||
(unless (file-exists-p nnbabyl-mbox-file)
|
||||
;; Create a new, empty RMAIL mbox file.
|
||||
(save-excursion
|
||||
(set-buffer (setq nnbabyl-mbox-buffer
|
||||
(create-file-buffer nnbabyl-mbox-file)))
|
||||
(setq buffer-file-name nnbabyl-mbox-file)
|
||||
(insert "BABYL OPTIONS:\n\n\^_")
|
||||
(nnmail-write-region
|
||||
(point-min) (point-max) nnbabyl-mbox-file t 'nomesg))))
|
||||
|
||||
(defun nnbabyl-read-mbox ()
|
||||
(nnmail-activate 'nnbabyl)
|
||||
(nnbabyl-create-mbox)
|
||||
|
||||
(unless (and nnbabyl-mbox-buffer
|
||||
(buffer-name nnbabyl-mbox-buffer)
|
||||
(save-excursion
|
||||
(set-buffer nnbabyl-mbox-buffer)
|
||||
(= (buffer-size) (nnheader-file-size nnbabyl-mbox-file))))
|
||||
;; This buffer has changed since we read it last. Possibly.
|
||||
(save-excursion
|
||||
(let ((delim (concat "^" nnbabyl-mail-delimiter))
|
||||
(alist nnbabyl-group-alist)
|
||||
start end number)
|
||||
(set-buffer (setq nnbabyl-mbox-buffer
|
||||
(nnheader-find-file-noselect
|
||||
nnbabyl-mbox-file nil 'raw)))
|
||||
;; Save previous buffer mode.
|
||||
(setq nnbabyl-previous-buffer-mode
|
||||
(cons (cons (point-min) (point-max))
|
||||
major-mode))
|
||||
|
||||
(buffer-disable-undo (current-buffer))
|
||||
(widen)
|
||||
(setq buffer-read-only nil)
|
||||
(fundamental-mode)
|
||||
|
||||
;; Go through the group alist and compare against
|
||||
;; the rmail file.
|
||||
(while alist
|
||||
(goto-char (point-max))
|
||||
(when (and (re-search-backward
|
||||
(format "^X-Gnus-Newsgroup: %s:\\([0-9]+\\) "
|
||||
(caar alist))
|
||||
nil t)
|
||||
(> (setq number
|
||||
(string-to-number
|
||||
(buffer-substring
|
||||
(match-beginning 1) (match-end 1))))
|
||||
(cdadar alist)))
|
||||
(setcdr (cadar alist) number))
|
||||
(setq alist (cdr alist)))
|
||||
|
||||
;; We go through the mbox and make sure that each and
|
||||
;; every mail belongs to some group or other.
|
||||
(goto-char (point-min))
|
||||
(if (looking-at "\^L")
|
||||
(setq start (point))
|
||||
(re-search-forward delim nil t)
|
||||
(setq start (match-end 0)))
|
||||
(while (re-search-forward delim nil t)
|
||||
(setq end (match-end 0))
|
||||
(unless (search-backward "\nX-Gnus-Newsgroup: " start t)
|
||||
(goto-char end)
|
||||
(save-excursion
|
||||
(save-restriction
|
||||
(narrow-to-region (goto-char start) end)
|
||||
(nnbabyl-save-mail
|
||||
(nnmail-article-group 'nnbabyl-active-number))
|
||||
(setq end (point-max)))))
|
||||
(goto-char (setq start end)))
|
||||
(when (buffer-modified-p (current-buffer))
|
||||
(save-buffer))
|
||||
(nnmail-save-active nnbabyl-group-alist nnbabyl-active-file)))))
|
||||
|
||||
(defun nnbabyl-remove-incoming-delims ()
|
||||
(goto-char (point-min))
|
||||
(while (search-forward "\^_" nil t)
|
||||
(replace-match "?" t t)))
|
||||
|
||||
(defun nnbabyl-check-mbox ()
|
||||
"Go through the nnbabyl mbox and make sure that no article numbers are reused."
|
||||
(interactive)
|
||||
(let ((idents (make-vector 1000 0))
|
||||
id)
|
||||
(save-excursion
|
||||
(when (or (not nnbabyl-mbox-buffer)
|
||||
(not (buffer-name nnbabyl-mbox-buffer)))
|
||||
(nnbabyl-read-mbox))
|
||||
(set-buffer nnbabyl-mbox-buffer)
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward "^X-Gnus-Newsgroup: \\([^ ]+\\) " nil t)
|
||||
(if (intern-soft (setq id (match-string 1)) idents)
|
||||
(progn
|
||||
(delete-region (progn (beginning-of-line) (point))
|
||||
(progn (forward-line 1) (point)))
|
||||
(nnheader-message 7 "Moving %s..." id)
|
||||
(nnbabyl-save-mail
|
||||
(nnmail-article-group 'nnbabyl-active-number)))
|
||||
(intern id idents)))
|
||||
(when (buffer-modified-p (current-buffer))
|
||||
(save-buffer))
|
||||
(nnmail-save-active nnbabyl-group-alist nnbabyl-active-file)
|
||||
(message ""))))
|
||||
|
||||
(provide 'nnbabyl)
|
||||
|
||||
;;; nnbabyl.el ends here
|
||||
99
lisp/gnus/nndir.el
Normal file
99
lisp/gnus/nndir.el
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
;;; nndir.el --- single directory newsgroup access for Gnus
|
||||
;; Copyright (C) 1995,96,97 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
|
||||
;; Keywords: news
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'nnheader)
|
||||
(require 'nnmh)
|
||||
(require 'nnml)
|
||||
(require 'nnoo)
|
||||
(eval-when-compile (require 'cl))
|
||||
|
||||
(nnoo-declare nndir
|
||||
nnml nnmh)
|
||||
|
||||
(defvoo nndir-directory nil
|
||||
"Where nndir will look for groups."
|
||||
nnml-current-directory nnmh-current-directory)
|
||||
|
||||
(defvoo nndir-nov-is-evil nil
|
||||
"*Non-nil means that nndir will never retrieve NOV headers."
|
||||
nnml-nov-is-evil)
|
||||
|
||||
|
||||
|
||||
(defvoo nndir-current-group "" nil nnml-current-group nnmh-current-group)
|
||||
(defvoo nndir-top-directory nil nil nnml-directory nnmh-directory)
|
||||
(defvoo nndir-get-new-mail nil nil nnml-get-new-mail nnmh-get-new-mail)
|
||||
|
||||
(defvoo nndir-status-string "" nil nnmh-status-string)
|
||||
(defconst nndir-version "nndir 1.0")
|
||||
|
||||
|
||||
|
||||
;;; Interface functions.
|
||||
|
||||
(nnoo-define-basics nndir)
|
||||
|
||||
(deffoo nndir-open-server (server &optional defs)
|
||||
(setq nndir-directory
|
||||
(or (cadr (assq 'nndir-directory defs))
|
||||
server))
|
||||
(unless (assq 'nndir-directory defs)
|
||||
(push `(nndir-directory ,server) defs))
|
||||
(push `(nndir-current-group
|
||||
,(file-name-nondirectory (directory-file-name nndir-directory)))
|
||||
defs)
|
||||
(push `(nndir-top-directory
|
||||
,(file-name-directory (directory-file-name nndir-directory)))
|
||||
defs)
|
||||
(nnoo-change-server 'nndir server defs)
|
||||
(let (err)
|
||||
(cond
|
||||
((not (condition-case arg
|
||||
(file-exists-p nndir-directory)
|
||||
(ftp-error (setq err (format "%s" arg)))))
|
||||
(nndir-close-server)
|
||||
(nnheader-report
|
||||
'nndir (or err "No such file or directory: %s" nndir-directory)))
|
||||
((not (file-directory-p (file-truename nndir-directory)))
|
||||
(nndir-close-server)
|
||||
(nnheader-report 'nndir "Not a directory: %s" nndir-directory))
|
||||
(t
|
||||
(nnheader-report 'nndir "Opened server %s using directory %s"
|
||||
server nndir-directory)
|
||||
t))))
|
||||
|
||||
(nnoo-map-functions nndir
|
||||
(nnml-retrieve-headers 0 nndir-current-group 0 0)
|
||||
(nnmh-request-article 0 nndir-current-group 0 0)
|
||||
(nnmh-request-group nndir-current-group 0 0)
|
||||
(nnml-close-group nndir-current-group 0)
|
||||
(nnmh-request-list (nnoo-current-server 'nndir) nndir-directory)
|
||||
(nnmh-request-newsgroups (nnoo-current-server 'nndir) nndir-directory))
|
||||
|
||||
(provide 'nndir)
|
||||
|
||||
;;; nndir.el ends here
|
||||
628
lisp/gnus/nndoc.el
Normal file
628
lisp/gnus/nndoc.el
Normal file
|
|
@ -0,0 +1,628 @@
|
|||
;;; nndoc.el --- single file access for Gnus
|
||||
;; Copyright (C) 1995,96,97 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
|
||||
;; Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
|
||||
;; Keywords: news
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'nnheader)
|
||||
(require 'message)
|
||||
(require 'nnmail)
|
||||
(require 'nnoo)
|
||||
(eval-when-compile (require 'cl))
|
||||
|
||||
(nnoo-declare nndoc)
|
||||
|
||||
(defvoo nndoc-article-type 'guess
|
||||
"*Type of the file.
|
||||
One of `mbox', `babyl', `digest', `news', `rnews', `mmdf', `forward',
|
||||
`rfc934', `rfc822-forward', `mime-digest', `standard-digest',
|
||||
`slack-digest', `clari-briefs' or `guess'.")
|
||||
|
||||
(defvoo nndoc-post-type 'mail
|
||||
"*Whether the nndoc group is `mail' or `post'.")
|
||||
|
||||
(defvar nndoc-type-alist
|
||||
`((mmdf
|
||||
(article-begin . "^\^A\^A\^A\^A\n")
|
||||
(body-end . "^\^A\^A\^A\^A\n"))
|
||||
(news
|
||||
(article-begin . "^Path:"))
|
||||
(rnews
|
||||
(article-begin . "^#! *rnews +\\([0-9]+\\) *\n")
|
||||
(body-end-function . nndoc-rnews-body-end))
|
||||
(mbox
|
||||
(article-begin-function . nndoc-mbox-article-begin)
|
||||
(body-end-function . nndoc-mbox-body-end))
|
||||
(babyl
|
||||
(article-begin . "\^_\^L *\n")
|
||||
(body-end . "\^_")
|
||||
(body-begin-function . nndoc-babyl-body-begin)
|
||||
(head-begin-function . nndoc-babyl-head-begin))
|
||||
(forward
|
||||
(article-begin . "^-+ Start of forwarded message -+\n+")
|
||||
(body-end . "^-+ End of forwarded message -+$")
|
||||
(prepare-body-function . nndoc-unquote-dashes))
|
||||
(rfc934
|
||||
(article-begin . "^--.*\n+")
|
||||
(body-end . "^--.*$")
|
||||
(prepare-body-function . nndoc-unquote-dashes))
|
||||
(clari-briefs
|
||||
(article-begin . "^ \\*")
|
||||
(body-end . "^\t------*[ \t]^*\n^ \\*")
|
||||
(body-begin . "^\t")
|
||||
(head-end . "^\t")
|
||||
(generate-head-function . nndoc-generate-clari-briefs-head)
|
||||
(article-transform-function . nndoc-transform-clari-briefs))
|
||||
(mime-digest
|
||||
(article-begin . "")
|
||||
(head-end . "^ ?$")
|
||||
(body-end . "")
|
||||
(file-end . "")
|
||||
(subtype digest guess))
|
||||
(standard-digest
|
||||
(first-article . ,(concat "^" (make-string 70 ?-) "\n\n+"))
|
||||
(article-begin . ,(concat "^\n" (make-string 30 ?-) "\n\n+"))
|
||||
(prepare-body-function . nndoc-unquote-dashes)
|
||||
(body-end-function . nndoc-digest-body-end)
|
||||
(head-end . "^ ?$")
|
||||
(body-begin . "^ ?\n")
|
||||
(file-end . "^End of .*digest.*[0-9].*\n\\*\\*\\|^End of.*Digest *$")
|
||||
(subtype digest guess))
|
||||
(slack-digest
|
||||
(article-begin . "^------------------------------*[\n \t]+")
|
||||
(head-end . "^ ?$")
|
||||
(body-end-function . nndoc-digest-body-end)
|
||||
(body-begin . "^ ?$")
|
||||
(file-end . "^End of")
|
||||
(prepare-body-function . nndoc-unquote-dashes)
|
||||
(subtype digest guess))
|
||||
(lanl-gov-announce
|
||||
(article-begin . "^\\\\\\\\\n")
|
||||
(head-begin . "^Paper.*:")
|
||||
(head-end . "\\(^\\\\\\\\.*\n\\|-----------------\\)")
|
||||
(body-begin . "")
|
||||
(body-end . "-------------------------------------------------")
|
||||
(file-end . "^Title: Recent Seminal")
|
||||
(generate-head-function . nndoc-generate-lanl-gov-head)
|
||||
(article-transform-function . nndoc-transform-lanl-gov-announce)
|
||||
(subtype preprints guess))
|
||||
(rfc822-forward
|
||||
(article-begin . "^\n")
|
||||
(body-end-function . nndoc-rfc822-forward-body-end-function))
|
||||
(guess
|
||||
(guess . t)
|
||||
(subtype nil))
|
||||
(digest
|
||||
(guess . t)
|
||||
(subtype nil))
|
||||
(preprints
|
||||
(guess . t)
|
||||
(subtype nil))))
|
||||
|
||||
|
||||
|
||||
(defvoo nndoc-file-begin nil)
|
||||
(defvoo nndoc-first-article nil)
|
||||
(defvoo nndoc-article-end nil)
|
||||
(defvoo nndoc-article-begin nil)
|
||||
(defvoo nndoc-head-begin nil)
|
||||
(defvoo nndoc-head-end nil)
|
||||
(defvoo nndoc-file-end nil)
|
||||
(defvoo nndoc-body-begin nil)
|
||||
(defvoo nndoc-body-end-function nil)
|
||||
(defvoo nndoc-body-begin-function nil)
|
||||
(defvoo nndoc-head-begin-function nil)
|
||||
(defvoo nndoc-body-end nil)
|
||||
(defvoo nndoc-dissection-alist nil)
|
||||
(defvoo nndoc-prepare-body-function nil)
|
||||
(defvoo nndoc-generate-head-function nil)
|
||||
(defvoo nndoc-article-transform-function nil)
|
||||
(defvoo nndoc-article-begin-function nil)
|
||||
|
||||
(defvoo nndoc-status-string "")
|
||||
(defvoo nndoc-group-alist nil)
|
||||
(defvoo nndoc-current-buffer nil
|
||||
"Current nndoc news buffer.")
|
||||
(defvoo nndoc-address nil)
|
||||
|
||||
(defconst nndoc-version "nndoc 1.0"
|
||||
"nndoc version.")
|
||||
|
||||
|
||||
|
||||
;;; Interface functions
|
||||
|
||||
(nnoo-define-basics nndoc)
|
||||
|
||||
(deffoo nndoc-retrieve-headers (articles &optional newsgroup server fetch-old)
|
||||
(when (nndoc-possibly-change-buffer newsgroup server)
|
||||
(save-excursion
|
||||
(set-buffer nntp-server-buffer)
|
||||
(erase-buffer)
|
||||
(let (article entry)
|
||||
(if (stringp (car articles))
|
||||
'headers
|
||||
(while articles
|
||||
(when (setq entry (cdr (assq (setq article (pop articles))
|
||||
nndoc-dissection-alist)))
|
||||
(insert (format "221 %d Article retrieved.\n" article))
|
||||
(if nndoc-generate-head-function
|
||||
(funcall nndoc-generate-head-function article)
|
||||
(insert-buffer-substring
|
||||
nndoc-current-buffer (car entry) (nth 1 entry)))
|
||||
(goto-char (point-max))
|
||||
(unless (= (char-after (1- (point))) ?\n)
|
||||
(insert "\n"))
|
||||
(insert (format "Lines: %d\n" (nth 4 entry)))
|
||||
(insert ".\n")))
|
||||
|
||||
(nnheader-fold-continuation-lines)
|
||||
'headers)))))
|
||||
|
||||
(deffoo nndoc-request-article (article &optional newsgroup server buffer)
|
||||
(nndoc-possibly-change-buffer newsgroup server)
|
||||
(save-excursion
|
||||
(let ((buffer (or buffer nntp-server-buffer))
|
||||
(entry (cdr (assq article nndoc-dissection-alist)))
|
||||
beg)
|
||||
(set-buffer buffer)
|
||||
(erase-buffer)
|
||||
(when entry
|
||||
(if (stringp article)
|
||||
nil
|
||||
(insert-buffer-substring
|
||||
nndoc-current-buffer (car entry) (nth 1 entry))
|
||||
(insert "\n")
|
||||
(setq beg (point))
|
||||
(insert-buffer-substring
|
||||
nndoc-current-buffer (nth 2 entry) (nth 3 entry))
|
||||
(goto-char beg)
|
||||
(when nndoc-prepare-body-function
|
||||
(funcall nndoc-prepare-body-function))
|
||||
(when nndoc-article-transform-function
|
||||
(funcall nndoc-article-transform-function article))
|
||||
t)))))
|
||||
|
||||
(deffoo nndoc-request-group (group &optional server dont-check)
|
||||
"Select news GROUP."
|
||||
(let (number)
|
||||
(cond
|
||||
((not (nndoc-possibly-change-buffer group server))
|
||||
(nnheader-report 'nndoc "No such file or buffer: %s"
|
||||
nndoc-address))
|
||||
(dont-check
|
||||
(nnheader-report 'nndoc "Selected group %s" group)
|
||||
t)
|
||||
((zerop (setq number (length nndoc-dissection-alist)))
|
||||
(nndoc-close-group group)
|
||||
(nnheader-report 'nndoc "No articles in group %s" group))
|
||||
(t
|
||||
(nnheader-insert "211 %d %d %d %s\n" number 1 number group)))))
|
||||
|
||||
(deffoo nndoc-request-type (group &optional article)
|
||||
(cond ((not article) 'unknown)
|
||||
(nndoc-post-type nndoc-post-type)
|
||||
(t 'unknown)))
|
||||
|
||||
(deffoo nndoc-close-group (group &optional server)
|
||||
(nndoc-possibly-change-buffer group server)
|
||||
(and nndoc-current-buffer
|
||||
(buffer-name nndoc-current-buffer)
|
||||
(kill-buffer nndoc-current-buffer))
|
||||
(setq nndoc-group-alist (delq (assoc group nndoc-group-alist)
|
||||
nndoc-group-alist))
|
||||
(setq nndoc-current-buffer nil)
|
||||
(nnoo-close-server 'nndoc server)
|
||||
(setq nndoc-dissection-alist nil)
|
||||
t)
|
||||
|
||||
(deffoo nndoc-request-list (&optional server)
|
||||
nil)
|
||||
|
||||
(deffoo nndoc-request-newgroups (date &optional server)
|
||||
nil)
|
||||
|
||||
(deffoo nndoc-request-list-newsgroups (&optional server)
|
||||
nil)
|
||||
|
||||
|
||||
;;; Internal functions.
|
||||
|
||||
(defun nndoc-possibly-change-buffer (group source)
|
||||
(let (buf)
|
||||
(cond
|
||||
;; The current buffer is this group's buffer.
|
||||
((and nndoc-current-buffer
|
||||
(buffer-name nndoc-current-buffer)
|
||||
(eq nndoc-current-buffer
|
||||
(setq buf (cdr (assoc group nndoc-group-alist))))))
|
||||
;; We change buffers by taking an old from the group alist.
|
||||
;; `source' is either a string (a file name) or a buffer object.
|
||||
(buf
|
||||
(setq nndoc-current-buffer buf))
|
||||
;; It's a totally new group.
|
||||
((or (and (bufferp nndoc-address)
|
||||
(buffer-name nndoc-address))
|
||||
(and (stringp nndoc-address)
|
||||
(file-exists-p nndoc-address)
|
||||
(not (file-directory-p nndoc-address))))
|
||||
(push (cons group (setq nndoc-current-buffer
|
||||
(get-buffer-create
|
||||
(concat " *nndoc " group "*"))))
|
||||
nndoc-group-alist)
|
||||
(setq nndoc-dissection-alist nil)
|
||||
(save-excursion
|
||||
(set-buffer nndoc-current-buffer)
|
||||
(buffer-disable-undo (current-buffer))
|
||||
(erase-buffer)
|
||||
(if (stringp nndoc-address)
|
||||
(nnheader-insert-file-contents nndoc-address)
|
||||
(insert-buffer-substring nndoc-address)))))
|
||||
;; Initialize the nndoc structures according to this new document.
|
||||
(when (and nndoc-current-buffer
|
||||
(not nndoc-dissection-alist))
|
||||
(save-excursion
|
||||
(set-buffer nndoc-current-buffer)
|
||||
(nndoc-set-delims)
|
||||
(nndoc-dissect-buffer)))
|
||||
(unless nndoc-current-buffer
|
||||
(nndoc-close-server))
|
||||
;; Return whether we managed to select a file.
|
||||
nndoc-current-buffer))
|
||||
|
||||
;;;
|
||||
;;; Deciding what document type we have
|
||||
;;;
|
||||
|
||||
(defun nndoc-set-delims ()
|
||||
"Set the nndoc delimiter variables according to the type of the document."
|
||||
(let ((vars '(nndoc-file-begin
|
||||
nndoc-first-article
|
||||
nndoc-article-end nndoc-head-begin nndoc-head-end
|
||||
nndoc-file-end nndoc-article-begin
|
||||
nndoc-body-begin nndoc-body-end-function nndoc-body-end
|
||||
nndoc-prepare-body-function nndoc-article-transform-function
|
||||
nndoc-generate-head-function nndoc-body-begin-function
|
||||
nndoc-head-begin-function)))
|
||||
(while vars
|
||||
(set (pop vars) nil)))
|
||||
(let (defs)
|
||||
;; Guess away until we find the real file type.
|
||||
(while (assq 'guess (setq defs (cdr (assq nndoc-article-type
|
||||
nndoc-type-alist))))
|
||||
(setq nndoc-article-type (nndoc-guess-type nndoc-article-type)))
|
||||
;; Set the nndoc variables.
|
||||
(while defs
|
||||
(set (intern (format "nndoc-%s" (caar defs)))
|
||||
(cdr (pop defs))))))
|
||||
|
||||
(defun nndoc-guess-type (subtype)
|
||||
(let ((alist nndoc-type-alist)
|
||||
results result entry)
|
||||
(while (and (not result)
|
||||
(setq entry (pop alist)))
|
||||
(when (memq subtype (or (cdr (assq 'subtype entry)) '(guess)))
|
||||
(goto-char (point-min))
|
||||
(when (numberp (setq result (funcall (intern
|
||||
(format "nndoc-%s-type-p"
|
||||
(car entry))))))
|
||||
(push (cons result entry) results)
|
||||
(setq result nil))))
|
||||
(unless (or result results)
|
||||
(error "Document is not of any recognized type"))
|
||||
(if result
|
||||
(car entry)
|
||||
(cadar (sort results (lambda (r1 r2) (< (car r1) (car r2))))))))
|
||||
|
||||
;;;
|
||||
;;; Built-in type predicates and functions
|
||||
;;;
|
||||
|
||||
(defun nndoc-mbox-type-p ()
|
||||
(when (looking-at message-unix-mail-delimiter)
|
||||
t))
|
||||
|
||||
(defun nndoc-mbox-article-begin ()
|
||||
(when (re-search-forward (concat "^" message-unix-mail-delimiter) nil t)
|
||||
(goto-char (match-beginning 0))))
|
||||
|
||||
(defun nndoc-mbox-body-end ()
|
||||
(let ((beg (point))
|
||||
len end)
|
||||
(when
|
||||
(save-excursion
|
||||
(and (re-search-backward
|
||||
(concat "^" message-unix-mail-delimiter) nil t)
|
||||
(setq end (point))
|
||||
(search-forward "\n\n" beg t)
|
||||
(re-search-backward
|
||||
"^Content-Length:[ \t]*\\([0-9]+\\) *$" end t)
|
||||
(setq len (string-to-int (match-string 1)))
|
||||
(search-forward "\n\n" beg t)
|
||||
(unless (= (setq len (+ (point) len)) (point-max))
|
||||
(and (< len (point-max))
|
||||
(goto-char len)
|
||||
(looking-at message-unix-mail-delimiter)))))
|
||||
(goto-char len))))
|
||||
|
||||
(defun nndoc-mmdf-type-p ()
|
||||
(when (looking-at "\^A\^A\^A\^A$")
|
||||
t))
|
||||
|
||||
(defun nndoc-news-type-p ()
|
||||
(when (looking-at "^Path:.*\n")
|
||||
t))
|
||||
|
||||
(defun nndoc-rnews-type-p ()
|
||||
(when (looking-at "#! *rnews")
|
||||
t))
|
||||
|
||||
(defun nndoc-rnews-body-end ()
|
||||
(and (re-search-backward nndoc-article-begin nil t)
|
||||
(forward-line 1)
|
||||
(goto-char (+ (point) (string-to-int (match-string 1))))))
|
||||
|
||||
(defun nndoc-babyl-type-p ()
|
||||
(when (re-search-forward "\^_\^L *\n" nil t)
|
||||
t))
|
||||
|
||||
(defun nndoc-babyl-body-begin ()
|
||||
(re-search-forward "^\n" nil t)
|
||||
(when (looking-at "\*\*\* EOOH \*\*\*")
|
||||
(let ((next (or (save-excursion
|
||||
(re-search-forward nndoc-article-begin nil t))
|
||||
(point-max))))
|
||||
(unless (re-search-forward "^\n" next t)
|
||||
(goto-char next)
|
||||
(forward-line -1)
|
||||
(insert "\n")
|
||||
(forward-line -1)))))
|
||||
|
||||
(defun nndoc-babyl-head-begin ()
|
||||
(when (re-search-forward "^[0-9].*\n" nil t)
|
||||
(when (looking-at "\*\*\* EOOH \*\*\*")
|
||||
(forward-line 1))
|
||||
t))
|
||||
|
||||
(defun nndoc-forward-type-p ()
|
||||
(when (and (re-search-forward "^-+ Start of forwarded message -+\n+" nil t)
|
||||
(not (re-search-forward "^Subject:.*digest" nil t))
|
||||
(not (re-search-backward "^From:" nil t 2))
|
||||
(not (re-search-forward "^From:" nil t 2)))
|
||||
t))
|
||||
|
||||
(defun nndoc-rfc934-type-p ()
|
||||
(when (and (re-search-forward "^-+ Start of forwarded.*\n+" nil t)
|
||||
(not (re-search-forward "^Subject:.*digest" nil t))
|
||||
(not (re-search-backward "^From:" nil t 2))
|
||||
(not (re-search-forward "^From:" nil t 2)))
|
||||
t))
|
||||
|
||||
(defun nndoc-rfc822-forward-type-p ()
|
||||
(save-restriction
|
||||
(message-narrow-to-head)
|
||||
(when (re-search-forward "^Content-Type: *message/rfc822" nil t)
|
||||
t)))
|
||||
|
||||
(defun nndoc-rfc822-forward-body-end-function ()
|
||||
(goto-char (point-max)))
|
||||
|
||||
(defun nndoc-clari-briefs-type-p ()
|
||||
(when (let ((case-fold-search nil))
|
||||
(re-search-forward "^\t[^a-z]+ ([^a-z]+) --" nil t))
|
||||
t))
|
||||
|
||||
(defun nndoc-transform-clari-briefs (article)
|
||||
(goto-char (point-min))
|
||||
(when (looking-at " *\\*\\(.*\\)\n")
|
||||
(replace-match "" t t))
|
||||
(nndoc-generate-clari-briefs-head article))
|
||||
|
||||
(defun nndoc-generate-clari-briefs-head (article)
|
||||
(let ((entry (cdr (assq article nndoc-dissection-alist)))
|
||||
subject from)
|
||||
(save-excursion
|
||||
(set-buffer nndoc-current-buffer)
|
||||
(save-restriction
|
||||
(narrow-to-region (car entry) (nth 3 entry))
|
||||
(goto-char (point-min))
|
||||
(when (looking-at " *\\*\\(.*\\)$")
|
||||
(setq subject (match-string 1))
|
||||
(when (string-match "[ \t]+$" subject)
|
||||
(setq subject (substring subject 0 (match-beginning 0)))))
|
||||
(when
|
||||
(let ((case-fold-search nil))
|
||||
(re-search-forward
|
||||
"^\t\\([^a-z]+\\(,[^(]+\\)? ([^a-z]+)\\) --" nil t))
|
||||
(setq from (match-string 1)))))
|
||||
(insert "From: " "clari@clari.net (" (or from "unknown") ")"
|
||||
"\nSubject: " (or subject "(no subject)") "\n")))
|
||||
|
||||
(defun nndoc-mime-digest-type-p ()
|
||||
(let ((case-fold-search t)
|
||||
boundary-id b-delimiter entry)
|
||||
(when (and
|
||||
(re-search-forward
|
||||
(concat "^Content-Type: *multipart/digest;[ \t\n]*[ \t]"
|
||||
"boundary=\"\\([^\"\n]*[^\" \t\n]\\)\"")
|
||||
nil t)
|
||||
(match-beginning 1))
|
||||
(setq boundary-id (match-string 1)
|
||||
b-delimiter (concat "\n--" boundary-id "[\n \t]+"))
|
||||
(setq entry (assq 'mime-digest nndoc-type-alist))
|
||||
(setcdr entry
|
||||
(list
|
||||
(cons 'head-end "^ ?$")
|
||||
(cons 'body-begin "^ ?\n")
|
||||
(cons 'article-begin b-delimiter)
|
||||
(cons 'body-end-function 'nndoc-digest-body-end)
|
||||
(cons 'file-end (concat "\n--" boundary-id "--[ \t]*$"))))
|
||||
t)))
|
||||
|
||||
(defun nndoc-standard-digest-type-p ()
|
||||
(when (and (re-search-forward (concat "^" (make-string 70 ?-) "\n\n") nil t)
|
||||
(re-search-forward
|
||||
(concat "\n\n" (make-string 30 ?-) "\n\n") nil t))
|
||||
t))
|
||||
|
||||
(defun nndoc-digest-body-end ()
|
||||
(and (re-search-forward nndoc-article-begin nil t)
|
||||
(goto-char (match-beginning 0))))
|
||||
|
||||
(defun nndoc-slack-digest-type-p ()
|
||||
0)
|
||||
|
||||
(defun nndoc-lanl-gov-announce-type-p ()
|
||||
(when (let ((case-fold-search nil))
|
||||
(re-search-forward "^\\\\\\\\\nPaper: [a-z-]+/[0-9]+" nil t))
|
||||
t))
|
||||
|
||||
(defun nndoc-transform-lanl-gov-announce (article)
|
||||
(goto-char (point-max))
|
||||
(when (re-search-backward "^\\\\\\\\ +(\\([^ ]*\\) , *\\([^ ]*\\))" nil t)
|
||||
(replace-match "\n\nGet it at \\1 (\\2)" t nil))
|
||||
;; (when (re-search-backward "^\\\\\\\\$" nil t)
|
||||
;; (replace-match "" t t))
|
||||
)
|
||||
|
||||
(defun nndoc-generate-lanl-gov-head (article)
|
||||
(let ((entry (cdr (assq article nndoc-dissection-alist)))
|
||||
(e-mail "no address given")
|
||||
subject from)
|
||||
(save-excursion
|
||||
(set-buffer nndoc-current-buffer)
|
||||
(save-restriction
|
||||
(narrow-to-region (car entry) (nth 1 entry))
|
||||
(goto-char (point-min))
|
||||
(when (looking-at "^Paper.*: \\([a-z-]+/[0-9]+\\)")
|
||||
(setq subject (concat " (" (match-string 1) ")"))
|
||||
(when (re-search-forward "^From: \\([^ ]+\\)" nil t)
|
||||
(setq e-mail (match-string 1)))
|
||||
(when (re-search-forward "^Title: \\([^\f]*\\)\nAuthors?: \\(.*\\)"
|
||||
nil t)
|
||||
(setq subject (concat (match-string 1) subject))
|
||||
(setq from (concat (match-string 2) " <" e-mail ">"))))
|
||||
))
|
||||
(while (and from (string-match "(\[^)\]*)" from))
|
||||
(setq from (replace-match "" t t from)))
|
||||
(insert "From: " (or from "unknown")
|
||||
"\nSubject: " (or subject "(no subject)") "\n")))
|
||||
|
||||
|
||||
|
||||
;;;
|
||||
;;; Functions for dissecting the documents
|
||||
;;;
|
||||
|
||||
(defun nndoc-search (regexp)
|
||||
(prog1
|
||||
(re-search-forward regexp nil t)
|
||||
(beginning-of-line)))
|
||||
|
||||
(defun nndoc-dissect-buffer ()
|
||||
"Go through the document and partition it into heads/bodies/articles."
|
||||
(let ((i 0)
|
||||
(first t)
|
||||
head-begin head-end body-begin body-end)
|
||||
(setq nndoc-dissection-alist nil)
|
||||
(save-excursion
|
||||
(set-buffer nndoc-current-buffer)
|
||||
(goto-char (point-min))
|
||||
;; Find the beginning of the file.
|
||||
(when nndoc-file-begin
|
||||
(nndoc-search nndoc-file-begin))
|
||||
;; Go through the file.
|
||||
(while (if (and first nndoc-first-article)
|
||||
(nndoc-search nndoc-first-article)
|
||||
(nndoc-article-begin))
|
||||
(setq first nil)
|
||||
(cond (nndoc-head-begin-function
|
||||
(funcall nndoc-head-begin-function))
|
||||
(nndoc-head-begin
|
||||
(nndoc-search nndoc-head-begin)))
|
||||
(if (or (>= (point) (point-max))
|
||||
(and nndoc-file-end
|
||||
(looking-at nndoc-file-end)))
|
||||
(goto-char (point-max))
|
||||
(setq head-begin (point))
|
||||
(nndoc-search (or nndoc-head-end "^$"))
|
||||
(setq head-end (point))
|
||||
(if nndoc-body-begin-function
|
||||
(funcall nndoc-body-begin-function)
|
||||
(nndoc-search (or nndoc-body-begin "^\n")))
|
||||
(setq body-begin (point))
|
||||
(or (and nndoc-body-end-function
|
||||
(funcall nndoc-body-end-function))
|
||||
(and nndoc-body-end
|
||||
(nndoc-search nndoc-body-end))
|
||||
(nndoc-article-begin)
|
||||
(progn
|
||||
(goto-char (point-max))
|
||||
(when nndoc-file-end
|
||||
(and (re-search-backward nndoc-file-end nil t)
|
||||
(beginning-of-line)))))
|
||||
(setq body-end (point))
|
||||
(push (list (incf i) head-begin head-end body-begin body-end
|
||||
(count-lines body-begin body-end))
|
||||
nndoc-dissection-alist))))))
|
||||
|
||||
(defun nndoc-article-begin ()
|
||||
(if nndoc-article-begin-function
|
||||
(funcall nndoc-article-begin-function)
|
||||
(ignore-errors
|
||||
(nndoc-search nndoc-article-begin))))
|
||||
|
||||
(defun nndoc-unquote-dashes ()
|
||||
"Unquote quoted non-separators in digests."
|
||||
(while (re-search-forward "^- -"nil t)
|
||||
(replace-match "-" t t)))
|
||||
|
||||
;;;###autoload
|
||||
(defun nndoc-add-type (definition &optional position)
|
||||
"Add document DEFINITION to the list of nndoc document definitions.
|
||||
If POSITION is nil or `last', the definition will be added
|
||||
as the last checked definition, if t or `first', add as the
|
||||
first definition, and if any other symbol, add after that
|
||||
symbol in the alist."
|
||||
;; First remove any old instances.
|
||||
(setq nndoc-type-alist
|
||||
(delq (assq (car definition) nndoc-type-alist)
|
||||
nndoc-type-alist))
|
||||
;; Then enter the new definition in the proper place.
|
||||
(cond
|
||||
((or (null position) (eq position 'last))
|
||||
(setq nndoc-type-alist (nconc nndoc-type-alist (list definition))))
|
||||
((or (eq position t) (eq position 'first))
|
||||
(push definition nndoc-type-alist))
|
||||
(t
|
||||
(let ((list (memq (assq position nndoc-type-alist)
|
||||
nndoc-type-alist)))
|
||||
(unless list
|
||||
(error "No such position: %s" position))
|
||||
(setcdr list (cons definition (cdr list)))))))
|
||||
|
||||
(provide 'nndoc)
|
||||
|
||||
;;; nndoc.el ends here
|
||||
248
lisp/gnus/nndraft.el
Normal file
248
lisp/gnus/nndraft.el
Normal file
|
|
@ -0,0 +1,248 @@
|
|||
;;; nndraft.el --- draft article access for Gnus
|
||||
;; Copyright (C) 1995,96,97 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
|
||||
;; Keywords: news
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'nnheader)
|
||||
(require 'nnmh)
|
||||
(require 'nnoo)
|
||||
(eval-and-compile (require 'cl))
|
||||
|
||||
(nnoo-declare nndraft)
|
||||
|
||||
(eval-and-compile
|
||||
(autoload 'mail-send-and-exit "sendmail"))
|
||||
|
||||
(defvoo nndraft-directory nil
|
||||
"Where nndraft will store its directory.")
|
||||
|
||||
|
||||
|
||||
(defconst nndraft-version "nndraft 1.0")
|
||||
(defvoo nndraft-status-string "")
|
||||
|
||||
|
||||
|
||||
;;; Interface functions.
|
||||
|
||||
(nnoo-define-basics nndraft)
|
||||
|
||||
(deffoo nndraft-retrieve-headers (articles &optional group server fetch-old)
|
||||
(save-excursion
|
||||
(set-buffer nntp-server-buffer)
|
||||
(erase-buffer)
|
||||
(let* ((buf (get-buffer-create " *draft headers*"))
|
||||
article)
|
||||
(set-buffer buf)
|
||||
(buffer-disable-undo (current-buffer))
|
||||
(erase-buffer)
|
||||
;; We don't support fetching by Message-ID.
|
||||
(if (stringp (car articles))
|
||||
'headers
|
||||
(while articles
|
||||
(set-buffer buf)
|
||||
(when (nndraft-request-article
|
||||
(setq article (pop articles)) group server (current-buffer))
|
||||
(goto-char (point-min))
|
||||
(if (search-forward "\n\n" nil t)
|
||||
(forward-line -1)
|
||||
(goto-char (point-max)))
|
||||
(delete-region (point) (point-max))
|
||||
(set-buffer nntp-server-buffer)
|
||||
(goto-char (point-max))
|
||||
(insert (format "221 %d Article retrieved.\n" article))
|
||||
(insert-buffer-substring buf)
|
||||
(insert ".\n")))
|
||||
|
||||
(nnheader-fold-continuation-lines)
|
||||
'headers))))
|
||||
|
||||
(deffoo nndraft-open-server (server &optional defs)
|
||||
(nnoo-change-server 'nndraft server defs)
|
||||
(unless (assq 'nndraft-directory defs)
|
||||
(setq nndraft-directory server))
|
||||
(cond
|
||||
((not (file-exists-p nndraft-directory))
|
||||
(nndraft-close-server)
|
||||
(nnheader-report 'nndraft "No such file or directory: %s"
|
||||
nndraft-directory))
|
||||
((not (file-directory-p (file-truename nndraft-directory)))
|
||||
(nndraft-close-server)
|
||||
(nnheader-report 'nndraft "Not a directory: %s" nndraft-directory))
|
||||
(t
|
||||
(nnheader-report 'nndraft "Opened server %s using directory %s"
|
||||
server nndraft-directory)
|
||||
t)))
|
||||
|
||||
(deffoo nndraft-request-article (id &optional group server buffer)
|
||||
(when (numberp id)
|
||||
;; We get the newest file of the auto-saved file and the
|
||||
;; "real" file.
|
||||
(let* ((file (nndraft-article-filename id))
|
||||
(auto (nndraft-auto-save-file-name file))
|
||||
(newest (if (file-newer-than-file-p file auto) file auto))
|
||||
(nntp-server-buffer (or buffer nntp-server-buffer)))
|
||||
(when (and (file-exists-p newest)
|
||||
(nnmail-find-file newest))
|
||||
(save-excursion
|
||||
(set-buffer nntp-server-buffer)
|
||||
(goto-char (point-min))
|
||||
;; If there's a mail header separator in this file,
|
||||
;; we remove it.
|
||||
(when (re-search-forward
|
||||
(concat "^" mail-header-separator "$") nil t)
|
||||
(replace-match "" t t)))
|
||||
t))))
|
||||
|
||||
(deffoo nndraft-request-restore-buffer (article &optional group server)
|
||||
"Request a new buffer that is restored to the state of ARTICLE."
|
||||
(let ((file (nndraft-article-filename article ".state"))
|
||||
nndraft-point nndraft-mode nndraft-buffer-name)
|
||||
(when (file-exists-p file)
|
||||
(load file t t t)
|
||||
(when nndraft-buffer-name
|
||||
(set-buffer (get-buffer-create
|
||||
(generate-new-buffer-name nndraft-buffer-name)))
|
||||
(nndraft-request-article article group server (current-buffer))
|
||||
(funcall nndraft-mode)
|
||||
(let ((gnus-verbose-backends nil))
|
||||
(nndraft-request-expire-articles (list article) group server t))
|
||||
(goto-char nndraft-point))
|
||||
nndraft-buffer-name)))
|
||||
|
||||
(deffoo nndraft-request-update-info (group info &optional server)
|
||||
(setcar (cddr info) nil)
|
||||
(when (nth 3 info)
|
||||
(setcar (nthcdr 3 info) nil))
|
||||
t)
|
||||
|
||||
(deffoo nndraft-request-associate-buffer (group)
|
||||
"Associate the current buffer with some article in the draft group."
|
||||
(let* ((gnus-verbose-backends nil)
|
||||
(article (cdr (nndraft-request-accept-article
|
||||
group (nnoo-current-server 'nndraft) t 'noinsert)))
|
||||
(file (nndraft-article-filename article)))
|
||||
(setq buffer-file-name file)
|
||||
(setq buffer-auto-save-file-name (make-auto-save-file-name))
|
||||
(clear-visited-file-modtime)
|
||||
article))
|
||||
|
||||
(deffoo nndraft-request-group (group &optional server dont-check)
|
||||
(prog1
|
||||
(nndraft-execute-nnmh-command
|
||||
`(nnmh-request-group group "" ,dont-check))
|
||||
(nnheader-report 'nndraft nnmh-status-string)))
|
||||
|
||||
(deffoo nndraft-request-list (&optional server dir)
|
||||
(nndraft-execute-nnmh-command
|
||||
`(nnmh-request-list nil ,dir)))
|
||||
|
||||
(deffoo nndraft-request-newgroups (date &optional server)
|
||||
(nndraft-execute-nnmh-command
|
||||
`(nnmh-request-newgroups ,date ,server)))
|
||||
|
||||
(deffoo nndraft-request-expire-articles
|
||||
(articles group &optional server force)
|
||||
(let ((res (nndraft-execute-nnmh-command
|
||||
`(nnmh-request-expire-articles
|
||||
',articles group ,server ,force)))
|
||||
article)
|
||||
;; Delete all the "state" files of articles that have been expired.
|
||||
(while articles
|
||||
(unless (memq (setq article (pop articles)) res)
|
||||
(let ((file (nndraft-article-filename article ".state"))
|
||||
(auto (nndraft-auto-save-file-name
|
||||
(nndraft-article-filename article))))
|
||||
(when (file-exists-p file)
|
||||
(funcall nnmail-delete-file-function file))
|
||||
(when (file-exists-p auto)
|
||||
(funcall nnmail-delete-file-function auto)))))
|
||||
res))
|
||||
|
||||
(deffoo nndraft-request-accept-article (group &optional server last noinsert)
|
||||
(let* ((point (point))
|
||||
(mode major-mode)
|
||||
(name (buffer-name))
|
||||
(gnus-verbose-backends nil)
|
||||
(gart (nndraft-execute-nnmh-command
|
||||
`(nnmh-request-accept-article group ,server ,last noinsert)))
|
||||
(state
|
||||
(nndraft-article-filename (cdr gart) ".state")))
|
||||
;; Write the "state" file.
|
||||
(save-excursion
|
||||
(nnheader-set-temp-buffer " *draft state*")
|
||||
(insert (format "%S\n" `(setq nndraft-mode (quote ,mode)
|
||||
nndraft-point ,point
|
||||
nndraft-buffer-name ,name)))
|
||||
(write-region (point-min) (point-max) state nil 'silent)
|
||||
(kill-buffer (current-buffer)))
|
||||
gart))
|
||||
|
||||
(deffoo nndraft-close-group (group &optional server)
|
||||
t)
|
||||
|
||||
(deffoo nndraft-request-create-group (group &optional server args)
|
||||
(if (file-exists-p nndraft-directory)
|
||||
(if (file-directory-p nndraft-directory)
|
||||
t
|
||||
nil)
|
||||
(condition-case ()
|
||||
(progn
|
||||
(gnus-make-directory nndraft-directory)
|
||||
t)
|
||||
(file-error nil))))
|
||||
|
||||
|
||||
;;; Low-Level Interface
|
||||
|
||||
(defun nndraft-execute-nnmh-command (command)
|
||||
(let ((dir (expand-file-name nndraft-directory)))
|
||||
(when (string-match "/$" dir)
|
||||
(setq dir (substring dir 0 (match-beginning 0))))
|
||||
(string-match "/[^/]+$" dir)
|
||||
(let ((group (substring dir (1+ (match-beginning 0))))
|
||||
(nnmh-directory (substring dir 0 (1+ (match-beginning 0))))
|
||||
(nnmail-keep-last-article nil)
|
||||
(nnmh-get-new-mail nil))
|
||||
(eval command))))
|
||||
|
||||
(defun nndraft-article-filename (article &rest args)
|
||||
(apply 'concat
|
||||
(file-name-as-directory nndraft-directory)
|
||||
(int-to-string article)
|
||||
args))
|
||||
|
||||
(defun nndraft-auto-save-file-name (file)
|
||||
(save-excursion
|
||||
(prog1
|
||||
(progn
|
||||
(set-buffer (get-buffer-create " *draft tmp*"))
|
||||
(setq buffer-file-name file)
|
||||
(make-auto-save-file-name))
|
||||
(kill-buffer (current-buffer)))))
|
||||
|
||||
(provide 'nndraft)
|
||||
|
||||
;;; nndraft.el ends here
|
||||
350
lisp/gnus/nneething.el
Normal file
350
lisp/gnus/nneething.el
Normal file
|
|
@ -0,0 +1,350 @@
|
|||
;;; nneething.el --- random file access for Gnus
|
||||
;; Copyright (C) 1995,96,97 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
|
||||
;; Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
|
||||
;; Keywords: news, mail
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'nnheader)
|
||||
(require 'nnmail)
|
||||
(require 'nnoo)
|
||||
(require 'gnus-util)
|
||||
(require 'cl)
|
||||
|
||||
(nnoo-declare nneething)
|
||||
|
||||
(defvoo nneething-map-file-directory "~/.nneething/"
|
||||
"Where nneething stores the map files.")
|
||||
|
||||
(defvoo nneething-map-file ".nneething"
|
||||
"Name of the map files.")
|
||||
|
||||
(defvoo nneething-exclude-files nil
|
||||
"Regexp saying what files to exclude from the group.
|
||||
If this variable is nil, no files will be excluded.")
|
||||
|
||||
|
||||
|
||||
;;; Internal variables.
|
||||
|
||||
(defconst nneething-version "nneething 1.0"
|
||||
"nneething version.")
|
||||
|
||||
(defvoo nneething-current-directory nil
|
||||
"Current news group directory.")
|
||||
|
||||
(defvoo nneething-status-string "")
|
||||
|
||||
(defvoo nneething-message-id-number 0)
|
||||
(defvoo nneething-work-buffer " *nneething work*")
|
||||
|
||||
(defvoo nneething-group nil)
|
||||
(defvoo nneething-map nil)
|
||||
(defvoo nneething-read-only nil)
|
||||
(defvoo nneething-active nil)
|
||||
|
||||
|
||||
|
||||
;;; Interface functions.
|
||||
|
||||
(nnoo-define-basics nneething)
|
||||
|
||||
(deffoo nneething-retrieve-headers (articles &optional group server fetch-old)
|
||||
(nneething-possibly-change-directory group)
|
||||
|
||||
(save-excursion
|
||||
(set-buffer nntp-server-buffer)
|
||||
(erase-buffer)
|
||||
(let* ((number (length articles))
|
||||
(count 0)
|
||||
(large (and (numberp nnmail-large-newsgroup)
|
||||
(> number nnmail-large-newsgroup)))
|
||||
article file)
|
||||
|
||||
(if (stringp (car articles))
|
||||
'headers
|
||||
|
||||
(while (setq article (pop articles))
|
||||
(setq file (nneething-file-name article))
|
||||
|
||||
(when (and (file-exists-p file)
|
||||
(or (file-directory-p file)
|
||||
(not (zerop (nnheader-file-size file)))))
|
||||
(insert (format "221 %d Article retrieved.\n" article))
|
||||
(nneething-insert-head file)
|
||||
(insert ".\n"))
|
||||
|
||||
(incf count)
|
||||
|
||||
(and large
|
||||
(zerop (% count 20))
|
||||
(message "nneething: Receiving headers... %d%%"
|
||||
(/ (* count 100) number))))
|
||||
|
||||
(when large
|
||||
(message "nneething: Receiving headers...done"))
|
||||
|
||||
(nnheader-fold-continuation-lines)
|
||||
'headers))))
|
||||
|
||||
(deffoo nneething-request-article (id &optional group server buffer)
|
||||
(nneething-possibly-change-directory group)
|
||||
(let ((file (unless (stringp id)
|
||||
(nneething-file-name id)))
|
||||
(nntp-server-buffer (or buffer nntp-server-buffer)))
|
||||
(and (stringp file) ; We did not request by Message-ID.
|
||||
(file-exists-p file) ; The file exists.
|
||||
(not (file-directory-p file)) ; It's not a dir.
|
||||
(save-excursion
|
||||
(nnmail-find-file file) ; Insert the file in the nntp buf.
|
||||
(unless (nnheader-article-p) ; Either it's a real article...
|
||||
(goto-char (point-min))
|
||||
(nneething-make-head file (current-buffer)) ; ... or we fake some headers.
|
||||
(insert "\n"))
|
||||
t))))
|
||||
|
||||
(deffoo nneething-request-group (group &optional server dont-check)
|
||||
(nneething-possibly-change-directory group server)
|
||||
(unless dont-check
|
||||
(nneething-create-mapping)
|
||||
(if (> (car nneething-active) (cdr nneething-active))
|
||||
(nnheader-insert "211 0 1 0 %s\n" group)
|
||||
(nnheader-insert
|
||||
"211 %d %d %d %s\n"
|
||||
(- (1+ (cdr nneething-active)) (car nneething-active))
|
||||
(car nneething-active) (cdr nneething-active)
|
||||
group)))
|
||||
t)
|
||||
|
||||
(deffoo nneething-request-list (&optional server dir)
|
||||
(nnheader-report 'nneething "LIST is not implemented."))
|
||||
|
||||
(deffoo nneething-request-newgroups (date &optional server)
|
||||
(nnheader-report 'nneething "NEWSGROUPS is not implemented."))
|
||||
|
||||
(deffoo nneething-request-type (group &optional article)
|
||||
'unknown)
|
||||
|
||||
(deffoo nneething-close-group (group &optional server)
|
||||
(setq nneething-current-directory nil)
|
||||
t)
|
||||
|
||||
(deffoo nneething-open-server (server &optional defs)
|
||||
(nnheader-init-server-buffer)
|
||||
(if (nneething-server-opened server)
|
||||
t
|
||||
(unless (assq 'nneething-directory defs)
|
||||
(setq defs (append defs (list (list 'nneething-directory server)))))
|
||||
(nnoo-change-server 'nneething server defs)))
|
||||
|
||||
|
||||
;;; Internal functions.
|
||||
|
||||
(defun nneething-possibly-change-directory (group &optional server)
|
||||
(when (and server
|
||||
(not (nneething-server-opened server)))
|
||||
(nneething-open-server server))
|
||||
(when (and group
|
||||
(not (equal nneething-group group)))
|
||||
(setq nneething-group group)
|
||||
(setq nneething-map nil)
|
||||
(setq nneething-active (cons 1 0))
|
||||
(nneething-create-mapping)))
|
||||
|
||||
(defun nneething-map-file ()
|
||||
;; We make sure that the .nneething directory exists.
|
||||
(gnus-make-directory nneething-map-file-directory)
|
||||
;; We store it in a special directory under the user's home dir.
|
||||
(concat (file-name-as-directory nneething-map-file-directory)
|
||||
nneething-group nneething-map-file))
|
||||
|
||||
(defun nneething-create-mapping ()
|
||||
;; Read nneething-active and nneething-map.
|
||||
(when (file-exists-p nneething-directory)
|
||||
(let ((map-file (nneething-map-file))
|
||||
(files (directory-files nneething-directory))
|
||||
touched map-files)
|
||||
(when (file-exists-p map-file)
|
||||
(ignore-errors
|
||||
(load map-file nil t t)))
|
||||
(unless nneething-active
|
||||
(setq nneething-active (cons 1 0)))
|
||||
;; Old nneething had a different map format.
|
||||
(when (and (cdar nneething-map)
|
||||
(atom (cdar nneething-map)))
|
||||
(setq nneething-map
|
||||
(mapcar (lambda (n)
|
||||
(list (cdr n) (car n)
|
||||
(nth 5 (file-attributes
|
||||
(nneething-file-name (car n))))))
|
||||
nneething-map)))
|
||||
;; Remove files matching the exclusion regexp.
|
||||
(when nneething-exclude-files
|
||||
(let ((f files)
|
||||
prev)
|
||||
(while f
|
||||
(if (string-match nneething-exclude-files (car f))
|
||||
(if prev (setcdr prev (cdr f))
|
||||
(setq files (cdr files)))
|
||||
(setq prev f))
|
||||
(setq f (cdr f)))))
|
||||
;; Remove deleted files from the map.
|
||||
(let ((map nneething-map)
|
||||
prev)
|
||||
(while map
|
||||
(if (and (member (cadar map) files)
|
||||
;; We also remove files that have changed mod times.
|
||||
(equal (nth 5 (file-attributes
|
||||
(nneething-file-name (cadar map))))
|
||||
(caddar map)))
|
||||
(progn
|
||||
(push (cadar map) map-files)
|
||||
(setq prev map))
|
||||
(setq touched t)
|
||||
(if prev
|
||||
(setcdr prev (cdr map))
|
||||
(setq nneething-map (cdr nneething-map))))
|
||||
(setq map (cdr map))))
|
||||
;; Find all new files and enter them into the map.
|
||||
(while files
|
||||
(unless (member (car files) map-files)
|
||||
;; This file is not in the map, so we enter it.
|
||||
(setq touched t)
|
||||
(setcdr nneething-active (1+ (cdr nneething-active)))
|
||||
(push (list (cdr nneething-active) (car files)
|
||||
(nth 5 (file-attributes
|
||||
(nneething-file-name (car files)))))
|
||||
nneething-map))
|
||||
(setq files (cdr files)))
|
||||
(when (and touched
|
||||
(not nneething-read-only))
|
||||
(nnheader-temp-write map-file
|
||||
(insert "(setq nneething-map '")
|
||||
(gnus-prin1 nneething-map)
|
||||
(insert ")\n(setq nneething-active '")
|
||||
(gnus-prin1 nneething-active)
|
||||
(insert ")\n"))))))
|
||||
|
||||
(defun nneething-insert-head (file)
|
||||
"Insert the head of FILE."
|
||||
(when (nneething-get-head file)
|
||||
(insert-buffer-substring nneething-work-buffer)
|
||||
(goto-char (point-max))))
|
||||
|
||||
(defun nneething-make-head (file &optional buffer)
|
||||
"Create a head by looking at the file attributes of FILE."
|
||||
(let ((atts (file-attributes file)))
|
||||
(insert
|
||||
"Subject: " (file-name-nondirectory file) "\n"
|
||||
"Message-ID: <nneething-"
|
||||
(int-to-string (incf nneething-message-id-number))
|
||||
"@" (system-name) ">\n"
|
||||
(if (equal '(0 0) (nth 5 atts)) ""
|
||||
(concat "Date: " (current-time-string (nth 5 atts)) "\n"))
|
||||
(or (when buffer
|
||||
(save-excursion
|
||||
(set-buffer buffer)
|
||||
(when (re-search-forward "<[a-zA-Z0-9_]@[-a-zA-Z0-9_]>" 1000 t)
|
||||
(concat "From: " (match-string 0) "\n"))))
|
||||
(nneething-from-line (nth 2 atts) file))
|
||||
(if (> (string-to-int (int-to-string (nth 7 atts))) 0)
|
||||
(concat "Chars: " (int-to-string (nth 7 atts)) "\n")
|
||||
"")
|
||||
(if buffer
|
||||
(save-excursion
|
||||
(set-buffer buffer)
|
||||
(concat "Lines: " (int-to-string
|
||||
(count-lines (point-min) (point-max)))
|
||||
"\n"))
|
||||
"")
|
||||
)))
|
||||
|
||||
(defun nneething-from-line (uid &optional file)
|
||||
"Return a From header based of UID."
|
||||
(let* ((login (condition-case nil
|
||||
(user-login-name uid)
|
||||
(error
|
||||
(cond ((= uid (user-uid)) (user-login-name))
|
||||
((zerop uid) "root")
|
||||
(t (int-to-string uid))))))
|
||||
(name (condition-case nil
|
||||
(user-full-name uid)
|
||||
(error
|
||||
(cond ((= uid (user-uid)) (user-full-name))
|
||||
((zerop uid) "Ms. Root")))))
|
||||
(host (if (string-match "\\`/[^/@]*@\\([^:/]+\\):" file)
|
||||
(prog1
|
||||
(substring file
|
||||
(match-beginning 1)
|
||||
(match-end 1))
|
||||
(when (string-match "/\\(users\\|home\\)/\\([^/]+\\)/" file)
|
||||
(setq login (substring file
|
||||
(match-beginning 2)
|
||||
(match-end 2))
|
||||
name nil)))
|
||||
(system-name))))
|
||||
(concat "From: " login "@" host
|
||||
(if name (concat " (" name ")") "") "\n")))
|
||||
|
||||
(defun nneething-get-head (file)
|
||||
"Either find the head in FILE or make a head for FILE."
|
||||
(save-excursion
|
||||
(set-buffer (get-buffer-create nneething-work-buffer))
|
||||
(setq case-fold-search nil)
|
||||
(buffer-disable-undo (current-buffer))
|
||||
(erase-buffer)
|
||||
(cond
|
||||
((not (file-exists-p file))
|
||||
;; The file do not exist.
|
||||
nil)
|
||||
((or (file-directory-p file)
|
||||
(file-symlink-p file))
|
||||
;; It's a dir, so we fudge a head.
|
||||
(nneething-make-head file) t)
|
||||
(t
|
||||
;; We examine the file.
|
||||
(nnheader-insert-head file)
|
||||
(if (nnheader-article-p)
|
||||
(delete-region
|
||||
(progn
|
||||
(goto-char (point-min))
|
||||
(or (and (search-forward "\n\n" nil t)
|
||||
(1- (point)))
|
||||
(point-max)))
|
||||
(point-max))
|
||||
(goto-char (point-min))
|
||||
(nneething-make-head file (current-buffer))
|
||||
(delete-region (point) (point-max)))
|
||||
t))))
|
||||
|
||||
(defun nneething-file-name (article)
|
||||
"Return the file name of ARTICLE."
|
||||
(concat (file-name-as-directory nneething-directory)
|
||||
(if (numberp article)
|
||||
(cadr (assq article nneething-map))
|
||||
article)))
|
||||
|
||||
(provide 'nneething)
|
||||
|
||||
;;; nneething.el ends here
|
||||
768
lisp/gnus/nnfolder.el
Normal file
768
lisp/gnus/nnfolder.el
Normal file
|
|
@ -0,0 +1,768 @@
|
|||
;;; nnfolder.el --- mail folder access for Gnus
|
||||
;; Copyright (C) 1995,96,97 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Scott Byer <byer@mv.us.adobe.com>
|
||||
;; Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
|
||||
;; Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
|
||||
;; Keywords: mail
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'nnheader)
|
||||
(require 'message)
|
||||
(require 'nnmail)
|
||||
(require 'nnoo)
|
||||
(require 'cl)
|
||||
(require 'gnus-util)
|
||||
|
||||
(nnoo-declare nnfolder)
|
||||
|
||||
(defvoo nnfolder-directory (expand-file-name message-directory)
|
||||
"The name of the nnfolder directory.")
|
||||
|
||||
(defvoo nnfolder-active-file
|
||||
(nnheader-concat nnfolder-directory "active")
|
||||
"The name of the active file.")
|
||||
|
||||
;; I renamed this variable to something more in keeping with the general GNU
|
||||
;; style. -SLB
|
||||
|
||||
(defvoo nnfolder-ignore-active-file nil
|
||||
"If non-nil, causes nnfolder to do some extra work in order to determine
|
||||
the true active ranges of an mbox file. Note that the active file is still
|
||||
saved, but it's values are not used. This costs some extra time when
|
||||
scanning an mbox when opening it.")
|
||||
|
||||
(defvoo nnfolder-distrust-mbox nil
|
||||
"If non-nil, causes nnfolder to not trust the user with respect to
|
||||
inserting unaccounted for mail in the middle of an mbox file. This can greatly
|
||||
slow down scans, which now must scan the entire file for unmarked messages.
|
||||
When nil, scans occur forward from the last marked message, a huge
|
||||
time saver for large mailboxes.")
|
||||
|
||||
(defvoo nnfolder-newsgroups-file
|
||||
(concat (file-name-as-directory nnfolder-directory) "newsgroups")
|
||||
"Mail newsgroups description file.")
|
||||
|
||||
(defvoo nnfolder-get-new-mail t
|
||||
"If non-nil, nnfolder will check the incoming mail file and split the mail.")
|
||||
|
||||
(defvoo nnfolder-prepare-save-mail-hook nil
|
||||
"Hook run narrowed to an article before saving.")
|
||||
|
||||
(defvoo nnfolder-save-buffer-hook nil
|
||||
"Hook run before saving the nnfolder mbox buffer.")
|
||||
|
||||
(defvoo nnfolder-inhibit-expiry nil
|
||||
"If non-nil, inhibit expiry.")
|
||||
|
||||
|
||||
|
||||
(defconst nnfolder-version "nnfolder 1.0"
|
||||
"nnfolder version.")
|
||||
|
||||
(defconst nnfolder-article-marker "X-Gnus-Article-Number: "
|
||||
"String used to demarcate what the article number for a message is.")
|
||||
|
||||
(defvoo nnfolder-current-group nil)
|
||||
(defvoo nnfolder-current-buffer nil)
|
||||
(defvoo nnfolder-status-string "")
|
||||
(defvoo nnfolder-group-alist nil)
|
||||
(defvoo nnfolder-buffer-alist nil)
|
||||
(defvoo nnfolder-scantime-alist nil)
|
||||
(defvoo nnfolder-active-timestamp nil)
|
||||
|
||||
|
||||
|
||||
;;; Interface functions
|
||||
|
||||
(nnoo-define-basics nnfolder)
|
||||
|
||||
(deffoo nnfolder-retrieve-headers (articles &optional group server fetch-old)
|
||||
(save-excursion
|
||||
(set-buffer nntp-server-buffer)
|
||||
(erase-buffer)
|
||||
(let (article art-string start stop)
|
||||
(nnfolder-possibly-change-group group server)
|
||||
(when nnfolder-current-buffer
|
||||
(set-buffer nnfolder-current-buffer)
|
||||
(goto-char (point-min))
|
||||
(if (stringp (car articles))
|
||||
'headers
|
||||
(while articles
|
||||
(setq article (car articles))
|
||||
(setq art-string (nnfolder-article-string article))
|
||||
(set-buffer nnfolder-current-buffer)
|
||||
(when (or (search-forward art-string nil t)
|
||||
;; Don't search the whole file twice! Also, articles
|
||||
;; probably have some locality by number, so searching
|
||||
;; backwards will be faster. Especially if we're at the
|
||||
;; beginning of the buffer :-). -SLB
|
||||
(search-backward art-string nil t))
|
||||
(nnmail-search-unix-mail-delim-backward)
|
||||
(setq start (point))
|
||||
(search-forward "\n\n" nil t)
|
||||
(setq stop (1- (point)))
|
||||
(set-buffer nntp-server-buffer)
|
||||
(insert (format "221 %d Article retrieved.\n" article))
|
||||
(insert-buffer-substring nnfolder-current-buffer start stop)
|
||||
(goto-char (point-max))
|
||||
(insert ".\n"))
|
||||
(setq articles (cdr articles)))
|
||||
|
||||
(set-buffer nntp-server-buffer)
|
||||
(nnheader-fold-continuation-lines)
|
||||
'headers)))))
|
||||
|
||||
(deffoo nnfolder-open-server (server &optional defs)
|
||||
(nnoo-change-server 'nnfolder server defs)
|
||||
(nnmail-activate 'nnfolder t)
|
||||
(gnus-make-directory nnfolder-directory)
|
||||
(cond
|
||||
((not (file-exists-p nnfolder-directory))
|
||||
(nnfolder-close-server)
|
||||
(nnheader-report 'nnfolder "Couldn't create directory: %s"
|
||||
nnfolder-directory))
|
||||
((not (file-directory-p (file-truename nnfolder-directory)))
|
||||
(nnfolder-close-server)
|
||||
(nnheader-report 'nnfolder "Not a directory: %s" nnfolder-directory))
|
||||
(t
|
||||
(nnmail-activate 'nnfolder)
|
||||
(nnheader-report 'nnfolder "Opened server %s using directory %s"
|
||||
server nnfolder-directory)
|
||||
t)))
|
||||
|
||||
(deffoo nnfolder-request-close ()
|
||||
(let ((alist nnfolder-buffer-alist))
|
||||
(while alist
|
||||
(nnfolder-close-group (caar alist) nil t)
|
||||
(setq alist (cdr alist))))
|
||||
(nnoo-close-server 'nnfolder)
|
||||
(setq nnfolder-buffer-alist nil
|
||||
nnfolder-group-alist nil))
|
||||
|
||||
(deffoo nnfolder-request-article (article &optional group server buffer)
|
||||
(nnfolder-possibly-change-group group server)
|
||||
(save-excursion
|
||||
(set-buffer nnfolder-current-buffer)
|
||||
(goto-char (point-min))
|
||||
(when (search-forward (nnfolder-article-string article) nil t)
|
||||
(let (start stop)
|
||||
(nnmail-search-unix-mail-delim-backward)
|
||||
(setq start (point))
|
||||
(forward-line 1)
|
||||
(unless (and (nnmail-search-unix-mail-delim)
|
||||
(forward-line -1))
|
||||
(goto-char (point-max)))
|
||||
(setq stop (point))
|
||||
(let ((nntp-server-buffer (or buffer nntp-server-buffer)))
|
||||
(set-buffer nntp-server-buffer)
|
||||
(erase-buffer)
|
||||
(insert-buffer-substring nnfolder-current-buffer start stop)
|
||||
(goto-char (point-min))
|
||||
(while (looking-at "From ")
|
||||
(delete-char 5)
|
||||
(insert "X-From-Line: ")
|
||||
(forward-line 1))
|
||||
(if (numberp article)
|
||||
(cons nnfolder-current-group article)
|
||||
(goto-char (point-min))
|
||||
(search-forward (concat "\n" nnfolder-article-marker))
|
||||
(cons nnfolder-current-group
|
||||
(string-to-int
|
||||
(buffer-substring
|
||||
(point) (progn (end-of-line) (point)))))))))))
|
||||
|
||||
(deffoo nnfolder-request-group (group &optional server dont-check)
|
||||
(nnfolder-possibly-change-group group server t)
|
||||
(save-excursion
|
||||
(if (not (assoc group nnfolder-group-alist))
|
||||
(nnheader-report 'nnfolder "No such group: %s" group)
|
||||
(if dont-check
|
||||
(progn
|
||||
(nnheader-report 'nnfolder "Selected group %s" group)
|
||||
t)
|
||||
(let* ((active (assoc group nnfolder-group-alist))
|
||||
(group (car active))
|
||||
(range (cadr active)))
|
||||
(cond
|
||||
((null active)
|
||||
(nnheader-report 'nnfolder "No such group: %s" group))
|
||||
((null nnfolder-current-group)
|
||||
(nnheader-report 'nnfolder "Empty group: %s" group))
|
||||
(t
|
||||
(nnheader-report 'nnfolder "Selected group %s" group)
|
||||
(nnheader-insert "211 %d %d %d %s\n"
|
||||
(1+ (- (cdr range) (car range)))
|
||||
(car range) (cdr range) group))))))))
|
||||
|
||||
(deffoo nnfolder-request-scan (&optional group server)
|
||||
(nnfolder-possibly-change-group nil server)
|
||||
(when nnfolder-get-new-mail
|
||||
(nnfolder-possibly-change-group group server)
|
||||
(nnmail-get-new-mail
|
||||
'nnfolder
|
||||
(lambda ()
|
||||
(let ((bufs nnfolder-buffer-alist))
|
||||
(save-excursion
|
||||
(while bufs
|
||||
(if (not (gnus-buffer-live-p (nth 1 (car bufs))))
|
||||
(setq nnfolder-buffer-alist
|
||||
(delq (car bufs) nnfolder-buffer-alist))
|
||||
(set-buffer (nth 1 (car bufs)))
|
||||
(nnfolder-save-buffer)
|
||||
(kill-buffer (current-buffer)))
|
||||
(setq bufs (cdr bufs))))))
|
||||
nnfolder-directory
|
||||
group)))
|
||||
|
||||
;; Don't close the buffer if we're not shutting down the server. This way,
|
||||
;; we can keep the buffer in the group buffer cache, and not have to grovel
|
||||
;; over the buffer again unless we add new mail to it or modify it in some
|
||||
;; way.
|
||||
|
||||
(deffoo nnfolder-close-group (group &optional server force)
|
||||
;; Make sure we _had_ the group open.
|
||||
(when (or (assoc group nnfolder-buffer-alist)
|
||||
(equal group nnfolder-current-group))
|
||||
(let ((inf (assoc group nnfolder-buffer-alist)))
|
||||
(when inf
|
||||
(when (and nnfolder-current-group
|
||||
nnfolder-current-buffer)
|
||||
(push (list nnfolder-current-group nnfolder-current-buffer)
|
||||
nnfolder-buffer-alist))
|
||||
(setq nnfolder-buffer-alist
|
||||
(delq inf nnfolder-buffer-alist))
|
||||
(setq nnfolder-current-buffer (cadr inf)
|
||||
nnfolder-current-group (car inf))))
|
||||
(when (and nnfolder-current-buffer
|
||||
(buffer-name nnfolder-current-buffer))
|
||||
(save-excursion
|
||||
(set-buffer nnfolder-current-buffer)
|
||||
;; If the buffer was modified, write the file out now.
|
||||
(nnfolder-save-buffer)
|
||||
;; If we're shutting the server down, we need to kill the
|
||||
;; buffer and remove it from the open buffer list. Or, of
|
||||
;; course, if we're trying to minimize our space impact.
|
||||
(kill-buffer (current-buffer))
|
||||
(setq nnfolder-buffer-alist (delq (assoc group nnfolder-buffer-alist)
|
||||
nnfolder-buffer-alist)))))
|
||||
(setq nnfolder-current-group nil
|
||||
nnfolder-current-buffer nil)
|
||||
t)
|
||||
|
||||
(deffoo nnfolder-request-create-group (group &optional server args)
|
||||
(nnfolder-possibly-change-group nil server)
|
||||
(nnmail-activate 'nnfolder)
|
||||
(when group
|
||||
(unless (assoc group nnfolder-group-alist)
|
||||
(push (list group (cons 1 0)) nnfolder-group-alist)
|
||||
(nnmail-save-active nnfolder-group-alist nnfolder-active-file)))
|
||||
t)
|
||||
|
||||
(deffoo nnfolder-request-list (&optional server)
|
||||
(nnfolder-possibly-change-group nil server)
|
||||
(save-excursion
|
||||
(nnmail-find-file nnfolder-active-file)
|
||||
(setq nnfolder-group-alist (nnmail-get-active))
|
||||
t))
|
||||
|
||||
(deffoo nnfolder-request-newgroups (date &optional server)
|
||||
(nnfolder-possibly-change-group nil server)
|
||||
(nnfolder-request-list server))
|
||||
|
||||
(deffoo nnfolder-request-list-newsgroups (&optional server)
|
||||
(nnfolder-possibly-change-group nil server)
|
||||
(save-excursion
|
||||
(nnmail-find-file nnfolder-newsgroups-file)))
|
||||
|
||||
(deffoo nnfolder-request-expire-articles
|
||||
(articles newsgroup &optional server force)
|
||||
(nnfolder-possibly-change-group newsgroup server)
|
||||
(let* ((is-old t)
|
||||
rest)
|
||||
(nnmail-activate 'nnfolder)
|
||||
|
||||
(save-excursion
|
||||
(set-buffer nnfolder-current-buffer)
|
||||
(while (and articles is-old)
|
||||
(goto-char (point-min))
|
||||
(when (search-forward (nnfolder-article-string (car articles)) nil t)
|
||||
(if (setq is-old
|
||||
(nnmail-expired-article-p
|
||||
newsgroup
|
||||
(buffer-substring
|
||||
(point) (progn (end-of-line) (point)))
|
||||
force nnfolder-inhibit-expiry))
|
||||
(progn
|
||||
(nnheader-message 5 "Deleting article %d..."
|
||||
(car articles) newsgroup)
|
||||
(nnfolder-delete-mail))
|
||||
(push (car articles) rest)))
|
||||
(setq articles (cdr articles)))
|
||||
(unless nnfolder-inhibit-expiry
|
||||
(nnheader-message 5 "Deleting articles...done"))
|
||||
(nnfolder-save-buffer)
|
||||
(nnfolder-adjust-min-active newsgroup)
|
||||
(nnmail-save-active nnfolder-group-alist nnfolder-active-file)
|
||||
(nconc rest articles))))
|
||||
|
||||
(deffoo nnfolder-request-move-article
|
||||
(article group server accept-form &optional last)
|
||||
(let ((buf (get-buffer-create " *nnfolder move*"))
|
||||
result)
|
||||
(and
|
||||
(nnfolder-request-article article group server)
|
||||
(save-excursion
|
||||
(set-buffer buf)
|
||||
(buffer-disable-undo (current-buffer))
|
||||
(erase-buffer)
|
||||
(insert-buffer-substring nntp-server-buffer)
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward
|
||||
(concat "^" nnfolder-article-marker)
|
||||
(save-excursion (search-forward "\n\n" nil t) (point)) t)
|
||||
(delete-region (progn (beginning-of-line) (point))
|
||||
(progn (forward-line 1) (point))))
|
||||
(setq result (eval accept-form))
|
||||
(kill-buffer buf)
|
||||
result)
|
||||
(save-excursion
|
||||
(nnfolder-possibly-change-group group server)
|
||||
(set-buffer nnfolder-current-buffer)
|
||||
(goto-char (point-min))
|
||||
(when (search-forward (nnfolder-article-string article) nil t)
|
||||
(nnfolder-delete-mail))
|
||||
(when last
|
||||
(nnfolder-save-buffer)
|
||||
(nnfolder-adjust-min-active group)
|
||||
(nnmail-save-active nnfolder-group-alist nnfolder-active-file))))
|
||||
result))
|
||||
|
||||
(deffoo nnfolder-request-accept-article (group &optional server last)
|
||||
(nnfolder-possibly-change-group group server)
|
||||
(nnmail-check-syntax)
|
||||
(let ((buf (current-buffer))
|
||||
result art-group)
|
||||
(goto-char (point-min))
|
||||
(when (looking-at "X-From-Line: ")
|
||||
(replace-match "From "))
|
||||
(and
|
||||
(nnfolder-request-list)
|
||||
(save-excursion
|
||||
(set-buffer buf)
|
||||
(goto-char (point-min))
|
||||
(search-forward "\n\n" nil t)
|
||||
(forward-line -1)
|
||||
(while (re-search-backward (concat "^" nnfolder-article-marker) nil t)
|
||||
(delete-region (point) (progn (forward-line 1) (point))))
|
||||
(when nnmail-cache-accepted-message-ids
|
||||
(nnmail-cache-insert (nnmail-fetch-field "message-id")))
|
||||
(setq result (if (stringp group)
|
||||
(list (cons group (nnfolder-active-number group)))
|
||||
(setq art-group
|
||||
(nnmail-article-group 'nnfolder-active-number))))
|
||||
(if (and (null result)
|
||||
(yes-or-no-p "Moved to `junk' group; delete article? "))
|
||||
(setq result 'junk)
|
||||
(setq result
|
||||
(car (nnfolder-save-mail result)))))
|
||||
(when last
|
||||
(save-excursion
|
||||
(nnfolder-possibly-change-folder (or (caar art-group) group))
|
||||
(nnfolder-save-buffer)
|
||||
(when nnmail-cache-accepted-message-ids
|
||||
(nnmail-cache-close)))))
|
||||
(nnmail-save-active nnfolder-group-alist nnfolder-active-file)
|
||||
(unless result
|
||||
(nnheader-report 'nnfolder "Couldn't store article"))
|
||||
result))
|
||||
|
||||
(deffoo nnfolder-request-replace-article (article group buffer)
|
||||
(nnfolder-possibly-change-group group)
|
||||
(save-excursion
|
||||
(set-buffer nnfolder-current-buffer)
|
||||
(goto-char (point-min))
|
||||
(if (not (search-forward (nnfolder-article-string article) nil t))
|
||||
nil
|
||||
(nnfolder-delete-mail t t)
|
||||
(insert-buffer-substring buffer)
|
||||
(nnfolder-save-buffer)
|
||||
t)))
|
||||
|
||||
(deffoo nnfolder-request-delete-group (group &optional force server)
|
||||
(nnfolder-close-group group server t)
|
||||
;; Delete all articles in GROUP.
|
||||
(if (not force)
|
||||
() ; Don't delete the articles.
|
||||
;; Delete the file that holds the group.
|
||||
(ignore-errors
|
||||
(delete-file (nnfolder-group-pathname group))))
|
||||
;; Remove the group from all structures.
|
||||
(setq nnfolder-group-alist
|
||||
(delq (assoc group nnfolder-group-alist) nnfolder-group-alist)
|
||||
nnfolder-current-group nil
|
||||
nnfolder-current-buffer nil)
|
||||
;; Save the active file.
|
||||
(nnmail-save-active nnfolder-group-alist nnfolder-active-file)
|
||||
t)
|
||||
|
||||
(deffoo nnfolder-request-rename-group (group new-name &optional server)
|
||||
(nnfolder-possibly-change-group group server)
|
||||
(save-excursion
|
||||
(set-buffer nnfolder-current-buffer)
|
||||
(and (file-writable-p buffer-file-name)
|
||||
(ignore-errors
|
||||
(rename-file
|
||||
buffer-file-name
|
||||
(nnfolder-group-pathname new-name))
|
||||
t)
|
||||
;; That went ok, so we change the internal structures.
|
||||
(let ((entry (assoc group nnfolder-group-alist)))
|
||||
(and entry (setcar entry new-name))
|
||||
(setq nnfolder-current-buffer nil
|
||||
nnfolder-current-group nil)
|
||||
;; Save the new group alist.
|
||||
(nnmail-save-active nnfolder-group-alist nnfolder-active-file)
|
||||
;; We kill the buffer instead of renaming it and stuff.
|
||||
(kill-buffer (current-buffer))
|
||||
t))))
|
||||
|
||||
|
||||
;;; Internal functions.
|
||||
|
||||
(defun nnfolder-adjust-min-active (group)
|
||||
;; Find the lowest active article in this group.
|
||||
(let* ((active (cadr (assoc group nnfolder-group-alist)))
|
||||
(marker (concat "\n" nnfolder-article-marker))
|
||||
(number "[0-9]+")
|
||||
(activemin (cdr active)))
|
||||
(save-excursion
|
||||
(set-buffer nnfolder-current-buffer)
|
||||
(goto-char (point-min))
|
||||
(while (and (search-forward marker nil t)
|
||||
(re-search-forward number nil t))
|
||||
(setq activemin (min activemin
|
||||
(string-to-number (buffer-substring
|
||||
(match-beginning 0)
|
||||
(match-end 0))))))
|
||||
(setcar active activemin))))
|
||||
|
||||
(defun nnfolder-article-string (article)
|
||||
(if (numberp article)
|
||||
(concat "\n" nnfolder-article-marker (int-to-string article) " ")
|
||||
(concat "\nMessage-ID: " article)))
|
||||
|
||||
(defun nnfolder-delete-mail (&optional force leave-delim)
|
||||
"Delete the message that point is in."
|
||||
(save-excursion
|
||||
(delete-region
|
||||
(save-excursion
|
||||
(nnmail-search-unix-mail-delim-backward)
|
||||
(if leave-delim (progn (forward-line 1) (point))
|
||||
(point)))
|
||||
(progn
|
||||
(forward-line 1)
|
||||
(if (nnmail-search-unix-mail-delim)
|
||||
(if (and (not (bobp)) leave-delim)
|
||||
(progn (forward-line -2) (point))
|
||||
(point))
|
||||
(point-max))))))
|
||||
|
||||
(defun nnfolder-possibly-change-group (group &optional server dont-check)
|
||||
;; Change servers.
|
||||
(when (and server
|
||||
(not (nnfolder-server-opened server)))
|
||||
(nnfolder-open-server server))
|
||||
(unless (gnus-buffer-live-p nnfolder-current-buffer)
|
||||
(setq nnfolder-current-buffer nil
|
||||
nnfolder-current-group nil))
|
||||
;; Change group.
|
||||
(when (and group
|
||||
(not (equal group nnfolder-current-group)))
|
||||
(nnmail-activate 'nnfolder)
|
||||
(when (and (not (assoc group nnfolder-group-alist))
|
||||
(not (file-exists-p
|
||||
(nnfolder-group-pathname group))))
|
||||
;; The group doesn't exist, so we create a new entry for it.
|
||||
(push (list group (cons 1 0)) nnfolder-group-alist)
|
||||
(nnmail-save-active nnfolder-group-alist nnfolder-active-file))
|
||||
|
||||
(if dont-check
|
||||
(setq nnfolder-current-group group)
|
||||
(let (inf file)
|
||||
;; If we have to change groups, see if we don't already have the
|
||||
;; folder in memory. If we do, verify the modtime and destroy
|
||||
;; the folder if needed so we can rescan it.
|
||||
(when (setq inf (assoc group nnfolder-buffer-alist))
|
||||
(setq nnfolder-current-buffer (nth 1 inf)))
|
||||
|
||||
;; If the buffer is not live, make sure it isn't in the alist. If it
|
||||
;; is live, verify that nobody else has touched the file since last
|
||||
;; time.
|
||||
(when (and nnfolder-current-buffer
|
||||
(not (gnus-buffer-live-p nnfolder-current-buffer)))
|
||||
(setq nnfolder-buffer-alist (delq inf nnfolder-buffer-alist)
|
||||
nnfolder-current-buffer nil))
|
||||
|
||||
(setq nnfolder-current-group group)
|
||||
|
||||
(when (or (not nnfolder-current-buffer)
|
||||
(not (verify-visited-file-modtime nnfolder-current-buffer)))
|
||||
(save-excursion
|
||||
(setq file (nnfolder-group-pathname group))
|
||||
;; See whether we need to create the new file.
|
||||
(unless (file-exists-p file)
|
||||
(gnus-make-directory (file-name-directory file))
|
||||
(nnmail-write-region 1 1 file t 'nomesg))
|
||||
(when (setq nnfolder-current-buffer (nnfolder-read-folder group))
|
||||
(set-buffer nnfolder-current-buffer)
|
||||
(push (list group nnfolder-current-buffer)
|
||||
nnfolder-buffer-alist))))))))
|
||||
|
||||
(defun nnfolder-save-mail (group-art-list)
|
||||
"Called narrowed to an article."
|
||||
(let* (save-list group-art)
|
||||
(goto-char (point-min))
|
||||
;; The From line may have been quoted by movemail.
|
||||
(when (looking-at (concat ">" message-unix-mail-delimiter))
|
||||
(delete-char 1))
|
||||
;; This might come from somewhere else.
|
||||
(unless (looking-at message-unix-mail-delimiter)
|
||||
(insert "From nobody " (current-time-string) "\n")
|
||||
(goto-char (point-min)))
|
||||
;; Quote all "From " lines in the article.
|
||||
(forward-line 1)
|
||||
(let (case-fold-search)
|
||||
(while (re-search-forward "^From " nil t)
|
||||
(beginning-of-line)
|
||||
(insert "> ")))
|
||||
(setq save-list group-art-list)
|
||||
(nnmail-insert-lines)
|
||||
(nnmail-insert-xref group-art-list)
|
||||
(run-hooks 'nnmail-prepare-save-mail-hook)
|
||||
(run-hooks 'nnfolder-prepare-save-mail-hook)
|
||||
|
||||
;; Insert the mail into each of the destination groups.
|
||||
(while (setq group-art (pop group-art-list))
|
||||
;; Kill any previous newsgroup markers.
|
||||
(goto-char (point-min))
|
||||
(search-forward "\n\n" nil t)
|
||||
(forward-line -1)
|
||||
(while (search-backward (concat "\n" nnfolder-article-marker) nil t)
|
||||
(delete-region (1+ (point)) (progn (forward-line 2) (point))))
|
||||
|
||||
;; Insert the new newsgroup marker.
|
||||
(nnfolder-insert-newsgroup-line group-art)
|
||||
|
||||
(save-excursion
|
||||
(let ((beg (point-min))
|
||||
(end (point-max))
|
||||
(obuf (current-buffer)))
|
||||
(nnfolder-possibly-change-folder (car group-art))
|
||||
(let ((buffer-read-only nil))
|
||||
(goto-char (point-max))
|
||||
(unless (eolp)
|
||||
(insert "\n"))
|
||||
(unless (bobp)
|
||||
(insert "\n"))
|
||||
(insert-buffer-substring obuf beg end)))))
|
||||
|
||||
;; Did we save it anywhere?
|
||||
save-list))
|
||||
|
||||
(defun nnfolder-insert-newsgroup-line (group-art)
|
||||
(save-excursion
|
||||
(goto-char (point-min))
|
||||
(when (search-forward "\n\n" nil t)
|
||||
(forward-char -1)
|
||||
(insert (format (concat nnfolder-article-marker "%d %s\n")
|
||||
(cdr group-art) (current-time-string))))))
|
||||
|
||||
(defun nnfolder-active-number (group)
|
||||
;; Find the next article number in GROUP.
|
||||
(let ((active (cadr (assoc group nnfolder-group-alist))))
|
||||
(if active
|
||||
(setcdr active (1+ (cdr active)))
|
||||
;; This group is new, so we create a new entry for it.
|
||||
;; This might be a bit naughty... creating groups on the drop of
|
||||
;; a hat, but I don't know...
|
||||
(push (list group (setq active (cons 1 1)))
|
||||
nnfolder-group-alist))
|
||||
(cdr active)))
|
||||
|
||||
(defun nnfolder-possibly-change-folder (group)
|
||||
(let ((inf (assoc group nnfolder-buffer-alist)))
|
||||
(if (and inf
|
||||
(gnus-buffer-live-p (cadr inf)))
|
||||
(set-buffer (cadr inf))
|
||||
(when inf
|
||||
(setq nnfolder-buffer-alist (delq inf nnfolder-buffer-alist)))
|
||||
(when nnfolder-group-alist
|
||||
(nnmail-save-active nnfolder-group-alist nnfolder-active-file))
|
||||
(push (list group (nnfolder-read-folder group))
|
||||
nnfolder-buffer-alist))))
|
||||
|
||||
;; This method has a problem if you've accidentally let the active list get
|
||||
;; out of sync with the files. This could happen, say, if you've
|
||||
;; accidentally gotten new mail with something other than Gnus (but why
|
||||
;; would _that_ ever happen? :-). In that case, we will be in the middle of
|
||||
;; processing the file, ready to add new X-Gnus article number markers, and
|
||||
;; we'll run across a message with no ID yet - the active list _may_not_ be
|
||||
;; ready for us yet.
|
||||
|
||||
;; To handle this, I'm modifying this routine to maintain the maximum ID seen
|
||||
;; so far, and when we hit a message with no ID, we will _manually_ scan the
|
||||
;; rest of the message looking for any more, possibly higher IDs. We'll
|
||||
;; assume the maximum that we find is the highest active. Note that this
|
||||
;; shouldn't cost us much extra time at all, but will be a lot less
|
||||
;; vulnerable to glitches between the mbox and the active file.
|
||||
|
||||
(defun nnfolder-read-folder (group)
|
||||
(let* ((file (nnfolder-group-pathname group))
|
||||
(buffer (set-buffer (nnheader-find-file-noselect file))))
|
||||
(if (equal (cadr (assoc group nnfolder-scantime-alist))
|
||||
(nth 5 (file-attributes file)))
|
||||
;; This looks up-to-date, so we don't do any scanning.
|
||||
buffer
|
||||
;; Parse the damn thing.
|
||||
(save-excursion
|
||||
(nnmail-activate 'nnfolder)
|
||||
;; Read in the file.
|
||||
(let ((delim (concat "^" message-unix-mail-delimiter))
|
||||
(marker (concat "\n" nnfolder-article-marker))
|
||||
(number "[0-9]+")
|
||||
(active (or (cadr (assoc group nnfolder-group-alist))
|
||||
(cons 1 0)))
|
||||
(scantime (assoc group nnfolder-scantime-alist))
|
||||
(minid (lsh -1 -1))
|
||||
maxid start end newscantime
|
||||
buffer-read-only)
|
||||
(buffer-disable-undo (current-buffer))
|
||||
(setq maxid (cdr active))
|
||||
(goto-char (point-min))
|
||||
|
||||
;; Anytime the active number is 1 or 0, it is suspect. In that
|
||||
;; case, search the file manually to find the active number. Or,
|
||||
;; of course, if we're being paranoid. (This would also be the
|
||||
;; place to build other lists from the header markers, such as
|
||||
;; expunge lists, etc., if we ever desired to abandon the active
|
||||
;; file entirely for mboxes.)
|
||||
(when (or nnfolder-ignore-active-file
|
||||
(< maxid 2))
|
||||
(while (and (search-forward marker nil t)
|
||||
(re-search-forward number nil t))
|
||||
(let ((newnum (string-to-number (match-string 0))))
|
||||
(setq maxid (max maxid newnum))
|
||||
(setq minid (min minid newnum))))
|
||||
(setcar active (max 1 (min minid maxid)))
|
||||
(setcdr active (max maxid (cdr active)))
|
||||
(goto-char (point-min)))
|
||||
|
||||
;; As long as we trust that the user will only insert unmarked mail
|
||||
;; at the end, go to the end and search backwards for the last
|
||||
;; marker. Find the start of that message, and begin to search for
|
||||
;; unmarked messages from there.
|
||||
(when (not (or nnfolder-distrust-mbox
|
||||
(< maxid 2)))
|
||||
(goto-char (point-max))
|
||||
(unless (re-search-backward marker nil t)
|
||||
(goto-char (point-min)))
|
||||
(when (nnmail-search-unix-mail-delim)
|
||||
(goto-char (point-min))))
|
||||
|
||||
;; Keep track of the active number on our own, and insert it back
|
||||
;; into the active list when we're done. Also, prime the pump to
|
||||
;; cut down on the number of searches we do.
|
||||
(unless (nnmail-search-unix-mail-delim)
|
||||
(goto-char (point-max)))
|
||||
(setq end (point-marker))
|
||||
(while (not (= end (point-max)))
|
||||
(setq start (marker-position end))
|
||||
(goto-char end)
|
||||
;; There may be more than one "From " line, so we skip past
|
||||
;; them.
|
||||
(while (looking-at delim)
|
||||
(forward-line 1))
|
||||
(set-marker end (if (nnmail-search-unix-mail-delim)
|
||||
(point)
|
||||
(point-max)))
|
||||
(goto-char start)
|
||||
(when (not (search-forward marker end t))
|
||||
(narrow-to-region start end)
|
||||
(nnmail-insert-lines)
|
||||
(nnfolder-insert-newsgroup-line
|
||||
(cons nil (nnfolder-active-number nnfolder-current-group)))
|
||||
(widen)))
|
||||
|
||||
(set-marker end nil)
|
||||
;; Make absolutely sure that the active list reflects reality!
|
||||
(nnmail-save-active nnfolder-group-alist nnfolder-active-file)
|
||||
;; Set the scantime for this group.
|
||||
(setq newscantime (visited-file-modtime))
|
||||
(if scantime
|
||||
(setcdr scantime (list newscantime))
|
||||
(push (list nnfolder-current-group newscantime)
|
||||
nnfolder-scantime-alist))
|
||||
(current-buffer))))))
|
||||
|
||||
;;;###autoload
|
||||
(defun nnfolder-generate-active-file ()
|
||||
"Look for mbox folders in the nnfolder directory and make them into groups."
|
||||
(interactive)
|
||||
(nnmail-activate 'nnfolder)
|
||||
(let ((files (directory-files nnfolder-directory))
|
||||
file)
|
||||
(while (setq file (pop files))
|
||||
(when (and (not (backup-file-name-p file))
|
||||
(message-mail-file-mbox-p
|
||||
(nnheader-concat nnfolder-directory file)))
|
||||
(let ((oldgroup (assoc file nnfolder-group-alist)))
|
||||
(if oldgroup
|
||||
(nnheader-message 5 "Refreshing group %s..." file)
|
||||
(nnheader-message 5 "Adding group %s..." file))
|
||||
(setq nnfolder-group-alist (remove oldgroup nnfolder-group-alist))
|
||||
(push (list file (cons 1 0)) nnfolder-group-alist)
|
||||
(nnfolder-possibly-change-folder file)
|
||||
(nnfolder-possibly-change-group file)
|
||||
(nnfolder-close-group file))))
|
||||
(message "")))
|
||||
|
||||
(defun nnfolder-group-pathname (group)
|
||||
"Make pathname for GROUP."
|
||||
(let ((dir (file-name-as-directory (expand-file-name nnfolder-directory))))
|
||||
;; If this file exists, we use it directly.
|
||||
(if (or nnmail-use-long-file-names
|
||||
(file-exists-p (concat dir group)))
|
||||
(concat dir group)
|
||||
;; If not, we translate dots into slashes.
|
||||
(concat dir (nnheader-replace-chars-in-string group ?. ?/)))))
|
||||
|
||||
(defun nnfolder-save-buffer ()
|
||||
"Save the buffer."
|
||||
(when (buffer-modified-p)
|
||||
(run-hooks 'nnfolder-save-buffer-hook)
|
||||
(save-buffer)))
|
||||
|
||||
(provide 'nnfolder)
|
||||
|
||||
;;; nnfolder.el ends here
|
||||
80
lisp/gnus/nngateway.el
Normal file
80
lisp/gnus/nngateway.el
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
;;; nngateway.el --- posting news via mail gateways
|
||||
;; Copyright (C) 1996,97 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
|
||||
;; Keywords: news, mail
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'nnoo)
|
||||
(require 'message)
|
||||
|
||||
(nnoo-declare nngateway)
|
||||
|
||||
(defvoo nngateway-address nil
|
||||
"Address of the mail-to-news gateway.")
|
||||
|
||||
(defvoo nngateway-header-transformation 'nngateway-simple-header-transformation
|
||||
"Function to be called to rewrite the news headers into mail headers.
|
||||
It is called narrowed to the headers to be transformed with one
|
||||
parameter -- the gateway address.")
|
||||
|
||||
;;; Interface functions
|
||||
|
||||
(nnoo-define-basics nngateway)
|
||||
|
||||
(deffoo nngateway-open-server (server &optional defs)
|
||||
(if (nngateway-server-opened server)
|
||||
t
|
||||
(unless (assq 'nngateway-address defs)
|
||||
(setq defs (append defs (list (list 'nngateway-address server)))))
|
||||
(nnoo-change-server 'nngateway server defs)))
|
||||
|
||||
(deffoo nngateway-request-post (&optional server)
|
||||
(when (or (nngateway-server-opened server)
|
||||
(nngateway-open-server server))
|
||||
;; Rewrite the header.
|
||||
(let ((buf (current-buffer)))
|
||||
(nnheader-temp-write nil
|
||||
(insert-buffer-substring buf)
|
||||
(message-narrow-to-head)
|
||||
(funcall nngateway-header-transformation nngateway-address)
|
||||
(widen)
|
||||
(let (message-required-mail-headers)
|
||||
(message-send-mail))))))
|
||||
|
||||
;;; Internal functions
|
||||
|
||||
(defun nngateway-simple-header-transformation (gateway)
|
||||
"Transform the headers to use GATEWAY."
|
||||
(let ((newsgroups (mail-fetch-field "newsgroups")))
|
||||
(message-remove-header "to")
|
||||
(message-remove-header "cc")
|
||||
(goto-char (point-min))
|
||||
(insert "To: " (nnheader-replace-chars-in-string newsgroups ?. ?-)
|
||||
"@" gateway "\n")))
|
||||
|
||||
(nnoo-define-skeleton nngateway)
|
||||
|
||||
(provide 'nngateway)
|
||||
|
||||
;;; nngateway.el ends here
|
||||
820
lisp/gnus/nnheader.el
Normal file
820
lisp/gnus/nnheader.el
Normal file
|
|
@ -0,0 +1,820 @@
|
|||
;;; nnheader.el --- header access macros for Gnus and its backends
|
||||
;; Copyright (C) 1987,88,89,90,93,94,95,96,97 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
|
||||
;; Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
|
||||
;; Keywords: news
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; These macros may look very much like the ones in GNUS 4.1. They
|
||||
;; are, in a way, but you should note that the indices they use have
|
||||
;; been changed from the internal GNUS format to the NOV format. The
|
||||
;; makes it possible to read headers from XOVER much faster.
|
||||
;;
|
||||
;; The format of a header is now:
|
||||
;; [number subject from date id references chars lines xref]
|
||||
;;
|
||||
;; (That last entry is defined as "misc" in the NOV format, but Gnus
|
||||
;; uses it for xrefs.)
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'mail-utils)
|
||||
|
||||
(defvar nnheader-max-head-length 4096
|
||||
"*Max length of the head of articles.")
|
||||
|
||||
(defvar nnheader-head-chop-length 2048
|
||||
"*Length of each read operation when trying to fetch HEAD headers.")
|
||||
|
||||
(defvar nnheader-file-name-translation-alist nil
|
||||
"*Alist that says how to translate characters in file names.
|
||||
For instance, if \":\" is illegal as a file character in file names
|
||||
on your system, you could say something like:
|
||||
|
||||
\(setq nnheader-file-name-translation-alist '((?: . ?_)))")
|
||||
|
||||
(eval-and-compile
|
||||
(autoload 'nnmail-message-id "nnmail")
|
||||
(autoload 'mail-position-on-field "sendmail")
|
||||
(autoload 'message-remove-header "message")
|
||||
(autoload 'cancel-function-timers "timers")
|
||||
(autoload 'gnus-point-at-eol "gnus-util"))
|
||||
|
||||
;;; Header access macros.
|
||||
|
||||
(defmacro mail-header-number (header)
|
||||
"Return article number in HEADER."
|
||||
`(aref ,header 0))
|
||||
|
||||
(defmacro mail-header-set-number (header number)
|
||||
"Set article number of HEADER to NUMBER."
|
||||
`(aset ,header 0 ,number))
|
||||
|
||||
(defmacro mail-header-subject (header)
|
||||
"Return subject string in HEADER."
|
||||
`(aref ,header 1))
|
||||
|
||||
(defmacro mail-header-set-subject (header subject)
|
||||
"Set article subject of HEADER to SUBJECT."
|
||||
`(aset ,header 1 ,subject))
|
||||
|
||||
(defmacro mail-header-from (header)
|
||||
"Return author string in HEADER."
|
||||
`(aref ,header 2))
|
||||
|
||||
(defmacro mail-header-set-from (header from)
|
||||
"Set article author of HEADER to FROM."
|
||||
`(aset ,header 2 ,from))
|
||||
|
||||
(defmacro mail-header-date (header)
|
||||
"Return date in HEADER."
|
||||
`(aref ,header 3))
|
||||
|
||||
(defmacro mail-header-set-date (header date)
|
||||
"Set article date of HEADER to DATE."
|
||||
`(aset ,header 3 ,date))
|
||||
|
||||
(defalias 'mail-header-message-id 'mail-header-id)
|
||||
(defmacro mail-header-id (header)
|
||||
"Return Id in HEADER."
|
||||
`(aref ,header 4))
|
||||
|
||||
(defalias 'mail-header-set-message-id 'mail-header-set-id)
|
||||
(defmacro mail-header-set-id (header id)
|
||||
"Set article Id of HEADER to ID."
|
||||
`(aset ,header 4 ,id))
|
||||
|
||||
(defmacro mail-header-references (header)
|
||||
"Return references in HEADER."
|
||||
`(aref ,header 5))
|
||||
|
||||
(defmacro mail-header-set-references (header ref)
|
||||
"Set article references of HEADER to REF."
|
||||
`(aset ,header 5 ,ref))
|
||||
|
||||
(defmacro mail-header-chars (header)
|
||||
"Return number of chars of article in HEADER."
|
||||
`(aref ,header 6))
|
||||
|
||||
(defmacro mail-header-set-chars (header chars)
|
||||
"Set number of chars in article of HEADER to CHARS."
|
||||
`(aset ,header 6 ,chars))
|
||||
|
||||
(defmacro mail-header-lines (header)
|
||||
"Return lines in HEADER."
|
||||
`(aref ,header 7))
|
||||
|
||||
(defmacro mail-header-set-lines (header lines)
|
||||
"Set article lines of HEADER to LINES."
|
||||
`(aset ,header 7 ,lines))
|
||||
|
||||
(defmacro mail-header-xref (header)
|
||||
"Return xref string in HEADER."
|
||||
`(aref ,header 8))
|
||||
|
||||
(defmacro mail-header-set-xref (header xref)
|
||||
"Set article xref of HEADER to xref."
|
||||
`(aset ,header 8 ,xref))
|
||||
|
||||
(defun make-mail-header (&optional init)
|
||||
"Create a new mail header structure initialized with INIT."
|
||||
(make-vector 9 init))
|
||||
|
||||
(defun make-full-mail-header (&optional number subject from date id
|
||||
references chars lines xref)
|
||||
"Create a new mail header structure initialized with the parameters given."
|
||||
(vector number subject from date id references chars lines xref))
|
||||
|
||||
;; fake message-ids: generation and detection
|
||||
|
||||
(defvar nnheader-fake-message-id 1)
|
||||
|
||||
(defsubst nnheader-generate-fake-message-id ()
|
||||
(concat "fake+none+" (int-to-string (incf nnheader-fake-message-id))))
|
||||
|
||||
(defsubst nnheader-fake-message-id-p (id)
|
||||
(save-match-data ; regular message-id's are <.*>
|
||||
(string-match "\\`fake\\+none\\+[0-9]+\\'" id)))
|
||||
|
||||
;; Parsing headers and NOV lines.
|
||||
|
||||
(defsubst nnheader-header-value ()
|
||||
(buffer-substring (match-end 0) (gnus-point-at-eol)))
|
||||
|
||||
(defun nnheader-parse-head (&optional naked)
|
||||
(let ((case-fold-search t)
|
||||
(cur (current-buffer))
|
||||
(buffer-read-only nil)
|
||||
in-reply-to lines p)
|
||||
(goto-char (point-min))
|
||||
(when naked
|
||||
(insert "\n"))
|
||||
;; Search to the beginning of the next header. Error messages
|
||||
;; do not begin with 2 or 3.
|
||||
(prog1
|
||||
(when (or naked (re-search-forward "^[23][0-9]+ " nil t))
|
||||
;; This implementation of this function, with nine
|
||||
;; search-forwards instead of the one re-search-forward and
|
||||
;; a case (which basically was the old function) is actually
|
||||
;; about twice as fast, even though it looks messier. You
|
||||
;; can't have everything, I guess. Speed and elegance
|
||||
;; don't always go hand in hand.
|
||||
(vector
|
||||
;; Number.
|
||||
(if naked
|
||||
(progn
|
||||
(setq p (point-min))
|
||||
0)
|
||||
(prog1
|
||||
(read cur)
|
||||
(end-of-line)
|
||||
(setq p (point))
|
||||
(narrow-to-region (point)
|
||||
(or (and (search-forward "\n.\n" nil t)
|
||||
(- (point) 2))
|
||||
(point)))))
|
||||
;; Subject.
|
||||
(progn
|
||||
(goto-char p)
|
||||
(if (search-forward "\nsubject: " nil t)
|
||||
(nnheader-header-value) "(none)"))
|
||||
;; From.
|
||||
(progn
|
||||
(goto-char p)
|
||||
(if (search-forward "\nfrom: " nil t)
|
||||
(nnheader-header-value) "(nobody)"))
|
||||
;; Date.
|
||||
(progn
|
||||
(goto-char p)
|
||||
(if (search-forward "\ndate: " nil t)
|
||||
(nnheader-header-value) ""))
|
||||
;; Message-ID.
|
||||
(progn
|
||||
(goto-char p)
|
||||
(if (search-forward "\nmessage-id:" nil t)
|
||||
(buffer-substring
|
||||
(1- (or (search-forward "<" nil t) (point)))
|
||||
(or (search-forward ">" nil t) (point)))
|
||||
;; If there was no message-id, we just fake one to make
|
||||
;; subsequent routines simpler.
|
||||
(nnheader-generate-fake-message-id)))
|
||||
;; References.
|
||||
(progn
|
||||
(goto-char p)
|
||||
(if (search-forward "\nreferences: " nil t)
|
||||
(nnheader-header-value)
|
||||
;; Get the references from the in-reply-to header if there
|
||||
;; were no references and the in-reply-to header looks
|
||||
;; promising.
|
||||
(if (and (search-forward "\nin-reply-to: " nil t)
|
||||
(setq in-reply-to (nnheader-header-value))
|
||||
(string-match "<[^>]+>" in-reply-to))
|
||||
(substring in-reply-to (match-beginning 0)
|
||||
(match-end 0))
|
||||
"")))
|
||||
;; Chars.
|
||||
0
|
||||
;; Lines.
|
||||
(progn
|
||||
(goto-char p)
|
||||
(if (search-forward "\nlines: " nil t)
|
||||
(if (numberp (setq lines (read cur)))
|
||||
lines 0)
|
||||
0))
|
||||
;; Xref.
|
||||
(progn
|
||||
(goto-char p)
|
||||
(and (search-forward "\nxref: " nil t)
|
||||
(nnheader-header-value)))))
|
||||
(when naked
|
||||
(goto-char (point-min))
|
||||
(delete-char 1)))))
|
||||
|
||||
(defmacro nnheader-nov-skip-field ()
|
||||
'(search-forward "\t" eol 'move))
|
||||
|
||||
(defmacro nnheader-nov-field ()
|
||||
'(buffer-substring (point) (if (nnheader-nov-skip-field) (1- (point)) eol)))
|
||||
|
||||
(defmacro nnheader-nov-read-integer ()
|
||||
'(prog1
|
||||
(if (= (following-char) ?\t)
|
||||
0
|
||||
(let ((num (ignore-errors (read (current-buffer)))))
|
||||
(if (numberp num) num 0)))
|
||||
(or (eobp) (forward-char 1))))
|
||||
|
||||
;; (defvar nnheader-none-counter 0)
|
||||
|
||||
(defun nnheader-parse-nov ()
|
||||
(let ((eol (gnus-point-at-eol)))
|
||||
(vector
|
||||
(nnheader-nov-read-integer) ; number
|
||||
(nnheader-nov-field) ; subject
|
||||
(nnheader-nov-field) ; from
|
||||
(nnheader-nov-field) ; date
|
||||
(or (nnheader-nov-field)
|
||||
(nnheader-generate-fake-message-id)) ; id
|
||||
(nnheader-nov-field) ; refs
|
||||
(nnheader-nov-read-integer) ; chars
|
||||
(nnheader-nov-read-integer) ; lines
|
||||
(if (= (following-char) ?\n)
|
||||
nil
|
||||
(nnheader-nov-field)) ; misc
|
||||
)))
|
||||
|
||||
(defun nnheader-insert-nov (header)
|
||||
(princ (mail-header-number header) (current-buffer))
|
||||
(insert
|
||||
"\t"
|
||||
(or (mail-header-subject header) "(none)") "\t"
|
||||
(or (mail-header-from header) "(nobody)") "\t"
|
||||
(or (mail-header-date header) "") "\t"
|
||||
(or (mail-header-id header)
|
||||
(nnmail-message-id))
|
||||
"\t"
|
||||
(or (mail-header-references header) "") "\t")
|
||||
(princ (or (mail-header-chars header) 0) (current-buffer))
|
||||
(insert "\t")
|
||||
(princ (or (mail-header-lines header) 0) (current-buffer))
|
||||
(insert "\t")
|
||||
(when (mail-header-xref header)
|
||||
(insert "Xref: " (mail-header-xref header) "\t"))
|
||||
(insert "\n"))
|
||||
|
||||
(defun nnheader-insert-article-line (article)
|
||||
(goto-char (point-min))
|
||||
(insert "220 ")
|
||||
(princ article (current-buffer))
|
||||
(insert " Article retrieved.\n")
|
||||
(search-forward "\n\n" nil 'move)
|
||||
(delete-region (point) (point-max))
|
||||
(forward-char -1)
|
||||
(insert "."))
|
||||
|
||||
(defun nnheader-nov-delete-outside-range (beg end)
|
||||
"Delete all NOV lines that lie outside the BEG to END range."
|
||||
;; First we find the first wanted line.
|
||||
(nnheader-find-nov-line beg)
|
||||
(delete-region (point-min) (point))
|
||||
;; Then we find the last wanted line.
|
||||
(when (nnheader-find-nov-line end)
|
||||
(forward-line 1))
|
||||
(delete-region (point) (point-max)))
|
||||
|
||||
(defun nnheader-find-nov-line (article)
|
||||
"Put point at the NOV line that start with ARTICLE.
|
||||
If ARTICLE doesn't exist, put point where that line
|
||||
would have been. The function will return non-nil if
|
||||
the line could be found."
|
||||
;; This function basically does a binary search.
|
||||
(let ((max (point-max))
|
||||
(min (goto-char (point-min)))
|
||||
(cur (current-buffer))
|
||||
(prev (point-min))
|
||||
num found)
|
||||
(while (not found)
|
||||
(goto-char (/ (+ max min) 2))
|
||||
(beginning-of-line)
|
||||
(if (or (= (point) prev)
|
||||
(eobp))
|
||||
(setq found t)
|
||||
(setq prev (point))
|
||||
(cond ((> (setq num (read cur)) article)
|
||||
(setq max (point)))
|
||||
((< num article)
|
||||
(setq min (point)))
|
||||
(t
|
||||
(setq found 'yes)))))
|
||||
;; We may be at the first line.
|
||||
(when (and (not num)
|
||||
(not (eobp)))
|
||||
(setq num (read cur)))
|
||||
;; Now we may have found the article we're looking for, or we
|
||||
;; may be somewhere near it.
|
||||
(when (and (not (eq found 'yes))
|
||||
(not (eq num article)))
|
||||
(setq found (point))
|
||||
(while (and (< (point) max)
|
||||
(or (not (numberp num))
|
||||
(< num article)))
|
||||
(forward-line 1)
|
||||
(setq found (point))
|
||||
(or (eobp)
|
||||
(= (setq num (read cur)) article)))
|
||||
(unless (eq num article)
|
||||
(goto-char found)))
|
||||
(beginning-of-line)
|
||||
(eq num article)))
|
||||
|
||||
;; Various cruft the backends and Gnus need to communicate.
|
||||
|
||||
(defvar nntp-server-buffer nil)
|
||||
(defvar gnus-verbose-backends 7
|
||||
"*A number that says how talkative the Gnus backends should be.")
|
||||
(defvar gnus-nov-is-evil nil
|
||||
"If non-nil, Gnus backends will never output headers in the NOV format.")
|
||||
(defvar news-reply-yank-from nil)
|
||||
(defvar news-reply-yank-message-id nil)
|
||||
|
||||
(defvar nnheader-callback-function nil)
|
||||
|
||||
(defun nnheader-init-server-buffer ()
|
||||
"Initialize the Gnus-backend communication buffer."
|
||||
(save-excursion
|
||||
(unless (gnus-buffer-live-p nntp-server-buffer)
|
||||
(setq nntp-server-buffer (get-buffer-create " *nntpd*")))
|
||||
(set-buffer nntp-server-buffer)
|
||||
(buffer-disable-undo (current-buffer))
|
||||
(erase-buffer)
|
||||
(kill-all-local-variables)
|
||||
(setq case-fold-search t) ;Should ignore case.
|
||||
t))
|
||||
|
||||
;;; Various functions the backends use.
|
||||
|
||||
(defun nnheader-file-error (file)
|
||||
"Return a string that says what is wrong with FILE."
|
||||
(format
|
||||
(cond
|
||||
((not (file-exists-p file))
|
||||
"%s does not exist")
|
||||
((file-directory-p file)
|
||||
"%s is a directory")
|
||||
((not (file-readable-p file))
|
||||
"%s is not readable"))
|
||||
file))
|
||||
|
||||
(defun nnheader-insert-head (file)
|
||||
"Insert the head of the article."
|
||||
(when (file-exists-p file)
|
||||
(if (eq nnheader-max-head-length t)
|
||||
;; Just read the entire file.
|
||||
(nnheader-insert-file-contents file)
|
||||
;; Read 1K blocks until we find a separator.
|
||||
(let ((beg 0)
|
||||
format-alist)
|
||||
(while (and (eq nnheader-head-chop-length
|
||||
(nth 1 (nnheader-insert-file-contents
|
||||
file nil beg
|
||||
(incf beg nnheader-head-chop-length))))
|
||||
(prog1 (not (search-forward "\n\n" nil t))
|
||||
(goto-char (point-max)))
|
||||
(or (null nnheader-max-head-length)
|
||||
(< beg nnheader-max-head-length))))))
|
||||
t))
|
||||
|
||||
(defun nnheader-article-p ()
|
||||
"Say whether the current buffer looks like an article."
|
||||
(goto-char (point-min))
|
||||
(if (not (search-forward "\n\n" nil t))
|
||||
nil
|
||||
(narrow-to-region (point-min) (1- (point)))
|
||||
(goto-char (point-min))
|
||||
(while (looking-at "[A-Z][^ \t]+:.*\n\\([ \t].*\n\\)*\\|From .*\n")
|
||||
(goto-char (match-end 0)))
|
||||
(prog1
|
||||
(eobp)
|
||||
(widen))))
|
||||
|
||||
(defun nnheader-insert-references (references message-id)
|
||||
"Insert a References header based on REFERENCES and MESSAGE-ID."
|
||||
(if (and (not references) (not message-id))
|
||||
() ; This is illegal, but not all articles have Message-IDs.
|
||||
(mail-position-on-field "References")
|
||||
(let ((begin (save-excursion (beginning-of-line) (point)))
|
||||
(fill-column 78)
|
||||
(fill-prefix "\t"))
|
||||
(when references
|
||||
(insert references))
|
||||
(when (and references message-id)
|
||||
(insert " "))
|
||||
(when message-id
|
||||
(insert message-id))
|
||||
;; Fold long References lines to conform to RFC1036 (sort of).
|
||||
;; The region must end with a newline to fill the region
|
||||
;; without inserting extra newline.
|
||||
(fill-region-as-paragraph begin (1+ (point))))))
|
||||
|
||||
(defun nnheader-replace-header (header new-value)
|
||||
"Remove HEADER and insert the NEW-VALUE."
|
||||
(save-excursion
|
||||
(save-restriction
|
||||
(nnheader-narrow-to-headers)
|
||||
(prog1
|
||||
(message-remove-header header)
|
||||
(goto-char (point-max))
|
||||
(insert header ": " new-value "\n")))))
|
||||
|
||||
(defun nnheader-narrow-to-headers ()
|
||||
"Narrow to the head of an article."
|
||||
(widen)
|
||||
(narrow-to-region
|
||||
(goto-char (point-min))
|
||||
(if (search-forward "\n\n" nil t)
|
||||
(1- (point))
|
||||
(point-max)))
|
||||
(goto-char (point-min)))
|
||||
|
||||
(defun nnheader-set-temp-buffer (name &optional noerase)
|
||||
"Set-buffer to an empty (possibly new) buffer called NAME with undo disabled."
|
||||
(set-buffer (get-buffer-create name))
|
||||
(buffer-disable-undo (current-buffer))
|
||||
(unless noerase
|
||||
(erase-buffer))
|
||||
(current-buffer))
|
||||
|
||||
(defmacro nnheader-temp-write (file &rest forms)
|
||||
"Create a new buffer, evaluate FORMS there, and write the buffer to FILE.
|
||||
Return the value of FORMS.
|
||||
If FILE is nil, just evaluate FORMS and don't save anything.
|
||||
If FILE is t, return the buffer contents as a string."
|
||||
(let ((temp-file (make-symbol "temp-file"))
|
||||
(temp-buffer (make-symbol "temp-buffer"))
|
||||
(temp-results (make-symbol "temp-results")))
|
||||
`(save-excursion
|
||||
(let* ((,temp-file ,file)
|
||||
(default-major-mode 'fundamental-mode)
|
||||
(,temp-buffer
|
||||
(set-buffer
|
||||
(get-buffer-create
|
||||
(generate-new-buffer-name " *nnheader temp*"))))
|
||||
,temp-results)
|
||||
(unwind-protect
|
||||
(progn
|
||||
(setq ,temp-results (progn ,@forms))
|
||||
(cond
|
||||
;; Don't save anything.
|
||||
((null ,temp-file)
|
||||
,temp-results)
|
||||
;; Return the buffer contents.
|
||||
((eq ,temp-file t)
|
||||
(set-buffer ,temp-buffer)
|
||||
(buffer-string))
|
||||
;; Save a file.
|
||||
(t
|
||||
(set-buffer ,temp-buffer)
|
||||
;; Make sure the directory where this file is
|
||||
;; to be saved exists.
|
||||
(when (not (file-directory-p
|
||||
(file-name-directory ,temp-file)))
|
||||
(make-directory (file-name-directory ,temp-file) t))
|
||||
;; Save the file.
|
||||
(write-region (point-min) (point-max)
|
||||
,temp-file nil 'nomesg)
|
||||
,temp-results)))
|
||||
;; Kill the buffer.
|
||||
(when (buffer-name ,temp-buffer)
|
||||
(kill-buffer ,temp-buffer)))))))
|
||||
|
||||
(put 'nnheader-temp-write 'lisp-indent-function 1)
|
||||
(put 'nnheader-temp-write 'edebug-form-spec '(form body))
|
||||
|
||||
(defvar jka-compr-compression-info-list)
|
||||
(defvar nnheader-numerical-files
|
||||
(if (boundp 'jka-compr-compression-info-list)
|
||||
(concat "\\([0-9]+\\)\\("
|
||||
(mapconcat (lambda (i) (aref i 0))
|
||||
jka-compr-compression-info-list "\\|")
|
||||
"\\)?")
|
||||
"[0-9]+$")
|
||||
"Regexp that match numerical files.")
|
||||
|
||||
(defvar nnheader-numerical-short-files (concat "^" nnheader-numerical-files)
|
||||
"Regexp that matches numerical file names.")
|
||||
|
||||
(defvar nnheader-numerical-full-files (concat "/" nnheader-numerical-files)
|
||||
"Regexp that matches numerical full file paths.")
|
||||
|
||||
(defsubst nnheader-file-to-number (file)
|
||||
"Take a file name and return the article number."
|
||||
(if (not (boundp 'jka-compr-compression-info-list))
|
||||
(string-to-int file)
|
||||
(string-match nnheader-numerical-short-files file)
|
||||
(string-to-int (match-string 0 file))))
|
||||
|
||||
(defun nnheader-directory-files-safe (&rest args)
|
||||
;; It has been reported numerous times that `directory-files'
|
||||
;; fails with an alarming frequency on NFS mounted file systems.
|
||||
;; This function executes that function twice and returns
|
||||
;; the longest result.
|
||||
(let ((first (apply 'directory-files args))
|
||||
(second (apply 'directory-files args)))
|
||||
(if (> (length first) (length second))
|
||||
first
|
||||
second)))
|
||||
|
||||
(defun nnheader-directory-articles (dir)
|
||||
"Return a list of all article files in a directory."
|
||||
(mapcar 'nnheader-file-to-number
|
||||
(nnheader-directory-files-safe
|
||||
dir nil nnheader-numerical-short-files t)))
|
||||
|
||||
(defun nnheader-article-to-file-alist (dir)
|
||||
"Return an alist of article/file pairs in DIR."
|
||||
(mapcar (lambda (file) (cons (nnheader-file-to-number file) file))
|
||||
(nnheader-directory-files-safe
|
||||
dir nil nnheader-numerical-short-files t)))
|
||||
|
||||
(defun nnheader-fold-continuation-lines ()
|
||||
"Fold continuation lines in the current buffer."
|
||||
(nnheader-replace-regexp "\\(\r?\n[ \t]+\\)+" " "))
|
||||
|
||||
(defun nnheader-translate-file-chars (file)
|
||||
(if (null nnheader-file-name-translation-alist)
|
||||
;; No translation is necessary.
|
||||
file
|
||||
;; We translate -- but only the file name. We leave the directory
|
||||
;; alone.
|
||||
(let* ((i 0)
|
||||
trans leaf path len)
|
||||
(if (string-match "/[^/]+\\'" file)
|
||||
;; This is needed on NT's and stuff.
|
||||
(setq leaf (substring file (1+ (match-beginning 0)))
|
||||
path (substring file 0 (1+ (match-beginning 0))))
|
||||
;; Fall back on this.
|
||||
(setq leaf (file-name-nondirectory file)
|
||||
path (file-name-directory file)))
|
||||
(setq len (length leaf))
|
||||
(while (< i len)
|
||||
(when (setq trans (cdr (assq (aref leaf i)
|
||||
nnheader-file-name-translation-alist)))
|
||||
(aset leaf i trans))
|
||||
(incf i))
|
||||
(concat path leaf))))
|
||||
|
||||
(defun nnheader-report (backend &rest args)
|
||||
"Report an error from the BACKEND.
|
||||
The first string in ARGS can be a format string."
|
||||
(set (intern (format "%s-status-string" backend))
|
||||
(if (< (length args) 2)
|
||||
(car args)
|
||||
(apply 'format args)))
|
||||
nil)
|
||||
|
||||
(defun nnheader-get-report (backend)
|
||||
"Get the most recent report from BACKEND."
|
||||
(condition-case ()
|
||||
(message "%s" (symbol-value (intern (format "%s-status-string"
|
||||
backend))))
|
||||
(error (message ""))))
|
||||
|
||||
(defun nnheader-insert (format &rest args)
|
||||
"Clear the communication buffer and insert FORMAT and ARGS into the buffer.
|
||||
If FORMAT isn't a format string, it and all ARGS will be inserted
|
||||
without formatting."
|
||||
(save-excursion
|
||||
(set-buffer nntp-server-buffer)
|
||||
(erase-buffer)
|
||||
(if (string-match "%" format)
|
||||
(insert (apply 'format format args))
|
||||
(apply 'insert format args))
|
||||
t))
|
||||
|
||||
(defun nnheader-replace-chars-in-string (string from to)
|
||||
"Replace characters in STRING from FROM to TO."
|
||||
(let ((string (substring string 0)) ;Copy string.
|
||||
(len (length string))
|
||||
(idx 0))
|
||||
;; Replace all occurrences of FROM with TO.
|
||||
(while (< idx len)
|
||||
(when (= (aref string idx) from)
|
||||
(aset string idx to))
|
||||
(setq idx (1+ idx)))
|
||||
string))
|
||||
|
||||
(defun nnheader-file-to-group (file &optional top)
|
||||
"Return a group name based on FILE and TOP."
|
||||
(nnheader-replace-chars-in-string
|
||||
(if (not top)
|
||||
file
|
||||
(condition-case ()
|
||||
(substring (expand-file-name file)
|
||||
(length
|
||||
(expand-file-name
|
||||
(file-name-as-directory top))))
|
||||
(error "")))
|
||||
?/ ?.))
|
||||
|
||||
(defun nnheader-message (level &rest args)
|
||||
"Message if the Gnus backends are talkative."
|
||||
(if (or (not (numberp gnus-verbose-backends))
|
||||
(<= level gnus-verbose-backends))
|
||||
(apply 'message args)
|
||||
(apply 'format args)))
|
||||
|
||||
(defun nnheader-be-verbose (level)
|
||||
"Return whether the backends should be verbose on LEVEL."
|
||||
(or (not (numberp gnus-verbose-backends))
|
||||
(<= level gnus-verbose-backends)))
|
||||
|
||||
(defun nnheader-group-pathname (group dir &optional file)
|
||||
"Make pathname for GROUP."
|
||||
(concat
|
||||
(let ((dir (file-name-as-directory (expand-file-name dir))))
|
||||
;; If this directory exists, we use it directly.
|
||||
(if (file-directory-p (concat dir group))
|
||||
(concat dir group "/")
|
||||
;; If not, we translate dots into slashes.
|
||||
(concat dir (nnheader-replace-chars-in-string group ?. ?/) "/")))
|
||||
(cond ((null file) "")
|
||||
((numberp file) (int-to-string file))
|
||||
(t file))))
|
||||
|
||||
(defun nnheader-functionp (form)
|
||||
"Return non-nil if FORM is funcallable."
|
||||
(or (and (symbolp form) (fboundp form))
|
||||
(and (listp form) (eq (car form) 'lambda))))
|
||||
|
||||
(defun nnheader-concat (dir &rest files)
|
||||
"Concat DIR as directory to FILE."
|
||||
(apply 'concat (file-name-as-directory dir) files))
|
||||
|
||||
(defun nnheader-ms-strip-cr ()
|
||||
"Strip ^M from the end of all lines."
|
||||
(save-excursion
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward "\r$" nil t)
|
||||
(delete-backward-char 1))))
|
||||
|
||||
(defun nnheader-file-size (file)
|
||||
"Return the file size of FILE or 0."
|
||||
(or (nth 7 (file-attributes file)) 0))
|
||||
|
||||
(defun nnheader-find-etc-directory (package &optional file)
|
||||
"Go through the path and find the \".../etc/PACKAGE\" directory.
|
||||
If FILE, find the \".../etc/PACKAGE\" file instead."
|
||||
(let ((path load-path)
|
||||
dir result)
|
||||
;; We try to find the dir by looking at the load path,
|
||||
;; stripping away the last component and adding "etc/".
|
||||
(while path
|
||||
(if (and (car path)
|
||||
(file-exists-p
|
||||
(setq dir (concat
|
||||
(file-name-directory
|
||||
(directory-file-name (car path)))
|
||||
"etc/" package
|
||||
(if file "" "/"))))
|
||||
(or file (file-directory-p dir)))
|
||||
(setq result dir
|
||||
path nil)
|
||||
(setq path (cdr path))))
|
||||
result))
|
||||
|
||||
(defvar ange-ftp-path-format)
|
||||
(defvar efs-path-regexp)
|
||||
(defun nnheader-re-read-dir (path)
|
||||
"Re-read directory PATH if PATH is on a remote system."
|
||||
(if (and (fboundp 'efs-re-read-dir) (boundp 'efs-path-regexp))
|
||||
(when (string-match efs-path-regexp path)
|
||||
(efs-re-read-dir path))
|
||||
(when (and (fboundp 'ange-ftp-re-read-dir) (boundp 'ange-ftp-path-format))
|
||||
(when (string-match (car ange-ftp-path-format) path)
|
||||
(ange-ftp-re-read-dir path)))))
|
||||
|
||||
(defun nnheader-insert-file-contents (filename &optional visit beg end replace)
|
||||
"Like `insert-file-contents', q.v., but only reads in the file.
|
||||
A buffer may be modified in several ways after reading into the buffer due
|
||||
to advanced Emacs features, such as file-name-handlers, format decoding,
|
||||
find-file-hooks, etc.
|
||||
This function ensures that none of these modifications will take place."
|
||||
(let ((format-alist nil)
|
||||
(auto-mode-alist (nnheader-auto-mode-alist))
|
||||
(default-major-mode 'fundamental-mode)
|
||||
(after-insert-file-functions nil))
|
||||
(insert-file-contents filename visit beg end replace)))
|
||||
|
||||
(defun nnheader-find-file-noselect (&rest args)
|
||||
(let ((format-alist nil)
|
||||
(auto-mode-alist (nnheader-auto-mode-alist))
|
||||
(default-major-mode 'fundamental-mode)
|
||||
(enable-local-variables nil)
|
||||
(after-insert-file-functions nil))
|
||||
(apply 'find-file-noselect args)))
|
||||
|
||||
(defun nnheader-auto-mode-alist ()
|
||||
"Return an `auto-mode-alist' with only the .gz (etc) thingies."
|
||||
(let ((alist auto-mode-alist)
|
||||
out)
|
||||
(while alist
|
||||
(when (listp (cdar alist))
|
||||
(push (car alist) out))
|
||||
(pop alist))
|
||||
(nreverse out)))
|
||||
|
||||
(defun nnheader-directory-regular-files (dir)
|
||||
"Return a list of all regular files in DIR."
|
||||
(let ((files (directory-files dir t))
|
||||
out)
|
||||
(while files
|
||||
(when (file-regular-p (car files))
|
||||
(push (car files) out))
|
||||
(pop files))
|
||||
(nreverse out)))
|
||||
|
||||
(defmacro nnheader-skeleton-replace (from &optional to regexp)
|
||||
`(let ((new (generate-new-buffer " *nnheader replace*"))
|
||||
(cur (current-buffer))
|
||||
(start (point-min)))
|
||||
(set-buffer new)
|
||||
(buffer-disable-undo (current-buffer))
|
||||
(set-buffer cur)
|
||||
(goto-char (point-min))
|
||||
(while (,(if regexp 're-search-forward 'search-forward)
|
||||
,from nil t)
|
||||
(insert-buffer-substring
|
||||
cur start (prog1 (match-beginning 0) (set-buffer new)))
|
||||
(goto-char (point-max))
|
||||
,(when to `(insert ,to))
|
||||
(set-buffer cur)
|
||||
(setq start (point)))
|
||||
(insert-buffer-substring
|
||||
cur start (prog1 (point-max) (set-buffer new)))
|
||||
(copy-to-buffer cur (point-min) (point-max))
|
||||
(kill-buffer (current-buffer))
|
||||
(set-buffer cur)))
|
||||
|
||||
(defun nnheader-replace-string (from to)
|
||||
"Do a fast replacement of FROM to TO from point to point-max."
|
||||
(nnheader-skeleton-replace from to))
|
||||
|
||||
(defun nnheader-replace-regexp (from to)
|
||||
"Do a fast regexp replacement of FROM to TO from point to point-max."
|
||||
(nnheader-skeleton-replace from to t))
|
||||
|
||||
(defun nnheader-strip-cr ()
|
||||
"Strip all \r's from the current buffer."
|
||||
(nnheader-skeleton-replace "\r"))
|
||||
|
||||
(fset 'nnheader-run-at-time 'run-at-time)
|
||||
(fset 'nnheader-cancel-timer 'cancel-timer)
|
||||
(fset 'nnheader-cancel-function-timers 'cancel-function-timers)
|
||||
|
||||
(when (string-match "XEmacs\\|Lucid" emacs-version)
|
||||
(require 'nnheaderxm))
|
||||
|
||||
(run-hooks 'nnheader-load-hook)
|
||||
|
||||
(provide 'nnheader)
|
||||
|
||||
;;; nnheader.el ends here
|
||||
156
lisp/gnus/nnheaderxm.el
Normal file
156
lisp/gnus/nnheaderxm.el
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
;;; nnheaderxm.el --- making Gnus backends work under XEmacs
|
||||
;; Copyright (C) 1996,97 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
|
||||
;; Keywords: news
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
(eval-and-compile
|
||||
(autoload 'nnheader-insert-file-contents "nnheader"))
|
||||
|
||||
(defun nnheader-xmas-run-at-time (time repeat function &rest args)
|
||||
(start-itimer
|
||||
"nnheader-run-at-time"
|
||||
`(lambda ()
|
||||
(,function ,@args))
|
||||
time repeat))
|
||||
|
||||
(defun nnheader-xmas-cancel-timer (timer)
|
||||
(delete-itimer timer))
|
||||
|
||||
(defun nnheader-xmas-cancel-function-timers (function)
|
||||
)
|
||||
|
||||
(defun nnheader-xmas-find-file-noselect (filename &optional nowarn rawfile)
|
||||
"Read file FILENAME into a buffer and return the buffer.
|
||||
If a buffer exists visiting FILENAME, return that one, but
|
||||
verify that the file has not changed since visited or saved.
|
||||
The buffer is not selected, just returned to the caller."
|
||||
(setq filename
|
||||
(abbreviate-file-name
|
||||
(expand-file-name filename)))
|
||||
(if (file-directory-p filename)
|
||||
(if find-file-run-dired
|
||||
(dired-noselect filename)
|
||||
(error "%s is a directory." filename))
|
||||
(let* ((buf (get-file-buffer filename))
|
||||
(truename (abbreviate-file-name (file-truename filename)))
|
||||
(number (nthcdr 10 (file-attributes truename)))
|
||||
;; Find any buffer for a file which has same truename.
|
||||
(other (and (not buf)
|
||||
(get-file-buffer filename)))
|
||||
error)
|
||||
;; Let user know if there is a buffer with the same truename.
|
||||
(when other
|
||||
(or nowarn
|
||||
(string-equal filename (buffer-file-name other))
|
||||
(message "%s and %s are the same file"
|
||||
filename (buffer-file-name other)))
|
||||
;; Optionally also find that buffer.
|
||||
(when (or (and (boundp 'find-file-existing-other-name)
|
||||
find-file-existing-other-name)
|
||||
find-file-visit-truename)
|
||||
(setq buf other)))
|
||||
(if buf
|
||||
(or nowarn
|
||||
(verify-visited-file-modtime buf)
|
||||
(cond ((not (file-exists-p filename))
|
||||
(error "File %s no longer exists!" filename))
|
||||
((yes-or-no-p
|
||||
(if (string= (file-name-nondirectory filename)
|
||||
(buffer-name buf))
|
||||
(format
|
||||
(if (buffer-modified-p buf)
|
||||
"File %s changed on disk. Discard your edits? "
|
||||
"File %s changed on disk. Reread from disk? ")
|
||||
(file-name-nondirectory filename))
|
||||
(format
|
||||
(if (buffer-modified-p buf)
|
||||
"File %s changed on disk. Discard your edits in %s? "
|
||||
"File %s changed on disk. Reread from disk into %s? ")
|
||||
(file-name-nondirectory filename)
|
||||
(buffer-name buf))))
|
||||
(save-excursion
|
||||
(set-buffer buf)
|
||||
(revert-buffer t t)))))
|
||||
(save-excursion
|
||||
;;; The truename stuff makes this obsolete.
|
||||
;;; (let* ((link-name (car (file-attributes filename)))
|
||||
;;; (linked-buf (and (stringp link-name)
|
||||
;;; (get-file-buffer link-name))))
|
||||
;;; (if (bufferp linked-buf)
|
||||
;;; (message "Symbolic link to file in buffer %s"
|
||||
;;; (buffer-name linked-buf))))
|
||||
(setq buf (create-file-buffer filename))
|
||||
;; (set-buffer-major-mode buf)
|
||||
(set-buffer buf)
|
||||
(erase-buffer)
|
||||
(if rawfile
|
||||
(condition-case ()
|
||||
(nnheader-insert-file-contents filename t)
|
||||
(file-error
|
||||
;; Unconditionally set error
|
||||
(setq error t)))
|
||||
(condition-case ()
|
||||
(insert-file-contents filename t)
|
||||
(file-error
|
||||
;; Run find-file-not-found-hooks until one returns non-nil.
|
||||
(or t ; (run-hook-with-args-until-success 'find-file-not-found-hooks)
|
||||
;; If they fail too, set error.
|
||||
(setq error t)))))
|
||||
;; Find the file's truename, and maybe use that as visited name.
|
||||
(setq buffer-file-truename truename)
|
||||
(setq buffer-file-number number)
|
||||
;; On VMS, we may want to remember which directory in a search list
|
||||
;; the file was found in.
|
||||
(and (eq system-type 'vax-vms)
|
||||
(let (logical)
|
||||
(when (string-match ":" (file-name-directory filename))
|
||||
(setq logical (substring (file-name-directory filename)
|
||||
0 (match-beginning 0))))
|
||||
(not (member logical find-file-not-true-dirname-list)))
|
||||
(setq buffer-file-name buffer-file-truename))
|
||||
(when find-file-visit-truename
|
||||
(setq buffer-file-name
|
||||
(setq filename
|
||||
(expand-file-name buffer-file-truename))))
|
||||
;; Set buffer's default directory to that of the file.
|
||||
(setq default-directory (file-name-directory filename))
|
||||
;; Turn off backup files for certain file names. Since
|
||||
;; this is a permanent local, the major mode won't eliminate it.
|
||||
(when (not (funcall backup-enable-predicate buffer-file-name))
|
||||
(make-local-variable 'backup-inhibited)
|
||||
(setq backup-inhibited t))
|
||||
(if rawfile
|
||||
nil
|
||||
(after-find-file error (not nowarn)))))
|
||||
buf)))
|
||||
|
||||
(fset 'nnheader-run-at-time 'nnheader-xmas-run-at-time)
|
||||
(fset 'nnheader-cancel-timer 'nnheader-xmas-cancel-timer)
|
||||
(fset 'nnheader-cancel-function-timers 'nnheader-xmas-cancel-function-timers)
|
||||
(fset 'nnheader-find-file-noselect 'nnheader-xmas-find-file-noselect)
|
||||
|
||||
(provide 'nnheaderxm)
|
||||
|
||||
;;; nnheaderxm.el ends here.
|
||||
349
lisp/gnus/nnkiboze.el
Normal file
349
lisp/gnus/nnkiboze.el
Normal file
|
|
@ -0,0 +1,349 @@
|
|||
;;; nnkiboze.el --- select virtual news access for Gnus
|
||||
;; Copyright (C) 1995,96,97 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
|
||||
;; Keywords: news
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; The other access methods (nntp, nnspool, etc) are general news
|
||||
;; access methods. This module relies on Gnus and can't be used
|
||||
;; separately.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'nntp)
|
||||
(require 'nnheader)
|
||||
(require 'gnus)
|
||||
(require 'gnus-score)
|
||||
(require 'nnoo)
|
||||
(eval-when-compile (require 'cl))
|
||||
|
||||
(nnoo-declare nnkiboze)
|
||||
(defvoo nnkiboze-directory (nnheader-concat gnus-directory "kiboze/")
|
||||
"nnkiboze will put its files in this directory.")
|
||||
|
||||
(defvoo nnkiboze-level 9
|
||||
"The maximum level to be searched for articles.")
|
||||
|
||||
(defvoo nnkiboze-remove-read-articles t
|
||||
"If non-nil, nnkiboze will remove read articles from the kiboze group.")
|
||||
|
||||
(defvoo nnkiboze-ephemeral nil
|
||||
"If non-nil, don't store any data anywhere.")
|
||||
|
||||
(defvoo nnkiboze-scores nil
|
||||
"Score rules for generating the nnkiboze group.")
|
||||
|
||||
(defvoo nnkiboze-regexp nil
|
||||
"Regexp for matching component groups.")
|
||||
|
||||
|
||||
|
||||
(defconst nnkiboze-version "nnkiboze 1.0")
|
||||
|
||||
(defvoo nnkiboze-current-group nil)
|
||||
(defvoo nnkiboze-status-string "")
|
||||
|
||||
(defvoo nnkiboze-headers nil)
|
||||
|
||||
|
||||
|
||||
;;; Interface functions.
|
||||
|
||||
(nnoo-define-basics nnkiboze)
|
||||
|
||||
(deffoo nnkiboze-retrieve-headers (articles &optional group server fetch-old)
|
||||
(nnkiboze-possibly-change-group group)
|
||||
(unless gnus-nov-is-evil
|
||||
(if (stringp (car articles))
|
||||
'headers
|
||||
(let ((nov (nnkiboze-nov-file-name)))
|
||||
(when (file-exists-p nov)
|
||||
(save-excursion
|
||||
(set-buffer nntp-server-buffer)
|
||||
(erase-buffer)
|
||||
(nnheader-insert-file-contents nov)
|
||||
(nnheader-nov-delete-outside-range
|
||||
(car articles) (car (last articles)))
|
||||
'nov))))))
|
||||
|
||||
(deffoo nnkiboze-request-article (article &optional newsgroup server buffer)
|
||||
(nnkiboze-possibly-change-group newsgroup)
|
||||
(if (not (numberp article))
|
||||
;; This is a real kludge. It might not work at times, but it
|
||||
;; does no harm I think. The only alternative is to offer no
|
||||
;; article fetching by message-id at all.
|
||||
(nntp-request-article article newsgroup gnus-nntp-server buffer)
|
||||
(let* ((header (gnus-summary-article-header article))
|
||||
(xref (mail-header-xref header)))
|
||||
(unless xref
|
||||
(error "nnkiboze: No xref"))
|
||||
(unless (string-match " \\([^ ]+\\):\\([0-9]+\\)" xref)
|
||||
(error "nnkiboze: Malformed xref"))
|
||||
(gnus-request-article (string-to-int (match-string 2 xref))
|
||||
(match-string 1 xref)
|
||||
buffer))))
|
||||
|
||||
(deffoo nnkiboze-request-scan (&optional group server)
|
||||
(nnkiboze-generate-group (concat "nnkiboze:" group)))
|
||||
|
||||
(deffoo nnkiboze-request-group (group &optional server dont-check)
|
||||
"Make GROUP the current newsgroup."
|
||||
(nnkiboze-possibly-change-group group)
|
||||
(if dont-check
|
||||
t
|
||||
(let ((nov-file (nnkiboze-nov-file-name))
|
||||
beg end total)
|
||||
(save-excursion
|
||||
(set-buffer nntp-server-buffer)
|
||||
(erase-buffer)
|
||||
(if (not (file-exists-p nov-file))
|
||||
(nnheader-report 'nnkiboze "Can't select group %s" group)
|
||||
(nnheader-insert-file-contents nov-file)
|
||||
(if (zerop (buffer-size))
|
||||
(nnheader-insert "211 0 0 0 %s\n" group)
|
||||
(goto-char (point-min))
|
||||
(when (looking-at "[0-9]+")
|
||||
(setq beg (read (current-buffer))))
|
||||
(goto-char (point-max))
|
||||
(when (re-search-backward "^[0-9]" nil t)
|
||||
(setq end (read (current-buffer))))
|
||||
(setq total (count-lines (point-min) (point-max)))
|
||||
(nnheader-insert "211 %d %d %d %s\n" total beg end group)))))))
|
||||
|
||||
(deffoo nnkiboze-close-group (group &optional server)
|
||||
(nnkiboze-possibly-change-group group)
|
||||
;; Remove NOV lines of articles that are marked as read.
|
||||
(when (and (file-exists-p (nnkiboze-nov-file-name))
|
||||
nnkiboze-remove-read-articles)
|
||||
(nnheader-temp-write (nnkiboze-nov-file-name)
|
||||
(let ((cur (current-buffer)))
|
||||
(nnheader-insert-file-contents (nnkiboze-nov-file-name))
|
||||
(goto-char (point-min))
|
||||
(while (not (eobp))
|
||||
(if (not (gnus-article-read-p (read cur)))
|
||||
(forward-line 1)
|
||||
(gnus-delete-line))))))
|
||||
(setq nnkiboze-current-group nil))
|
||||
|
||||
(deffoo nnkiboze-open-server (server &optional defs)
|
||||
(unless (assq 'nnkiboze-regexp defs)
|
||||
(push `(nnkiboze-regexp ,server)
|
||||
defs))
|
||||
(nnoo-change-server 'nnkiboze server defs))
|
||||
|
||||
(deffoo nnkiboze-request-delete-group (group &optional force server)
|
||||
(nnkiboze-possibly-change-group group)
|
||||
(when force
|
||||
(let ((files (list (nnkiboze-nov-file-name)
|
||||
(concat nnkiboze-directory group ".newsrc")
|
||||
(nnkiboze-score-file group))))
|
||||
(while files
|
||||
(and (file-exists-p (car files))
|
||||
(file-writable-p (car files))
|
||||
(delete-file (car files)))
|
||||
(setq files (cdr files)))))
|
||||
(setq nnkiboze-current-group nil))
|
||||
|
||||
(nnoo-define-skeleton nnkiboze)
|
||||
|
||||
|
||||
;;; Internal functions.
|
||||
|
||||
(defun nnkiboze-possibly-change-group (group)
|
||||
(setq nnkiboze-current-group group))
|
||||
|
||||
(defun nnkiboze-prefixed-name (group)
|
||||
(gnus-group-prefixed-name group '(nnkiboze "")))
|
||||
|
||||
;;;###autoload
|
||||
(defun nnkiboze-generate-groups ()
|
||||
"Usage: emacs -batch -l nnkiboze -f nnkiboze-generate-groups
|
||||
Finds out what articles are to be part of the nnkiboze groups."
|
||||
(interactive)
|
||||
(let ((nnmail-spool-file nil)
|
||||
(gnus-use-dribble-file nil)
|
||||
(gnus-read-active-file t)
|
||||
(gnus-expert-user t))
|
||||
(gnus))
|
||||
(let* ((gnus-newsrc-alist (gnus-copy-sequence gnus-newsrc-alist))
|
||||
(newsrc (cdr gnus-newsrc-alist))
|
||||
gnus-newsrc-hashtb info)
|
||||
(gnus-make-hashtable-from-newsrc-alist)
|
||||
;; We have copied all the newsrc alist info over to local copies
|
||||
;; so that we can mess all we want with these lists.
|
||||
(while (setq info (pop newsrc))
|
||||
(when (string-match "nnkiboze" (gnus-info-group info))
|
||||
;; For each kiboze group, we call this function to generate
|
||||
;; it.
|
||||
(nnkiboze-generate-group (gnus-info-group info))))))
|
||||
|
||||
(defun nnkiboze-score-file (group)
|
||||
(list (expand-file-name
|
||||
(concat (file-name-as-directory gnus-kill-files-directory)
|
||||
(nnheader-translate-file-chars
|
||||
(concat (nnkiboze-prefixed-name nnkiboze-current-group)
|
||||
"." gnus-score-file-suffix))))))
|
||||
|
||||
(defun nnkiboze-generate-group (group)
|
||||
(let* ((info (nth 2 (gnus-gethash group gnus-newsrc-hashtb)))
|
||||
(newsrc-file (concat nnkiboze-directory group ".newsrc"))
|
||||
(nov-file (concat nnkiboze-directory group ".nov"))
|
||||
method nnkiboze-newsrc gname newsrc active
|
||||
ginfo lowest glevel orig-info nov-buffer
|
||||
;; Bind various things to nil to make group entry faster.
|
||||
(gnus-expert-user t)
|
||||
(gnus-large-newsgroup nil)
|
||||
(gnus-score-find-score-files-function 'nnkiboze-score-file)
|
||||
(gnus-verbose (min gnus-verbose 3))
|
||||
gnus-select-group-hook gnus-summary-prepare-hook
|
||||
gnus-thread-sort-functions gnus-show-threads
|
||||
gnus-visual gnus-suppress-duplicates)
|
||||
(unless info
|
||||
(error "No such group: %s" group))
|
||||
;; Load the kiboze newsrc file for this group.
|
||||
(when (file-exists-p newsrc-file)
|
||||
(load newsrc-file))
|
||||
(nnheader-temp-write nov-file
|
||||
(when (file-exists-p nov-file)
|
||||
(insert-file-contents nov-file))
|
||||
(setq nov-buffer (current-buffer))
|
||||
;; Go through the active hashtb and add new all groups that match the
|
||||
;; kiboze regexp.
|
||||
(mapatoms
|
||||
(lambda (group)
|
||||
(and (string-match nnkiboze-regexp
|
||||
(setq gname (symbol-name group))) ; Match
|
||||
(not (assoc gname nnkiboze-newsrc)) ; It isn't registered
|
||||
(numberp (car (symbol-value group))) ; It is active
|
||||
(or (> nnkiboze-level 7)
|
||||
(and (setq glevel (nth 1 (nth 2 (gnus-gethash
|
||||
gname gnus-newsrc-hashtb))))
|
||||
(>= nnkiboze-level glevel)))
|
||||
(not (string-match "^nnkiboze:" gname)) ; Exclude kibozes
|
||||
(push (cons gname (1- (car (symbol-value group))))
|
||||
nnkiboze-newsrc)))
|
||||
gnus-active-hashtb)
|
||||
;; `newsrc' is set to the list of groups that possibly are
|
||||
;; component groups to this kiboze group. This list has elements
|
||||
;; on the form `(GROUP . NUMBER)', where NUMBER is the highest
|
||||
;; number that has been kibozed in GROUP in this kiboze group.
|
||||
(setq newsrc nnkiboze-newsrc)
|
||||
(while newsrc
|
||||
(if (not (setq active (gnus-gethash
|
||||
(caar newsrc) gnus-active-hashtb)))
|
||||
;; This group isn't active after all, so we remove it from
|
||||
;; the list of component groups.
|
||||
(setq nnkiboze-newsrc (delq (car newsrc) nnkiboze-newsrc))
|
||||
(setq lowest (cdar newsrc))
|
||||
;; Ok, we have a valid component group, so we jump to it.
|
||||
(switch-to-buffer gnus-group-buffer)
|
||||
(gnus-group-jump-to-group (caar newsrc))
|
||||
(gnus-message 3 "nnkiboze: Checking %s..." (caar newsrc))
|
||||
(setq ginfo (gnus-get-info (gnus-group-group-name))
|
||||
orig-info (gnus-copy-sequence ginfo))
|
||||
(unwind-protect
|
||||
(progn
|
||||
;; We set all list of article marks to nil. Since we operate
|
||||
;; on copies of the real lists, we can destroy anything we
|
||||
;; want here.
|
||||
(when (nth 3 ginfo)
|
||||
(setcar (nthcdr 3 ginfo) nil))
|
||||
;; We set the list of read articles to be what we expect for
|
||||
;; this kiboze group -- either nil or `(1 . LOWEST)'.
|
||||
(when ginfo
|
||||
(setcar (nthcdr 2 ginfo)
|
||||
(and (not (= lowest 1)) (cons 1 lowest))))
|
||||
(when (and (or (not ginfo)
|
||||
(> (length (gnus-list-of-unread-articles
|
||||
(car ginfo)))
|
||||
0))
|
||||
(progn
|
||||
(gnus-group-select-group nil)
|
||||
(eq major-mode 'gnus-summary-mode)))
|
||||
;; We are now in the group where we want to be.
|
||||
(setq method (gnus-find-method-for-group
|
||||
gnus-newsgroup-name))
|
||||
(when (eq method gnus-select-method)
|
||||
(setq method nil))
|
||||
;; We go through the list of scored articles.
|
||||
(while gnus-newsgroup-scored
|
||||
(when (> (caar gnus-newsgroup-scored) lowest)
|
||||
;; If it has a good score, then we enter this article
|
||||
;; into the kiboze group.
|
||||
(nnkiboze-enter-nov
|
||||
nov-buffer
|
||||
(gnus-summary-article-header
|
||||
(caar gnus-newsgroup-scored))
|
||||
gnus-newsgroup-name))
|
||||
(setq gnus-newsgroup-scored (cdr gnus-newsgroup-scored)))
|
||||
;; That's it. We exit this group.
|
||||
(gnus-summary-exit-no-update)))
|
||||
;; Restore the proper info.
|
||||
(when ginfo
|
||||
(setcdr ginfo (cdr orig-info)))))
|
||||
(setcdr (car newsrc) (car active))
|
||||
(gnus-message 3 "nnkiboze: Checking %s...done" (caar newsrc))
|
||||
(setq newsrc (cdr newsrc))))
|
||||
;; We save the kiboze newsrc for this group.
|
||||
(nnheader-temp-write newsrc-file
|
||||
(insert "(setq nnkiboze-newsrc '")
|
||||
(gnus-prin1 nnkiboze-newsrc)
|
||||
(insert ")\n"))
|
||||
t))
|
||||
|
||||
(defun nnkiboze-enter-nov (buffer header group)
|
||||
(save-excursion
|
||||
(set-buffer buffer)
|
||||
(goto-char (point-max))
|
||||
(let ((xref (mail-header-xref header))
|
||||
(prefix (gnus-group-real-prefix group))
|
||||
(oheader (copy-sequence header))
|
||||
(first t)
|
||||
article)
|
||||
(if (zerop (forward-line -1))
|
||||
(progn
|
||||
(setq article (1+ (read (current-buffer))))
|
||||
(forward-line 1))
|
||||
(setq article 1))
|
||||
(mail-header-set-number oheader article)
|
||||
(nnheader-insert-nov oheader)
|
||||
(search-backward "\t" nil t 2)
|
||||
(if (re-search-forward " [^ ]+:[0-9]+" nil t)
|
||||
(goto-char (match-beginning 0))
|
||||
(forward-char 1))
|
||||
;; The first Xref has to be the group this article
|
||||
;; really came for - this is the article nnkiboze
|
||||
;; will request when it is asked for the article.
|
||||
(insert group ":"
|
||||
(int-to-string (mail-header-number header)) " ")
|
||||
(while (re-search-forward " [^ ]+:[0-9]+" nil t)
|
||||
(goto-char (1+ (match-beginning 0)))
|
||||
(insert prefix)))))
|
||||
|
||||
(defun nnkiboze-nov-file-name ()
|
||||
(concat (file-name-as-directory nnkiboze-directory)
|
||||
(nnheader-translate-file-chars
|
||||
(concat (nnkiboze-prefixed-name nnkiboze-current-group) ".nov"))))
|
||||
|
||||
(provide 'nnkiboze)
|
||||
|
||||
;;; nnkiboze.el ends here
|
||||
1705
lisp/gnus/nnmail.el
Normal file
1705
lisp/gnus/nnmail.el
Normal file
File diff suppressed because it is too large
Load diff
552
lisp/gnus/nnmbox.el
Normal file
552
lisp/gnus/nnmbox.el
Normal file
|
|
@ -0,0 +1,552 @@
|
|||
;;; nnmbox.el --- mail mbox access for Gnus
|
||||
;; Copyright (C) 1995,96,97 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
|
||||
;; Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
|
||||
;; Keywords: news, mail
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; For an overview of what the interface functions do, please see the
|
||||
;; Gnus sources.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'nnheader)
|
||||
(require 'message)
|
||||
(require 'nnmail)
|
||||
(require 'nnoo)
|
||||
(eval-when-compile (require 'cl))
|
||||
|
||||
(nnoo-declare nnmbox)
|
||||
|
||||
(defvoo nnmbox-mbox-file (expand-file-name "~/mbox")
|
||||
"The name of the mail box file in the user's home directory.")
|
||||
|
||||
(defvoo nnmbox-active-file (expand-file-name "~/.mbox-active")
|
||||
"The name of the active file for the mail box.")
|
||||
|
||||
(defvoo nnmbox-get-new-mail t
|
||||
"If non-nil, nnmbox will check the incoming mail file and split the mail.")
|
||||
|
||||
(defvoo nnmbox-prepare-save-mail-hook nil
|
||||
"Hook run narrowed to an article before saving.")
|
||||
|
||||
|
||||
|
||||
(defconst nnmbox-version "nnmbox 1.0"
|
||||
"nnmbox version.")
|
||||
|
||||
(defvoo nnmbox-current-group nil
|
||||
"Current nnmbox news group directory.")
|
||||
|
||||
(defconst nnmbox-mbox-buffer nil)
|
||||
|
||||
(defvoo nnmbox-status-string "")
|
||||
|
||||
(defvoo nnmbox-group-alist nil)
|
||||
(defvoo nnmbox-active-timestamp nil)
|
||||
|
||||
|
||||
|
||||
;;; Interface functions
|
||||
|
||||
(nnoo-define-basics nnmbox)
|
||||
|
||||
(deffoo nnmbox-retrieve-headers (sequence &optional newsgroup server fetch-old)
|
||||
(save-excursion
|
||||
(set-buffer nntp-server-buffer)
|
||||
(erase-buffer)
|
||||
(let ((number (length sequence))
|
||||
(count 0)
|
||||
article art-string start stop)
|
||||
(nnmbox-possibly-change-newsgroup newsgroup server)
|
||||
(while sequence
|
||||
(setq article (car sequence))
|
||||
(setq art-string (nnmbox-article-string article))
|
||||
(set-buffer nnmbox-mbox-buffer)
|
||||
(when (or (search-forward art-string nil t)
|
||||
(progn (goto-char (point-min))
|
||||
(search-forward art-string nil t)))
|
||||
(setq start
|
||||
(save-excursion
|
||||
(re-search-backward
|
||||
(concat "^" message-unix-mail-delimiter) nil t)
|
||||
(point)))
|
||||
(search-forward "\n\n" nil t)
|
||||
(setq stop (1- (point)))
|
||||
(set-buffer nntp-server-buffer)
|
||||
(insert (format "221 %d Article retrieved.\n" article))
|
||||
(insert-buffer-substring nnmbox-mbox-buffer start stop)
|
||||
(goto-char (point-max))
|
||||
(insert ".\n"))
|
||||
(setq sequence (cdr sequence))
|
||||
(setq count (1+ count))
|
||||
(and (numberp nnmail-large-newsgroup)
|
||||
(> number nnmail-large-newsgroup)
|
||||
(zerop (% count 20))
|
||||
(nnheader-message 5 "nnmbox: Receiving headers... %d%%"
|
||||
(/ (* count 100) number))))
|
||||
|
||||
(and (numberp nnmail-large-newsgroup)
|
||||
(> number nnmail-large-newsgroup)
|
||||
(nnheader-message 5 "nnmbox: Receiving headers...done"))
|
||||
|
||||
(set-buffer nntp-server-buffer)
|
||||
(nnheader-fold-continuation-lines)
|
||||
'headers)))
|
||||
|
||||
(deffoo nnmbox-open-server (server &optional defs)
|
||||
(nnoo-change-server 'nnmbox server defs)
|
||||
(nnmbox-create-mbox)
|
||||
(cond
|
||||
((not (file-exists-p nnmbox-mbox-file))
|
||||
(nnmbox-close-server)
|
||||
(nnheader-report 'nnmbox "No such file: %s" nnmbox-mbox-file))
|
||||
((file-directory-p nnmbox-mbox-file)
|
||||
(nnmbox-close-server)
|
||||
(nnheader-report 'nnmbox "Not a regular file: %s" nnmbox-mbox-file))
|
||||
(t
|
||||
(nnheader-report 'nnmbox "Opened server %s using mbox %s" server
|
||||
nnmbox-mbox-file)
|
||||
t)))
|
||||
|
||||
(deffoo nnmbox-close-server (&optional server)
|
||||
(when (and nnmbox-mbox-buffer
|
||||
(buffer-name nnmbox-mbox-buffer))
|
||||
(kill-buffer nnmbox-mbox-buffer))
|
||||
(nnoo-close-server 'nnmbox server)
|
||||
t)
|
||||
|
||||
(deffoo nnmbox-server-opened (&optional server)
|
||||
(and (nnoo-current-server-p 'nnmbox server)
|
||||
nnmbox-mbox-buffer
|
||||
(buffer-name nnmbox-mbox-buffer)
|
||||
nntp-server-buffer
|
||||
(buffer-name nntp-server-buffer)))
|
||||
|
||||
(deffoo nnmbox-request-article (article &optional newsgroup server buffer)
|
||||
(nnmbox-possibly-change-newsgroup newsgroup server)
|
||||
(save-excursion
|
||||
(set-buffer nnmbox-mbox-buffer)
|
||||
(goto-char (point-min))
|
||||
(when (search-forward (nnmbox-article-string article) nil t)
|
||||
(let (start stop)
|
||||
(re-search-backward (concat "^" message-unix-mail-delimiter) nil t)
|
||||
(setq start (point))
|
||||
(forward-line 1)
|
||||
(or (and (re-search-forward
|
||||
(concat "^" message-unix-mail-delimiter) nil t)
|
||||
(forward-line -1))
|
||||
(goto-char (point-max)))
|
||||
(setq stop (point))
|
||||
(let ((nntp-server-buffer (or buffer nntp-server-buffer)))
|
||||
(set-buffer nntp-server-buffer)
|
||||
(erase-buffer)
|
||||
(insert-buffer-substring nnmbox-mbox-buffer start stop)
|
||||
(goto-char (point-min))
|
||||
(while (looking-at "From ")
|
||||
(delete-char 5)
|
||||
(insert "X-From-Line: ")
|
||||
(forward-line 1))
|
||||
(if (numberp article)
|
||||
(cons nnmbox-current-group article)
|
||||
(nnmbox-article-group-number)))))))
|
||||
|
||||
(deffoo nnmbox-request-group (group &optional server dont-check)
|
||||
(let ((active (cadr (assoc group nnmbox-group-alist))))
|
||||
(cond
|
||||
((or (null active)
|
||||
(null (nnmbox-possibly-change-newsgroup group server)))
|
||||
(nnheader-report 'nnmbox "No such group: %s" group))
|
||||
(dont-check
|
||||
(nnheader-report 'nnmbox "Selected group %s" group)
|
||||
(nnheader-insert ""))
|
||||
(t
|
||||
(nnheader-report 'nnmbox "Selected group %s" group)
|
||||
(nnheader-insert "211 %d %d %d %s\n"
|
||||
(1+ (- (cdr active) (car active)))
|
||||
(car active) (cdr active) group)))))
|
||||
|
||||
(deffoo nnmbox-request-scan (&optional group server)
|
||||
(nnmbox-possibly-change-newsgroup group server)
|
||||
(nnmbox-read-mbox)
|
||||
(nnmail-get-new-mail
|
||||
'nnmbox
|
||||
(lambda ()
|
||||
(save-excursion
|
||||
(set-buffer nnmbox-mbox-buffer)
|
||||
(save-buffer)))
|
||||
(file-name-directory nnmbox-mbox-file)
|
||||
group
|
||||
(lambda ()
|
||||
(save-excursion
|
||||
(let ((in-buf (current-buffer)))
|
||||
(set-buffer nnmbox-mbox-buffer)
|
||||
(goto-char (point-max))
|
||||
(insert-buffer-substring in-buf)))
|
||||
(nnmail-save-active nnmbox-group-alist nnmbox-active-file))))
|
||||
|
||||
(deffoo nnmbox-close-group (group &optional server)
|
||||
t)
|
||||
|
||||
(deffoo nnmbox-request-list (&optional server)
|
||||
(save-excursion
|
||||
(nnmail-find-file nnmbox-active-file)
|
||||
(setq nnmbox-group-alist (nnmail-get-active))
|
||||
t))
|
||||
|
||||
(deffoo nnmbox-request-newgroups (date &optional server)
|
||||
(nnmbox-request-list server))
|
||||
|
||||
(deffoo nnmbox-request-list-newsgroups (&optional server)
|
||||
(nnheader-report 'nnmbox "LIST NEWSGROUPS is not implemented."))
|
||||
|
||||
(deffoo nnmbox-request-expire-articles
|
||||
(articles newsgroup &optional server force)
|
||||
(nnmbox-possibly-change-newsgroup newsgroup server)
|
||||
(let* ((is-old t)
|
||||
rest)
|
||||
(nnmail-activate 'nnmbox)
|
||||
|
||||
(save-excursion
|
||||
(set-buffer nnmbox-mbox-buffer)
|
||||
(while (and articles is-old)
|
||||
(goto-char (point-min))
|
||||
(when (search-forward (nnmbox-article-string (car articles)) nil t)
|
||||
(if (setq is-old
|
||||
(nnmail-expired-article-p
|
||||
newsgroup
|
||||
(buffer-substring
|
||||
(point) (progn (end-of-line) (point))) force))
|
||||
(progn
|
||||
(nnheader-message 5 "Deleting article %d in %s..."
|
||||
(car articles) newsgroup)
|
||||
(nnmbox-delete-mail))
|
||||
(push (car articles) rest)))
|
||||
(setq articles (cdr articles)))
|
||||
(save-buffer)
|
||||
;; Find the lowest active article in this group.
|
||||
(let ((active (nth 1 (assoc newsgroup nnmbox-group-alist))))
|
||||
(goto-char (point-min))
|
||||
(while (and (not (search-forward
|
||||
(nnmbox-article-string (car active)) nil t))
|
||||
(<= (car active) (cdr active)))
|
||||
(setcar active (1+ (car active)))
|
||||
(goto-char (point-min))))
|
||||
(nnmail-save-active nnmbox-group-alist nnmbox-active-file)
|
||||
(nconc rest articles))))
|
||||
|
||||
(deffoo nnmbox-request-move-article
|
||||
(article group server accept-form &optional last)
|
||||
(let ((buf (get-buffer-create " *nnmbox move*"))
|
||||
result)
|
||||
(and
|
||||
(nnmbox-request-article article group server)
|
||||
(save-excursion
|
||||
(set-buffer buf)
|
||||
(buffer-disable-undo (current-buffer))
|
||||
(erase-buffer)
|
||||
(insert-buffer-substring nntp-server-buffer)
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward
|
||||
"^X-Gnus-Newsgroup:"
|
||||
(save-excursion (search-forward "\n\n" nil t) (point)) t)
|
||||
(delete-region (progn (beginning-of-line) (point))
|
||||
(progn (forward-line 1) (point))))
|
||||
(setq result (eval accept-form))
|
||||
(kill-buffer buf)
|
||||
result)
|
||||
(save-excursion
|
||||
(nnmbox-possibly-change-newsgroup group server)
|
||||
(set-buffer nnmbox-mbox-buffer)
|
||||
(goto-char (point-min))
|
||||
(when (search-forward (nnmbox-article-string article) nil t)
|
||||
(nnmbox-delete-mail))
|
||||
(and last (save-buffer))))
|
||||
result))
|
||||
|
||||
(deffoo nnmbox-request-accept-article (group &optional server last)
|
||||
(nnmbox-possibly-change-newsgroup group server)
|
||||
(nnmail-check-syntax)
|
||||
(let ((buf (current-buffer))
|
||||
result)
|
||||
(goto-char (point-min))
|
||||
;; The From line may have been quoted by movemail.
|
||||
(when (looking-at (concat ">" message-unix-mail-delimiter))
|
||||
(delete-char 1))
|
||||
(if (looking-at "X-From-Line: ")
|
||||
(replace-match "From ")
|
||||
(insert "From nobody " (current-time-string) "\n"))
|
||||
(and
|
||||
(nnmail-activate 'nnmbox)
|
||||
(progn
|
||||
(set-buffer buf)
|
||||
(goto-char (point-min))
|
||||
(search-forward "\n\n" nil t)
|
||||
(forward-line -1)
|
||||
(while (re-search-backward "^X-Gnus-Newsgroup: " nil t)
|
||||
(delete-region (point) (progn (forward-line 1) (point))))
|
||||
(when nnmail-cache-accepted-message-ids
|
||||
(nnmail-cache-insert (nnmail-fetch-field "message-id")))
|
||||
(setq result (if (stringp group)
|
||||
(list (cons group (nnmbox-active-number group)))
|
||||
(nnmail-article-group 'nnmbox-active-number)))
|
||||
(if (and (null result)
|
||||
(yes-or-no-p "Moved to `junk' group; delete article? "))
|
||||
(setq result 'junk)
|
||||
(setq result (car (nnmbox-save-mail result)))))
|
||||
(save-excursion
|
||||
(set-buffer nnmbox-mbox-buffer)
|
||||
(goto-char (point-max))
|
||||
(insert-buffer-substring buf)
|
||||
(when last
|
||||
(when nnmail-cache-accepted-message-ids
|
||||
(nnmail-cache-close))
|
||||
(nnmail-save-active nnmbox-group-alist nnmbox-active-file)
|
||||
(save-buffer))))
|
||||
result))
|
||||
|
||||
(deffoo nnmbox-request-replace-article (article group buffer)
|
||||
(nnmbox-possibly-change-newsgroup group)
|
||||
(save-excursion
|
||||
(set-buffer nnmbox-mbox-buffer)
|
||||
(goto-char (point-min))
|
||||
(if (not (search-forward (nnmbox-article-string article) nil t))
|
||||
nil
|
||||
(nnmbox-delete-mail t t)
|
||||
(insert-buffer-substring buffer)
|
||||
(save-buffer)
|
||||
t)))
|
||||
|
||||
(deffoo nnmbox-request-delete-group (group &optional force server)
|
||||
(nnmbox-possibly-change-newsgroup group server)
|
||||
;; Delete all articles in GROUP.
|
||||
(if (not force)
|
||||
() ; Don't delete the articles.
|
||||
(save-excursion
|
||||
(set-buffer nnmbox-mbox-buffer)
|
||||
(goto-char (point-min))
|
||||
;; Delete all articles in this group.
|
||||
(let ((ident (concat "\nX-Gnus-Newsgroup: " nnmbox-current-group ":"))
|
||||
found)
|
||||
(while (search-forward ident nil t)
|
||||
(setq found t)
|
||||
(nnmbox-delete-mail))
|
||||
(when found
|
||||
(save-buffer)))))
|
||||
;; Remove the group from all structures.
|
||||
(setq nnmbox-group-alist
|
||||
(delq (assoc group nnmbox-group-alist) nnmbox-group-alist)
|
||||
nnmbox-current-group nil)
|
||||
;; Save the active file.
|
||||
(nnmail-save-active nnmbox-group-alist nnmbox-active-file)
|
||||
t)
|
||||
|
||||
(deffoo nnmbox-request-rename-group (group new-name &optional server)
|
||||
(nnmbox-possibly-change-newsgroup group server)
|
||||
(save-excursion
|
||||
(set-buffer nnmbox-mbox-buffer)
|
||||
(goto-char (point-min))
|
||||
(let ((ident (concat "\nX-Gnus-Newsgroup: " nnmbox-current-group ":"))
|
||||
(new-ident (concat "\nX-Gnus-Newsgroup: " new-name ":"))
|
||||
found)
|
||||
(while (search-forward ident nil t)
|
||||
(replace-match new-ident t t)
|
||||
(setq found t))
|
||||
(when found
|
||||
(save-buffer))))
|
||||
(let ((entry (assoc group nnmbox-group-alist)))
|
||||
(when entry
|
||||
(setcar entry new-name))
|
||||
(setq nnmbox-current-group nil)
|
||||
;; Save the new group alist.
|
||||
(nnmail-save-active nnmbox-group-alist nnmbox-active-file)
|
||||
t))
|
||||
|
||||
|
||||
;;; Internal functions.
|
||||
|
||||
;; If FORCE, delete article no matter how many X-Gnus-Newsgroup
|
||||
;; headers there are. If LEAVE-DELIM, don't delete the Unix mbox
|
||||
;; delimiter line.
|
||||
(defun nnmbox-delete-mail (&optional force leave-delim)
|
||||
;; Delete the current X-Gnus-Newsgroup line.
|
||||
(or force
|
||||
(delete-region
|
||||
(progn (beginning-of-line) (point))
|
||||
(progn (forward-line 1) (point))))
|
||||
;; Beginning of the article.
|
||||
(save-excursion
|
||||
(save-restriction
|
||||
(narrow-to-region
|
||||
(save-excursion
|
||||
(re-search-backward (concat "^" message-unix-mail-delimiter) nil t)
|
||||
(if leave-delim (progn (forward-line 1) (point))
|
||||
(match-beginning 0)))
|
||||
(progn
|
||||
(forward-line 1)
|
||||
(or (and (re-search-forward (concat "^" message-unix-mail-delimiter)
|
||||
nil t)
|
||||
(if (and (not (bobp)) leave-delim)
|
||||
(progn (forward-line -2) (point))
|
||||
(match-beginning 0)))
|
||||
(point-max))))
|
||||
(goto-char (point-min))
|
||||
;; Only delete the article if no other groups owns it as well.
|
||||
(when (or force (not (re-search-forward "^X-Gnus-Newsgroup: " nil t)))
|
||||
(delete-region (point-min) (point-max))))))
|
||||
|
||||
(defun nnmbox-possibly-change-newsgroup (newsgroup &optional server)
|
||||
(when (and server
|
||||
(not (nnmbox-server-opened server)))
|
||||
(nnmbox-open-server server))
|
||||
(when (or (not nnmbox-mbox-buffer)
|
||||
(not (buffer-name nnmbox-mbox-buffer)))
|
||||
(save-excursion
|
||||
(set-buffer (setq nnmbox-mbox-buffer
|
||||
(nnheader-find-file-noselect
|
||||
nnmbox-mbox-file nil 'raw)))
|
||||
(buffer-disable-undo (current-buffer))))
|
||||
(when (not nnmbox-group-alist)
|
||||
(nnmail-activate 'nnmbox))
|
||||
(if newsgroup
|
||||
(when (assoc newsgroup nnmbox-group-alist)
|
||||
(setq nnmbox-current-group newsgroup))
|
||||
t))
|
||||
|
||||
(defun nnmbox-article-string (article)
|
||||
(if (numberp article)
|
||||
(concat "\nX-Gnus-Newsgroup: " nnmbox-current-group ":"
|
||||
(int-to-string article) " ")
|
||||
(concat "\nMessage-ID: " article)))
|
||||
|
||||
(defun nnmbox-article-group-number ()
|
||||
(save-excursion
|
||||
(goto-char (point-min))
|
||||
(when (re-search-forward "^X-Gnus-Newsgroup: +\\([^:]+\\):\\([0-9]+\\) "
|
||||
nil t)
|
||||
(cons (buffer-substring (match-beginning 1) (match-end 1))
|
||||
(string-to-int
|
||||
(buffer-substring (match-beginning 2) (match-end 2)))))))
|
||||
|
||||
(defun nnmbox-save-mail (group-art)
|
||||
"Called narrowed to an article."
|
||||
(let ((delim (concat "^" message-unix-mail-delimiter)))
|
||||
(goto-char (point-min))
|
||||
;; This might come from somewhere else.
|
||||
(unless (looking-at delim)
|
||||
(insert "From nobody " (current-time-string) "\n")
|
||||
(goto-char (point-min)))
|
||||
;; Quote all "From " lines in the article.
|
||||
(forward-line 1)
|
||||
(while (re-search-forward delim nil t)
|
||||
(beginning-of-line)
|
||||
(insert "> "))
|
||||
(nnmail-insert-lines)
|
||||
(nnmail-insert-xref group-art)
|
||||
(nnmbox-insert-newsgroup-line group-art)
|
||||
(run-hooks 'nnmail-prepare-save-mail-hook)
|
||||
(run-hooks 'nnmbox-prepare-save-mail-hook)
|
||||
group-art))
|
||||
|
||||
(defun nnmbox-insert-newsgroup-line (group-art)
|
||||
(save-excursion
|
||||
(goto-char (point-min))
|
||||
(when (search-forward "\n\n" nil t)
|
||||
(forward-char -1)
|
||||
(while group-art
|
||||
(insert (format "X-Gnus-Newsgroup: %s:%d %s\n"
|
||||
(caar group-art) (cdar group-art)
|
||||
(current-time-string)))
|
||||
(setq group-art (cdr group-art))))
|
||||
t))
|
||||
|
||||
(defun nnmbox-active-number (group)
|
||||
;; Find the next article number in GROUP.
|
||||
(let ((active (cadr (assoc group nnmbox-group-alist))))
|
||||
(if active
|
||||
(setcdr active (1+ (cdr active)))
|
||||
;; This group is new, so we create a new entry for it.
|
||||
;; This might be a bit naughty... creating groups on the drop of
|
||||
;; a hat, but I don't know...
|
||||
(push (list group (setq active (cons 1 1)))
|
||||
nnmbox-group-alist))
|
||||
(cdr active)))
|
||||
|
||||
(defun nnmbox-create-mbox ()
|
||||
(when (not (file-exists-p nnmbox-mbox-file))
|
||||
(nnmail-write-region 1 1 nnmbox-mbox-file t 'nomesg)))
|
||||
|
||||
(defun nnmbox-read-mbox ()
|
||||
(nnmail-activate 'nnmbox)
|
||||
(nnmbox-create-mbox)
|
||||
(if (and nnmbox-mbox-buffer
|
||||
(buffer-name nnmbox-mbox-buffer)
|
||||
(save-excursion
|
||||
(set-buffer nnmbox-mbox-buffer)
|
||||
(= (buffer-size) (nnheader-file-size nnmbox-mbox-file))))
|
||||
()
|
||||
(save-excursion
|
||||
(let ((delim (concat "^" message-unix-mail-delimiter))
|
||||
(alist nnmbox-group-alist)
|
||||
start end number)
|
||||
(set-buffer (setq nnmbox-mbox-buffer
|
||||
(nnheader-find-file-noselect
|
||||
nnmbox-mbox-file nil 'raw)))
|
||||
(buffer-disable-undo (current-buffer))
|
||||
|
||||
;; Go through the group alist and compare against
|
||||
;; the mbox file.
|
||||
(while alist
|
||||
(goto-char (point-max))
|
||||
(when (and (re-search-backward
|
||||
(format "^X-Gnus-Newsgroup: %s:\\([0-9]+\\) "
|
||||
(caar alist)) nil t)
|
||||
(>= (setq number
|
||||
(string-to-number
|
||||
(buffer-substring
|
||||
(match-beginning 1) (match-end 1))))
|
||||
(cdadar alist)))
|
||||
(setcdr (cadar alist) (1+ number)))
|
||||
(setq alist (cdr alist)))
|
||||
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward delim nil t)
|
||||
(setq start (match-beginning 0))
|
||||
(when (not (search-forward "\nX-Gnus-Newsgroup: "
|
||||
(save-excursion
|
||||
(setq end
|
||||
(or
|
||||
(and
|
||||
(re-search-forward delim nil t)
|
||||
(match-beginning 0))
|
||||
(point-max))))
|
||||
t))
|
||||
(save-excursion
|
||||
(save-restriction
|
||||
(narrow-to-region start end)
|
||||
(nnmbox-save-mail
|
||||
(nnmail-article-group 'nnmbox-active-number)))))
|
||||
(goto-char end))))))
|
||||
|
||||
(provide 'nnmbox)
|
||||
|
||||
;;; nnmbox.el ends here
|
||||
547
lisp/gnus/nnmh.el
Normal file
547
lisp/gnus/nnmh.el
Normal file
|
|
@ -0,0 +1,547 @@
|
|||
;;; nnmh.el --- mhspool access for Gnus
|
||||
;; Copyright (C) 1995,96,97 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
|
||||
;; Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
|
||||
;; Keywords: news, mail
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; Based on nnspool.el by Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>.
|
||||
;; For an overview of what the interface functions do, please see the
|
||||
;; Gnus sources.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'nnheader)
|
||||
(require 'nnmail)
|
||||
(require 'gnus-start)
|
||||
(require 'nnoo)
|
||||
(require 'cl)
|
||||
|
||||
(nnoo-declare nnmh)
|
||||
|
||||
(defvoo nnmh-directory message-directory
|
||||
"*Mail spool directory.")
|
||||
|
||||
(defvoo nnmh-get-new-mail t
|
||||
"*If non-nil, nnmh will check the incoming mail file and split the mail.")
|
||||
|
||||
(defvoo nnmh-prepare-save-mail-hook nil
|
||||
"*Hook run narrowed to an article before saving.")
|
||||
|
||||
(defvoo nnmh-be-safe nil
|
||||
"*If non-nil, nnmh will check all articles to make sure whether they are new or not.")
|
||||
|
||||
|
||||
|
||||
(defconst nnmh-version "nnmh 1.0"
|
||||
"nnmh version.")
|
||||
|
||||
(defvoo nnmh-current-directory nil
|
||||
"Current news group directory.")
|
||||
|
||||
(defvoo nnmh-status-string "")
|
||||
(defvoo nnmh-group-alist nil)
|
||||
|
||||
|
||||
|
||||
;;; Interface functions.
|
||||
|
||||
(nnoo-define-basics nnmh)
|
||||
|
||||
(deffoo nnmh-retrieve-headers (articles &optional newsgroup server fetch-old)
|
||||
(save-excursion
|
||||
(set-buffer nntp-server-buffer)
|
||||
(erase-buffer)
|
||||
(let* ((file nil)
|
||||
(number (length articles))
|
||||
(large (and (numberp nnmail-large-newsgroup)
|
||||
(> number nnmail-large-newsgroup)))
|
||||
(count 0)
|
||||
beg article)
|
||||
(nnmh-possibly-change-directory newsgroup server)
|
||||
;; We don't support fetching by Message-ID.
|
||||
(if (stringp (car articles))
|
||||
'headers
|
||||
(while articles
|
||||
(when (and (file-exists-p
|
||||
(setq file (concat (file-name-as-directory
|
||||
nnmh-current-directory)
|
||||
(int-to-string
|
||||
(setq article (pop articles))))))
|
||||
(not (file-directory-p file)))
|
||||
(insert (format "221 %d Article retrieved.\n" article))
|
||||
(setq beg (point))
|
||||
(nnheader-insert-head file)
|
||||
(goto-char beg)
|
||||
(if (search-forward "\n\n" nil t)
|
||||
(forward-char -1)
|
||||
(goto-char (point-max))
|
||||
(insert "\n\n"))
|
||||
(insert ".\n")
|
||||
(delete-region (point) (point-max)))
|
||||
(setq count (1+ count))
|
||||
|
||||
(and large
|
||||
(zerop (% count 20))
|
||||
(message "nnmh: Receiving headers... %d%%"
|
||||
(/ (* count 100) number))))
|
||||
|
||||
(when large
|
||||
(message "nnmh: Receiving headers...done"))
|
||||
|
||||
(nnheader-fold-continuation-lines)
|
||||
'headers))))
|
||||
|
||||
(deffoo nnmh-open-server (server &optional defs)
|
||||
(nnoo-change-server 'nnmh server defs)
|
||||
(when (not (file-exists-p nnmh-directory))
|
||||
(condition-case ()
|
||||
(make-directory nnmh-directory t)
|
||||
(error t)))
|
||||
(cond
|
||||
((not (file-exists-p nnmh-directory))
|
||||
(nnmh-close-server)
|
||||
(nnheader-report 'nnmh "Couldn't create directory: %s" nnmh-directory))
|
||||
((not (file-directory-p (file-truename nnmh-directory)))
|
||||
(nnmh-close-server)
|
||||
(nnheader-report 'nnmh "Not a directory: %s" nnmh-directory))
|
||||
(t
|
||||
(nnheader-report 'nnmh "Opened server %s using directory %s"
|
||||
server nnmh-directory)
|
||||
t)))
|
||||
|
||||
(deffoo nnmh-request-article (id &optional newsgroup server buffer)
|
||||
(nnmh-possibly-change-directory newsgroup server)
|
||||
(let ((file (if (stringp id)
|
||||
nil
|
||||
(concat nnmh-current-directory (int-to-string id))))
|
||||
(nntp-server-buffer (or buffer nntp-server-buffer)))
|
||||
(and (stringp file)
|
||||
(file-exists-p file)
|
||||
(not (file-directory-p file))
|
||||
(save-excursion (nnmail-find-file file))
|
||||
(string-to-int (file-name-nondirectory file)))))
|
||||
|
||||
(deffoo nnmh-request-group (group &optional server dont-check)
|
||||
(let ((pathname (nnmail-group-pathname group nnmh-directory))
|
||||
dir)
|
||||
(cond
|
||||
((not (file-directory-p pathname))
|
||||
(nnheader-report
|
||||
'nnmh "Can't select group (no such directory): %s" group))
|
||||
(t
|
||||
(setq nnmh-current-directory pathname)
|
||||
(and nnmh-get-new-mail
|
||||
nnmh-be-safe
|
||||
(nnmh-update-gnus-unreads group))
|
||||
(cond
|
||||
(dont-check
|
||||
(nnheader-report 'nnmh "Selected group %s" group)
|
||||
t)
|
||||
(t
|
||||
;; Re-scan the directory if it's on a foreign system.
|
||||
(nnheader-re-read-dir pathname)
|
||||
(setq dir
|
||||
(sort
|
||||
(mapcar (lambda (name) (string-to-int name))
|
||||
(directory-files pathname nil "^[0-9]+$" t))
|
||||
'<))
|
||||
(cond
|
||||
(dir
|
||||
(nnheader-report 'nnmh "Selected group %s" group)
|
||||
(nnheader-insert
|
||||
"211 %d %d %d %s\n" (length dir) (car dir)
|
||||
(progn (while (cdr dir) (setq dir (cdr dir))) (car dir))
|
||||
group))
|
||||
(t
|
||||
(nnheader-report 'nnmh "Empty group %s" group)
|
||||
(nnheader-insert (format "211 0 1 0 %s\n" group))))))))))
|
||||
|
||||
(deffoo nnmh-request-scan (&optional group server)
|
||||
(nnmail-get-new-mail 'nnmh nil nnmh-directory group))
|
||||
|
||||
(deffoo nnmh-request-list (&optional server dir)
|
||||
(nnheader-insert "")
|
||||
(let ((nnmh-toplev
|
||||
(or dir (file-truename (file-name-as-directory nnmh-directory)))))
|
||||
(nnmh-request-list-1 nnmh-toplev))
|
||||
(setq nnmh-group-alist (nnmail-get-active))
|
||||
t)
|
||||
|
||||
(defvar nnmh-toplev)
|
||||
(defun nnmh-request-list-1 (dir)
|
||||
(setq dir (expand-file-name dir))
|
||||
;; Recurse down all directories.
|
||||
(let ((dirs (and (file-readable-p dir)
|
||||
(> (nth 1 (file-attributes (file-chase-links dir))) 2)
|
||||
(directory-files dir t nil t)))
|
||||
dir)
|
||||
;; Recurse down directories.
|
||||
(while (setq dir (pop dirs))
|
||||
(when (and (not (member (file-name-nondirectory dir) '("." "..")))
|
||||
(file-directory-p dir)
|
||||
(file-readable-p dir))
|
||||
(nnmh-request-list-1 dir))))
|
||||
;; For each directory, generate an active file line.
|
||||
(unless (string= (expand-file-name nnmh-toplev) dir)
|
||||
(let ((files (mapcar
|
||||
(lambda (name) (string-to-int name))
|
||||
(directory-files dir nil "^[0-9]+$" t))))
|
||||
(when files
|
||||
(save-excursion
|
||||
(set-buffer nntp-server-buffer)
|
||||
(goto-char (point-max))
|
||||
(insert
|
||||
(format
|
||||
"%s %d %d y\n"
|
||||
(progn
|
||||
(string-match
|
||||
(regexp-quote
|
||||
(file-truename (file-name-as-directory
|
||||
(expand-file-name nnmh-toplev))))
|
||||
dir)
|
||||
(nnheader-replace-chars-in-string
|
||||
(substring dir (match-end 0)) ?/ ?.))
|
||||
(apply 'max files)
|
||||
(apply 'min files)))))))
|
||||
t)
|
||||
|
||||
(deffoo nnmh-request-newgroups (date &optional server)
|
||||
(nnmh-request-list server))
|
||||
|
||||
(deffoo nnmh-request-expire-articles (articles newsgroup
|
||||
&optional server force)
|
||||
(nnmh-possibly-change-directory newsgroup server)
|
||||
(let* ((active-articles
|
||||
(mapcar
|
||||
(function
|
||||
(lambda (name)
|
||||
(string-to-int name)))
|
||||
(directory-files nnmh-current-directory nil "^[0-9]+$" t)))
|
||||
(is-old t)
|
||||
article rest mod-time)
|
||||
(nnmail-activate 'nnmh)
|
||||
|
||||
(while (and articles is-old)
|
||||
(setq article (concat nnmh-current-directory
|
||||
(int-to-string (car articles))))
|
||||
(when (setq mod-time (nth 5 (file-attributes article)))
|
||||
(if (and (nnmh-deletable-article-p newsgroup (car articles))
|
||||
(setq is-old
|
||||
(nnmail-expired-article-p newsgroup mod-time force)))
|
||||
(progn
|
||||
(nnheader-message 5 "Deleting article %s in %s..."
|
||||
article newsgroup)
|
||||
(condition-case ()
|
||||
(funcall nnmail-delete-file-function article)
|
||||
(file-error
|
||||
(nnheader-message 1 "Couldn't delete article %s in %s"
|
||||
article newsgroup)
|
||||
(push (car articles) rest))))
|
||||
(push (car articles) rest)))
|
||||
(setq articles (cdr articles)))
|
||||
(message "")
|
||||
(nconc rest articles)))
|
||||
|
||||
(deffoo nnmh-close-group (group &optional server)
|
||||
t)
|
||||
|
||||
(deffoo nnmh-request-move-article
|
||||
(article group server accept-form &optional last)
|
||||
(let ((buf (get-buffer-create " *nnmh move*"))
|
||||
result)
|
||||
(and
|
||||
(nnmh-deletable-article-p group article)
|
||||
(nnmh-request-article article group server)
|
||||
(save-excursion
|
||||
(set-buffer buf)
|
||||
(erase-buffer)
|
||||
(insert-buffer-substring nntp-server-buffer)
|
||||
(setq result (eval accept-form))
|
||||
(kill-buffer (current-buffer))
|
||||
result)
|
||||
(progn
|
||||
(nnmh-possibly-change-directory group server)
|
||||
(condition-case ()
|
||||
(funcall nnmail-delete-file-function
|
||||
(concat nnmh-current-directory (int-to-string article)))
|
||||
(file-error nil))))
|
||||
result))
|
||||
|
||||
(deffoo nnmh-request-accept-article (group &optional server last noinsert)
|
||||
(nnmh-possibly-change-directory group server)
|
||||
(nnmail-check-syntax)
|
||||
(when nnmail-cache-accepted-message-ids
|
||||
(nnmail-cache-insert (nnmail-fetch-field "message-id")))
|
||||
(prog1
|
||||
(if (stringp group)
|
||||
(and
|
||||
(nnmail-activate 'nnmh)
|
||||
(car (nnmh-save-mail
|
||||
(list (cons group (nnmh-active-number group)))
|
||||
noinsert)))
|
||||
(and
|
||||
(nnmail-activate 'nnmh)
|
||||
(let ((res (nnmail-article-group 'nnmh-active-number)))
|
||||
(if (and (null res)
|
||||
(yes-or-no-p "Moved to `junk' group; delete article? "))
|
||||
'junk
|
||||
(car (nnmh-save-mail res noinsert))))))
|
||||
(when (and last nnmail-cache-accepted-message-ids)
|
||||
(nnmail-cache-close))))
|
||||
|
||||
(deffoo nnmh-request-replace-article (article group buffer)
|
||||
(nnmh-possibly-change-directory group)
|
||||
(save-excursion
|
||||
(set-buffer buffer)
|
||||
(nnmh-possibly-create-directory group)
|
||||
(ignore-errors
|
||||
(nnmail-write-region
|
||||
(point-min) (point-max)
|
||||
(concat nnmh-current-directory (int-to-string article))
|
||||
nil (if (nnheader-be-verbose 5) nil 'nomesg))
|
||||
t)))
|
||||
|
||||
(deffoo nnmh-request-create-group (group &optional server args)
|
||||
(nnmail-activate 'nnmh)
|
||||
(unless (assoc group nnmh-group-alist)
|
||||
(let (active)
|
||||
(push (list group (setq active (cons 1 0)))
|
||||
nnmh-group-alist)
|
||||
(nnmh-possibly-create-directory group)
|
||||
(nnmh-possibly-change-directory group server)
|
||||
(let ((articles (mapcar
|
||||
(lambda (file)
|
||||
(string-to-int file))
|
||||
(directory-files
|
||||
nnmh-current-directory nil "^[0-9]+$"))))
|
||||
(when articles
|
||||
(setcar active (apply 'min articles))
|
||||
(setcdr active (apply 'max articles))))))
|
||||
t)
|
||||
|
||||
(deffoo nnmh-request-delete-group (group &optional force server)
|
||||
(nnmh-possibly-change-directory group server)
|
||||
;; Delete all articles in GROUP.
|
||||
(if (not force)
|
||||
() ; Don't delete the articles.
|
||||
(let ((articles (directory-files nnmh-current-directory t "^[0-9]+$")))
|
||||
(while articles
|
||||
(when (file-writable-p (car articles))
|
||||
(nnheader-message 5 "Deleting article %s in %s..."
|
||||
(car articles) group)
|
||||
(funcall nnmail-delete-file-function (car articles)))
|
||||
(setq articles (cdr articles))))
|
||||
;; Try to delete the directory itself.
|
||||
(ignore-errors
|
||||
(delete-directory nnmh-current-directory)))
|
||||
;; Remove the group from all structures.
|
||||
(setq nnmh-group-alist
|
||||
(delq (assoc group nnmh-group-alist) nnmh-group-alist)
|
||||
nnmh-current-directory nil)
|
||||
t)
|
||||
|
||||
(deffoo nnmh-request-rename-group (group new-name &optional server)
|
||||
(nnmh-possibly-change-directory group server)
|
||||
(let ((new-dir (nnmail-group-pathname new-name nnmh-directory))
|
||||
(old-dir (nnmail-group-pathname group nnmh-directory)))
|
||||
(when (ignore-errors
|
||||
(make-directory new-dir t)
|
||||
t)
|
||||
;; We move the articles file by file instead of renaming
|
||||
;; the directory -- there may be subgroups in this group.
|
||||
;; One might be more clever, I guess.
|
||||
(let ((files (nnheader-article-to-file-alist old-dir)))
|
||||
(while files
|
||||
(rename-file
|
||||
(concat old-dir (cdar files))
|
||||
(concat new-dir (cdar files)))
|
||||
(pop files)))
|
||||
(when (<= (length (directory-files old-dir)) 2)
|
||||
(ignore-errors
|
||||
(delete-directory old-dir)))
|
||||
;; That went ok, so we change the internal structures.
|
||||
(let ((entry (assoc group nnmh-group-alist)))
|
||||
(when entry
|
||||
(setcar entry new-name))
|
||||
(setq nnmh-current-directory nil)
|
||||
t))))
|
||||
|
||||
(nnoo-define-skeleton nnmh)
|
||||
|
||||
|
||||
;;; Internal functions.
|
||||
|
||||
(defun nnmh-possibly-change-directory (newsgroup &optional server)
|
||||
(when (and server
|
||||
(not (nnmh-server-opened server)))
|
||||
(nnmh-open-server server))
|
||||
(when newsgroup
|
||||
(let ((pathname (nnmail-group-pathname newsgroup nnmh-directory)))
|
||||
(if (file-directory-p pathname)
|
||||
(setq nnmh-current-directory pathname)
|
||||
(error "No such newsgroup: %s" newsgroup)))))
|
||||
|
||||
(defun nnmh-possibly-create-directory (group)
|
||||
(let (dir dirs)
|
||||
(setq dir (nnmail-group-pathname group nnmh-directory))
|
||||
(while (not (file-directory-p dir))
|
||||
(push dir dirs)
|
||||
(setq dir (file-name-directory (directory-file-name dir))))
|
||||
(while dirs
|
||||
(when (make-directory (directory-file-name (car dirs)))
|
||||
(error "Could not create directory %s" (car dirs)))
|
||||
(nnheader-message 5 "Creating mail directory %s" (car dirs))
|
||||
(setq dirs (cdr dirs)))))
|
||||
|
||||
(defun nnmh-save-mail (group-art &optional noinsert)
|
||||
"Called narrowed to an article."
|
||||
(unless noinsert
|
||||
(nnmail-insert-lines)
|
||||
(nnmail-insert-xref group-art))
|
||||
(run-hooks 'nnmail-prepare-save-mail-hook)
|
||||
(run-hooks 'nnmh-prepare-save-mail-hook)
|
||||
(goto-char (point-min))
|
||||
(while (looking-at "From ")
|
||||
(replace-match "X-From-Line: ")
|
||||
(forward-line 1))
|
||||
;; We save the article in all the newsgroups it belongs in.
|
||||
(let ((ga group-art)
|
||||
first)
|
||||
(while ga
|
||||
(nnmh-possibly-create-directory (caar ga))
|
||||
(let ((file (concat (nnmail-group-pathname
|
||||
(caar ga) nnmh-directory)
|
||||
(int-to-string (cdar ga)))))
|
||||
(if first
|
||||
;; It was already saved, so we just make a hard link.
|
||||
(funcall nnmail-crosspost-link-function first file t)
|
||||
;; Save the article.
|
||||
(nnmail-write-region (point-min) (point-max) file nil nil)
|
||||
(setq first file)))
|
||||
(setq ga (cdr ga))))
|
||||
group-art)
|
||||
|
||||
(defun nnmh-active-number (group)
|
||||
"Compute the next article number in GROUP."
|
||||
(let ((active (cadr (assoc group nnmh-group-alist)))
|
||||
(dir (nnmail-group-pathname group nnmh-directory)))
|
||||
(unless active
|
||||
;; The group wasn't known to nnmh, so we just create an active
|
||||
;; entry for it.
|
||||
(setq active (cons 1 0))
|
||||
(push (list group active) nnmh-group-alist)
|
||||
(unless (file-exists-p dir)
|
||||
(make-directory dir))
|
||||
;; Find the highest number in the group.
|
||||
(let ((files (sort
|
||||
(mapcar
|
||||
(lambda (f)
|
||||
(string-to-int f))
|
||||
(directory-files dir nil "^[0-9]+$"))
|
||||
'>)))
|
||||
(when files
|
||||
(setcdr active (car files)))))
|
||||
(setcdr active (1+ (cdr active)))
|
||||
(while (file-exists-p
|
||||
(concat (nnmail-group-pathname group nnmh-directory)
|
||||
(int-to-string (cdr active))))
|
||||
(setcdr active (1+ (cdr active))))
|
||||
(cdr active)))
|
||||
|
||||
(defun nnmh-update-gnus-unreads (group)
|
||||
;; Go through the .nnmh-articles file and compare with the actual
|
||||
;; articles in this folder. The articles that are "new" will be
|
||||
;; marked as unread by Gnus.
|
||||
(let* ((dir nnmh-current-directory)
|
||||
(files (sort (mapcar (function (lambda (name) (string-to-int name)))
|
||||
(directory-files nnmh-current-directory
|
||||
nil "^[0-9]+$" t))
|
||||
'<))
|
||||
(nnmh-file (concat dir ".nnmh-articles"))
|
||||
new articles)
|
||||
;; Load the .nnmh-articles file.
|
||||
(when (file-exists-p nnmh-file)
|
||||
(setq articles
|
||||
(let (nnmh-newsgroup-articles)
|
||||
(ignore-errors (load nnmh-file nil t t))
|
||||
nnmh-newsgroup-articles)))
|
||||
;; Add all new articles to the `new' list.
|
||||
(let ((art files))
|
||||
(while art
|
||||
(unless (assq (car art) articles)
|
||||
(push (car art) new))
|
||||
(setq art (cdr art))))
|
||||
;; Remove all deleted articles.
|
||||
(let ((art articles))
|
||||
(while art
|
||||
(unless (memq (caar art) files)
|
||||
(setq articles (delq (car art) articles)))
|
||||
(setq art (cdr art))))
|
||||
;; Check whether the articles really are the ones that Gnus thinks
|
||||
;; they are by looking at the time-stamps.
|
||||
(let ((arts articles)
|
||||
art)
|
||||
(while (setq art (pop arts))
|
||||
(when (not (equal
|
||||
(nth 5 (file-attributes
|
||||
(concat dir (int-to-string (car art)))))
|
||||
(cdr art)))
|
||||
(setq articles (delq art articles))
|
||||
(push (car art) new))))
|
||||
;; Go through all the new articles and add them, and their
|
||||
;; time-stamps, to the list.
|
||||
(setq articles
|
||||
(nconc articles
|
||||
(mapcar
|
||||
(lambda (art)
|
||||
(cons art
|
||||
(nth 5 (file-attributes
|
||||
(concat dir (int-to-string art))))))
|
||||
new)))
|
||||
;; Make Gnus mark all new articles as unread.
|
||||
(when new
|
||||
(gnus-make-articles-unread
|
||||
(gnus-group-prefixed-name group (list 'nnmh ""))
|
||||
(setq new (sort new '<))))
|
||||
;; Sort the article list with highest numbers first.
|
||||
(setq articles (sort articles (lambda (art1 art2)
|
||||
(> (car art1) (car art2)))))
|
||||
;; Finally write this list back to the .nnmh-articles file.
|
||||
(nnheader-temp-write nnmh-file
|
||||
(insert ";; Gnus article active file for " group "\n\n")
|
||||
(insert "(setq nnmh-newsgroup-articles '")
|
||||
(gnus-prin1 articles)
|
||||
(insert ")\n"))))
|
||||
|
||||
(defun nnmh-deletable-article-p (group article)
|
||||
"Say whether ARTICLE in GROUP can be deleted."
|
||||
(let ((path (concat nnmh-current-directory (int-to-string article))))
|
||||
;; Writable.
|
||||
(and (file-writable-p path)
|
||||
;; We can never delete the last article in the group.
|
||||
(not (eq (cdr (nth 1 (assoc group nnmh-group-alist)))
|
||||
article)))))
|
||||
|
||||
(provide 'nnmh)
|
||||
|
||||
;;; nnmh.el ends here
|
||||
793
lisp/gnus/nnml.el
Normal file
793
lisp/gnus/nnml.el
Normal file
|
|
@ -0,0 +1,793 @@
|
|||
;;; nnml.el --- mail spool access for Gnus
|
||||
;; Copyright (C) 1995,96,97 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
|
||||
;; Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
|
||||
;; Keywords: news, mail
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; Based on nnspool.el by Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>.
|
||||
;; For an overview of what the interface functions do, please see the
|
||||
;; Gnus sources.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'nnheader)
|
||||
(require 'nnmail)
|
||||
(require 'nnoo)
|
||||
(require 'cl)
|
||||
|
||||
(nnoo-declare nnml)
|
||||
|
||||
(defvoo nnml-directory message-directory
|
||||
"Mail spool directory.")
|
||||
|
||||
(defvoo nnml-active-file
|
||||
(concat (file-name-as-directory nnml-directory) "active")
|
||||
"Mail active file.")
|
||||
|
||||
(defvoo nnml-newsgroups-file
|
||||
(concat (file-name-as-directory nnml-directory) "newsgroups")
|
||||
"Mail newsgroups description file.")
|
||||
|
||||
(defvoo nnml-get-new-mail t
|
||||
"If non-nil, nnml will check the incoming mail file and split the mail.")
|
||||
|
||||
(defvoo nnml-nov-is-evil nil
|
||||
"If non-nil, Gnus will never generate and use nov databases for mail groups.
|
||||
Using nov databases will speed up header fetching considerably.
|
||||
This variable shouldn't be flipped much. If you have, for some reason,
|
||||
set this to t, and want to set it to nil again, you should always run
|
||||
the `nnml-generate-nov-databases' command. The function will go
|
||||
through all nnml directories and generate nov databases for them
|
||||
all. This may very well take some time.")
|
||||
|
||||
(defvoo nnml-prepare-save-mail-hook nil
|
||||
"Hook run narrowed to an article before saving.")
|
||||
|
||||
(defvoo nnml-inhibit-expiry nil
|
||||
"If non-nil, inhibit expiry.")
|
||||
|
||||
|
||||
|
||||
|
||||
(defconst nnml-version "nnml 1.0"
|
||||
"nnml version.")
|
||||
|
||||
(defvoo nnml-nov-file-name ".overview")
|
||||
|
||||
(defvoo nnml-current-directory nil)
|
||||
(defvoo nnml-current-group nil)
|
||||
(defvoo nnml-status-string "")
|
||||
(defvoo nnml-nov-buffer-alist nil)
|
||||
(defvoo nnml-group-alist nil)
|
||||
(defvoo nnml-active-timestamp nil)
|
||||
(defvoo nnml-article-file-alist nil)
|
||||
|
||||
(defvoo nnml-generate-active-function 'nnml-generate-active-info)
|
||||
|
||||
|
||||
|
||||
;;; Interface functions.
|
||||
|
||||
(nnoo-define-basics nnml)
|
||||
|
||||
(deffoo nnml-retrieve-headers (sequence &optional group server fetch-old)
|
||||
(when (nnml-possibly-change-directory group server)
|
||||
(save-excursion
|
||||
(set-buffer nntp-server-buffer)
|
||||
(erase-buffer)
|
||||
(let ((file nil)
|
||||
(number (length sequence))
|
||||
(count 0)
|
||||
beg article)
|
||||
(if (stringp (car sequence))
|
||||
'headers
|
||||
(if (nnml-retrieve-headers-with-nov sequence fetch-old)
|
||||
'nov
|
||||
(while sequence
|
||||
(setq article (car sequence))
|
||||
(setq file (nnml-article-to-file article))
|
||||
(when (and file
|
||||
(file-exists-p file)
|
||||
(not (file-directory-p file)))
|
||||
(insert (format "221 %d Article retrieved.\n" article))
|
||||
(setq beg (point))
|
||||
(nnheader-insert-head file)
|
||||
(goto-char beg)
|
||||
(if (search-forward "\n\n" nil t)
|
||||
(forward-char -1)
|
||||
(goto-char (point-max))
|
||||
(insert "\n\n"))
|
||||
(insert ".\n")
|
||||
(delete-region (point) (point-max)))
|
||||
(setq sequence (cdr sequence))
|
||||
(setq count (1+ count))
|
||||
(and (numberp nnmail-large-newsgroup)
|
||||
(> number nnmail-large-newsgroup)
|
||||
(zerop (% count 20))
|
||||
(nnheader-message 6 "nnml: Receiving headers... %d%%"
|
||||
(/ (* count 100) number))))
|
||||
|
||||
(and (numberp nnmail-large-newsgroup)
|
||||
(> number nnmail-large-newsgroup)
|
||||
(nnheader-message 6 "nnml: Receiving headers...done"))
|
||||
|
||||
(nnheader-fold-continuation-lines)
|
||||
'headers))))))
|
||||
|
||||
(deffoo nnml-open-server (server &optional defs)
|
||||
(nnoo-change-server 'nnml server defs)
|
||||
(when (not (file-exists-p nnml-directory))
|
||||
(condition-case ()
|
||||
(make-directory nnml-directory t)
|
||||
(error)))
|
||||
(cond
|
||||
((not (file-exists-p nnml-directory))
|
||||
(nnml-close-server)
|
||||
(nnheader-report 'nnml "Couldn't create directory: %s" nnml-directory))
|
||||
((not (file-directory-p (file-truename nnml-directory)))
|
||||
(nnml-close-server)
|
||||
(nnheader-report 'nnml "Not a directory: %s" nnml-directory))
|
||||
(t
|
||||
(nnheader-report 'nnml "Opened server %s using directory %s"
|
||||
server nnml-directory)
|
||||
t)))
|
||||
|
||||
(defun nnml-request-regenerate (server)
|
||||
(nnml-possibly-change-directory nil server)
|
||||
(nnml-generate-nov-databases)
|
||||
t)
|
||||
|
||||
(deffoo nnml-request-article (id &optional group server buffer)
|
||||
(nnml-possibly-change-directory group server)
|
||||
(let* ((nntp-server-buffer (or buffer nntp-server-buffer))
|
||||
path gpath group-num)
|
||||
(if (stringp id)
|
||||
(when (and (setq group-num (nnml-find-group-number id))
|
||||
(cdr
|
||||
(assq (cdr group-num)
|
||||
(nnheader-article-to-file-alist
|
||||
(setq gpath
|
||||
(nnmail-group-pathname
|
||||
(car group-num)
|
||||
nnml-directory))))))
|
||||
(setq path (concat gpath (int-to-string (cdr group-num)))))
|
||||
(setq path (nnml-article-to-file id)))
|
||||
(cond
|
||||
((not path)
|
||||
(nnheader-report 'nnml "No such article: %s" id))
|
||||
((not (file-exists-p path))
|
||||
(nnheader-report 'nnml "No such file: %s" path))
|
||||
((file-directory-p path)
|
||||
(nnheader-report 'nnml "File is a directory: %s" path))
|
||||
((not (save-excursion (nnmail-find-file path)))
|
||||
(nnheader-report 'nnml "Couldn't read file: %s" path))
|
||||
(t
|
||||
(nnheader-report 'nnml "Article %s retrieved" id)
|
||||
;; We return the article number.
|
||||
(cons (if group-num (car group-num) group)
|
||||
(string-to-int (file-name-nondirectory path)))))))
|
||||
|
||||
(deffoo nnml-request-group (group &optional server dont-check)
|
||||
(cond
|
||||
((not (nnml-possibly-change-directory group server))
|
||||
(nnheader-report 'nnml "Invalid group (no such directory)"))
|
||||
((not (file-exists-p nnml-current-directory))
|
||||
(nnheader-report 'nnml "Directory %s does not exist"
|
||||
nnml-current-directory))
|
||||
((not (file-directory-p nnml-current-directory))
|
||||
(nnheader-report 'nnml "%s is not a directory" nnml-current-directory))
|
||||
(dont-check
|
||||
(nnheader-report 'nnml "Group %s selected" group)
|
||||
t)
|
||||
(t
|
||||
(nnheader-re-read-dir nnml-current-directory)
|
||||
(nnmail-activate 'nnml)
|
||||
(let ((active (nth 1 (assoc group nnml-group-alist))))
|
||||
(if (not active)
|
||||
(nnheader-report 'nnml "No such group: %s" group)
|
||||
(nnheader-report 'nnml "Selected group %s" group)
|
||||
(nnheader-insert "211 %d %d %d %s\n"
|
||||
(max (1+ (- (cdr active) (car active))) 0)
|
||||
(car active) (cdr active) group))))))
|
||||
|
||||
(deffoo nnml-request-scan (&optional group server)
|
||||
(setq nnml-article-file-alist nil)
|
||||
(nnml-possibly-change-directory group server)
|
||||
(nnmail-get-new-mail 'nnml 'nnml-save-nov nnml-directory group))
|
||||
|
||||
(deffoo nnml-close-group (group &optional server)
|
||||
(setq nnml-article-file-alist nil)
|
||||
t)
|
||||
|
||||
(deffoo nnml-request-create-group (group &optional server args)
|
||||
(nnmail-activate 'nnml)
|
||||
(unless (assoc group nnml-group-alist)
|
||||
(let (active)
|
||||
(push (list group (setq active (cons 1 0)))
|
||||
nnml-group-alist)
|
||||
(nnml-possibly-create-directory group)
|
||||
(nnml-possibly-change-directory group server)
|
||||
(let ((articles (nnheader-directory-articles nnml-current-directory)))
|
||||
(when articles
|
||||
(setcar active (apply 'min articles))
|
||||
(setcdr active (apply 'max articles))))
|
||||
(nnmail-save-active nnml-group-alist nnml-active-file)))
|
||||
t)
|
||||
|
||||
(deffoo nnml-request-list (&optional server)
|
||||
(save-excursion
|
||||
(nnmail-find-file nnml-active-file)
|
||||
(setq nnml-group-alist (nnmail-get-active))
|
||||
t))
|
||||
|
||||
(deffoo nnml-request-newgroups (date &optional server)
|
||||
(nnml-request-list server))
|
||||
|
||||
(deffoo nnml-request-list-newsgroups (&optional server)
|
||||
(save-excursion
|
||||
(nnmail-find-file nnml-newsgroups-file)))
|
||||
|
||||
(deffoo nnml-request-expire-articles (articles group
|
||||
&optional server force)
|
||||
(nnml-possibly-change-directory group server)
|
||||
(let* ((active-articles
|
||||
(nnheader-directory-articles nnml-current-directory))
|
||||
(is-old t)
|
||||
article rest mod-time number)
|
||||
(nnmail-activate 'nnml)
|
||||
|
||||
(while (and articles is-old)
|
||||
(when (setq article (nnml-article-to-file (setq number (pop articles))))
|
||||
(when (setq mod-time (nth 5 (file-attributes article)))
|
||||
(if (and (nnml-deletable-article-p group number)
|
||||
(setq is-old
|
||||
(nnmail-expired-article-p group mod-time force
|
||||
nnml-inhibit-expiry)))
|
||||
(progn
|
||||
(nnheader-message 5 "Deleting article %s in %s"
|
||||
article group)
|
||||
(condition-case ()
|
||||
(funcall nnmail-delete-file-function article)
|
||||
(file-error
|
||||
(push number rest)))
|
||||
(setq active-articles (delq number active-articles))
|
||||
(nnml-nov-delete-article group number))
|
||||
(push number rest)))))
|
||||
(let ((active (nth 1 (assoc group nnml-group-alist))))
|
||||
(when active
|
||||
(setcar active (or (and active-articles
|
||||
(apply 'min active-articles))
|
||||
(1+ (cdr active)))))
|
||||
(nnmail-save-active nnml-group-alist nnml-active-file))
|
||||
(nnml-save-nov)
|
||||
(nconc rest articles)))
|
||||
|
||||
(deffoo nnml-request-move-article
|
||||
(article group server accept-form &optional last)
|
||||
(let ((buf (get-buffer-create " *nnml move*"))
|
||||
result)
|
||||
(nnml-possibly-change-directory group server)
|
||||
(nnml-update-file-alist)
|
||||
(and
|
||||
(nnml-deletable-article-p group article)
|
||||
(nnml-request-article article group server)
|
||||
(save-excursion
|
||||
(set-buffer buf)
|
||||
(insert-buffer-substring nntp-server-buffer)
|
||||
(setq result (eval accept-form))
|
||||
(kill-buffer (current-buffer))
|
||||
result)
|
||||
(progn
|
||||
(nnml-possibly-change-directory group server)
|
||||
(condition-case ()
|
||||
(funcall nnmail-delete-file-function
|
||||
(nnml-article-to-file article))
|
||||
(file-error nil))
|
||||
(nnml-nov-delete-article group article)
|
||||
(when last
|
||||
(nnml-save-nov)
|
||||
(nnmail-save-active nnml-group-alist nnml-active-file))))
|
||||
result))
|
||||
|
||||
(deffoo nnml-request-accept-article (group &optional server last)
|
||||
(nnml-possibly-change-directory group server)
|
||||
(nnmail-check-syntax)
|
||||
(let (result)
|
||||
(when nnmail-cache-accepted-message-ids
|
||||
(nnmail-cache-insert (nnmail-fetch-field "message-id")))
|
||||
(if (stringp group)
|
||||
(and
|
||||
(nnmail-activate 'nnml)
|
||||
(setq result (car (nnml-save-mail
|
||||
(list (cons group (nnml-active-number group))))))
|
||||
(progn
|
||||
(nnmail-save-active nnml-group-alist nnml-active-file)
|
||||
(and last (nnml-save-nov))))
|
||||
(and
|
||||
(nnmail-activate 'nnml)
|
||||
(if (and (not (setq result (nnmail-article-group 'nnml-active-number)))
|
||||
(yes-or-no-p "Moved to `junk' group; delete article? "))
|
||||
(setq result 'junk)
|
||||
(setq result (car (nnml-save-mail result))))
|
||||
(when last
|
||||
(nnmail-save-active nnml-group-alist nnml-active-file)
|
||||
(when nnmail-cache-accepted-message-ids
|
||||
(nnmail-cache-close))
|
||||
(nnml-save-nov))))
|
||||
result))
|
||||
|
||||
(deffoo nnml-request-replace-article (article group buffer)
|
||||
(nnml-possibly-change-directory group)
|
||||
(save-excursion
|
||||
(set-buffer buffer)
|
||||
(nnml-possibly-create-directory group)
|
||||
(let ((chars (nnmail-insert-lines))
|
||||
(art (concat (int-to-string article) "\t"))
|
||||
headers)
|
||||
(when (condition-case ()
|
||||
(progn
|
||||
(nnmail-write-region
|
||||
(point-min) (point-max)
|
||||
(or (nnml-article-to-file article)
|
||||
(concat nnml-current-directory
|
||||
(int-to-string article)))
|
||||
nil (if (nnheader-be-verbose 5) nil 'nomesg))
|
||||
t)
|
||||
(error nil))
|
||||
(setq headers (nnml-parse-head chars article))
|
||||
;; Replace the NOV line in the NOV file.
|
||||
(save-excursion
|
||||
(set-buffer (nnml-open-nov group))
|
||||
(goto-char (point-min))
|
||||
(if (or (looking-at art)
|
||||
(search-forward (concat "\n" art) nil t))
|
||||
;; Delete the old NOV line.
|
||||
(delete-region (progn (beginning-of-line) (point))
|
||||
(progn (forward-line 1) (point)))
|
||||
;; The line isn't here, so we have to find out where
|
||||
;; we should insert it. (This situation should never
|
||||
;; occur, but one likes to make sure...)
|
||||
(while (and (looking-at "[0-9]+\t")
|
||||
(< (string-to-int
|
||||
(buffer-substring
|
||||
(match-beginning 0) (match-end 0)))
|
||||
article)
|
||||
(zerop (forward-line 1)))))
|
||||
(beginning-of-line)
|
||||
(nnheader-insert-nov headers)
|
||||
(nnml-save-nov)
|
||||
t)))))
|
||||
|
||||
(deffoo nnml-request-delete-group (group &optional force server)
|
||||
(nnml-possibly-change-directory group server)
|
||||
(when force
|
||||
;; Delete all articles in GROUP.
|
||||
(let ((articles
|
||||
(directory-files
|
||||
nnml-current-directory t
|
||||
(concat nnheader-numerical-short-files
|
||||
"\\|" (regexp-quote nnml-nov-file-name) "$")))
|
||||
article)
|
||||
(while articles
|
||||
(setq article (pop articles))
|
||||
(when (file-writable-p article)
|
||||
(nnheader-message 5 "Deleting article %s in %s..." article group)
|
||||
(funcall nnmail-delete-file-function article))))
|
||||
;; Try to delete the directory itself.
|
||||
(condition-case ()
|
||||
(delete-directory nnml-current-directory)
|
||||
(error nil)))
|
||||
;; Remove the group from all structures.
|
||||
(setq nnml-group-alist
|
||||
(delq (assoc group nnml-group-alist) nnml-group-alist)
|
||||
nnml-current-group nil
|
||||
nnml-current-directory nil)
|
||||
;; Save the active file.
|
||||
(nnmail-save-active nnml-group-alist nnml-active-file)
|
||||
t)
|
||||
|
||||
(deffoo nnml-request-rename-group (group new-name &optional server)
|
||||
(nnml-possibly-change-directory group server)
|
||||
(let ((new-dir (nnmail-group-pathname new-name nnml-directory))
|
||||
(old-dir (nnmail-group-pathname group nnml-directory)))
|
||||
(when (condition-case ()
|
||||
(progn
|
||||
(make-directory new-dir t)
|
||||
t)
|
||||
(error nil))
|
||||
;; We move the articles file by file instead of renaming
|
||||
;; the directory -- there may be subgroups in this group.
|
||||
;; One might be more clever, I guess.
|
||||
(let ((files (nnheader-article-to-file-alist old-dir)))
|
||||
(while files
|
||||
(rename-file
|
||||
(concat old-dir (cdar files))
|
||||
(concat new-dir (cdar files)))
|
||||
(pop files)))
|
||||
;; Move .overview file.
|
||||
(let ((overview (concat old-dir nnml-nov-file-name)))
|
||||
(when (file-exists-p overview)
|
||||
(rename-file overview (concat new-dir nnml-nov-file-name))))
|
||||
(when (<= (length (directory-files old-dir)) 2)
|
||||
(condition-case ()
|
||||
(delete-directory old-dir)
|
||||
(error nil)))
|
||||
;; That went ok, so we change the internal structures.
|
||||
(let ((entry (assoc group nnml-group-alist)))
|
||||
(when entry
|
||||
(setcar entry new-name))
|
||||
(setq nnml-current-directory nil
|
||||
nnml-current-group nil)
|
||||
;; Save the new group alist.
|
||||
(nnmail-save-active nnml-group-alist nnml-active-file)
|
||||
t))))
|
||||
|
||||
(deffoo nnml-set-status (article name value &optional group server)
|
||||
(nnml-possibly-change-directory group server)
|
||||
(let ((file (nnml-article-to-file article)))
|
||||
(cond
|
||||
((not (file-exists-p file))
|
||||
(nnheader-report 'nnml "File %s does not exist" file))
|
||||
(t
|
||||
(nnheader-temp-write file
|
||||
(nnheader-insert-file-contents file)
|
||||
(nnmail-replace-status name value))
|
||||
t))))
|
||||
|
||||
|
||||
;;; Internal functions.
|
||||
|
||||
(defun nnml-article-to-file (article)
|
||||
(nnml-update-file-alist)
|
||||
(let (file)
|
||||
(when (setq file (cdr (assq article nnml-article-file-alist)))
|
||||
(concat nnml-current-directory file))))
|
||||
|
||||
(defun nnml-deletable-article-p (group article)
|
||||
"Say whether ARTICLE in GROUP can be deleted."
|
||||
(let (path)
|
||||
(when (setq path (nnml-article-to-file article))
|
||||
(when (file-writable-p path)
|
||||
(or (not nnmail-keep-last-article)
|
||||
(not (eq (cdr (nth 1 (assoc group nnml-group-alist)))
|
||||
article)))))))
|
||||
|
||||
;; Find an article number in the current group given the Message-ID.
|
||||
(defun nnml-find-group-number (id)
|
||||
(save-excursion
|
||||
(set-buffer (get-buffer-create " *nnml id*"))
|
||||
(buffer-disable-undo (current-buffer))
|
||||
(let ((alist nnml-group-alist)
|
||||
number)
|
||||
;; We want to look through all .overview files, but we want to
|
||||
;; start with the one in the current directory. It seems most
|
||||
;; likely that the article we are looking for is in that group.
|
||||
(if (setq number (nnml-find-id nnml-current-group id))
|
||||
(cons nnml-current-group number)
|
||||
;; It wasn't there, so we look through the other groups as well.
|
||||
(while (and (not number)
|
||||
alist)
|
||||
(or (string= (caar alist) nnml-current-group)
|
||||
(setq number (nnml-find-id (caar alist) id)))
|
||||
(or number
|
||||
(setq alist (cdr alist))))
|
||||
(and number
|
||||
(cons (caar alist) number))))))
|
||||
|
||||
(defun nnml-find-id (group id)
|
||||
(erase-buffer)
|
||||
(let ((nov (concat (nnmail-group-pathname group nnml-directory)
|
||||
nnml-nov-file-name))
|
||||
number found)
|
||||
(when (file-exists-p nov)
|
||||
(nnheader-insert-file-contents nov)
|
||||
(while (and (not found)
|
||||
(search-forward id nil t)) ; We find the ID.
|
||||
;; And the id is in the fourth field.
|
||||
(if (not (and (search-backward "\t" nil t 4)
|
||||
(not (search-backward"\t" (gnus-point-at-bol) t))))
|
||||
(forward-line 1)
|
||||
(beginning-of-line)
|
||||
(setq found t)
|
||||
;; We return the article number.
|
||||
(setq number
|
||||
(condition-case ()
|
||||
(read (current-buffer))
|
||||
(error nil)))))
|
||||
number)))
|
||||
|
||||
(defun nnml-retrieve-headers-with-nov (articles &optional fetch-old)
|
||||
(if (or gnus-nov-is-evil nnml-nov-is-evil)
|
||||
nil
|
||||
(let ((nov (concat nnml-current-directory nnml-nov-file-name)))
|
||||
(when (file-exists-p nov)
|
||||
(save-excursion
|
||||
(set-buffer nntp-server-buffer)
|
||||
(erase-buffer)
|
||||
(nnheader-insert-file-contents nov)
|
||||
(if (and fetch-old
|
||||
(not (numberp fetch-old)))
|
||||
t ; Don't remove anything.
|
||||
(nnheader-nov-delete-outside-range
|
||||
(if fetch-old (max 1 (- (car articles) fetch-old))
|
||||
(car articles))
|
||||
(car (last articles)))
|
||||
t))))))
|
||||
|
||||
(defun nnml-possibly-change-directory (group &optional server)
|
||||
(when (and server
|
||||
(not (nnml-server-opened server)))
|
||||
(nnml-open-server server))
|
||||
(if (not group)
|
||||
t
|
||||
(let ((pathname (nnmail-group-pathname group nnml-directory)))
|
||||
(when (not (equal pathname nnml-current-directory))
|
||||
(setq nnml-current-directory pathname
|
||||
nnml-current-group group
|
||||
nnml-article-file-alist nil))
|
||||
(file-exists-p nnml-current-directory))))
|
||||
|
||||
(defun nnml-possibly-create-directory (group)
|
||||
(let (dir dirs)
|
||||
(setq dir (nnmail-group-pathname group nnml-directory))
|
||||
(while (not (file-directory-p dir))
|
||||
(push dir dirs)
|
||||
(setq dir (file-name-directory (directory-file-name dir))))
|
||||
(while dirs
|
||||
(make-directory (directory-file-name (car dirs)))
|
||||
(nnheader-message 5 "Creating mail directory %s" (car dirs))
|
||||
(setq dirs (cdr dirs)))))
|
||||
|
||||
(defun nnml-save-mail (group-art)
|
||||
"Called narrowed to an article."
|
||||
(let (chars headers)
|
||||
(setq chars (nnmail-insert-lines))
|
||||
(nnmail-insert-xref group-art)
|
||||
(run-hooks 'nnmail-prepare-save-mail-hook)
|
||||
(run-hooks 'nnml-prepare-save-mail-hook)
|
||||
(goto-char (point-min))
|
||||
(while (looking-at "From ")
|
||||
(replace-match "X-From-Line: ")
|
||||
(forward-line 1))
|
||||
;; We save the article in all the groups it belongs in.
|
||||
(let ((ga group-art)
|
||||
first)
|
||||
(while ga
|
||||
(nnml-possibly-create-directory (caar ga))
|
||||
(let ((file (concat (nnmail-group-pathname
|
||||
(caar ga) nnml-directory)
|
||||
(int-to-string (cdar ga)))))
|
||||
(if first
|
||||
;; It was already saved, so we just make a hard link.
|
||||
(funcall nnmail-crosspost-link-function first file t)
|
||||
;; Save the article.
|
||||
(nnmail-write-region (point-min) (point-max) file nil
|
||||
(if (nnheader-be-verbose 5) nil 'nomesg))
|
||||
(setq first file)))
|
||||
(setq ga (cdr ga))))
|
||||
;; Generate a nov line for this article. We generate the nov
|
||||
;; line after saving, because nov generation destroys the
|
||||
;; header.
|
||||
(setq headers (nnml-parse-head chars))
|
||||
;; Output the nov line to all nov databases that should have it.
|
||||
(let ((ga group-art))
|
||||
(while ga
|
||||
(nnml-add-nov (caar ga) (cdar ga) headers)
|
||||
(setq ga (cdr ga))))
|
||||
group-art))
|
||||
|
||||
(defun nnml-active-number (group)
|
||||
"Compute the next article number in GROUP."
|
||||
(let ((active (cadr (assoc group nnml-group-alist))))
|
||||
;; The group wasn't known to nnml, so we just create an active
|
||||
;; entry for it.
|
||||
(unless active
|
||||
;; Perhaps the active file was corrupt? See whether
|
||||
;; there are any articles in this group.
|
||||
(nnml-possibly-create-directory group)
|
||||
(nnml-possibly-change-directory group)
|
||||
(unless nnml-article-file-alist
|
||||
(setq nnml-article-file-alist
|
||||
(sort
|
||||
(nnheader-article-to-file-alist nnml-current-directory)
|
||||
(lambda (a1 a2) (< (car a1) (car a2))))))
|
||||
(setq active
|
||||
(if nnml-article-file-alist
|
||||
(cons (caar nnml-article-file-alist)
|
||||
(caar (last nnml-article-file-alist)))
|
||||
(cons 1 0)))
|
||||
(push (list group active) nnml-group-alist))
|
||||
(setcdr active (1+ (cdr active)))
|
||||
(while (file-exists-p
|
||||
(concat (nnmail-group-pathname group nnml-directory)
|
||||
(int-to-string (cdr active))))
|
||||
(setcdr active (1+ (cdr active))))
|
||||
(cdr active)))
|
||||
|
||||
(defun nnml-add-nov (group article headers)
|
||||
"Add a nov line for the GROUP base."
|
||||
(save-excursion
|
||||
(set-buffer (nnml-open-nov group))
|
||||
(goto-char (point-max))
|
||||
(mail-header-set-number headers article)
|
||||
(nnheader-insert-nov headers)))
|
||||
|
||||
(defsubst nnml-header-value ()
|
||||
(buffer-substring (match-end 0) (progn (end-of-line) (point))))
|
||||
|
||||
(defun nnml-parse-head (chars &optional number)
|
||||
"Parse the head of the current buffer."
|
||||
(save-excursion
|
||||
(save-restriction
|
||||
(goto-char (point-min))
|
||||
(narrow-to-region
|
||||
(point)
|
||||
(1- (or (search-forward "\n\n" nil t) (point-max))))
|
||||
;; Fold continuation lines.
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward "\\(\r?\n[ \t]+\\)+" nil t)
|
||||
(replace-match " " t t))
|
||||
;; Remove any tabs; they are too confusing.
|
||||
(subst-char-in-region (point-min) (point-max) ?\t ? )
|
||||
(let ((headers (nnheader-parse-head t)))
|
||||
(mail-header-set-chars headers chars)
|
||||
(mail-header-set-number headers number)
|
||||
headers))))
|
||||
|
||||
(defun nnml-open-nov (group)
|
||||
(or (cdr (assoc group nnml-nov-buffer-alist))
|
||||
(let ((buffer (nnheader-find-file-noselect
|
||||
(concat (nnmail-group-pathname group nnml-directory)
|
||||
nnml-nov-file-name))))
|
||||
(save-excursion
|
||||
(set-buffer buffer)
|
||||
(buffer-disable-undo (current-buffer)))
|
||||
(push (cons group buffer) nnml-nov-buffer-alist)
|
||||
buffer)))
|
||||
|
||||
(defun nnml-save-nov ()
|
||||
(save-excursion
|
||||
(while nnml-nov-buffer-alist
|
||||
(when (buffer-name (cdar nnml-nov-buffer-alist))
|
||||
(set-buffer (cdar nnml-nov-buffer-alist))
|
||||
(when (buffer-modified-p)
|
||||
(nnmail-write-region 1 (point-max) (buffer-file-name) nil 'nomesg))
|
||||
(set-buffer-modified-p nil)
|
||||
(kill-buffer (current-buffer)))
|
||||
(setq nnml-nov-buffer-alist (cdr nnml-nov-buffer-alist)))))
|
||||
|
||||
;;;###autoload
|
||||
(defun nnml-generate-nov-databases ()
|
||||
"Generate NOV databases in all nnml directories."
|
||||
(interactive)
|
||||
;; Read the active file to make sure we don't re-use articles
|
||||
;; numbers in empty groups.
|
||||
(nnmail-activate 'nnml)
|
||||
(nnml-open-server (or (nnoo-current-server 'nnml) ""))
|
||||
(setq nnml-directory (expand-file-name nnml-directory))
|
||||
;; Recurse down the directories.
|
||||
(nnml-generate-nov-databases-1 nnml-directory nil t)
|
||||
;; Save the active file.
|
||||
(nnmail-save-active nnml-group-alist nnml-active-file))
|
||||
|
||||
(defun nnml-generate-nov-databases-1 (dir &optional seen no-active)
|
||||
"Regenerate the NOV database in DIR."
|
||||
(interactive "DRegenerate NOV in: ")
|
||||
(setq dir (file-name-as-directory dir))
|
||||
;; Only scan this sub-tree if we haven't been here yet.
|
||||
(unless (member (file-truename dir) seen)
|
||||
(push (file-truename dir) seen)
|
||||
;; We descend recursively
|
||||
(let ((dirs (directory-files dir t nil t))
|
||||
dir)
|
||||
(while (setq dir (pop dirs))
|
||||
(when (and (not (member (file-name-nondirectory dir) '("." "..")))
|
||||
(file-directory-p dir))
|
||||
(nnml-generate-nov-databases-1 dir seen))))
|
||||
;; Do this directory.
|
||||
(let ((files (sort (nnheader-article-to-file-alist dir)
|
||||
(lambda (a b) (< (car a) (car b))))))
|
||||
(when files
|
||||
(funcall nnml-generate-active-function dir)
|
||||
;; Generate the nov file.
|
||||
(nnml-generate-nov-file dir files)
|
||||
(unless no-active
|
||||
(nnmail-save-active nnml-group-alist nnml-active-file))))))
|
||||
|
||||
(defvar files)
|
||||
(defun nnml-generate-active-info (dir)
|
||||
;; Update the active info for this group.
|
||||
(let ((group (nnheader-file-to-group
|
||||
(directory-file-name dir) nnml-directory)))
|
||||
(setq nnml-group-alist
|
||||
(delq (assoc group nnml-group-alist) nnml-group-alist))
|
||||
(push (list group
|
||||
(cons (caar files)
|
||||
(let ((f files))
|
||||
(while (cdr f) (setq f (cdr f)))
|
||||
(caar f))))
|
||||
nnml-group-alist)))
|
||||
|
||||
(defun nnml-generate-nov-file (dir files)
|
||||
(let* ((dir (file-name-as-directory dir))
|
||||
(nov (concat dir nnml-nov-file-name))
|
||||
(nov-buffer (get-buffer-create " *nov*"))
|
||||
chars file headers)
|
||||
(save-excursion
|
||||
;; Init the nov buffer.
|
||||
(set-buffer nov-buffer)
|
||||
(buffer-disable-undo (current-buffer))
|
||||
(erase-buffer)
|
||||
(set-buffer nntp-server-buffer)
|
||||
;; Delete the old NOV file.
|
||||
(when (file-exists-p nov)
|
||||
(funcall nnmail-delete-file-function nov))
|
||||
(while files
|
||||
(unless (file-directory-p (setq file (concat dir (cdar files))))
|
||||
(erase-buffer)
|
||||
(nnheader-insert-file-contents file)
|
||||
(narrow-to-region
|
||||
(goto-char (point-min))
|
||||
(progn
|
||||
(search-forward "\n\n" nil t)
|
||||
(setq chars (- (point-max) (point)))
|
||||
(max 1 (1- (point)))))
|
||||
(when (and (not (= 0 chars)) ; none of them empty files...
|
||||
(not (= (point-min) (point-max))))
|
||||
(goto-char (point-min))
|
||||
(setq headers (nnml-parse-head chars (caar files)))
|
||||
(save-excursion
|
||||
(set-buffer nov-buffer)
|
||||
(goto-char (point-max))
|
||||
(nnheader-insert-nov headers)))
|
||||
(widen))
|
||||
(setq files (cdr files)))
|
||||
(save-excursion
|
||||
(set-buffer nov-buffer)
|
||||
(nnmail-write-region 1 (point-max) nov nil 'nomesg)
|
||||
(kill-buffer (current-buffer))))))
|
||||
|
||||
(defun nnml-nov-delete-article (group article)
|
||||
(save-excursion
|
||||
(set-buffer (nnml-open-nov group))
|
||||
(when (nnheader-find-nov-line article)
|
||||
(delete-region (point) (progn (forward-line 1) (point)))
|
||||
(when (bobp)
|
||||
(let ((active (cadr (assoc group nnml-group-alist)))
|
||||
num)
|
||||
(when active
|
||||
(if (eobp)
|
||||
(setf (car active) (1+ (cdr active)))
|
||||
(when (and (setq num (ignore-errors (read (current-buffer))))
|
||||
(numberp num))
|
||||
(setf (car active) num)))))))
|
||||
t))
|
||||
|
||||
(defun nnml-update-file-alist ()
|
||||
(unless nnml-article-file-alist
|
||||
(setq nnml-article-file-alist
|
||||
(nnheader-article-to-file-alist nnml-current-directory))))
|
||||
|
||||
(provide 'nnml)
|
||||
|
||||
;;; nnml.el ends here
|
||||
279
lisp/gnus/nnoo.el
Normal file
279
lisp/gnus/nnoo.el
Normal file
|
|
@ -0,0 +1,279 @@
|
|||
;;; nnoo.el --- OO Gnus Backends
|
||||
;; Copyright (C) 1996,97 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
|
||||
;; Keywords: news
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'nnheader)
|
||||
(require 'cl)
|
||||
|
||||
(defvar nnoo-definition-alist nil)
|
||||
(defvar nnoo-state-alist nil)
|
||||
|
||||
(defmacro defvoo (var init &optional doc &rest map)
|
||||
"The same as `defvar', only takes list of variables to MAP to."
|
||||
`(prog1
|
||||
,(if doc
|
||||
`(defvar ,var ,init ,doc)
|
||||
`(defvar ,var ,init))
|
||||
(nnoo-define ',var ',map)))
|
||||
(put 'defvoo 'lisp-indent-function 2)
|
||||
(put 'defvoo 'edebug-form-spec '(var init &optional doc &rest map))
|
||||
|
||||
(defmacro deffoo (func args &rest forms)
|
||||
"The same as `defun', only register FUNC."
|
||||
`(prog1
|
||||
(defun ,func ,args ,@forms)
|
||||
(nnoo-register-function ',func)))
|
||||
(put 'deffoo 'lisp-indent-function 2)
|
||||
(put 'deffoo 'edebug-form-spec '(&define name lambda-list def-body))
|
||||
|
||||
(defun nnoo-register-function (func)
|
||||
(let ((funcs (nthcdr 3 (assoc (nnoo-backend func)
|
||||
nnoo-definition-alist))))
|
||||
(unless funcs
|
||||
(error "%s belongs to a backend that hasn't been declared" func))
|
||||
(setcar funcs (cons func (car funcs)))))
|
||||
|
||||
(defmacro nnoo-declare (backend &rest parents)
|
||||
`(eval-and-compile
|
||||
(push (list ',backend
|
||||
(mapcar (lambda (p) (list p)) ',parents)
|
||||
nil nil)
|
||||
nnoo-definition-alist)
|
||||
(push (list ',backend "*internal-non-initialized-backend*")
|
||||
nnoo-state-alist)))
|
||||
(put 'nnoo-declare 'lisp-indent-function 1)
|
||||
|
||||
(defun nnoo-parents (backend)
|
||||
(nth 1 (assoc backend nnoo-definition-alist)))
|
||||
|
||||
(defun nnoo-variables (backend)
|
||||
(nth 2 (assoc backend nnoo-definition-alist)))
|
||||
|
||||
(defun nnoo-functions (backend)
|
||||
(nth 3 (assoc backend nnoo-definition-alist)))
|
||||
|
||||
(defmacro nnoo-import (backend &rest imports)
|
||||
`(nnoo-import-1 ',backend ',imports))
|
||||
(put 'nnoo-import 'lisp-indent-function 1)
|
||||
|
||||
(defun nnoo-import-1 (backend imports)
|
||||
(let ((call-function
|
||||
(if (symbolp (car imports)) (pop imports) 'nnoo-parent-function))
|
||||
imp functions function)
|
||||
(while (setq imp (pop imports))
|
||||
(setq functions
|
||||
(or (cdr imp)
|
||||
(nnoo-functions (car imp))))
|
||||
(while functions
|
||||
(unless (fboundp (setq function
|
||||
(nnoo-symbol backend (nnoo-rest-symbol
|
||||
(car functions)))))
|
||||
(eval `(deffoo ,function (&rest args)
|
||||
(,call-function ',backend ',(car functions) args))))
|
||||
(pop functions)))))
|
||||
|
||||
(defun nnoo-parent-function (backend function args)
|
||||
(let* ((pbackend (nnoo-backend function)))
|
||||
(nnoo-change-server pbackend (nnoo-current-server backend)
|
||||
(cdr (assq pbackend (nnoo-parents backend))))
|
||||
(apply function args)))
|
||||
|
||||
(defun nnoo-execute (backend function &rest args)
|
||||
"Execute FUNCTION on behalf of BACKEND."
|
||||
(let* ((pbackend (nnoo-backend function)))
|
||||
(nnoo-change-server pbackend (nnoo-current-server backend)
|
||||
(cdr (assq pbackend (nnoo-parents backend))))
|
||||
(apply function args)))
|
||||
|
||||
(defmacro nnoo-map-functions (backend &rest maps)
|
||||
`(nnoo-map-functions-1 ',backend ',maps))
|
||||
(put 'nnoo-map-functions 'lisp-indent-function 1)
|
||||
|
||||
(defun nnoo-map-functions-1 (backend maps)
|
||||
(let (m margs i)
|
||||
(while (setq m (pop maps))
|
||||
(setq i 0
|
||||
margs nil)
|
||||
(while (< i (length (cdr m)))
|
||||
(if (numberp (nth i (cdr m)))
|
||||
(push `(nth ,i args) margs)
|
||||
(push (nth i (cdr m)) margs))
|
||||
(incf i))
|
||||
(eval `(deffoo ,(nnoo-symbol backend (nnoo-rest-symbol (car m)))
|
||||
(&rest args)
|
||||
(nnoo-parent-function ',backend ',(car m)
|
||||
,(cons 'list (nreverse margs))))))))
|
||||
|
||||
(defun nnoo-backend (symbol)
|
||||
(string-match "^[^-]+-" (symbol-name symbol))
|
||||
(intern (substring (symbol-name symbol) 0 (1- (match-end 0)))))
|
||||
|
||||
(defun nnoo-rest-symbol (symbol)
|
||||
(string-match "^[^-]+-" (symbol-name symbol))
|
||||
(intern (substring (symbol-name symbol) (match-end 0))))
|
||||
|
||||
(defun nnoo-symbol (backend symbol)
|
||||
(intern (format "%s-%s" backend symbol)))
|
||||
|
||||
(defun nnoo-define (var map)
|
||||
(let* ((backend (nnoo-backend var))
|
||||
(def (assq backend nnoo-definition-alist))
|
||||
(parents (nth 1 def)))
|
||||
(unless def
|
||||
(error "%s belongs to a backend that hasn't been declared." var))
|
||||
(setcar (nthcdr 2 def)
|
||||
(delq (assq var (nth 2 def)) (nth 2 def)))
|
||||
(setcar (nthcdr 2 def)
|
||||
(cons (cons var (symbol-value var))
|
||||
(nth 2 def)))
|
||||
(while map
|
||||
(nconc (assq (nnoo-backend (car map)) parents)
|
||||
(list (list (pop map) var))))))
|
||||
|
||||
(defun nnoo-change-server (backend server defs)
|
||||
(let* ((bstate (cdr (assq backend nnoo-state-alist)))
|
||||
(current (car bstate))
|
||||
(parents (nnoo-parents backend))
|
||||
(bvariables (nnoo-variables backend))
|
||||
state def)
|
||||
(unless bstate
|
||||
(push (setq bstate (list backend nil))
|
||||
nnoo-state-alist)
|
||||
(pop bstate))
|
||||
(if (equal server current)
|
||||
t
|
||||
(nnoo-push-server backend current)
|
||||
(setq state (or (cdr (assoc server (cddr bstate)))
|
||||
(nnoo-variables backend)))
|
||||
(while state
|
||||
(set (caar state) (cdar state))
|
||||
(pop state))
|
||||
(setcar bstate server)
|
||||
(unless (cdr (assoc server (cddr bstate)))
|
||||
(while (setq def (pop defs))
|
||||
(unless (assq (car def) bvariables)
|
||||
(nconc bvariables
|
||||
(list (cons (car def) (and (boundp (car def))
|
||||
(symbol-value (car def)))))))
|
||||
(set (car def) (cadr def))))
|
||||
(while parents
|
||||
(nnoo-change-server
|
||||
(caar parents) server
|
||||
(mapcar (lambda (def) (list (car def) (symbol-value (cadr def))))
|
||||
(cdar parents)))
|
||||
(pop parents))))
|
||||
t)
|
||||
|
||||
(defun nnoo-push-server (backend current)
|
||||
(let ((bstate (assq backend nnoo-state-alist))
|
||||
(defs (nnoo-variables backend)))
|
||||
;; Remove the old definition.
|
||||
(setcdr (cdr bstate) (delq (assoc current (cddr bstate)) (cddr bstate)))
|
||||
;; If this is the first time we push the server (i. e., this is
|
||||
;; the nil server), then we update the default values of
|
||||
;; all the variables to reflect the current values.
|
||||
(when (equal current "*internal-non-initialized-backend*")
|
||||
(let ((defaults (nnoo-variables backend))
|
||||
def)
|
||||
(while (setq def (pop defaults))
|
||||
(setcdr def (symbol-value (car def))))))
|
||||
(let (state)
|
||||
(while defs
|
||||
(push (cons (caar defs) (symbol-value (caar defs)))
|
||||
state)
|
||||
(pop defs))
|
||||
(nconc bstate (list (cons current state))))))
|
||||
|
||||
(defsubst nnoo-current-server-p (backend server)
|
||||
(equal (nnoo-current-server backend) server))
|
||||
|
||||
(defun nnoo-current-server (backend)
|
||||
(nth 1 (assq backend nnoo-state-alist)))
|
||||
|
||||
(defun nnoo-close-server (backend &optional server)
|
||||
(unless server
|
||||
(setq server (nnoo-current-server backend)))
|
||||
(when server
|
||||
(let* ((bstate (cdr (assq backend nnoo-state-alist)))
|
||||
(defs (assoc server (cdr bstate))))
|
||||
(when bstate
|
||||
(setcar bstate nil)
|
||||
(setcdr bstate (delq defs (cdr bstate)))
|
||||
(pop defs)
|
||||
(while defs
|
||||
(set (car (pop defs)) nil)))))
|
||||
t)
|
||||
|
||||
(defun nnoo-close (backend)
|
||||
(setq nnoo-state-alist
|
||||
(delq (assq backend nnoo-state-alist)
|
||||
nnoo-state-alist))
|
||||
t)
|
||||
|
||||
(defun nnoo-status-message (backend server)
|
||||
(nnheader-get-report backend))
|
||||
|
||||
(defun nnoo-server-opened (backend server)
|
||||
(and (nnoo-current-server-p backend server)
|
||||
nntp-server-buffer
|
||||
(buffer-name nntp-server-buffer)))
|
||||
|
||||
(defmacro nnoo-define-basics (backend)
|
||||
"Define `close-server', `server-opened' and `status-message'."
|
||||
`(eval-and-compile
|
||||
(nnoo-define-basics-1 ',backend)))
|
||||
|
||||
(defun nnoo-define-basics-1 (backend)
|
||||
(let ((functions '(close-server server-opened status-message)))
|
||||
(while functions
|
||||
(eval `(deffoo ,(nnoo-symbol backend (car functions))
|
||||
(&optional server)
|
||||
(,(nnoo-symbol 'nnoo (pop functions)) ',backend server)))))
|
||||
(eval `(deffoo ,(nnoo-symbol backend 'open-server)
|
||||
(server &optional defs)
|
||||
(nnoo-change-server ',backend server defs))))
|
||||
|
||||
(defmacro nnoo-define-skeleton (backend)
|
||||
"Define all required backend functions for BACKEND.
|
||||
All functions will return nil and report an error."
|
||||
`(eval-and-compile
|
||||
(nnoo-define-skeleton-1 ',backend)))
|
||||
|
||||
(defun nnoo-define-skeleton-1 (backend)
|
||||
(let ((functions '(retrieve-headers
|
||||
request-close request-article
|
||||
request-group close-group
|
||||
request-list request-post request-list-newsgroups))
|
||||
function fun)
|
||||
(while (setq function (pop functions))
|
||||
(when (not (fboundp (setq fun (nnoo-symbol backend function))))
|
||||
(eval `(deffoo ,fun
|
||||
(&rest args)
|
||||
(nnheader-report ',backend ,(format "%s-%s not implemented"
|
||||
backend function))))))))
|
||||
(provide 'nnoo)
|
||||
|
||||
;;; nnoo.el ends here.
|
||||
804
lisp/gnus/nnsoup.el
Normal file
804
lisp/gnus/nnsoup.el
Normal file
|
|
@ -0,0 +1,804 @@
|
|||
;;; nnsoup.el --- SOUP access for Gnus
|
||||
;; Copyright (C) 1995,96,97 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
|
||||
;; Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
|
||||
;; Keywords: news, mail
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'nnheader)
|
||||
(require 'nnmail)
|
||||
(require 'gnus-soup)
|
||||
(require 'gnus-msg)
|
||||
(require 'nnoo)
|
||||
(eval-when-compile (require 'cl))
|
||||
|
||||
(nnoo-declare nnsoup)
|
||||
|
||||
(defvoo nnsoup-directory "~/SOUP/"
|
||||
"*SOUP packet directory.")
|
||||
|
||||
(defvoo nnsoup-tmp-directory "/tmp/"
|
||||
"*Where nnsoup will store temporary files.")
|
||||
|
||||
(defvoo nnsoup-replies-directory (concat nnsoup-directory "replies/")
|
||||
"*Directory where outgoing packets will be composed.")
|
||||
|
||||
(defvoo nnsoup-replies-format-type ?n
|
||||
"*Format of the replies packages.")
|
||||
|
||||
(defvoo nnsoup-replies-index-type ?n
|
||||
"*Index type of the replies packages.")
|
||||
|
||||
(defvoo nnsoup-active-file (concat nnsoup-directory "active")
|
||||
"Active file.")
|
||||
|
||||
(defvoo nnsoup-packer "tar cf - %s | gzip > $HOME/Soupin%d.tgz"
|
||||
"Format string command for packing a SOUP packet.
|
||||
The SOUP files will be inserted where the %s is in the string.
|
||||
This string MUST contain both %s and %d. The file number will be
|
||||
inserted where %d appears.")
|
||||
|
||||
(defvoo nnsoup-unpacker "gunzip -c %s | tar xvf -"
|
||||
"*Format string command for unpacking a SOUP packet.
|
||||
The SOUP packet file name will be inserted at the %s.")
|
||||
|
||||
(defvoo nnsoup-packet-directory "~/"
|
||||
"*Where nnsoup will look for incoming packets.")
|
||||
|
||||
(defvoo nnsoup-packet-regexp "Soupout"
|
||||
"*Regular expression matching SOUP packets in `nnsoup-packet-directory'.")
|
||||
|
||||
|
||||
|
||||
(defconst nnsoup-version "nnsoup 0.0"
|
||||
"nnsoup version.")
|
||||
|
||||
(defvoo nnsoup-status-string "")
|
||||
(defvoo nnsoup-group-alist nil)
|
||||
(defvoo nnsoup-current-prefix 0)
|
||||
(defvoo nnsoup-replies-list nil)
|
||||
(defvoo nnsoup-buffers nil)
|
||||
(defvoo nnsoup-current-group nil)
|
||||
(defvoo nnsoup-group-alist-touched nil)
|
||||
(defvoo nnsoup-article-alist nil)
|
||||
|
||||
|
||||
|
||||
;;; Interface functions.
|
||||
|
||||
(nnoo-define-basics nnsoup)
|
||||
|
||||
(deffoo nnsoup-retrieve-headers (sequence &optional group server fetch-old)
|
||||
(nnsoup-possibly-change-group group)
|
||||
(save-excursion
|
||||
(set-buffer nntp-server-buffer)
|
||||
(erase-buffer)
|
||||
(let ((areas (cddr (assoc nnsoup-current-group nnsoup-group-alist)))
|
||||
(articles sequence)
|
||||
(use-nov t)
|
||||
useful-areas this-area-seq msg-buf)
|
||||
(if (stringp (car sequence))
|
||||
;; We don't support fetching by Message-ID.
|
||||
'headers
|
||||
;; We go through all the areas and find which files the
|
||||
;; articles in SEQUENCE come from.
|
||||
(while (and areas sequence)
|
||||
;; Peel off areas that are below sequence.
|
||||
(while (and areas (< (cdaar areas) (car sequence)))
|
||||
(setq areas (cdr areas)))
|
||||
(when areas
|
||||
;; This is a useful area.
|
||||
(push (car areas) useful-areas)
|
||||
(setq this-area-seq nil)
|
||||
;; We take note whether this MSG has a corresponding IDX
|
||||
;; for later use.
|
||||
(when (or (= (gnus-soup-encoding-index
|
||||
(gnus-soup-area-encoding (nth 1 (car areas)))) ?n)
|
||||
(not (file-exists-p
|
||||
(nnsoup-file
|
||||
(gnus-soup-area-prefix (nth 1 (car areas)))))))
|
||||
(setq use-nov nil))
|
||||
;; We assign the portion of `sequence' that is relevant to
|
||||
;; this MSG packet to this packet.
|
||||
(while (and sequence (<= (car sequence) (cdaar areas)))
|
||||
(push (car sequence) this-area-seq)
|
||||
(setq sequence (cdr sequence)))
|
||||
(setcar useful-areas (cons (nreverse this-area-seq)
|
||||
(car useful-areas)))))
|
||||
|
||||
;; We now have a list of article numbers and corresponding
|
||||
;; areas.
|
||||
(setq useful-areas (nreverse useful-areas))
|
||||
|
||||
;; Two different approaches depending on whether all the MSG
|
||||
;; files have corresponding IDX files. If they all do, we
|
||||
;; simply return the relevant IDX files and let Gnus sort out
|
||||
;; what lines are relevant. If some of the IDX files are
|
||||
;; missing, we must return HEADs for all the articles.
|
||||
(if use-nov
|
||||
;; We have IDX files for all areas.
|
||||
(progn
|
||||
(while useful-areas
|
||||
(goto-char (point-max))
|
||||
(let ((b (point))
|
||||
(number (car (nth 1 (car useful-areas))))
|
||||
(index-buffer (nnsoup-index-buffer
|
||||
(gnus-soup-area-prefix
|
||||
(nth 2 (car useful-areas))))))
|
||||
(when index-buffer
|
||||
(insert-buffer-substring index-buffer)
|
||||
(goto-char b)
|
||||
;; We have to remove the index number entires and
|
||||
;; insert article numbers instead.
|
||||
(while (looking-at "[0-9]+")
|
||||
(replace-match (int-to-string number) t t)
|
||||
(incf number)
|
||||
(forward-line 1))))
|
||||
(setq useful-areas (cdr useful-areas)))
|
||||
'nov)
|
||||
;; We insert HEADs.
|
||||
(while useful-areas
|
||||
(setq articles (caar useful-areas)
|
||||
useful-areas (cdr useful-areas))
|
||||
(while articles
|
||||
(when (setq msg-buf
|
||||
(nnsoup-narrow-to-article
|
||||
(car articles) (cdar useful-areas) 'head))
|
||||
(goto-char (point-max))
|
||||
(insert (format "221 %d Article retrieved.\n" (car articles)))
|
||||
(insert-buffer-substring msg-buf)
|
||||
(goto-char (point-max))
|
||||
(insert ".\n"))
|
||||
(setq articles (cdr articles))))
|
||||
|
||||
(nnheader-fold-continuation-lines)
|
||||
'headers)))))
|
||||
|
||||
(deffoo nnsoup-open-server (server &optional defs)
|
||||
(nnoo-change-server 'nnsoup server defs)
|
||||
(when (not (file-exists-p nnsoup-directory))
|
||||
(condition-case ()
|
||||
(make-directory nnsoup-directory t)
|
||||
(error t)))
|
||||
(cond
|
||||
((not (file-exists-p nnsoup-directory))
|
||||
(nnsoup-close-server)
|
||||
(nnheader-report 'nnsoup "Couldn't create directory: %s" nnsoup-directory))
|
||||
((not (file-directory-p (file-truename nnsoup-directory)))
|
||||
(nnsoup-close-server)
|
||||
(nnheader-report 'nnsoup "Not a directory: %s" nnsoup-directory))
|
||||
(t
|
||||
(nnsoup-read-active-file)
|
||||
(nnheader-report 'nnsoup "Opened server %s using directory %s"
|
||||
server nnsoup-directory)
|
||||
t)))
|
||||
|
||||
(deffoo nnsoup-request-close ()
|
||||
(nnsoup-write-active-file)
|
||||
(nnsoup-write-replies)
|
||||
(gnus-soup-save-areas)
|
||||
;; Kill all nnsoup buffers.
|
||||
(let (buffer)
|
||||
(while nnsoup-buffers
|
||||
(setq buffer (cdr (pop nnsoup-buffers)))
|
||||
(and buffer
|
||||
(buffer-name buffer)
|
||||
(kill-buffer buffer))))
|
||||
(setq nnsoup-group-alist nil
|
||||
nnsoup-group-alist-touched nil
|
||||
nnsoup-current-group nil
|
||||
nnsoup-replies-list nil)
|
||||
(nnoo-close-server 'nnoo)
|
||||
t)
|
||||
|
||||
(deffoo nnsoup-request-article (id &optional newsgroup server buffer)
|
||||
(nnsoup-possibly-change-group newsgroup)
|
||||
(let (buf)
|
||||
(save-excursion
|
||||
(set-buffer (or buffer nntp-server-buffer))
|
||||
(erase-buffer)
|
||||
(when (and (not (stringp id))
|
||||
(setq buf (nnsoup-narrow-to-article id)))
|
||||
(insert-buffer-substring buf)
|
||||
t))))
|
||||
|
||||
(deffoo nnsoup-request-group (group &optional server dont-check)
|
||||
(nnsoup-possibly-change-group group)
|
||||
(if dont-check
|
||||
t
|
||||
(let ((active (cadr (assoc group nnsoup-group-alist))))
|
||||
(if (not active)
|
||||
(nnheader-report 'nnsoup "No such group: %s" group)
|
||||
(nnheader-insert
|
||||
"211 %d %d %d %s\n"
|
||||
(max (1+ (- (cdr active) (car active))) 0)
|
||||
(car active) (cdr active) group)))))
|
||||
|
||||
(deffoo nnsoup-request-type (group &optional article)
|
||||
(nnsoup-possibly-change-group group)
|
||||
;; Try to guess the type based on the first articl ein the group.
|
||||
(when (not article)
|
||||
(setq article
|
||||
(cdaar (cddr (assoc group nnsoup-group-alist)))))
|
||||
(if (not article)
|
||||
'unknown
|
||||
(let ((kind (gnus-soup-encoding-kind
|
||||
(gnus-soup-area-encoding
|
||||
(nth 1 (nnsoup-article-to-area
|
||||
article nnsoup-current-group))))))
|
||||
(cond ((= kind ?m) 'mail)
|
||||
((= kind ?n) 'news)
|
||||
(t 'unknown)))))
|
||||
|
||||
(deffoo nnsoup-close-group (group &optional server)
|
||||
;; Kill all nnsoup buffers.
|
||||
(let ((buffers nnsoup-buffers)
|
||||
elem)
|
||||
(while buffers
|
||||
(when (equal (car (setq elem (pop buffers))) group)
|
||||
(setq nnsoup-buffers (delq elem nnsoup-buffers))
|
||||
(and (cdr elem) (buffer-name (cdr elem))
|
||||
(kill-buffer (cdr elem))))))
|
||||
t)
|
||||
|
||||
(deffoo nnsoup-request-list (&optional server)
|
||||
(save-excursion
|
||||
(set-buffer nntp-server-buffer)
|
||||
(erase-buffer)
|
||||
(unless nnsoup-group-alist
|
||||
(nnsoup-read-active-file))
|
||||
(let ((alist nnsoup-group-alist)
|
||||
(standard-output (current-buffer))
|
||||
entry)
|
||||
(while (setq entry (pop alist))
|
||||
(insert (car entry) " ")
|
||||
(princ (cdadr entry))
|
||||
(insert " ")
|
||||
(princ (caadr entry))
|
||||
(insert " y\n"))
|
||||
t)))
|
||||
|
||||
(deffoo nnsoup-request-scan (group &optional server)
|
||||
(nnsoup-unpack-packets))
|
||||
|
||||
(deffoo nnsoup-request-newgroups (date &optional server)
|
||||
(nnsoup-request-list))
|
||||
|
||||
(deffoo nnsoup-request-list-newsgroups (&optional server)
|
||||
nil)
|
||||
|
||||
(deffoo nnsoup-request-post (&optional server)
|
||||
(nnsoup-store-reply "news")
|
||||
t)
|
||||
|
||||
(deffoo nnsoup-request-mail (&optional server)
|
||||
(nnsoup-store-reply "mail")
|
||||
t)
|
||||
|
||||
(deffoo nnsoup-request-expire-articles (articles group &optional server force)
|
||||
(nnsoup-possibly-change-group group)
|
||||
(let* ((total-infolist (assoc group nnsoup-group-alist))
|
||||
(active (cadr total-infolist))
|
||||
(infolist (cddr total-infolist))
|
||||
info range-list mod-time prefix)
|
||||
(while infolist
|
||||
(setq info (pop infolist)
|
||||
range-list (gnus-uncompress-range (car info))
|
||||
prefix (gnus-soup-area-prefix (nth 1 info)))
|
||||
(when ;; All the articles in this file are marked for expiry.
|
||||
(and (or (setq mod-time (nth 5 (file-attributes
|
||||
(nnsoup-file prefix))))
|
||||
(setq mod-time (nth 5 (file-attributes
|
||||
(nnsoup-file prefix t)))))
|
||||
(gnus-sublist-p articles range-list)
|
||||
;; This file is old enough.
|
||||
(nnmail-expired-article-p group mod-time force))
|
||||
;; Ok, we delete this file.
|
||||
(when (ignore-errors
|
||||
(nnheader-message
|
||||
5 "Deleting %s in group %s..." (nnsoup-file prefix)
|
||||
group)
|
||||
(when (file-exists-p (nnsoup-file prefix))
|
||||
(delete-file (nnsoup-file prefix)))
|
||||
(nnheader-message
|
||||
5 "Deleting %s in group %s..." (nnsoup-file prefix t)
|
||||
group)
|
||||
(when (file-exists-p (nnsoup-file prefix t))
|
||||
(delete-file (nnsoup-file prefix t)))
|
||||
t)
|
||||
(setcdr (cdr total-infolist) (delq info (cddr total-infolist)))
|
||||
(setq articles (gnus-sorted-complement articles range-list))))
|
||||
(when (not mod-time)
|
||||
(setcdr (cdr total-infolist) (delq info (cddr total-infolist)))))
|
||||
(if (cddr total-infolist)
|
||||
(setcar active (caaadr (cdr total-infolist)))
|
||||
(setcar active (1+ (cdr active))))
|
||||
(nnsoup-write-active-file t)
|
||||
;; Return the articles that weren't expired.
|
||||
articles))
|
||||
|
||||
|
||||
;;; Internal functions
|
||||
|
||||
(defun nnsoup-possibly-change-group (group &optional force)
|
||||
(when (and group
|
||||
(not (equal nnsoup-current-group group)))
|
||||
(setq nnsoup-article-alist nil)
|
||||
(setq nnsoup-current-group group))
|
||||
t)
|
||||
|
||||
(defun nnsoup-read-active-file ()
|
||||
(setq nnsoup-group-alist nil)
|
||||
(when (file-exists-p nnsoup-active-file)
|
||||
(ignore-errors
|
||||
(load nnsoup-active-file t t t))
|
||||
;; Be backwards compatible.
|
||||
(when (and nnsoup-group-alist
|
||||
(not (atom (caadar nnsoup-group-alist))))
|
||||
(let ((alist nnsoup-group-alist)
|
||||
entry e min max)
|
||||
(while (setq e (cdr (setq entry (pop alist))))
|
||||
(setq min (caaar e))
|
||||
(while (cdr e)
|
||||
(setq e (cdr e)))
|
||||
(setq max (cdaar e))
|
||||
(setcdr entry (cons (cons min max) (cdr entry)))))
|
||||
(setq nnsoup-group-alist-touched t))
|
||||
nnsoup-group-alist))
|
||||
|
||||
(defun nnsoup-write-active-file (&optional force)
|
||||
(when (and nnsoup-group-alist
|
||||
(or force
|
||||
nnsoup-group-alist-touched))
|
||||
(setq nnsoup-group-alist-touched nil)
|
||||
(nnheader-temp-write nnsoup-active-file
|
||||
(gnus-prin1 `(setq nnsoup-group-alist ',nnsoup-group-alist))
|
||||
(insert "\n")
|
||||
(gnus-prin1 `(setq nnsoup-current-prefix ,nnsoup-current-prefix))
|
||||
(insert "\n"))))
|
||||
|
||||
(defun nnsoup-next-prefix ()
|
||||
"Return the next free prefix."
|
||||
(let (prefix)
|
||||
(while (or (file-exists-p
|
||||
(nnsoup-file (setq prefix (int-to-string
|
||||
nnsoup-current-prefix))))
|
||||
(file-exists-p (nnsoup-file prefix t)))
|
||||
(incf nnsoup-current-prefix))
|
||||
(incf nnsoup-current-prefix)
|
||||
prefix))
|
||||
|
||||
(defun nnsoup-file-name (dir file)
|
||||
"Return the full path of FILE (in any case) in DIR."
|
||||
(let* ((case-fold-search t)
|
||||
(files (directory-files dir t))
|
||||
(regexp (concat (regexp-quote file) "$")))
|
||||
(car (delq nil
|
||||
(mapcar
|
||||
(lambda (file)
|
||||
(if (string-match regexp file)
|
||||
file
|
||||
nil))
|
||||
files)))))
|
||||
|
||||
(defun nnsoup-read-areas ()
|
||||
(let ((areas-file (nnsoup-file-name nnsoup-tmp-directory "areas")))
|
||||
(when areas-file
|
||||
(save-excursion
|
||||
(set-buffer nntp-server-buffer)
|
||||
(let ((areas (gnus-soup-parse-areas areas-file))
|
||||
entry number area lnum cur-prefix file)
|
||||
;; Go through all areas in the new AREAS file.
|
||||
(while (setq area (pop areas))
|
||||
;; Change the name to the permanent name and move the files.
|
||||
(setq cur-prefix (nnsoup-next-prefix))
|
||||
(message "Incorporating file %s..." cur-prefix)
|
||||
(when (file-exists-p
|
||||
(setq file (concat nnsoup-tmp-directory
|
||||
(gnus-soup-area-prefix area) ".IDX")))
|
||||
(rename-file file (nnsoup-file cur-prefix)))
|
||||
(when (file-exists-p
|
||||
(setq file (concat nnsoup-tmp-directory
|
||||
(gnus-soup-area-prefix area) ".MSG")))
|
||||
(rename-file file (nnsoup-file cur-prefix t))
|
||||
(gnus-soup-set-area-prefix area cur-prefix)
|
||||
;; Find the number of new articles in this area.
|
||||
(setq number (nnsoup-number-of-articles area))
|
||||
(if (not (setq entry (assoc (gnus-soup-area-name area)
|
||||
nnsoup-group-alist)))
|
||||
;; If this is a new area (group), we just add this info to
|
||||
;; the group alist.
|
||||
(push (list (gnus-soup-area-name area)
|
||||
(cons 1 number)
|
||||
(list (cons 1 number) area))
|
||||
nnsoup-group-alist)
|
||||
;; There are already articles in this group, so we add this
|
||||
;; info to the end of the entry.
|
||||
(nconc entry (list (list (cons (1+ (setq lnum (cdadr entry)))
|
||||
(+ lnum number))
|
||||
area)))
|
||||
(setcdr (cadr entry) (+ lnum number))))))
|
||||
(nnsoup-write-active-file t)
|
||||
(delete-file areas-file)))))
|
||||
|
||||
(defun nnsoup-number-of-articles (area)
|
||||
(save-excursion
|
||||
(cond
|
||||
;; If the number is in the area info, we just return it.
|
||||
((gnus-soup-area-number area)
|
||||
(gnus-soup-area-number area))
|
||||
;; If there is an index file, we just count the lines.
|
||||
((/= (gnus-soup-encoding-index (gnus-soup-area-encoding area)) ?n)
|
||||
(set-buffer (nnsoup-index-buffer (gnus-soup-area-prefix area)))
|
||||
(count-lines (point-min) (point-max)))
|
||||
;; We do it the hard way - re-searching through the message
|
||||
;; buffer.
|
||||
(t
|
||||
(set-buffer (nnsoup-message-buffer (gnus-soup-area-prefix area)))
|
||||
(unless (assoc (gnus-soup-area-prefix area) nnsoup-article-alist)
|
||||
(nnsoup-dissect-buffer area))
|
||||
(length (cdr (assoc (gnus-soup-area-prefix area)
|
||||
nnsoup-article-alist)))))))
|
||||
|
||||
(defun nnsoup-dissect-buffer (area)
|
||||
(let ((mbox-delim (concat "^" message-unix-mail-delimiter))
|
||||
(format (gnus-soup-encoding-format (gnus-soup-area-encoding area)))
|
||||
(i 0)
|
||||
alist len)
|
||||
(goto-char (point-min))
|
||||
(cond
|
||||
;; rnews batch format
|
||||
((= format ?n)
|
||||
(while (looking-at "^#! *rnews \\(+[0-9]+\\) *$")
|
||||
(forward-line 1)
|
||||
(push (list
|
||||
(incf i) (point)
|
||||
(progn
|
||||
(forward-char (string-to-number (match-string 1)))
|
||||
(point)))
|
||||
alist)))
|
||||
;; Unix mbox format
|
||||
((= format ?m)
|
||||
(while (looking-at mbox-delim)
|
||||
(forward-line 1)
|
||||
(push (list
|
||||
(incf i) (point)
|
||||
(progn
|
||||
(if (re-search-forward mbox-delim nil t)
|
||||
(beginning-of-line)
|
||||
(goto-char (point-max)))
|
||||
(point)))
|
||||
alist)))
|
||||
;; MMDF format
|
||||
((= format ?M)
|
||||
(while (looking-at "\^A\^A\^A\^A\n")
|
||||
(forward-line 1)
|
||||
(push (list
|
||||
(incf i) (point)
|
||||
(progn
|
||||
(if (search-forward "\n\^A\^A\^A\^A\n" nil t)
|
||||
(beginning-of-line)
|
||||
(goto-char (point-max)))
|
||||
(point)))
|
||||
alist)))
|
||||
;; Binary format
|
||||
((or (= format ?B) (= format ?b))
|
||||
(while (not (eobp))
|
||||
(setq len (+ (* (char-after (point)) (expt 2.0 24))
|
||||
(* (char-after (+ (point) 1)) (expt 2 16))
|
||||
(* (char-after (+ (point) 2)) (expt 2 8))
|
||||
(char-after (+ (point) 3))))
|
||||
(push (list
|
||||
(incf i) (+ (point) 4)
|
||||
(progn
|
||||
(forward-char (floor (+ len 4)))
|
||||
(point)))
|
||||
alist)))
|
||||
(t
|
||||
(error "Unknown format: %c" format)))
|
||||
(push (cons (gnus-soup-area-prefix area) alist) nnsoup-article-alist)))
|
||||
|
||||
(defun nnsoup-index-buffer (prefix &optional message)
|
||||
(let* ((file (concat prefix (if message ".MSG" ".IDX")))
|
||||
(buffer-name (concat " *nnsoup " file "*")))
|
||||
(or (get-buffer buffer-name) ; File already loaded.
|
||||
(when (file-exists-p (concat nnsoup-directory file))
|
||||
(save-excursion ; Load the file.
|
||||
(set-buffer (get-buffer-create buffer-name))
|
||||
(buffer-disable-undo (current-buffer))
|
||||
(push (cons nnsoup-current-group (current-buffer)) nnsoup-buffers)
|
||||
(nnheader-insert-file-contents (concat nnsoup-directory file))
|
||||
(current-buffer))))))
|
||||
|
||||
(defun nnsoup-file (prefix &optional message)
|
||||
(expand-file-name
|
||||
(concat nnsoup-directory prefix (if message ".MSG" ".IDX"))))
|
||||
|
||||
(defun nnsoup-message-buffer (prefix)
|
||||
(nnsoup-index-buffer prefix 'msg))
|
||||
|
||||
(defun nnsoup-unpack-packets ()
|
||||
"Unpack all packets in `nnsoup-packet-directory'."
|
||||
(let ((packets (directory-files
|
||||
nnsoup-packet-directory t nnsoup-packet-regexp))
|
||||
packet)
|
||||
(while (setq packet (pop packets))
|
||||
(message "nnsoup: unpacking %s..." packet)
|
||||
(if (not (gnus-soup-unpack-packet
|
||||
nnsoup-tmp-directory nnsoup-unpacker packet))
|
||||
(message "Couldn't unpack %s" packet)
|
||||
(delete-file packet)
|
||||
(nnsoup-read-areas)
|
||||
(message "Unpacking...done")))))
|
||||
|
||||
(defun nnsoup-narrow-to-article (article &optional area head)
|
||||
(let* ((area (or area (nnsoup-article-to-area article nnsoup-current-group)))
|
||||
(prefix (and area (gnus-soup-area-prefix (nth 1 area))))
|
||||
(msg-buf (and prefix (nnsoup-index-buffer prefix 'msg)))
|
||||
beg end)
|
||||
(when area
|
||||
(save-excursion
|
||||
(cond
|
||||
;; There is no MSG file.
|
||||
((null msg-buf)
|
||||
nil)
|
||||
;; We use the index file to find out where the article
|
||||
;; begins and ends.
|
||||
((and (= (gnus-soup-encoding-index
|
||||
(gnus-soup-area-encoding (nth 1 area)))
|
||||
?c)
|
||||
(file-exists-p (nnsoup-file prefix)))
|
||||
(set-buffer (nnsoup-index-buffer prefix))
|
||||
(widen)
|
||||
(goto-char (point-min))
|
||||
(forward-line (- article (caar area)))
|
||||
(setq beg (read (current-buffer)))
|
||||
(forward-line 1)
|
||||
(if (looking-at "[0-9]+")
|
||||
(progn
|
||||
(setq end (read (current-buffer)))
|
||||
(set-buffer msg-buf)
|
||||
(widen)
|
||||
(let ((format (gnus-soup-encoding-format
|
||||
(gnus-soup-area-encoding (nth 1 area)))))
|
||||
(goto-char end)
|
||||
(when (or (= format ?n) (= format ?m))
|
||||
(setq end (progn (forward-line -1) (point))))))
|
||||
(set-buffer msg-buf))
|
||||
(widen)
|
||||
(narrow-to-region beg (or end (point-max))))
|
||||
(t
|
||||
(set-buffer msg-buf)
|
||||
(widen)
|
||||
(unless (assoc (gnus-soup-area-prefix (nth 1 area))
|
||||
nnsoup-article-alist)
|
||||
(nnsoup-dissect-buffer (nth 1 area)))
|
||||
(let ((entry (assq article (cdr (assoc (gnus-soup-area-prefix
|
||||
(nth 1 area))
|
||||
nnsoup-article-alist)))))
|
||||
(when entry
|
||||
(narrow-to-region (cadr entry) (caddr entry))))))
|
||||
(goto-char (point-min))
|
||||
(if (not head)
|
||||
()
|
||||
(narrow-to-region
|
||||
(point-min)
|
||||
(if (search-forward "\n\n" nil t)
|
||||
(1- (point))
|
||||
(point-max))))
|
||||
msg-buf))))
|
||||
|
||||
;;;###autoload
|
||||
(defun nnsoup-pack-replies ()
|
||||
"Make an outbound package of SOUP replies."
|
||||
(interactive)
|
||||
(unless (file-exists-p nnsoup-replies-directory)
|
||||
(message "No such directory: %s" nnsoup-replies-directory))
|
||||
;; Write all data buffers.
|
||||
(gnus-soup-save-areas)
|
||||
;; Write the active file.
|
||||
(nnsoup-write-active-file)
|
||||
;; Write the REPLIES file.
|
||||
(nnsoup-write-replies)
|
||||
;; Check whether there is anything here.
|
||||
(when (null (directory-files nnsoup-replies-directory nil "\\.MSG$"))
|
||||
(error "No files to pack."))
|
||||
;; Pack all these files into a SOUP packet.
|
||||
(gnus-soup-pack nnsoup-replies-directory nnsoup-packer))
|
||||
|
||||
(defun nnsoup-write-replies ()
|
||||
"Write the REPLIES file."
|
||||
(when nnsoup-replies-list
|
||||
(gnus-soup-write-replies nnsoup-replies-directory nnsoup-replies-list)
|
||||
(setq nnsoup-replies-list nil)))
|
||||
|
||||
(defun nnsoup-article-to-area (article group)
|
||||
"Return the area that ARTICLE in GROUP is located in."
|
||||
(let ((areas (cddr (assoc group nnsoup-group-alist))))
|
||||
(while (and areas (< (cdaar areas) article))
|
||||
(setq areas (cdr areas)))
|
||||
(and areas (car areas))))
|
||||
|
||||
(defvar nnsoup-old-functions
|
||||
(list message-send-mail-function message-send-news-function))
|
||||
|
||||
;;;###autoload
|
||||
(defun nnsoup-set-variables ()
|
||||
"Use the SOUP methods for posting news and mailing mail."
|
||||
(interactive)
|
||||
(setq message-send-news-function 'nnsoup-request-post)
|
||||
(setq message-send-mail-function 'nnsoup-request-mail))
|
||||
|
||||
;;;###autoload
|
||||
(defun nnsoup-revert-variables ()
|
||||
"Revert posting and mailing methods to the standard Emacs methods."
|
||||
(interactive)
|
||||
(setq message-send-mail-function (car nnsoup-old-functions))
|
||||
(setq message-send-news-function (cadr nnsoup-old-functions)))
|
||||
|
||||
(defun nnsoup-store-reply (kind)
|
||||
;; Mostly stolen from `message.el'.
|
||||
(require 'mail-utils)
|
||||
(let ((tembuf (generate-new-buffer " message temp"))
|
||||
(case-fold-search nil)
|
||||
delimline
|
||||
(mailbuf (current-buffer)))
|
||||
(unwind-protect
|
||||
(save-excursion
|
||||
(save-restriction
|
||||
(message-narrow-to-headers)
|
||||
(if (equal kind "mail")
|
||||
(message-generate-headers message-required-mail-headers)
|
||||
(message-generate-headers message-required-news-headers)))
|
||||
(set-buffer tembuf)
|
||||
(erase-buffer)
|
||||
(insert-buffer-substring mailbuf)
|
||||
;; Remove some headers.
|
||||
(save-restriction
|
||||
(message-narrow-to-headers)
|
||||
;; Remove some headers.
|
||||
(message-remove-header message-ignored-mail-headers t))
|
||||
(goto-char (point-max))
|
||||
;; require one newline at the end.
|
||||
(or (= (preceding-char) ?\n)
|
||||
(insert ?\n))
|
||||
(let ((case-fold-search t))
|
||||
;; Change header-delimiter to be what sendmail expects.
|
||||
(goto-char (point-min))
|
||||
(re-search-forward
|
||||
(concat "^" (regexp-quote mail-header-separator) "\n"))
|
||||
(replace-match "\n")
|
||||
(backward-char 1)
|
||||
(setq delimline (point-marker))
|
||||
;; Insert an extra newline if we need it to work around
|
||||
;; Sun's bug that swallows newlines.
|
||||
(goto-char (1+ delimline))
|
||||
(when (eval message-mailer-swallows-blank-line)
|
||||
(newline))
|
||||
(let ((msg-buf
|
||||
(gnus-soup-store
|
||||
nnsoup-replies-directory
|
||||
(nnsoup-kind-to-prefix kind) nil nnsoup-replies-format-type
|
||||
nnsoup-replies-index-type))
|
||||
(num 0))
|
||||
(when (and msg-buf (bufferp msg-buf))
|
||||
(save-excursion
|
||||
(set-buffer msg-buf)
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward "^#! *rnews" nil t)
|
||||
(incf num)))
|
||||
(message "Stored %d messages" num)))
|
||||
(nnsoup-write-replies)
|
||||
(kill-buffer tembuf))))))
|
||||
|
||||
(defun nnsoup-kind-to-prefix (kind)
|
||||
(unless nnsoup-replies-list
|
||||
(setq nnsoup-replies-list
|
||||
(gnus-soup-parse-replies
|
||||
(concat nnsoup-replies-directory "REPLIES"))))
|
||||
(let ((replies nnsoup-replies-list))
|
||||
(while (and replies
|
||||
(not (string= kind (gnus-soup-reply-kind (car replies)))))
|
||||
(setq replies (cdr replies)))
|
||||
(if replies
|
||||
(gnus-soup-reply-prefix (car replies))
|
||||
(push (vector (gnus-soup-unique-prefix nnsoup-replies-directory)
|
||||
kind
|
||||
(format "%c%c%c"
|
||||
nnsoup-replies-format-type
|
||||
nnsoup-replies-index-type
|
||||
(if (string= kind "news")
|
||||
?n ?m)))
|
||||
nnsoup-replies-list)
|
||||
(gnus-soup-reply-prefix (car nnsoup-replies-list)))))
|
||||
|
||||
(defun nnsoup-make-active ()
|
||||
"(Re-)create the SOUP active file."
|
||||
(interactive)
|
||||
(let ((files (sort (directory-files nnsoup-directory t "IDX$")
|
||||
(lambda (f1 f2)
|
||||
(< (progn (string-match "/\\([0-9]+\\)\\." f1)
|
||||
(string-to-int (match-string 1 f1)))
|
||||
(progn (string-match "/\\([0-9]+\\)\\." f2)
|
||||
(string-to-int (match-string 1 f2)))))))
|
||||
active group lines ident elem min)
|
||||
(set-buffer (get-buffer-create " *nnsoup work*"))
|
||||
(buffer-disable-undo (current-buffer))
|
||||
(while files
|
||||
(message "Doing %s..." (car files))
|
||||
(erase-buffer)
|
||||
(nnheader-insert-file-contents (car files))
|
||||
(goto-char (point-min))
|
||||
(if (not (re-search-forward "^[^\t]*\t[^\t]*\t[^\t]*\t[^\t]*\t[^\t]*\t[^\t]*\t[^\t]*\t[^\t]*\t *\\(Xref: \\)? *[^ ]* \\([^ ]+\\):[0-9]" nil t))
|
||||
(setq group "unknown")
|
||||
(setq group (match-string 2)))
|
||||
(setq lines (count-lines (point-min) (point-max)))
|
||||
(setq ident (progn (string-match
|
||||
"/\\([0-9]+\\)\\." (car files))
|
||||
(substring
|
||||
(car files) (match-beginning 1)
|
||||
(match-end 1))))
|
||||
(if (not (setq elem (assoc group active)))
|
||||
(push (list group (cons 1 lines)
|
||||
(list (cons 1 lines)
|
||||
(vector ident group "ncm" "" lines)))
|
||||
active)
|
||||
(nconc elem
|
||||
(list
|
||||
(list (cons (1+ (setq min (cdadr elem)))
|
||||
(+ min lines))
|
||||
(vector ident group "ncm" "" lines))))
|
||||
(setcdr (cadr elem) (+ min lines)))
|
||||
(setq files (cdr files)))
|
||||
(message "")
|
||||
(setq nnsoup-group-alist active)
|
||||
(nnsoup-write-active-file t)))
|
||||
|
||||
(defun nnsoup-delete-unreferenced-message-files ()
|
||||
"Delete any *.MSG and *.IDX files that aren't known by nnsoup."
|
||||
(interactive)
|
||||
(let* ((known (apply 'nconc (mapcar
|
||||
(lambda (ga)
|
||||
(mapcar
|
||||
(lambda (area)
|
||||
(gnus-soup-area-prefix (cadr area)))
|
||||
(cddr ga)))
|
||||
nnsoup-group-alist)))
|
||||
(regexp "\\.MSG$\\|\\.IDX$")
|
||||
(files (directory-files nnsoup-directory nil regexp))
|
||||
non-files file)
|
||||
;; Find all files that aren't known by nnsoup.
|
||||
(while (setq file (pop files))
|
||||
(string-match regexp file)
|
||||
(unless (member (substring file 0 (match-beginning 0)) known)
|
||||
(push file non-files)))
|
||||
;; Sort and delete the files.
|
||||
(setq non-files (sort non-files 'string<))
|
||||
(map-y-or-n-p "Delete file %s? "
|
||||
(lambda (file) (delete-file (concat nnsoup-directory file)))
|
||||
non-files)))
|
||||
|
||||
(provide 'nnsoup)
|
||||
|
||||
;;; nnsoup.el ends here
|
||||
463
lisp/gnus/nnspool.el
Normal file
463
lisp/gnus/nnspool.el
Normal file
|
|
@ -0,0 +1,463 @@
|
|||
;;; nnspool.el --- spool access for GNU Emacs
|
||||
;; Copyright (C) 1988,89,90,93,94,95,96,97 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
|
||||
;; Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
|
||||
;; Keywords: news
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'nnheader)
|
||||
(require 'nntp)
|
||||
(require 'timezone)
|
||||
(require 'nnoo)
|
||||
(eval-when-compile (require 'cl))
|
||||
|
||||
(nnoo-declare nnspool)
|
||||
|
||||
(defvoo nnspool-inews-program news-inews-program
|
||||
"Program to post news.
|
||||
This is most commonly `inews' or `injnews'.")
|
||||
|
||||
(defvoo nnspool-inews-switches '("-h" "-S")
|
||||
"Switches for nnspool-request-post to pass to `inews' for posting news.
|
||||
If you are using Cnews, you probably should set this variable to nil.")
|
||||
|
||||
(defvoo nnspool-spool-directory (file-name-as-directory news-path)
|
||||
"Local news spool directory.")
|
||||
|
||||
(defvoo nnspool-nov-directory (concat nnspool-spool-directory "over.view/")
|
||||
"Local news nov directory.")
|
||||
|
||||
(defvoo nnspool-lib-dir "/usr/lib/news/"
|
||||
"Where the local news library files are stored.")
|
||||
|
||||
(defvoo nnspool-active-file (concat nnspool-lib-dir "active")
|
||||
"Local news active file.")
|
||||
|
||||
(defvoo nnspool-newsgroups-file (concat nnspool-lib-dir "newsgroups")
|
||||
"Local news newsgroups file.")
|
||||
|
||||
(defvoo nnspool-distributions-file (concat nnspool-lib-dir "distribs.pat")
|
||||
"Local news distributions file.")
|
||||
|
||||
(defvoo nnspool-history-file (concat nnspool-lib-dir "history")
|
||||
"Local news history file.")
|
||||
|
||||
(defvoo nnspool-active-times-file (concat nnspool-lib-dir "active.times")
|
||||
"Local news active date file.")
|
||||
|
||||
(defvoo nnspool-large-newsgroup 50
|
||||
"The number of the articles which indicates a large newsgroup.
|
||||
If the number of the articles is greater than the value, verbose
|
||||
messages will be shown to indicate the current status.")
|
||||
|
||||
(defvoo nnspool-nov-is-evil nil
|
||||
"Non-nil means that nnspool will never return NOV lines instead of headers.")
|
||||
|
||||
(defconst nnspool-sift-nov-with-sed nil
|
||||
"If non-nil, use sed to get the relevant portion from the overview file.
|
||||
If nil, nnspool will load the entire file into a buffer and process it
|
||||
there.")
|
||||
|
||||
(defvoo nnspool-rejected-article-hook nil
|
||||
"*A hook that will be run when an article has been rejected by the server.")
|
||||
|
||||
|
||||
|
||||
(defconst nnspool-version "nnspool 2.0"
|
||||
"Version numbers of this version of NNSPOOL.")
|
||||
|
||||
(defvoo nnspool-current-directory nil
|
||||
"Current news group directory.")
|
||||
|
||||
(defvoo nnspool-current-group nil)
|
||||
(defvoo nnspool-status-string "")
|
||||
|
||||
|
||||
;;; Interface functions.
|
||||
|
||||
(nnoo-define-basics nnspool)
|
||||
|
||||
(deffoo nnspool-retrieve-headers (articles &optional group server fetch-old)
|
||||
"Retrieve the headers of ARTICLES."
|
||||
(save-excursion
|
||||
(set-buffer nntp-server-buffer)
|
||||
(erase-buffer)
|
||||
(when (nnspool-possibly-change-directory group)
|
||||
(let* ((number (length articles))
|
||||
(count 0)
|
||||
(default-directory nnspool-current-directory)
|
||||
(do-message (and (numberp nnspool-large-newsgroup)
|
||||
(> number nnspool-large-newsgroup)))
|
||||
file beg article ag)
|
||||
(if (and (numberp (car articles))
|
||||
(nnspool-retrieve-headers-with-nov articles fetch-old))
|
||||
;; We successfully retrieved the NOV headers.
|
||||
'nov
|
||||
;; No NOV headers here, so we do it the hard way.
|
||||
(while (setq article (pop articles))
|
||||
(if (stringp article)
|
||||
;; This is a Message-ID.
|
||||
(setq ag (nnspool-find-id article)
|
||||
file (and ag (nnspool-article-pathname
|
||||
(car ag) (cdr ag)))
|
||||
article (cdr ag))
|
||||
;; This is an article in the current group.
|
||||
(setq file (int-to-string article)))
|
||||
;; Insert the head of the article.
|
||||
(when (and file
|
||||
(file-exists-p file))
|
||||
(insert "221 ")
|
||||
(princ article (current-buffer))
|
||||
(insert " Article retrieved.\n")
|
||||
(setq beg (point))
|
||||
(inline (nnheader-insert-head file))
|
||||
(goto-char beg)
|
||||
(search-forward "\n\n" nil t)
|
||||
(forward-char -1)
|
||||
(insert ".\n")
|
||||
(delete-region (point) (point-max)))
|
||||
|
||||
(and do-message
|
||||
(zerop (% (incf count) 20))
|
||||
(message "nnspool: Receiving headers... %d%%"
|
||||
(/ (* count 100) number))))
|
||||
|
||||
(when do-message
|
||||
(message "nnspool: Receiving headers...done"))
|
||||
|
||||
;; Fold continuation lines.
|
||||
(nnheader-fold-continuation-lines)
|
||||
'headers)))))
|
||||
|
||||
(deffoo nnspool-open-server (server &optional defs)
|
||||
(nnoo-change-server 'nnspool server defs)
|
||||
(cond
|
||||
((not (file-exists-p nnspool-spool-directory))
|
||||
(nnspool-close-server)
|
||||
(nnheader-report 'nnspool "Spool directory doesn't exist: %s"
|
||||
nnspool-spool-directory))
|
||||
((not (file-directory-p
|
||||
(directory-file-name
|
||||
(file-truename nnspool-spool-directory))))
|
||||
(nnspool-close-server)
|
||||
(nnheader-report 'nnspool "Not a directory: %s" nnspool-spool-directory))
|
||||
((not (file-exists-p nnspool-active-file))
|
||||
(nnheader-report 'nnspool "The active file doesn't exist: %s"
|
||||
nnspool-active-file))
|
||||
(t
|
||||
(nnheader-report 'nnspool "Opened server %s using directory %s"
|
||||
server nnspool-spool-directory)
|
||||
t)))
|
||||
|
||||
(deffoo nnspool-request-article (id &optional group server buffer)
|
||||
"Select article by message ID (or number)."
|
||||
(nnspool-possibly-change-directory group)
|
||||
(let ((nntp-server-buffer (or buffer nntp-server-buffer))
|
||||
file ag)
|
||||
(if (stringp id)
|
||||
;; This is a Message-ID.
|
||||
(when (setq ag (nnspool-find-id id))
|
||||
(setq file (nnspool-article-pathname (car ag) (cdr ag))))
|
||||
(setq file (nnspool-article-pathname nnspool-current-group id)))
|
||||
(and file
|
||||
(file-exists-p file)
|
||||
(not (file-directory-p file))
|
||||
(save-excursion (nnspool-find-file file))
|
||||
;; We return the article number and group name.
|
||||
(if (numberp id)
|
||||
(cons nnspool-current-group id)
|
||||
ag))))
|
||||
|
||||
(deffoo nnspool-request-body (id &optional group server)
|
||||
"Select article body by message ID (or number)."
|
||||
(nnspool-possibly-change-directory group)
|
||||
(let ((res (nnspool-request-article id)))
|
||||
(when res
|
||||
(save-excursion
|
||||
(set-buffer nntp-server-buffer)
|
||||
(goto-char (point-min))
|
||||
(when (search-forward "\n\n" nil t)
|
||||
(delete-region (point-min) (point)))
|
||||
res))))
|
||||
|
||||
(deffoo nnspool-request-head (id &optional group server)
|
||||
"Select article head by message ID (or number)."
|
||||
(nnspool-possibly-change-directory group)
|
||||
(let ((res (nnspool-request-article id)))
|
||||
(when res
|
||||
(save-excursion
|
||||
(set-buffer nntp-server-buffer)
|
||||
(goto-char (point-min))
|
||||
(when (search-forward "\n\n" nil t)
|
||||
(delete-region (1- (point)) (point-max)))
|
||||
(nnheader-fold-continuation-lines)))
|
||||
res))
|
||||
|
||||
(deffoo nnspool-request-group (group &optional server dont-check)
|
||||
"Select news GROUP."
|
||||
(let ((pathname (nnspool-article-pathname group))
|
||||
dir)
|
||||
(if (not (file-directory-p pathname))
|
||||
(nnheader-report
|
||||
'nnspool "Invalid group name (no such directory): %s" group)
|
||||
(setq nnspool-current-directory pathname)
|
||||
(nnheader-report 'nnspool "Selected group %s" group)
|
||||
(if dont-check
|
||||
(progn
|
||||
(nnheader-report 'nnspool "Selected group %s" group)
|
||||
t)
|
||||
;; Yes, completely empty spool directories *are* possible.
|
||||
;; Fix by Sudish Joseph <joseph@cis.ohio-state.edu>
|
||||
(when (setq dir (directory-files pathname nil "^[0-9]+$" t))
|
||||
(setq dir
|
||||
(sort (mapcar (lambda (name) (string-to-int name)) dir) '<)))
|
||||
(if dir
|
||||
(nnheader-insert
|
||||
"211 %d %d %d %s\n" (length dir) (car dir)
|
||||
(progn (while (cdr dir) (setq dir (cdr dir))) (car dir))
|
||||
group)
|
||||
(nnheader-report 'nnspool "Empty group %s" group)
|
||||
(nnheader-insert "211 0 0 0 %s\n" group))))))
|
||||
|
||||
(deffoo nnspool-request-type (group &optional article)
|
||||
'news)
|
||||
|
||||
(deffoo nnspool-close-group (group &optional server)
|
||||
t)
|
||||
|
||||
(deffoo nnspool-request-list (&optional server)
|
||||
"List active newsgroups."
|
||||
(save-excursion
|
||||
(or (nnspool-find-file nnspool-active-file)
|
||||
(nnheader-report 'nnspool (nnheader-file-error nnspool-active-file)))))
|
||||
|
||||
(deffoo nnspool-request-list-newsgroups (&optional server)
|
||||
"List newsgroups (defined in NNTP2)."
|
||||
(save-excursion
|
||||
(or (nnspool-find-file nnspool-newsgroups-file)
|
||||
(nnheader-report 'nnspool (nnheader-file-error
|
||||
nnspool-newsgroups-file)))))
|
||||
|
||||
(deffoo nnspool-request-list-distributions (&optional server)
|
||||
"List distributions (defined in NNTP2)."
|
||||
(save-excursion
|
||||
(or (nnspool-find-file nnspool-distributions-file)
|
||||
(nnheader-report 'nnspool (nnheader-file-error
|
||||
nnspool-distributions-file)))))
|
||||
|
||||
;; Suggested by Hallvard B Furuseth <h.b.furuseth@usit.uio.no>.
|
||||
(deffoo nnspool-request-newgroups (date &optional server)
|
||||
"List groups created after DATE."
|
||||
(if (nnspool-find-file nnspool-active-times-file)
|
||||
(save-excursion
|
||||
;; Find the last valid line.
|
||||
(goto-char (point-max))
|
||||
(while (and (not (looking-at
|
||||
"\\([^ ]+\\) +\\([0-9]+\\)[0-9][0-9][0-9] "))
|
||||
(zerop (forward-line -1))))
|
||||
(let ((seconds (nnspool-seconds-since-epoch date))
|
||||
groups)
|
||||
;; Go through lines and add the latest groups to a list.
|
||||
(while (and (looking-at "\\([^ ]+\\) +[0-9]+ ")
|
||||
(progn
|
||||
;; We insert a .0 to make the list reader
|
||||
;; interpret the number as a float. It is far
|
||||
;; too big to be stored in a lisp integer.
|
||||
(goto-char (1- (match-end 0)))
|
||||
(insert ".0")
|
||||
(> (progn
|
||||
(goto-char (match-end 1))
|
||||
(read (current-buffer)))
|
||||
seconds))
|
||||
(push (buffer-substring
|
||||
(match-beginning 1) (match-end 1))
|
||||
groups)
|
||||
(zerop (forward-line -1))))
|
||||
(erase-buffer)
|
||||
(while groups
|
||||
(insert (car groups) " 0 0 y\n")
|
||||
(setq groups (cdr groups))))
|
||||
t)
|
||||
nil))
|
||||
|
||||
(deffoo nnspool-request-post (&optional server)
|
||||
"Post a new news in current buffer."
|
||||
(save-excursion
|
||||
(let* ((process-connection-type nil) ; t bugs out on Solaris
|
||||
(inews-buffer (generate-new-buffer " *nnspool post*"))
|
||||
(proc
|
||||
(condition-case err
|
||||
(apply 'start-process "*nnspool inews*" inews-buffer
|
||||
nnspool-inews-program nnspool-inews-switches)
|
||||
(error
|
||||
(nnheader-report 'nnspool "inews error: %S" err)))))
|
||||
(if (not proc)
|
||||
;; The inews program failed.
|
||||
()
|
||||
(nnheader-report 'nnspool "")
|
||||
(set-process-sentinel proc 'nnspool-inews-sentinel)
|
||||
(process-send-region proc (point-min) (point-max))
|
||||
;; We slap a condition-case around this, because the process may
|
||||
;; have exited already...
|
||||
(ignore-errors
|
||||
(process-send-eof proc))
|
||||
t))))
|
||||
|
||||
|
||||
|
||||
;;; Internal functions.
|
||||
|
||||
(defun nnspool-inews-sentinel (proc status)
|
||||
(save-excursion
|
||||
(set-buffer (process-buffer proc))
|
||||
(goto-char (point-min))
|
||||
(if (or (zerop (buffer-size))
|
||||
(search-forward "spooled" nil t))
|
||||
(kill-buffer (current-buffer))
|
||||
;; Make status message by folding lines.
|
||||
(while (re-search-forward "[ \t\n]+" nil t)
|
||||
(replace-match " " t t))
|
||||
(nnheader-report 'nnspool "%s" (buffer-string))
|
||||
(message "nnspool: %s" nnspool-status-string)
|
||||
(ding)
|
||||
(run-hooks 'nnspool-rejected-article-hook))))
|
||||
|
||||
(defun nnspool-retrieve-headers-with-nov (articles &optional fetch-old)
|
||||
(if (or gnus-nov-is-evil nnspool-nov-is-evil)
|
||||
nil
|
||||
(let ((nov (nnheader-group-pathname
|
||||
nnspool-current-group nnspool-nov-directory ".overview"))
|
||||
(arts articles)
|
||||
last)
|
||||
(if (not (file-exists-p nov))
|
||||
()
|
||||
(save-excursion
|
||||
(set-buffer nntp-server-buffer)
|
||||
(erase-buffer)
|
||||
(if nnspool-sift-nov-with-sed
|
||||
(nnspool-sift-nov-with-sed articles nov)
|
||||
(nnheader-insert-file-contents nov)
|
||||
(if (and fetch-old
|
||||
(not (numberp fetch-old)))
|
||||
t ; We want all the headers.
|
||||
(ignore-errors
|
||||
;; Delete unwanted NOV lines.
|
||||
(nnheader-nov-delete-outside-range
|
||||
(if fetch-old (max 1 (- (car articles) fetch-old))
|
||||
(car articles))
|
||||
(car (last articles)))
|
||||
;; If the buffer is empty, this wasn't very successful.
|
||||
(unless (zerop (buffer-size))
|
||||
;; We check what the last article number was.
|
||||
;; The NOV file may be out of sync with the articles
|
||||
;; in the group.
|
||||
(forward-line -1)
|
||||
(setq last (read (current-buffer)))
|
||||
(if (= last (car articles))
|
||||
;; Yup, it's all there.
|
||||
t
|
||||
;; Perhaps not. We try to find the missing articles.
|
||||
(while (and arts
|
||||
(<= last (car arts)))
|
||||
(pop arts))
|
||||
;; The articles in `arts' are missing from the buffer.
|
||||
(while arts
|
||||
(nnspool-insert-nov-head (pop arts)))
|
||||
t))))))))))
|
||||
|
||||
(defun nnspool-insert-nov-head (article)
|
||||
"Read the head of ARTICLE, convert to NOV headers, and insert."
|
||||
(save-excursion
|
||||
(let ((cur (current-buffer))
|
||||
buf)
|
||||
(setq buf (nnheader-set-temp-buffer " *nnspool head*"))
|
||||
(when (nnheader-insert-head
|
||||
(nnspool-article-pathname nnspool-current-group article))
|
||||
(nnheader-insert-article-line article)
|
||||
(let ((headers (nnheader-parse-head)))
|
||||
(set-buffer cur)
|
||||
(goto-char (point-max))
|
||||
(nnheader-insert-nov headers)))
|
||||
(kill-buffer buf))))
|
||||
|
||||
(defun nnspool-sift-nov-with-sed (articles file)
|
||||
(let ((first (car articles))
|
||||
(last (progn (while (cdr articles) (setq articles (cdr articles)))
|
||||
(car articles))))
|
||||
(call-process "awk" nil t nil
|
||||
(format "BEGIN {firstmsg=%d; lastmsg=%d;}\n $1 >= firstmsg && $1 <= lastmsg {print;}"
|
||||
(1- first) (1+ last))
|
||||
file)))
|
||||
|
||||
;; Fixed by fdc@cliwe.ping.de (Frank D. Cringle).
|
||||
;; Find out what group an article identified by a Message-ID is in.
|
||||
(defun nnspool-find-id (id)
|
||||
(save-excursion
|
||||
(set-buffer (get-buffer-create " *nnspool work*"))
|
||||
(buffer-disable-undo (current-buffer))
|
||||
(erase-buffer)
|
||||
(ignore-errors
|
||||
(call-process "grep" nil t nil (regexp-quote id) nnspool-history-file))
|
||||
(goto-char (point-min))
|
||||
(prog1
|
||||
(when (looking-at "<[^>]+>[ \t]+[-0-9~]+[ \t]+\\([^ /\t\n]+\\)/\\([0-9]+\\)[ \t\n]")
|
||||
(cons (match-string 1) (string-to-int (match-string 2))))
|
||||
(kill-buffer (current-buffer)))))
|
||||
|
||||
(defun nnspool-find-file (file)
|
||||
"Insert FILE in server buffer safely."
|
||||
(set-buffer nntp-server-buffer)
|
||||
(erase-buffer)
|
||||
(condition-case ()
|
||||
(progn (nnheader-insert-file-contents file) t)
|
||||
(file-error nil)))
|
||||
|
||||
(defun nnspool-possibly-change-directory (group)
|
||||
(if (not group)
|
||||
t
|
||||
(let ((pathname (nnspool-article-pathname group)))
|
||||
(if (file-directory-p pathname)
|
||||
(setq nnspool-current-directory pathname
|
||||
nnspool-current-group group)
|
||||
(nnheader-report 'nnspool "No such newsgroup: %s" group)))))
|
||||
|
||||
(defun nnspool-article-pathname (group &optional article)
|
||||
"Find the path for GROUP."
|
||||
(nnheader-group-pathname group nnspool-spool-directory article))
|
||||
|
||||
(defun nnspool-seconds-since-epoch (date)
|
||||
(let* ((tdate (mapcar (lambda (ti) (and ti (string-to-int ti)))
|
||||
(timezone-parse-date date)))
|
||||
(ttime (mapcar (lambda (ti) (and ti (string-to-int ti)))
|
||||
(timezone-parse-time
|
||||
(aref (timezone-parse-date date) 3))))
|
||||
(unix (encode-time (nth 2 ttime) (nth 1 ttime) (nth 0 ttime)
|
||||
(nth 2 tdate) (nth 1 tdate) (nth 0 tdate)
|
||||
(nth 4 tdate))))
|
||||
(+ (* (car unix) 65536.0)
|
||||
(cadr unix))))
|
||||
|
||||
(provide 'nnspool)
|
||||
|
||||
;;; nnspool.el ends here
|
||||
1138
lisp/gnus/nntp.el
Normal file
1138
lisp/gnus/nntp.el
Normal file
File diff suppressed because it is too large
Load diff
766
lisp/gnus/nnvirtual.el
Normal file
766
lisp/gnus/nnvirtual.el
Normal file
|
|
@ -0,0 +1,766 @@
|
|||
;;; nnvirtual.el --- virtual newsgroups access for Gnus
|
||||
;; Copyright (C) 1994,95,96,97 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: David Moore <dmoore@ucsd.edu>
|
||||
;; Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
|
||||
;; Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
|
||||
;; Keywords: news
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; The other access methods (nntp, nnspool, etc) are general news
|
||||
;; access methods. This module relies on Gnus and can not be used
|
||||
;; separately.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'nntp)
|
||||
(require 'nnheader)
|
||||
(require 'gnus)
|
||||
(require 'nnoo)
|
||||
(require 'gnus-util)
|
||||
(require 'gnus-start)
|
||||
(require 'gnus-sum)
|
||||
(eval-when-compile (require 'cl))
|
||||
|
||||
(nnoo-declare nnvirtual)
|
||||
|
||||
(defvoo nnvirtual-always-rescan nil
|
||||
"*If non-nil, always scan groups for unread articles when entering a group.
|
||||
If this variable is nil (which is the default) and you read articles
|
||||
in a component group after the virtual group has been activated, the
|
||||
read articles from the component group will show up when you enter the
|
||||
virtual group.")
|
||||
|
||||
(defvoo nnvirtual-component-regexp nil
|
||||
"*Regexp to match component groups.")
|
||||
|
||||
(defvoo nnvirtual-component-groups nil
|
||||
"Component group in this nnvirtual group.")
|
||||
|
||||
|
||||
|
||||
(defconst nnvirtual-version "nnvirtual 1.1")
|
||||
|
||||
(defvoo nnvirtual-current-group nil)
|
||||
|
||||
(defvoo nnvirtual-mapping-table nil
|
||||
"Table of rules on how to map between component group and article number
|
||||
to virtual article number.")
|
||||
|
||||
(defvoo nnvirtual-mapping-offsets nil
|
||||
"Table indexed by component group to an offset to be applied to article numbers in that group.")
|
||||
|
||||
(defvoo nnvirtual-mapping-len 0
|
||||
"Number of articles in this virtual group.")
|
||||
|
||||
(defvoo nnvirtual-mapping-reads nil
|
||||
"Compressed sequence of read articles on the virtual group as computed from the unread status of individual component groups.")
|
||||
|
||||
(defvoo nnvirtual-mapping-marks nil
|
||||
"Compressed marks alist for the virtual group as computed from the marks of individual component groups.")
|
||||
|
||||
(defvoo nnvirtual-info-installed nil
|
||||
"T if we have already installed the group info for this group, and shouldn't blast over it again.")
|
||||
|
||||
(defvoo nnvirtual-status-string "")
|
||||
|
||||
(eval-and-compile
|
||||
(autoload 'gnus-cache-articles-in-group "gnus-cache"))
|
||||
|
||||
|
||||
|
||||
;;; Interface functions.
|
||||
|
||||
(nnoo-define-basics nnvirtual)
|
||||
|
||||
|
||||
(deffoo nnvirtual-retrieve-headers (articles &optional newsgroup
|
||||
server fetch-old)
|
||||
(when (nnvirtual-possibly-change-server server)
|
||||
(save-excursion
|
||||
(set-buffer nntp-server-buffer)
|
||||
(erase-buffer)
|
||||
(if (stringp (car articles))
|
||||
'headers
|
||||
(let ((vbuf (nnheader-set-temp-buffer
|
||||
(get-buffer-create " *virtual headers*")))
|
||||
(carticles (nnvirtual-partition-sequence articles))
|
||||
(system-name (system-name))
|
||||
cgroup carticle article result prefix)
|
||||
(while carticles
|
||||
(setq cgroup (caar carticles))
|
||||
(setq articles (cdar carticles))
|
||||
(pop carticles)
|
||||
(when (and articles
|
||||
(gnus-check-server
|
||||
(gnus-find-method-for-group cgroup) t)
|
||||
(gnus-request-group cgroup t)
|
||||
(setq prefix (gnus-group-real-prefix cgroup))
|
||||
;; FIX FIX FIX we want to check the cache!
|
||||
;; This is probably evil if people have set
|
||||
;; gnus-use-cache to nil themselves, but I
|
||||
;; have no way of finding the true value of it.
|
||||
(let ((gnus-use-cache t))
|
||||
(setq result (gnus-retrieve-headers
|
||||
articles cgroup nil))))
|
||||
(set-buffer nntp-server-buffer)
|
||||
;; If we got HEAD headers, we convert them into NOV
|
||||
;; headers. This is slow, inefficient and, come to think
|
||||
;; of it, downright evil. So sue me. I couldn't be
|
||||
;; bothered to write a header parse routine that could
|
||||
;; parse a mixed HEAD/NOV buffer.
|
||||
(when (eq result 'headers)
|
||||
(nnvirtual-convert-headers))
|
||||
(goto-char (point-min))
|
||||
(while (not (eobp))
|
||||
(delete-region (point)
|
||||
(progn
|
||||
(setq carticle (read nntp-server-buffer))
|
||||
(point)))
|
||||
|
||||
;; We remove this article from the articles list, if
|
||||
;; anything is left in the articles list after going through
|
||||
;; the entire buffer, then those articles have been
|
||||
;; expired or canceled, so we appropriately update the
|
||||
;; component group below. They should be coming up
|
||||
;; generally in order, so this shouldn't be slow.
|
||||
(setq articles (delq carticle articles))
|
||||
|
||||
(setq article (nnvirtual-reverse-map-article cgroup carticle))
|
||||
(if (null article)
|
||||
;; This line has no reverse mapping, that means it
|
||||
;; was an extra article reference returned by nntp.
|
||||
(progn
|
||||
(beginning-of-line)
|
||||
(delete-region (point) (progn (forward-line 1) (point))))
|
||||
;; Otherwise insert the virtual article number,
|
||||
;; and clean up the xrefs.
|
||||
(princ article nntp-server-buffer)
|
||||
(nnvirtual-update-xref-header cgroup carticle
|
||||
prefix system-name)
|
||||
(forward-line 1))
|
||||
)
|
||||
|
||||
(set-buffer vbuf)
|
||||
(goto-char (point-max))
|
||||
(insert-buffer-substring nntp-server-buffer))
|
||||
;; Anything left in articles is expired or canceled.
|
||||
;; Could be smart and not tell it about articles already known?
|
||||
(when articles
|
||||
(gnus-group-make-articles-read cgroup articles))
|
||||
)
|
||||
|
||||
;; The headers are ready for reading, so they are inserted into
|
||||
;; the nntp-server-buffer, which is where Gnus expects to find
|
||||
;; them.
|
||||
(prog1
|
||||
(save-excursion
|
||||
(set-buffer nntp-server-buffer)
|
||||
(erase-buffer)
|
||||
(insert-buffer-substring vbuf)
|
||||
;; FIX FIX FIX, we should be able to sort faster than
|
||||
;; this if needed, since each cgroup is sorted, we just
|
||||
;; need to merge
|
||||
(sort-numeric-fields 1 (point-min) (point-max))
|
||||
'nov)
|
||||
(kill-buffer vbuf)))))))
|
||||
|
||||
|
||||
(defvoo nnvirtual-last-accessed-component-group nil)
|
||||
|
||||
(deffoo nnvirtual-request-article (article &optional group server buffer)
|
||||
(when (nnvirtual-possibly-change-server server)
|
||||
(if (stringp article)
|
||||
;; This is a fetch by Message-ID.
|
||||
(cond
|
||||
((not nnvirtual-last-accessed-component-group)
|
||||
(nnheader-report
|
||||
'nnvirtual "Don't know what server to request from"))
|
||||
(t
|
||||
(save-excursion
|
||||
(when buffer
|
||||
(set-buffer buffer))
|
||||
(let ((method (gnus-find-method-for-group
|
||||
nnvirtual-last-accessed-component-group)))
|
||||
(funcall (gnus-get-function method 'request-article)
|
||||
article nil (nth 1 method) buffer)))))
|
||||
;; This is a fetch by number.
|
||||
(let* ((amap (nnvirtual-map-article article))
|
||||
(cgroup (car amap)))
|
||||
(cond
|
||||
((not amap)
|
||||
(nnheader-report 'nnvirtual "No such article: %s" article))
|
||||
((not (gnus-check-group cgroup))
|
||||
(nnheader-report
|
||||
'nnvirtual "Can't open server where %s exists" cgroup))
|
||||
((not (gnus-request-group cgroup t))
|
||||
(nnheader-report 'nnvirtual "Can't open component group %s" cgroup))
|
||||
(t
|
||||
(setq nnvirtual-last-accessed-component-group cgroup)
|
||||
(if buffer
|
||||
(save-excursion
|
||||
(set-buffer buffer)
|
||||
(gnus-request-article-this-buffer (cdr amap) cgroup))
|
||||
(gnus-request-article (cdr amap) cgroup))))))))
|
||||
|
||||
|
||||
(deffoo nnvirtual-open-server (server &optional defs)
|
||||
(unless (assq 'nnvirtual-component-regexp defs)
|
||||
(push `(nnvirtual-component-regexp ,server)
|
||||
defs))
|
||||
(nnoo-change-server 'nnvirtual server defs)
|
||||
(if nnvirtual-component-groups
|
||||
t
|
||||
(setq nnvirtual-mapping-table nil
|
||||
nnvirtual-mapping-offsets nil
|
||||
nnvirtual-mapping-len 0
|
||||
nnvirtual-mapping-reads nil
|
||||
nnvirtual-mapping-marks nil
|
||||
nnvirtual-info-installed nil)
|
||||
(when nnvirtual-component-regexp
|
||||
;; Go through the newsrc alist and find all component groups.
|
||||
(let ((newsrc (cdr gnus-newsrc-alist))
|
||||
group)
|
||||
(while (setq group (car (pop newsrc)))
|
||||
(when (string-match nnvirtual-component-regexp group) ; Match
|
||||
;; Add this group to the list of component groups.
|
||||
(setq nnvirtual-component-groups
|
||||
(cons group (delete group nnvirtual-component-groups)))))))
|
||||
(if (not nnvirtual-component-groups)
|
||||
(nnheader-report 'nnvirtual "No component groups: %s" server)
|
||||
t)))
|
||||
|
||||
|
||||
(deffoo nnvirtual-request-group (group &optional server dont-check)
|
||||
(nnvirtual-possibly-change-server server)
|
||||
(setq nnvirtual-component-groups
|
||||
(delete (nnvirtual-current-group) nnvirtual-component-groups))
|
||||
(cond
|
||||
((null nnvirtual-component-groups)
|
||||
(setq nnvirtual-current-group nil)
|
||||
(nnheader-report 'nnvirtual "No component groups in %s" group))
|
||||
(t
|
||||
(when (or (not dont-check)
|
||||
nnvirtual-always-rescan)
|
||||
(nnvirtual-create-mapping))
|
||||
(setq nnvirtual-current-group group)
|
||||
(nnheader-insert "211 %d 1 %d %s\n"
|
||||
nnvirtual-mapping-len nnvirtual-mapping-len group))))
|
||||
|
||||
|
||||
(deffoo nnvirtual-request-type (group &optional article)
|
||||
(if (not article)
|
||||
'unknown
|
||||
(let ((mart (nnvirtual-map-article article)))
|
||||
(when mart
|
||||
(gnus-request-type (car mart) (cdr mart))))))
|
||||
|
||||
(deffoo nnvirtual-request-update-mark (group article mark)
|
||||
(let* ((nart (nnvirtual-map-article article))
|
||||
(cgroup (car nart))
|
||||
;; The component group might be a virtual group.
|
||||
(nmark (gnus-request-update-mark cgroup (cdr nart) mark)))
|
||||
(when (and nart
|
||||
(= mark nmark)
|
||||
(gnus-group-auto-expirable-p cgroup))
|
||||
(setq mark gnus-expirable-mark)))
|
||||
mark)
|
||||
|
||||
|
||||
(deffoo nnvirtual-close-group (group &optional server)
|
||||
(when (and (nnvirtual-possibly-change-server server)
|
||||
(not (gnus-ephemeral-group-p (nnvirtual-current-group))))
|
||||
(nnvirtual-update-read-and-marked t t))
|
||||
t)
|
||||
|
||||
|
||||
(deffoo nnvirtual-request-list (&optional server)
|
||||
(nnheader-report 'nnvirtual "LIST is not implemented."))
|
||||
|
||||
|
||||
(deffoo nnvirtual-request-newgroups (date &optional server)
|
||||
(nnheader-report 'nnvirtual "NEWGROUPS is not supported."))
|
||||
|
||||
|
||||
(deffoo nnvirtual-request-list-newsgroups (&optional server)
|
||||
(nnheader-report 'nnvirtual "LIST NEWSGROUPS is not implemented."))
|
||||
|
||||
|
||||
(deffoo nnvirtual-request-update-info (group info &optional server)
|
||||
(when (and (nnvirtual-possibly-change-server server)
|
||||
(not nnvirtual-info-installed))
|
||||
;; Install the precomputed lists atomically, so the virtual group
|
||||
;; is not left in a half-way state in case of C-g.
|
||||
(gnus-atomic-progn
|
||||
(setcar (cddr info) nnvirtual-mapping-reads)
|
||||
(if (nthcdr 3 info)
|
||||
(setcar (nthcdr 3 info) nnvirtual-mapping-marks)
|
||||
(when nnvirtual-mapping-marks
|
||||
(setcdr (nthcdr 2 info) (list nnvirtual-mapping-marks))))
|
||||
(setq nnvirtual-info-installed t))
|
||||
t))
|
||||
|
||||
|
||||
(deffoo nnvirtual-catchup-group (group &optional server all)
|
||||
(when (and (nnvirtual-possibly-change-server server)
|
||||
(not (gnus-ephemeral-group-p (nnvirtual-current-group))))
|
||||
;; copy over existing marks first, in case they set anything
|
||||
(nnvirtual-update-read-and-marked nil nil)
|
||||
;; do a catchup on all component groups
|
||||
(let ((gnus-group-marked (copy-sequence nnvirtual-component-groups))
|
||||
(gnus-expert-user t))
|
||||
;; Make sure all groups are activated.
|
||||
(mapcar
|
||||
(lambda (g)
|
||||
(when (not (numberp (car (gnus-gethash g gnus-newsrc-hashtb))))
|
||||
(gnus-activate-group g)))
|
||||
nnvirtual-component-groups)
|
||||
(save-excursion
|
||||
(set-buffer gnus-group-buffer)
|
||||
(gnus-group-catchup-current nil all)))))
|
||||
|
||||
|
||||
(deffoo nnvirtual-find-group-art (group article)
|
||||
"Return the real group and article for virtual GROUP and ARTICLE."
|
||||
(nnvirtual-map-article article))
|
||||
|
||||
|
||||
;;; Internal functions.
|
||||
|
||||
(defun nnvirtual-convert-headers ()
|
||||
"Convert HEAD headers into NOV headers."
|
||||
(save-excursion
|
||||
(set-buffer nntp-server-buffer)
|
||||
(let* ((dependencies (make-vector 100 0))
|
||||
(headers (gnus-get-newsgroup-headers dependencies))
|
||||
header)
|
||||
(erase-buffer)
|
||||
(while (setq header (pop headers))
|
||||
(nnheader-insert-nov header)))))
|
||||
|
||||
|
||||
(defun nnvirtual-update-xref-header (group article prefix system-name)
|
||||
"Edit current NOV header in current buffer to have an xref to the component group, and also server prefix any existing xref lines."
|
||||
;; Move to beginning of Xref field, creating a slot if needed.
|
||||
(beginning-of-line)
|
||||
(looking-at
|
||||
"[^\t]*\t[^\t]*\t[^\t]*\t[^\t]*\t[^\t]*\t[^\t]*\t[^\t]*\t")
|
||||
(goto-char (match-end 0))
|
||||
(unless (search-forward "\t" (gnus-point-at-eol) 'move)
|
||||
(insert "\t"))
|
||||
|
||||
;; Remove any spaces at the beginning of the Xref field.
|
||||
(while (= (char-after (1- (point))) ? )
|
||||
(forward-char -1)
|
||||
(delete-char 1))
|
||||
|
||||
(insert "Xref: " system-name " " group ":")
|
||||
(princ article (current-buffer))
|
||||
|
||||
;; If there were existing xref lines, clean them up to have the correct
|
||||
;; component server prefix.
|
||||
(let ((xref-end (save-excursion
|
||||
(search-forward "\t" (gnus-point-at-eol) 'move)
|
||||
(point)))
|
||||
(len (length prefix)))
|
||||
(unless (= (point) xref-end)
|
||||
(insert " ")
|
||||
(when (not (string= "" prefix))
|
||||
(while (re-search-forward "[^ ]+:[0-9]+" xref-end t)
|
||||
(save-excursion
|
||||
(goto-char (match-beginning 0))
|
||||
(insert prefix))
|
||||
(setq xref-end (+ xref-end len)))
|
||||
)))
|
||||
|
||||
;; Ensure a trailing \t.
|
||||
(end-of-line)
|
||||
(or (= (char-after (1- (point))) ?\t)
|
||||
(insert ?\t)))
|
||||
|
||||
|
||||
(defun nnvirtual-possibly-change-server (server)
|
||||
(or (not server)
|
||||
(nnoo-current-server-p 'nnvirtual server)
|
||||
(nnvirtual-open-server server)))
|
||||
|
||||
|
||||
(defun nnvirtual-update-read-and-marked (read-p update-p)
|
||||
"Copy marks from the virtual group to the component groups.
|
||||
If READ-P is not nil, update the (un)read status of the components.
|
||||
If UPDATE-P is not nil, call gnus-group-update-group on the components."
|
||||
(when nnvirtual-current-group
|
||||
(let ((unreads (and read-p
|
||||
(nnvirtual-partition-sequence
|
||||
(gnus-list-of-unread-articles
|
||||
(nnvirtual-current-group)))))
|
||||
(type-marks (mapcar (lambda (ml)
|
||||
(cons (car ml)
|
||||
(nnvirtual-partition-sequence (cdr ml))))
|
||||
(gnus-info-marks (gnus-get-info
|
||||
(nnvirtual-current-group)))))
|
||||
mark type groups carticles info entry)
|
||||
|
||||
;; Ok, atomically move all of the (un)read info, clear any old
|
||||
;; marks, and move all of the current marks. This way if someone
|
||||
;; hits C-g, you won't leave the component groups in a half-way state.
|
||||
(gnus-atomic-progn
|
||||
;; move (un)read
|
||||
(let ((gnus-newsgroup-active nil)) ;workaround guns-update-read-articles
|
||||
(while (setq entry (pop unreads))
|
||||
(gnus-update-read-articles (car entry) (cdr entry))))
|
||||
|
||||
;; clear all existing marks on the component groups
|
||||
(setq groups nnvirtual-component-groups)
|
||||
(while groups
|
||||
(when (and (setq info (gnus-get-info (pop groups)))
|
||||
(gnus-info-marks info))
|
||||
(gnus-info-set-marks info nil)))
|
||||
|
||||
;; Ok, currently type-marks is an assq list with keys of a mark type,
|
||||
;; with data of an assq list with keys of component group names
|
||||
;; and the articles which correspond to that key/group pair.
|
||||
(while (setq mark (pop type-marks))
|
||||
(setq type (car mark))
|
||||
(setq groups (cdr mark))
|
||||
(while (setq carticles (pop groups))
|
||||
(gnus-add-marked-articles (car carticles) type (cdr carticles)
|
||||
nil t))))
|
||||
|
||||
;; possibly update the display, it is really slow
|
||||
(when update-p
|
||||
(setq groups nnvirtual-component-groups)
|
||||
(while groups
|
||||
(gnus-group-update-group (pop groups) t))))))
|
||||
|
||||
|
||||
(defun nnvirtual-current-group ()
|
||||
"Return the prefixed name of the current nnvirtual group."
|
||||
(concat "nnvirtual:" nnvirtual-current-group))
|
||||
|
||||
|
||||
|
||||
;;; This is currently O(kn^2) to merge n lists of length k.
|
||||
;;; You could do it in O(knlogn), but we have a small n, and the
|
||||
;;; overhead of the other approach is probably greater.
|
||||
(defun nnvirtual-merge-sorted-lists (&rest lists)
|
||||
"Merge many sorted lists of numbers."
|
||||
(if (null (cdr lists))
|
||||
(car lists)
|
||||
(apply 'nnvirtual-merge-sorted-lists
|
||||
(merge 'list (car lists) (cadr lists) '<)
|
||||
(cddr lists))))
|
||||
|
||||
|
||||
|
||||
;;; We map between virtual articles and real articles in a manner
|
||||
;;; which keeps the size of the virtual active list the same as
|
||||
;;; the sum of the component active lists.
|
||||
;;; To achieve fair mixing of the groups, the last article in
|
||||
;;; each of N component groups will be in the the last N articles
|
||||
;;; in the virtual group.
|
||||
|
||||
;;; If you have 3 components A, B and C, with articles 1-8, 1-5, and 6-7
|
||||
;;; resprectively, then the virtual article numbers look like:
|
||||
;;;
|
||||
;;; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
||||
;;; A1 A2 A3 A4 B1 A5 B2 A6 B3 A7 B4 C6 A8 B5 C7
|
||||
|
||||
;;; To compute these mappings we generate a couple tables and then
|
||||
;;; do some fast operations on them. Tables for the example above:
|
||||
;;;
|
||||
;;; Offsets - [(A 0) (B -3) (C -1)]
|
||||
;;;
|
||||
;;; a b c d e
|
||||
;;; Mapping - ([ 3 0 1 3 0 ]
|
||||
;;; [ 6 3 2 9 3 ]
|
||||
;;; [ 8 6 3 15 9 ])
|
||||
;;;
|
||||
;;; (note column 'e' is different in real algorithm, which is slightly
|
||||
;;; different than described here, but this gives you the methodology.)
|
||||
;;;
|
||||
;;; The basic idea is this, when going from component->virtual, apply
|
||||
;;; the appropriate offset to the article number. Then search the first
|
||||
;;; column of the table for a row where 'a' is less than or equal to the
|
||||
;;; modified number. You can see that only group A can therefore go to
|
||||
;;; the first row, groups A and B to the second, and all to the last.
|
||||
;;; The third column of the table is telling us the number of groups
|
||||
;;; which might be able to reach that row (it might increase by more than
|
||||
;;; 1 if several groups have the same size).
|
||||
;;; Then column 'b' provides an additional offset you apply when you have
|
||||
;;; found the correct row. You then multiply by 'c' and add on the groups
|
||||
;;; _position_ in the offset table. The basic idea here is that on
|
||||
;;; any given row we are going to map back and forth using X'=X*c+Y and
|
||||
;;; X=(X'/c), Y=(X' mod c). Then once you've done this transformation,
|
||||
;;; you apply a final offset from column 'e' to give the virtual article.
|
||||
;;;
|
||||
;;; Going the other direction, you instead search on column 'd' instead
|
||||
;;; of 'a', and apply everything in reverse order.
|
||||
|
||||
;;; Convert component -> virtual:
|
||||
;;; set num = num - Offset(group)
|
||||
;;; find first row in Mapping where num <= 'a'
|
||||
;;; num = (num-'b')*c + Position(group) + 'e'
|
||||
|
||||
;;; Convert virtual -> component:
|
||||
;;; find first row in Mapping where num <= 'd'
|
||||
;;; num = num - 'e'
|
||||
;;; group_pos = num mod 'c'
|
||||
;;; num = (num / 'c') + 'b' + Offset(group_pos)
|
||||
|
||||
;;; Easy no? :)
|
||||
;;;
|
||||
;;; Well actually, you need to keep column e offset smaller by the 'c'
|
||||
;;; column for that line, and always add 1 more when going from
|
||||
;;; component -> virtual. Otherwise you run into a problem with
|
||||
;;; unique reverse mapping.
|
||||
|
||||
(defun nnvirtual-map-article (article)
|
||||
"Return a cons of the component group and article corresponding to the given virtual ARTICLE."
|
||||
(let ((table nnvirtual-mapping-table)
|
||||
entry group-pos)
|
||||
(while (and table
|
||||
(> article (aref (car table) 3)))
|
||||
(setq table (cdr table)))
|
||||
(when (and table
|
||||
(> article 0))
|
||||
(setq entry (car table))
|
||||
(setq article (- article (aref entry 4) 1))
|
||||
(setq group-pos (mod article (aref entry 2)))
|
||||
(cons (car (aref nnvirtual-mapping-offsets group-pos))
|
||||
(+ (/ article (aref entry 2))
|
||||
(aref entry 1)
|
||||
(cdr (aref nnvirtual-mapping-offsets group-pos)))
|
||||
))
|
||||
))
|
||||
|
||||
|
||||
|
||||
(defun nnvirtual-reverse-map-article (group article)
|
||||
"Return the virtual article number corresponding to the given component GROUP and ARTICLE."
|
||||
(let ((table nnvirtual-mapping-table)
|
||||
(group-pos 0)
|
||||
entry)
|
||||
(while (not (string= group (car (aref nnvirtual-mapping-offsets
|
||||
group-pos))))
|
||||
(setq group-pos (1+ group-pos)))
|
||||
(setq article (- article (cdr (aref nnvirtual-mapping-offsets
|
||||
group-pos))))
|
||||
(while (and table
|
||||
(> article (aref (car table) 0)))
|
||||
(setq table (cdr table)))
|
||||
(setq entry (car table))
|
||||
(when (and entry
|
||||
(> article 0)
|
||||
(< group-pos (aref entry 2))) ; article not out of range below
|
||||
(+ (aref entry 4)
|
||||
group-pos
|
||||
(* (- article (aref entry 1))
|
||||
(aref entry 2))
|
||||
1))
|
||||
))
|
||||
|
||||
|
||||
(defsubst nnvirtual-reverse-map-sequence (group articles)
|
||||
"Return list of virtual article numbers for all ARTICLES in GROUP.
|
||||
The ARTICLES should be sorted, and can be a compressed sequence.
|
||||
If any of the article numbers has no corresponding virtual article,
|
||||
then it is left out of the result."
|
||||
(when (numberp (cdr-safe articles))
|
||||
(setq articles (list articles)))
|
||||
(let (result a i j new-a)
|
||||
(while (setq a (pop articles))
|
||||
(if (atom a)
|
||||
(setq i a
|
||||
j a)
|
||||
(setq i (car a)
|
||||
j (cdr a)))
|
||||
(while (<= i j)
|
||||
;; If this is slow, you can optimize by moving article checking
|
||||
;; into here. You don't have to recompute the group-pos,
|
||||
;; nor scan the table every time.
|
||||
(when (setq new-a (nnvirtual-reverse-map-article group i))
|
||||
(push new-a result))
|
||||
(setq i (1+ i))))
|
||||
(nreverse result)))
|
||||
|
||||
|
||||
(defun nnvirtual-partition-sequence (articles)
|
||||
"Return an association list of component article numbers.
|
||||
These are indexed by elements of nnvirtual-component-groups, based on
|
||||
the sequence ARTICLES of virtual article numbers. ARTICLES should be
|
||||
sorted, and can be a compressed sequence. If any of the article
|
||||
numbers has no corresponding component article, then it is left out of
|
||||
the result."
|
||||
(when (numberp (cdr-safe articles))
|
||||
(setq articles (list articles)))
|
||||
(let ((carticles (mapcar (lambda (g) (list g))
|
||||
nnvirtual-component-groups))
|
||||
a i j article entry)
|
||||
(while (setq a (pop articles))
|
||||
(if (atom a)
|
||||
(setq i a
|
||||
j a)
|
||||
(setq i (car a)
|
||||
j (cdr a)))
|
||||
(while (<= i j)
|
||||
(when (setq article (nnvirtual-map-article i))
|
||||
(setq entry (assoc (car article) carticles))
|
||||
(setcdr entry (cons (cdr article) (cdr entry))))
|
||||
(setq i (1+ i))))
|
||||
(mapc (lambda (x) (setcdr x (nreverse (cdr x))))
|
||||
carticles)
|
||||
carticles))
|
||||
|
||||
|
||||
(defun nnvirtual-create-mapping ()
|
||||
"Build the tables necessary to map between component (group, article) to virtual article.
|
||||
Generate the set of read messages and marks for the virtual group
|
||||
based on the marks on the component groups."
|
||||
(let ((cnt 0)
|
||||
(tot 0)
|
||||
(M 0)
|
||||
(i 0)
|
||||
actives all-unreads all-marks
|
||||
active min max size unreads marks
|
||||
next-M next-tot
|
||||
reads beg)
|
||||
;; Ok, we loop over all component groups and collect a lot of
|
||||
;; information:
|
||||
;; Into actives we place (g size max), where size is max-min+1.
|
||||
;; Into all-unreads we put (g unreads).
|
||||
;; Into all-marks we put (g marks).
|
||||
;; We also increment cnt and tot here, and compute M (max of sizes).
|
||||
(mapc (lambda (g)
|
||||
(setq active (gnus-activate-group g)
|
||||
min (car active)
|
||||
max (cdr active))
|
||||
(when (and active (>= max min) (not (zerop max)))
|
||||
;; store active information
|
||||
(push (list g (- max min -1) max) actives)
|
||||
;; collect unread/mark info for later
|
||||
(setq unreads (gnus-list-of-unread-articles g))
|
||||
(setq marks (gnus-info-marks (gnus-get-info g)))
|
||||
(when gnus-use-cache
|
||||
(push (cons 'cache
|
||||
(gnus-cache-articles-in-group g))
|
||||
marks))
|
||||
(push (cons g unreads) all-unreads)
|
||||
(push (cons g marks) all-marks)
|
||||
;; count groups, total #articles, and max size
|
||||
(setq size (- max min -1))
|
||||
(setq cnt (1+ cnt)
|
||||
tot (+ tot size)
|
||||
M (max M size))))
|
||||
nnvirtual-component-groups)
|
||||
|
||||
;; Number of articles in the virtual group.
|
||||
(setq nnvirtual-mapping-len tot)
|
||||
|
||||
|
||||
;; We want the actives list sorted by size, to build the tables.
|
||||
(setq actives (sort actives (lambda (g1 g2) (< (nth 1 g1) (nth 1 g2)))))
|
||||
|
||||
;; Build the offset table. Largest sized groups are at the front.
|
||||
(setq nnvirtual-mapping-offsets
|
||||
(vconcat
|
||||
(nreverse
|
||||
(mapcar (lambda (entry)
|
||||
(cons (nth 0 entry)
|
||||
(- (nth 2 entry) M)))
|
||||
actives))))
|
||||
|
||||
;; Build the mapping table.
|
||||
(setq nnvirtual-mapping-table nil)
|
||||
(setq actives (mapcar (lambda (entry) (nth 1 entry)) actives))
|
||||
(while actives
|
||||
(setq size (car actives))
|
||||
(setq next-M (- M size))
|
||||
(setq next-tot (- tot (* cnt size)))
|
||||
;; make current row in table
|
||||
(push (vector M next-M cnt tot (- next-tot cnt))
|
||||
nnvirtual-mapping-table)
|
||||
;; update M and tot
|
||||
(setq M next-M)
|
||||
(setq tot next-tot)
|
||||
;; subtract the current size from all entries.
|
||||
(setq actives (mapcar (lambda (x) (- x size)) actives))
|
||||
;; remove anything that went to 0.
|
||||
(while (and actives
|
||||
(= (car actives) 0))
|
||||
(pop actives)
|
||||
(setq cnt (- cnt 1))))
|
||||
|
||||
|
||||
;; Now that the mapping tables are generated, we can convert
|
||||
;; and combine the separate component unreads and marks lists
|
||||
;; into single lists of virtual article numbers.
|
||||
(setq unreads (apply 'nnvirtual-merge-sorted-lists
|
||||
(mapcar (lambda (x)
|
||||
(nnvirtual-reverse-map-sequence
|
||||
(car x) (cdr x)))
|
||||
all-unreads)))
|
||||
(setq marks (mapcar
|
||||
(lambda (type)
|
||||
(cons (cdr type)
|
||||
(gnus-compress-sequence
|
||||
(apply
|
||||
'nnvirtual-merge-sorted-lists
|
||||
(mapcar (lambda (x)
|
||||
(nnvirtual-reverse-map-sequence
|
||||
(car x)
|
||||
(cdr (assq (cdr type) (cdr x)))))
|
||||
all-marks)))))
|
||||
gnus-article-mark-lists))
|
||||
|
||||
;; Remove any empty marks lists, and store.
|
||||
(setq nnvirtual-mapping-marks (delete-if-not 'cdr marks))
|
||||
|
||||
;; We need to convert the unreads to reads. We compress the
|
||||
;; sequence as we go, otherwise it could be huge.
|
||||
(while (and (<= (incf i) nnvirtual-mapping-len)
|
||||
unreads)
|
||||
(if (= i (car unreads))
|
||||
(setq unreads (cdr unreads))
|
||||
;; try to get a range.
|
||||
(setq beg i)
|
||||
(while (and (<= (incf i) nnvirtual-mapping-len)
|
||||
(not (= i (car unreads)))))
|
||||
(setq i (- i 1))
|
||||
(if (= i beg)
|
||||
(push i reads)
|
||||
(push (cons beg i) reads))
|
||||
))
|
||||
(when (<= i nnvirtual-mapping-len)
|
||||
(if (= i nnvirtual-mapping-len)
|
||||
(push i reads)
|
||||
(push (cons i nnvirtual-mapping-len) reads)))
|
||||
|
||||
;; Store the reads list for later use.
|
||||
(setq nnvirtual-mapping-reads (nreverse reads))
|
||||
|
||||
;; Throw flag to show we changed the info.
|
||||
(setq nnvirtual-info-installed nil)
|
||||
))
|
||||
|
||||
(provide 'nnvirtual)
|
||||
|
||||
;;; nnvirtual.el ends here
|
||||
689
lisp/gnus/nnweb.el
Normal file
689
lisp/gnus/nnweb.el
Normal file
|
|
@ -0,0 +1,689 @@
|
|||
;;; nnweb.el --- retrieving articles via web search engines
|
||||
;; Copyright (C) 1996,97 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
|
||||
;; Keywords: news
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; Note: You need to have `url' and `w3' installed for this
|
||||
;; backend to work.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'nnoo)
|
||||
(require 'message)
|
||||
(require 'gnus-util)
|
||||
(require 'gnus)
|
||||
(require 'w3)
|
||||
(require 'url)
|
||||
(require 'nnmail)
|
||||
(ignore-errors
|
||||
(require 'w3-forms))
|
||||
|
||||
(nnoo-declare nnweb)
|
||||
|
||||
(defvoo nnweb-directory (nnheader-concat gnus-directory "nnweb/")
|
||||
"Where nnweb will save its files.")
|
||||
|
||||
(defvoo nnweb-type 'dejanews
|
||||
"What search engine type is being used.")
|
||||
|
||||
(defvar nnweb-type-definition
|
||||
'((dejanews
|
||||
(article . nnweb-dejanews-wash-article)
|
||||
(map . nnweb-dejanews-create-mapping)
|
||||
(search . nnweb-dejanews-search)
|
||||
(address . "http://xp9.dejanews.com/dnquery.xp")
|
||||
(identifier . nnweb-dejanews-identity))
|
||||
(reference
|
||||
(article . nnweb-reference-wash-article)
|
||||
(map . nnweb-reference-create-mapping)
|
||||
(search . nnweb-reference-search)
|
||||
(address . "http://www.reference.com/cgi-bin/pn/go")
|
||||
(identifier . identity))
|
||||
(altavista
|
||||
(article . nnweb-altavista-wash-article)
|
||||
(map . nnweb-altavista-create-mapping)
|
||||
(search . nnweb-altavista-search)
|
||||
(address . "http://www.altavista.digital.com/cgi-bin/query")
|
||||
(id . "/cgi-bin/news?id@%s")
|
||||
(identifier . identity)))
|
||||
"Type-definition alist.")
|
||||
|
||||
(defvoo nnweb-search nil
|
||||
"Search string to feed to DejaNews.")
|
||||
|
||||
(defvoo nnweb-max-hits 100
|
||||
"Maximum number of hits to display.")
|
||||
|
||||
(defvoo nnweb-ephemeral-p nil
|
||||
"Whether this nnweb server is ephemeral.")
|
||||
|
||||
;;; Internal variables
|
||||
|
||||
(defvoo nnweb-articles nil)
|
||||
(defvoo nnweb-buffer nil)
|
||||
(defvoo nnweb-group-alist nil)
|
||||
(defvoo nnweb-group nil)
|
||||
(defvoo nnweb-hashtb nil)
|
||||
|
||||
;;; Interface functions
|
||||
|
||||
(nnoo-define-basics nnweb)
|
||||
|
||||
(deffoo nnweb-retrieve-headers (articles &optional group server fetch-old)
|
||||
(nnweb-possibly-change-server group server)
|
||||
(save-excursion
|
||||
(set-buffer nntp-server-buffer)
|
||||
(erase-buffer)
|
||||
(let (article header)
|
||||
(while (setq article (pop articles))
|
||||
(when (setq header (cadr (assq article nnweb-articles)))
|
||||
(nnheader-insert-nov header)))
|
||||
'nov)))
|
||||
|
||||
(deffoo nnweb-request-scan (&optional group server)
|
||||
(nnweb-possibly-change-server group server)
|
||||
(setq nnweb-hashtb (gnus-make-hashtable 4095))
|
||||
(funcall (nnweb-definition 'map))
|
||||
(unless nnweb-ephemeral-p
|
||||
(nnweb-write-active)
|
||||
(nnweb-write-overview group)))
|
||||
|
||||
(deffoo nnweb-request-group (group &optional server dont-check)
|
||||
(nnweb-possibly-change-server nil server)
|
||||
(when (and group
|
||||
(not (equal group nnweb-group))
|
||||
(not nnweb-ephemeral-p))
|
||||
(let ((info (assoc group nnweb-group-alist)))
|
||||
(setq nnweb-group group)
|
||||
(setq nnweb-type (nth 2 info))
|
||||
(setq nnweb-search (nth 3 info))
|
||||
(unless dont-check
|
||||
(nnweb-read-overview group))))
|
||||
(cond
|
||||
((not nnweb-articles)
|
||||
(nnheader-report 'nnweb "No matching articles"))
|
||||
(t
|
||||
(let ((active (if nnweb-ephemeral-p
|
||||
(cons (caar nnweb-articles)
|
||||
(caar (last nnweb-articles)))
|
||||
(cadr (assoc group nnweb-group-alist)))))
|
||||
(nnheader-report 'nnweb "Opened group %s" group)
|
||||
(nnheader-insert
|
||||
"211 %d %d %d %s\n" (length nnweb-articles)
|
||||
(car active) (cdr active) group)))))
|
||||
|
||||
(deffoo nnweb-close-group (group &optional server)
|
||||
(nnweb-possibly-change-server group server)
|
||||
(when (gnus-buffer-live-p nnweb-buffer)
|
||||
(save-excursion
|
||||
(set-buffer nnweb-buffer)
|
||||
(set-buffer-modified-p nil)
|
||||
(kill-buffer nnweb-buffer)))
|
||||
t)
|
||||
|
||||
(deffoo nnweb-request-article (article &optional group server buffer)
|
||||
(nnweb-possibly-change-server group server)
|
||||
(save-excursion
|
||||
(set-buffer (or buffer nntp-server-buffer))
|
||||
(let* ((header (cadr (assq article nnweb-articles)))
|
||||
(url (and header (mail-header-xref header))))
|
||||
(when (or (and url
|
||||
(nnweb-fetch-url url))
|
||||
(and (stringp article)
|
||||
(nnweb-definition 'id t)
|
||||
(let ((fetch (nnweb-definition 'id))
|
||||
art)
|
||||
(when (string-match "^<\\(.*\\)>$" article)
|
||||
(setq art (match-string 1 article)))
|
||||
(and fetch
|
||||
art
|
||||
(nnweb-fetch-url
|
||||
(format fetch article))))))
|
||||
(unless nnheader-callback-function
|
||||
(funcall (nnweb-definition 'article))
|
||||
(nnweb-decode-entities))
|
||||
(nnheader-report 'nnweb "Fetched article %s" article)
|
||||
t))))
|
||||
|
||||
(deffoo nnweb-close-server (&optional server)
|
||||
(when (and (nnweb-server-opened server)
|
||||
(gnus-buffer-live-p nnweb-buffer))
|
||||
(save-excursion
|
||||
(set-buffer nnweb-buffer)
|
||||
(set-buffer-modified-p nil)
|
||||
(kill-buffer nnweb-buffer)))
|
||||
(nnoo-close-server 'nnweb server))
|
||||
|
||||
(deffoo nnweb-request-list (&optional server)
|
||||
(nnweb-possibly-change-server nil server)
|
||||
(save-excursion
|
||||
(set-buffer nntp-server-buffer)
|
||||
(nnmail-generate-active nnweb-group-alist)
|
||||
t))
|
||||
|
||||
(deffoo nnweb-request-update-info (group info &optional server)
|
||||
(nnweb-possibly-change-server group server)
|
||||
;;(setcar (cddr info) nil)
|
||||
)
|
||||
|
||||
(deffoo nnweb-asynchronous-p ()
|
||||
t)
|
||||
|
||||
(deffoo nnweb-request-create-group (group &optional server args)
|
||||
(nnweb-possibly-change-server nil server)
|
||||
(nnweb-request-delete-group group)
|
||||
(push `(,group ,(cons 1 0) ,@args) nnweb-group-alist)
|
||||
(nnweb-write-active)
|
||||
t)
|
||||
|
||||
(deffoo nnweb-request-delete-group (group &optional force server)
|
||||
(nnweb-possibly-change-server group server)
|
||||
(gnus-delete-assoc group nnweb-group-alist)
|
||||
(gnus-delete-file (nnweb-overview-file group))
|
||||
t)
|
||||
|
||||
(nnoo-define-skeleton nnweb)
|
||||
|
||||
;;; Internal functions
|
||||
|
||||
(defun nnweb-read-overview (group)
|
||||
"Read the overview of GROUP and build the map."
|
||||
(when (file-exists-p (nnweb-overview-file group))
|
||||
(nnheader-temp-write nil
|
||||
(nnheader-insert-file-contents (nnweb-overview-file group))
|
||||
(goto-char (point-min))
|
||||
(let (header)
|
||||
(while (not (eobp))
|
||||
(setq header (nnheader-parse-nov))
|
||||
(forward-line 1)
|
||||
(push (list (mail-header-number header)
|
||||
header (mail-header-xref header))
|
||||
nnweb-articles)
|
||||
(nnweb-set-hashtb header (car nnweb-articles)))))))
|
||||
|
||||
(defun nnweb-write-overview (group)
|
||||
"Write the overview file for GROUP."
|
||||
(nnheader-temp-write (nnweb-overview-file group)
|
||||
(let ((articles nnweb-articles))
|
||||
(while articles
|
||||
(nnheader-insert-nov (cadr (pop articles)))))))
|
||||
|
||||
(defun nnweb-set-hashtb (header data)
|
||||
(gnus-sethash (nnweb-identifier (mail-header-xref header))
|
||||
data nnweb-hashtb))
|
||||
|
||||
(defun nnweb-get-hashtb (url)
|
||||
(gnus-gethash (nnweb-identifier url) nnweb-hashtb))
|
||||
|
||||
(defun nnweb-identifier (ident)
|
||||
(funcall (nnweb-definition 'identifier) ident))
|
||||
|
||||
(defun nnweb-overview-file (group)
|
||||
"Return the name of the overview file of GROUP."
|
||||
(nnheader-concat nnweb-directory group ".overview"))
|
||||
|
||||
(defun nnweb-write-active ()
|
||||
"Save the active file."
|
||||
(nnheader-temp-write (nnheader-concat nnweb-directory "active")
|
||||
(prin1 `(setq nnweb-group-alist ',nnweb-group-alist) (current-buffer))))
|
||||
|
||||
(defun nnweb-read-active ()
|
||||
"Read the active file."
|
||||
(load (nnheader-concat nnweb-directory "active") t t t))
|
||||
|
||||
(defun nnweb-definition (type &optional noerror)
|
||||
"Return the definition of TYPE."
|
||||
(let ((def (cdr (assq type (assq nnweb-type nnweb-type-definition)))))
|
||||
(when (and (not def)
|
||||
(not noerror))
|
||||
(error "Undefined definition %s" type))
|
||||
def))
|
||||
|
||||
(defun nnweb-possibly-change-server (&optional group server)
|
||||
(nnweb-init server)
|
||||
(when server
|
||||
(unless (nnweb-server-opened server)
|
||||
(nnweb-open-server server)))
|
||||
(unless nnweb-group-alist
|
||||
(nnweb-read-active))
|
||||
(when group
|
||||
(when (and (not nnweb-ephemeral-p)
|
||||
(not (equal group nnweb-group)))
|
||||
(nnweb-request-group group nil t))))
|
||||
|
||||
(defun nnweb-init (server)
|
||||
"Initialize buffers and such."
|
||||
(unless (gnus-buffer-live-p nnweb-buffer)
|
||||
(setq nnweb-buffer
|
||||
(save-excursion
|
||||
(nnheader-set-temp-buffer
|
||||
(format " *nnweb %s %s %s*" nnweb-type nnweb-search server))))))
|
||||
|
||||
(defun nnweb-fetch-url (url)
|
||||
(save-excursion
|
||||
(if (not nnheader-callback-function)
|
||||
(let ((buf (current-buffer)))
|
||||
(save-excursion
|
||||
(set-buffer nnweb-buffer)
|
||||
(erase-buffer)
|
||||
(prog1
|
||||
(url-insert-file-contents url)
|
||||
(copy-to-buffer buf (point-min) (point-max)))))
|
||||
(nnweb-url-retrieve-asynch
|
||||
url 'nnweb-callback (current-buffer) nnheader-callback-function)
|
||||
t)))
|
||||
|
||||
(defun nnweb-callback (buffer callback)
|
||||
(when (gnus-buffer-live-p url-working-buffer)
|
||||
(save-excursion
|
||||
(set-buffer url-working-buffer)
|
||||
(funcall (nnweb-definition 'article))
|
||||
(nnweb-decode-entities)
|
||||
(set-buffer buffer)
|
||||
(goto-char (point-max))
|
||||
(insert-buffer-substring url-working-buffer))
|
||||
(funcall callback t)
|
||||
(gnus-kill-buffer url-working-buffer)))
|
||||
|
||||
(defun nnweb-url-retrieve-asynch (url callback &rest data)
|
||||
(let ((url-request-method "GET")
|
||||
(old-asynch url-be-asynchronous)
|
||||
(url-request-data nil)
|
||||
(url-request-extra-headers nil)
|
||||
(url-working-buffer (generate-new-buffer-name " *nnweb*")))
|
||||
(setq-default url-be-asynchronous t)
|
||||
(save-excursion
|
||||
(set-buffer (get-buffer-create url-working-buffer))
|
||||
(setq url-current-callback-data data
|
||||
url-be-asynchronous t
|
||||
url-current-callback-func callback)
|
||||
(url-retrieve url))
|
||||
(setq-default url-be-asynchronous old-asynch)))
|
||||
|
||||
(defun nnweb-encode-www-form-urlencoded (pairs)
|
||||
"Return PAIRS encoded for forms."
|
||||
(mapconcat
|
||||
(function
|
||||
(lambda (data)
|
||||
(concat (w3-form-encode-xwfu (car data)) "="
|
||||
(w3-form-encode-xwfu (cdr data)))))
|
||||
pairs "&"))
|
||||
|
||||
(defun nnweb-fetch-form (url pairs)
|
||||
(let ((url-request-data (nnweb-encode-www-form-urlencoded pairs))
|
||||
(url-request-method "POST")
|
||||
(url-request-extra-headers
|
||||
'(("Content-type" . "application/x-www-form-urlencoded"))))
|
||||
(url-insert-file-contents url)
|
||||
(setq buffer-file-name nil))
|
||||
t)
|
||||
|
||||
(defun nnweb-decode-entities ()
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward "&\\([a-z]+\\);" nil t)
|
||||
(replace-match (char-to-string (or (cdr (assq (intern (match-string 1))
|
||||
w3-html-entities ))
|
||||
?#))
|
||||
t t)))
|
||||
|
||||
(defun nnweb-remove-markup ()
|
||||
(goto-char (point-min))
|
||||
(while (search-forward "<!--" nil t)
|
||||
(delete-region (match-beginning 0)
|
||||
(or (search-forward "-->" nil t)
|
||||
(point-max))))
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward "<[^>]+>" nil t)
|
||||
(replace-match "" t t)))
|
||||
|
||||
;;;
|
||||
;;; DejaNews functions.
|
||||
;;;
|
||||
|
||||
(defun nnweb-dejanews-create-mapping ()
|
||||
"Perform the search and create an number-to-url alist."
|
||||
(save-excursion
|
||||
(set-buffer nnweb-buffer)
|
||||
(erase-buffer)
|
||||
(when (funcall (nnweb-definition 'search) nnweb-search)
|
||||
(let ((i 0)
|
||||
(more t)
|
||||
(case-fold-search t)
|
||||
(active (or (cadr (assoc nnweb-group nnweb-group-alist))
|
||||
(cons 1 0)))
|
||||
Subject Score Date Newsgroup Author
|
||||
map url)
|
||||
(while more
|
||||
;; Go through all the article hits on this page.
|
||||
(goto-char (point-min))
|
||||
(nnweb-decode-entities)
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward "^ +[0-9]+\\." nil t)
|
||||
(narrow-to-region
|
||||
(point)
|
||||
(cond ((re-search-forward "^ +[0-9]+\\." nil t)
|
||||
(match-beginning 0))
|
||||
((search-forward "\n\n" nil t)
|
||||
(point))
|
||||
(t
|
||||
(point-max))))
|
||||
(goto-char (point-min))
|
||||
(when (looking-at ".*HREF=\"\\([^\"]+\\)\"")
|
||||
(setq url (match-string 1)))
|
||||
(nnweb-remove-markup)
|
||||
(goto-char (point-min))
|
||||
(while (search-forward "\t" nil t)
|
||||
(replace-match " "))
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward "^ +\\([^:]+\\): +\\(.*\\)$" nil t)
|
||||
(set (intern (match-string 1)) (match-string 2)))
|
||||
(widen)
|
||||
(when (string-match "#[0-9]+/[0-9]+ *$" Subject)
|
||||
(setq Subject (substring Subject 0 (match-beginning 0))))
|
||||
(incf i)
|
||||
(unless (nnweb-get-hashtb url)
|
||||
(push
|
||||
(list
|
||||
(incf (cdr active))
|
||||
(make-full-mail-header
|
||||
(cdr active) (concat "(" Newsgroup ") " Subject) Author Date
|
||||
(concat "<" (nnweb-identifier url) "@dejanews>")
|
||||
nil 0 (string-to-int Score) url))
|
||||
map)
|
||||
(nnweb-set-hashtb (cadar map) (car map))))
|
||||
;; See whether there is a "Get next 20 hits" button here.
|
||||
(if (or (not (re-search-forward
|
||||
"HREF=\"\\([^\"]+\\)\">Get next" nil t))
|
||||
(>= i nnweb-max-hits))
|
||||
(setq more nil)
|
||||
;; Yup -- fetch it.
|
||||
(setq more (match-string 1))
|
||||
(erase-buffer)
|
||||
(url-insert-file-contents more)))
|
||||
;; Return the articles in the right order.
|
||||
(setq nnweb-articles
|
||||
(sort (nconc nnweb-articles map)
|
||||
(lambda (s1 s2) (< (car s1) (car s2)))))))))
|
||||
|
||||
(defun nnweb-dejanews-wash-article ()
|
||||
(let ((case-fold-search t))
|
||||
(goto-char (point-min))
|
||||
(re-search-forward "<PRE>" nil t)
|
||||
(delete-region (point-min) (point))
|
||||
(re-search-forward "</PRE>" nil t)
|
||||
(delete-region (point) (point-max))
|
||||
(nnweb-remove-markup)
|
||||
(goto-char (point-min))
|
||||
(while (and (looking-at " *$")
|
||||
(not (eobp)))
|
||||
(gnus-delete-line))
|
||||
(while (looking-at "\\(^[^ ]+:\\) *")
|
||||
(replace-match "\\1 " t)
|
||||
(forward-line 1))
|
||||
(when (re-search-forward "\n\n+" nil t)
|
||||
(replace-match "\n" t t))))
|
||||
|
||||
(defun nnweb-dejanews-search (search)
|
||||
(nnweb-fetch-form
|
||||
(nnweb-definition 'address)
|
||||
`(("query" . ,search)
|
||||
("defaultOp" . "AND")
|
||||
("svcclass" . "dncurrent")
|
||||
("maxhits" . "100")
|
||||
("format" . "verbose")
|
||||
("threaded" . "0")
|
||||
("showsort" . "score")
|
||||
("agesign" . "1")
|
||||
("ageweight" . "1")))
|
||||
t)
|
||||
|
||||
(defun nnweb-dejanews-identity (url)
|
||||
"Return an unique identifier based on URL."
|
||||
(if (string-match "recnum=\\([0-9]+\\)" url)
|
||||
(match-string 1 url)
|
||||
url))
|
||||
|
||||
;;;
|
||||
;;; InReference
|
||||
;;;
|
||||
|
||||
(defun nnweb-reference-create-mapping ()
|
||||
"Perform the search and create an number-to-url alist."
|
||||
(save-excursion
|
||||
(set-buffer nnweb-buffer)
|
||||
(erase-buffer)
|
||||
(when (funcall (nnweb-definition 'search) nnweb-search)
|
||||
(let ((i 0)
|
||||
(more t)
|
||||
(case-fold-search t)
|
||||
(active (or (cadr (assoc nnweb-group nnweb-group-alist))
|
||||
(cons 1 0)))
|
||||
Subject Score Date Newsgroups From Message-ID
|
||||
map url)
|
||||
(while more
|
||||
;; Go through all the article hits on this page.
|
||||
(goto-char (point-min))
|
||||
(search-forward "</pre><hr>" nil t)
|
||||
(delete-region (point-min) (point))
|
||||
;(nnweb-decode-entities)
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward "^ +[0-9]+\\." nil t)
|
||||
(narrow-to-region
|
||||
(point)
|
||||
(if (re-search-forward "^$" nil t)
|
||||
(match-beginning 0)
|
||||
(point-max)))
|
||||
(goto-char (point-min))
|
||||
(when (looking-at ".*href=\"\\([^\"]+\\)\"")
|
||||
(setq url (match-string 1)))
|
||||
(nnweb-remove-markup)
|
||||
(goto-char (point-min))
|
||||
(while (search-forward "\t" nil t)
|
||||
(replace-match " "))
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward "^\\([^:]+\\): \\(.*\\)$" nil t)
|
||||
(set (intern (match-string 1)) (match-string 2)))
|
||||
(widen)
|
||||
(search-forward "</pre>" nil t)
|
||||
(incf i)
|
||||
(unless (nnweb-get-hashtb url)
|
||||
(push
|
||||
(list
|
||||
(incf (cdr active))
|
||||
(make-full-mail-header
|
||||
(cdr active) (concat "(" Newsgroups ") " Subject) From Date
|
||||
Message-ID
|
||||
nil 0 (string-to-int Score) url))
|
||||
map)
|
||||
(nnweb-set-hashtb (cadar map) (car map))))
|
||||
(setq more nil))
|
||||
;; Return the articles in the right order.
|
||||
(setq nnweb-articles
|
||||
(sort (nconc nnweb-articles map)
|
||||
(lambda (s1 s2) (< (car s1) (car s2)))))))))
|
||||
|
||||
(defun nnweb-reference-wash-article ()
|
||||
(let ((case-fold-search t))
|
||||
(goto-char (point-min))
|
||||
(re-search-forward "^</center><hr>" nil t)
|
||||
(delete-region (point-min) (point))
|
||||
(search-forward "<pre>" nil t)
|
||||
(forward-line -1)
|
||||
(let ((body (point-marker)))
|
||||
(search-forward "</pre>" nil t)
|
||||
(delete-region (point) (point-max))
|
||||
(nnweb-remove-markup)
|
||||
(goto-char (point-min))
|
||||
(while (looking-at " *$")
|
||||
(gnus-delete-line))
|
||||
(narrow-to-region (point-min) body)
|
||||
(while (and (re-search-forward "^$" nil t)
|
||||
(not (eobp)))
|
||||
(gnus-delete-line))
|
||||
(goto-char (point-min))
|
||||
(while (looking-at "\\(^[^ ]+:\\) *")
|
||||
(replace-match "\\1 " t)
|
||||
(forward-line 1))
|
||||
(goto-char (point-min))
|
||||
(when (re-search-forward "^References:" nil t)
|
||||
(narrow-to-region
|
||||
(point) (if (re-search-forward "^$\\|^[^:]+:" nil t)
|
||||
(match-beginning 0)
|
||||
(point-max)))
|
||||
(goto-char (point-min))
|
||||
(while (not (eobp))
|
||||
(unless (looking-at "References")
|
||||
(insert "\t")
|
||||
(forward-line 1)))
|
||||
(goto-char (point-min))
|
||||
(while (search-forward "," nil t)
|
||||
(replace-match " " t t)))
|
||||
(widen)
|
||||
(set-marker body nil))))
|
||||
|
||||
(defun nnweb-reference-search (search)
|
||||
(prog1
|
||||
(url-insert-file-contents
|
||||
(concat
|
||||
(nnweb-definition 'address)
|
||||
"?"
|
||||
(nnweb-encode-www-form-urlencoded
|
||||
`(("search" . "advanced")
|
||||
("querytext" . ,search)
|
||||
("subj" . "")
|
||||
("name" . "")
|
||||
("login" . "")
|
||||
("host" . "")
|
||||
("organization" . "")
|
||||
("groups" . "")
|
||||
("keywords" . "")
|
||||
("choice" . "Search")
|
||||
("startmonth" . "Jul")
|
||||
("startday" . "25")
|
||||
("startyear" . "1996")
|
||||
("endmonth" . "Aug")
|
||||
("endday" . "24")
|
||||
("endyear" . "1996")
|
||||
("mode" . "Quick")
|
||||
("verbosity" . "Verbose")
|
||||
("ranking" . "Relevance")
|
||||
("first" . "1")
|
||||
("last" . "25")
|
||||
("score" . "50")))))
|
||||
(setq buffer-file-name nil))
|
||||
t)
|
||||
|
||||
;;;
|
||||
;;; Alta Vista
|
||||
;;;
|
||||
|
||||
(defun nnweb-altavista-create-mapping ()
|
||||
"Perform the search and create an number-to-url alist."
|
||||
(save-excursion
|
||||
(set-buffer nnweb-buffer)
|
||||
(erase-buffer)
|
||||
(let ((part 0))
|
||||
(when (funcall (nnweb-definition 'search) nnweb-search part)
|
||||
(let ((i 0)
|
||||
(more t)
|
||||
(case-fold-search t)
|
||||
(active (or (cadr (assoc nnweb-group nnweb-group-alist))
|
||||
(cons 1 0)))
|
||||
subject date from id group
|
||||
map url)
|
||||
(while more
|
||||
;; Go through all the article hits on this page.
|
||||
(goto-char (point-min))
|
||||
(search-forward "<dt>" nil t)
|
||||
(delete-region (point-min) (match-beginning 0))
|
||||
(goto-char (point-min))
|
||||
(while (search-forward "<dt>" nil t)
|
||||
(replace-match "\n<blubb>"))
|
||||
(nnweb-decode-entities)
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward "<blubb>.*href=\"\\([^\"]+\\)\"><strong>\\([^>]*\\)</strong></a><dd>\\([^-]+\\)- <b>\\([^<]+\\)<.*href=\"news:\\([^\"]+\\)\">.*\">\\(.+\\)</a><P>"
|
||||
nil t)
|
||||
(setq url (match-string 1)
|
||||
subject (match-string 2)
|
||||
date (match-string 3)
|
||||
group (match-string 4)
|
||||
id (concat "<" (match-string 5) ">")
|
||||
from (match-string 6))
|
||||
(incf i)
|
||||
(unless (nnweb-get-hashtb url)
|
||||
(push
|
||||
(list
|
||||
(incf (cdr active))
|
||||
(make-full-mail-header
|
||||
(cdr active) (concat "(" group ") " subject) from date
|
||||
id nil 0 0 url))
|
||||
map)
|
||||
(nnweb-set-hashtb (cadar map) (car map))))
|
||||
;; See if we want more.
|
||||
(when (or (not nnweb-articles)
|
||||
(>= i nnweb-max-hits)
|
||||
(not (funcall (nnweb-definition 'search)
|
||||
nnweb-search (incf part))))
|
||||
(setq more nil)))
|
||||
;; Return the articles in the right order.
|
||||
(setq nnweb-articles
|
||||
(sort (nconc nnweb-articles map)
|
||||
(lambda (s1 s2) (< (car s1) (car s2))))))))))
|
||||
|
||||
(defun nnweb-altavista-wash-article ()
|
||||
(goto-char (point-min))
|
||||
(let ((case-fold-search t))
|
||||
(when (re-search-forward "^<strong>" nil t)
|
||||
(delete-region (point-min) (match-beginning 0)))
|
||||
(goto-char (point-min))
|
||||
(while (looking-at "<strong>\\([^ ]+\\) +</strong> +\\(.*\\)$")
|
||||
(replace-match "\\1: \\2" t)
|
||||
(forward-line 1))
|
||||
(when (re-search-backward "^References:" nil t)
|
||||
(narrow-to-region (point) (progn (forward-line 1) (point)))
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward "<A.*\\?id@\\([^\"]+\\)\">[0-9]+</A>" nil t)
|
||||
(replace-match "<\\1> " t)))
|
||||
(widen)
|
||||
(nnweb-remove-markup)))
|
||||
|
||||
(defun nnweb-altavista-search (search &optional part)
|
||||
(prog1
|
||||
(url-insert-file-contents
|
||||
(concat
|
||||
(nnweb-definition 'address)
|
||||
"?"
|
||||
(nnweb-encode-www-form-urlencoded
|
||||
`(("pg" . "aq")
|
||||
("what" . "news")
|
||||
,@(when part `(("stq" . ,(int-to-string (* part 30)))))
|
||||
("fmt" . "d")
|
||||
("q" . ,search)
|
||||
("r" . "")
|
||||
("d0" . "")
|
||||
("d1" . "")))))
|
||||
(setq buffer-file-name nil)))
|
||||
|
||||
(provide 'nnweb)
|
||||
|
||||
;;; nnweb.el ends here
|
||||
199
lisp/gnus/parse-time.el
Normal file
199
lisp/gnus/parse-time.el
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
;;; parse-time.el --- Parsing time strings
|
||||
|
||||
;; Copyright (C) 1996 by Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Erik Naggum <erik@arcana.naggum.no>
|
||||
;; Keywords: util
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs; see the file COPYING. If not, write to
|
||||
;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; With the introduction of the `encode-time', `decode-time', and
|
||||
;; `format-time-string' functions, dealing with time became simpler in
|
||||
;; Emacs. However, parsing time strings is still largely a matter of
|
||||
;; heuristics and no common interface has been designed.
|
||||
|
||||
;; `parse-time-string' parses a time in a string and returns a list of 9
|
||||
;; values, just like `decode-time', where unspecified elements in the
|
||||
;; string are returned as nil. `encode-time' may be applied on these
|
||||
;; valuse to obtain an internal time value.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'cl) ;and ah ain't kiddin' 'bout it
|
||||
|
||||
(put 'parse-time-syntax 'char-table-extra-slots 0)
|
||||
|
||||
(defvar parse-time-syntax (make-char-table 'parse-time-syntax))
|
||||
(defvar parse-time-digits (make-char-table 'parse-time-syntax))
|
||||
|
||||
;; Byte-compiler warnings
|
||||
(defvar elt)
|
||||
(defvar val)
|
||||
|
||||
(unless (aref parse-time-digits ?0)
|
||||
(loop for i from ?0 to ?9
|
||||
do (set-char-table-range parse-time-digits i (- i ?0))))
|
||||
|
||||
(unless (aref parse-time-syntax ?0)
|
||||
(loop for i from ?0 to ?9
|
||||
do (set-char-table-range parse-time-syntax i ?0))
|
||||
(loop for i from ?A to ?Z
|
||||
do (set-char-table-range parse-time-syntax i ?A))
|
||||
(loop for i from ?a to ?z
|
||||
do (set-char-table-range parse-time-syntax i ?a))
|
||||
(set-char-table-range parse-time-syntax ?+ 1)
|
||||
(set-char-table-range parse-time-syntax ?- -1)
|
||||
(set-char-table-range parse-time-syntax ?: ?d)
|
||||
)
|
||||
|
||||
(defsubst digit-char-p (char)
|
||||
(aref parse-time-digits char))
|
||||
|
||||
(defsubst parse-time-string-chars (char)
|
||||
(aref parse-time-syntax char))
|
||||
|
||||
(put 'parse-error 'error-conditions '(parse-error error))
|
||||
(put 'parse-error 'error-message "Parsing error")
|
||||
|
||||
(defsubst parse-integer (string &optional start end)
|
||||
"[CL] Parse and return the integer in STRING, or nil if none."
|
||||
(let ((integer 0)
|
||||
(digit 0)
|
||||
(index (or start 0))
|
||||
(end (or end (length string))))
|
||||
(when (< index end)
|
||||
(let ((sign (aref string index)))
|
||||
(if (or (eq sign ?+) (eq sign ?-))
|
||||
(setq sign (parse-time-string-chars sign)
|
||||
index (1+ index))
|
||||
(setq sign 1))
|
||||
(while (and (< index end)
|
||||
(setq digit (digit-char-p (aref string index))))
|
||||
(setq integer (+ (* integer 10) digit)
|
||||
index (1+ index)))
|
||||
(if (/= index end)
|
||||
(signal 'parse-error `("not an integer" ,(substring string (or start 0) end)))
|
||||
(* sign integer))))))
|
||||
|
||||
(defun parse-time-tokenize (string)
|
||||
"Tokenize STRING into substrings."
|
||||
(let ((start nil)
|
||||
(end (length string))
|
||||
(all-digits nil)
|
||||
(list ())
|
||||
(index 0)
|
||||
(c nil))
|
||||
(while (< index end)
|
||||
(while (and (< index end) ;skip invalid characters
|
||||
(not (setq c (parse-time-string-chars (aref string index)))))
|
||||
(incf index))
|
||||
(setq start index all-digits (eq c ?0))
|
||||
(while (and (< (incf index) end) ;scan valid characters
|
||||
(setq c (parse-time-string-chars (aref string index))))
|
||||
(setq all-digits (and all-digits (eq c ?0))))
|
||||
(if (<= index end)
|
||||
(push (if all-digits (parse-integer string start index)
|
||||
(substring string start index))
|
||||
list)))
|
||||
(nreverse list)))
|
||||
|
||||
(defvar parse-time-months '(("Jan" . 1) ("Feb" . 2) ("Mar" . 3)
|
||||
("Apr" . 4) ("May" . 5) ("Jun" . 6)
|
||||
("Jul" . 7) ("Aug" . 8) ("Sep" . 9)
|
||||
("Oct" . 10) ("Nov" . 11) ("Dec" . 12)))
|
||||
(defvar parse-time-weekdays '(("Sun" . 0) ("Mon" . 1) ("Tue" . 2)
|
||||
("Wed" . 3) ("Thu" . 4) ("Fri" . 5) ("Sat" . 6)))
|
||||
(defvar parse-time-zoneinfo `(("Z" 0) ("UT" 0) ("GMT" 0)
|
||||
("PST" ,(* -8 3600)) ("PDT" ,(* -7 3600) t)
|
||||
("MST" ,(* -7 3600)) ("MDT" ,(* -6 3600) t)
|
||||
("CST" ,(* -6 3600)) ("CDT" ,(* -5 3600) t)
|
||||
("EST" ,(* -5 3600)) ("EDT" ,(* -4 3600) t))
|
||||
"(zoneinfo seconds-off daylight-savings-time-p)")
|
||||
|
||||
(defvar parse-time-rules
|
||||
`(((6) parse-time-weekdays)
|
||||
((3) (1 31))
|
||||
((4) parse-time-months)
|
||||
((5) (1970 2038))
|
||||
((2 1 0)
|
||||
,#'(lambda () (and (stringp elt)
|
||||
(= (length elt) 8)
|
||||
(= (aref elt 2) ?:)
|
||||
(= (aref elt 5) ?:)))
|
||||
[0 2] [3 5] [6 8])
|
||||
((8 7) parse-time-zoneinfo
|
||||
,#'(lambda () (car val))
|
||||
,#'(lambda () (cadr val)))
|
||||
((8)
|
||||
,#'(lambda ()
|
||||
(and (stringp elt)
|
||||
(= 5 (length elt))
|
||||
(or (= (aref elt 0) ?+) (= (aref elt 0) ?-))))
|
||||
,#'(lambda () (* 60 (+ (parse-integer elt 3 5)
|
||||
(* 60 (parse-integer elt 1 3)))
|
||||
(if (= (aref elt 0) ?-) -1 1))))
|
||||
((5 4 3)
|
||||
,#'(lambda () (and (stringp elt) (= (length elt) 10) (= (aref elt 4) ?-) (= (aref elt 7) ?-)))
|
||||
[0 4] [5 7] [8 10])
|
||||
((2 1)
|
||||
,#'(lambda () (and (stringp elt) (= (length elt) 5) (= (aref elt 2) ?:)))
|
||||
[0 2] [3 5])
|
||||
((5) (70 99) ,#'(lambda () (+ 1900 elt))))
|
||||
"(slots predicate extractor...)")
|
||||
|
||||
(defun parse-time-string (string)
|
||||
"Parse the time-string STRING into (SEC MIN HOUR DAY MON YEAR DOW DST TZ).
|
||||
The values are identical to those of `decode-time', but any values that are
|
||||
unknown are returned as nil."
|
||||
(let ((time (list nil nil nil nil nil nil nil nil nil nil))
|
||||
(temp (parse-time-tokenize string)))
|
||||
(while temp
|
||||
(let ((elt (pop temp))
|
||||
(rules parse-time-rules)
|
||||
(exit nil))
|
||||
(while (and (not (null rules)) (not exit))
|
||||
(let* ((rule (pop rules))
|
||||
(slots (pop rule))
|
||||
(predicate (pop rule))
|
||||
(val))
|
||||
(if (and (not (nth (car slots) time)) ;not already set
|
||||
(setq val (cond ((and (consp predicate)
|
||||
(not (eq (car predicate) 'lambda)))
|
||||
(and (numberp elt)
|
||||
(<= (car predicate) elt)
|
||||
(<= elt (cadr predicate))
|
||||
elt))
|
||||
((symbolp predicate)
|
||||
(cdr (assoc elt (symbol-value predicate))))
|
||||
((funcall predicate)))))
|
||||
(progn
|
||||
(setq exit t)
|
||||
(while slots
|
||||
(let ((new-val (and rule
|
||||
(let ((this (pop rule)))
|
||||
(if (vectorp this)
|
||||
(parse-integer elt (aref this 0) (aref this 1))
|
||||
(funcall this))))))
|
||||
(rplaca (nthcdr (pop slots) time) (or new-val val))))))))))
|
||||
time))
|
||||
|
||||
(provide 'parse-time)
|
||||
|
||||
;;; parse-time.el ends here
|
||||
443
lisp/gnus/pop3.el
Normal file
443
lisp/gnus/pop3.el
Normal file
|
|
@ -0,0 +1,443 @@
|
|||
;;; pop3.el --- Post Office Protocol (RFC 1460) interface
|
||||
|
||||
;; Copyright (C) 1996, Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Richard L. Pieri <ratinox@peorth.gweep.net>
|
||||
;; Keywords: mail, pop3
|
||||
;; Version: 1.3e
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; Most of the standard Post Office Protocol version 3 (RFC 1460) commands
|
||||
;; are implemented. The LIST command has not been implemented due to lack
|
||||
;; of actual usefulness.
|
||||
;; The optional POP3 command TOP has not been implemented.
|
||||
|
||||
;; This program was inspired by Kyle E. Jones's vm-pop program.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'mail-utils)
|
||||
(provide 'pop3)
|
||||
|
||||
(defconst pop3-version "1.3c")
|
||||
|
||||
(defvar pop3-maildrop (or user-login-name (getenv "LOGNAME") (getenv "USER") nil)
|
||||
"*POP3 maildrop.")
|
||||
(defvar pop3-mailhost (or (getenv "MAILHOST") nil)
|
||||
"*POP3 mailhost.")
|
||||
(defvar pop3-port 110
|
||||
"*POP3 port.")
|
||||
|
||||
(defvar pop3-password-required t
|
||||
"*Non-nil if a password is required when connecting to POP server.")
|
||||
(defvar pop3-password nil
|
||||
"*Password to use when connecting to POP server.")
|
||||
|
||||
(defvar pop3-authentication-scheme 'pass
|
||||
"*POP3 authentication scheme.
|
||||
Defaults to 'pass, for the standard USER/PASS authentication. Other valid
|
||||
values are 'apop.")
|
||||
|
||||
(defvar pop3-timestamp nil
|
||||
"Timestamp returned when initially connected to the POP server.
|
||||
Used for APOP authentication.")
|
||||
|
||||
(defvar pop3-read-point nil)
|
||||
(defvar pop3-debug nil)
|
||||
|
||||
(defun pop3-movemail (&optional crashbox)
|
||||
"Transfer contents of a maildrop to the specified CRASHBOX."
|
||||
(or crashbox (setq crashbox (expand-file-name "~/.crashbox")))
|
||||
(let* ((process (pop3-open-server pop3-mailhost pop3-port))
|
||||
(crashbuf (get-buffer-create " *pop3-retr*"))
|
||||
(n 1)
|
||||
message-count)
|
||||
;; for debugging only
|
||||
(if pop3-debug (switch-to-buffer (process-buffer process)))
|
||||
(cond ((equal 'apop pop3-authentication-scheme)
|
||||
(pop3-apop process pop3-maildrop))
|
||||
((equal 'pass pop3-authentication-scheme)
|
||||
(pop3-user process pop3-maildrop)
|
||||
(pop3-pass process))
|
||||
(t (error "Invalid POP3 authentication scheme.")))
|
||||
(setq message-count (car (pop3-stat process)))
|
||||
(while (<= n message-count)
|
||||
(message (format "Retrieving message %d of %d from %s..."
|
||||
n message-count pop3-mailhost))
|
||||
(pop3-retr process n crashbuf)
|
||||
(save-excursion
|
||||
(set-buffer crashbuf)
|
||||
(append-to-file (point-min) (point-max) crashbox)
|
||||
(set-buffer (process-buffer process))
|
||||
(while (> (buffer-size) 5000)
|
||||
(goto-char (point-min))
|
||||
(forward-line 50)
|
||||
(delete-region (point-min) (point))))
|
||||
(pop3-dele process n)
|
||||
(setq n (+ 1 n))
|
||||
(if pop3-debug (sit-for 1) (sit-for 0.1))
|
||||
)
|
||||
(pop3-quit process)
|
||||
(kill-buffer crashbuf)
|
||||
)
|
||||
)
|
||||
|
||||
(defun pop3-open-server (mailhost port)
|
||||
"Open TCP connection to MAILHOST.
|
||||
Returns the process associated with the connection."
|
||||
(let ((process-buffer
|
||||
(get-buffer-create (format "trace of POP session to %s" mailhost)))
|
||||
(process))
|
||||
(save-excursion
|
||||
(set-buffer process-buffer)
|
||||
(erase-buffer))
|
||||
(setq process
|
||||
(open-network-stream "POP" process-buffer mailhost port))
|
||||
(setq pop3-read-point (point-min))
|
||||
(let ((response (pop3-read-response process t)))
|
||||
(setq pop3-timestamp
|
||||
(substring response (or (string-match "<" response) 0)
|
||||
(+ 1 (or (string-match ">" response) -1)))))
|
||||
process
|
||||
))
|
||||
|
||||
;; Support functions
|
||||
|
||||
(defun pop3-process-filter (process output)
|
||||
(save-excursion
|
||||
(set-buffer (process-buffer process))
|
||||
(goto-char (point-max))
|
||||
(insert output)))
|
||||
|
||||
(defun pop3-send-command (process command)
|
||||
(set-buffer (process-buffer process))
|
||||
(goto-char (point-max))
|
||||
;; (if (= (aref command 0) ?P)
|
||||
;; (insert "PASS <omitted>\r\n")
|
||||
;; (insert command "\r\n"))
|
||||
(setq pop3-read-point (point))
|
||||
(goto-char (point-max))
|
||||
(process-send-string process command)
|
||||
(process-send-string process "\r\n")
|
||||
)
|
||||
|
||||
(defun pop3-read-response (process &optional return)
|
||||
"Read the response from the server.
|
||||
Return the response string if optional second argument is non-nil."
|
||||
(let ((case-fold-search nil)
|
||||
match-end)
|
||||
(save-excursion
|
||||
(set-buffer (process-buffer process))
|
||||
(goto-char pop3-read-point)
|
||||
(while (not (search-forward "\r\n" nil t))
|
||||
(accept-process-output process)
|
||||
(goto-char pop3-read-point))
|
||||
(setq match-end (point))
|
||||
(goto-char pop3-read-point)
|
||||
(if (looking-at "-ERR")
|
||||
(error (buffer-substring (point) (- match-end 2)))
|
||||
(if (not (looking-at "+OK"))
|
||||
(progn (setq pop3-read-point match-end) nil)
|
||||
(setq pop3-read-point match-end)
|
||||
(if return
|
||||
(buffer-substring (point) match-end)
|
||||
t)
|
||||
)))))
|
||||
|
||||
(defun pop3-string-to-list (string &optional regexp)
|
||||
"Chop up a string into a list."
|
||||
(let ((list)
|
||||
(regexp (or regexp " "))
|
||||
(string (if (string-match "\r" string)
|
||||
(substring string 0 (match-beginning 0))
|
||||
string)))
|
||||
(store-match-data nil)
|
||||
(while string
|
||||
(if (string-match regexp string)
|
||||
(setq list (cons (substring string 0 (- (match-end 0) 1)) list)
|
||||
string (substring string (match-end 0)))
|
||||
(setq list (cons string list)
|
||||
string nil)))
|
||||
(nreverse list)))
|
||||
|
||||
(defvar pop3-read-passwd nil)
|
||||
(defun pop3-read-passwd (prompt)
|
||||
(if (not pop3-read-passwd)
|
||||
(if (load "passwd" t)
|
||||
(setq pop3-read-passwd 'read-passwd)
|
||||
(autoload 'ange-ftp-read-passwd "ange-ftp")
|
||||
(setq pop3-read-passwd 'ange-ftp-read-passwd)))
|
||||
(funcall pop3-read-passwd prompt))
|
||||
|
||||
(defun pop3-clean-region (start end)
|
||||
(setq end (set-marker (make-marker) end))
|
||||
(save-excursion
|
||||
(goto-char start)
|
||||
(while (and (< (point) end) (search-forward "\r\n" end t))
|
||||
(replace-match "\n" t t))
|
||||
(goto-char start)
|
||||
(while (and (< (point) end) (re-search-forward "^\\." end t))
|
||||
(replace-match "" t t)
|
||||
(forward-char)))
|
||||
(set-marker end nil))
|
||||
|
||||
(defun pop3-munge-message-separator (start end)
|
||||
"Check to see if a message separator exists. If not, generate one."
|
||||
(save-excursion
|
||||
(save-restriction
|
||||
(narrow-to-region start end)
|
||||
(goto-char (point-min))
|
||||
(if (not (or (looking-at "From .?") ; Unix mail
|
||||
(looking-at "\001\001\001\001\n") ; MMDF
|
||||
(looking-at "BABYL OPTIONS:") ; Babyl
|
||||
))
|
||||
(let ((from (mail-strip-quoted-names (mail-fetch-field "From")))
|
||||
(date (pop3-string-to-list (mail-fetch-field "Date")))
|
||||
(From_))
|
||||
;; sample date formats I have seen
|
||||
;; Date: Tue, 9 Jul 1996 09:04:21 -0400 (EDT)
|
||||
;; Date: 08 Jul 1996 23:22:24 -0400
|
||||
;; should be
|
||||
;; Tue Jul 9 09:04:21 1996
|
||||
(setq date
|
||||
(cond ((string-match "[A-Z]" (nth 0 date))
|
||||
(format "%s %s %s %s %s"
|
||||
(nth 0 date) (nth 2 date) (nth 1 date)
|
||||
(nth 4 date) (nth 3 date)))
|
||||
(t
|
||||
;; this really needs to be better but I don't feel
|
||||
;; like writing a date to day converter.
|
||||
(format "Sun %s %s %s %s"
|
||||
(nth 1 date) (nth 0 date)
|
||||
(nth 3 date) (nth 2 date)))
|
||||
))
|
||||
(setq From_ (format "\nFrom %s %s\n" from date))
|
||||
(while (string-match "," From_)
|
||||
(setq From_ (concat (substring From_ 0 (match-beginning 0))
|
||||
(substring From_ (match-end 0)))))
|
||||
(goto-char (point-min))
|
||||
(insert From_))))))
|
||||
|
||||
;; The Command Set
|
||||
|
||||
;; AUTHORIZATION STATE
|
||||
|
||||
(defun pop3-user (process user)
|
||||
"Send USER information to POP3 server."
|
||||
(pop3-send-command process (format "USER %s" user))
|
||||
(let ((response (pop3-read-response process t)))
|
||||
(if (not (and response (string-match "+OK" response)))
|
||||
(error (format "USER %s not valid." user)))))
|
||||
|
||||
(defun pop3-pass (process)
|
||||
"Send authentication information to the server."
|
||||
(let ((pass pop3-password))
|
||||
(if (and pop3-password-required (not pass))
|
||||
(setq pass
|
||||
(pop3-read-passwd (format "Password for %s: " pop3-maildrop))))
|
||||
(if pass
|
||||
(progn
|
||||
(pop3-send-command process (format "PASS %s" pass))
|
||||
(let ((response (pop3-read-response process t)))
|
||||
(if (not (and response (string-match "+OK" response)))
|
||||
(pop3-quit process)))))
|
||||
))
|
||||
|
||||
(defun pop3-apop (process user)
|
||||
"Send alternate authentication information to the server."
|
||||
(if (not (fboundp 'md5)) (autoload 'md5 "md5"))
|
||||
(let ((pass pop3-password))
|
||||
(if (and pop3-password-required (not pass))
|
||||
(setq pass
|
||||
(pop3-read-passwd (format "Password for %s: " pop3-maildrop))))
|
||||
(if pass
|
||||
(let ((hash (md5 (concat pop3-timestamp pass))))
|
||||
(pop3-send-command process (format "APOP %s %s" user hash))
|
||||
(let ((response (pop3-read-response process t)))
|
||||
(if (not (and response (string-match "+OK" response)))
|
||||
(pop3-quit process)))))
|
||||
))
|
||||
|
||||
;; TRANSACTION STATE
|
||||
|
||||
(defun pop3-stat (process)
|
||||
"Return the number of messages in the maildrop and the maildrop's size."
|
||||
(pop3-send-command process "STAT")
|
||||
(let ((response (pop3-read-response process t)))
|
||||
(list (string-to-int (nth 1 (pop3-string-to-list response)))
|
||||
(string-to-int (nth 2 (pop3-string-to-list response))))
|
||||
))
|
||||
|
||||
(defun pop3-list (process &optional msg)
|
||||
"Scan listing of available messages.
|
||||
This function currently does nothing.")
|
||||
|
||||
(defun pop3-retr (process msg crashbuf)
|
||||
"Retrieve message-id MSG to buffer CRASHBUF."
|
||||
(pop3-send-command process (format "RETR %s" msg))
|
||||
(pop3-read-response process)
|
||||
(let ((start pop3-read-point) end)
|
||||
(save-excursion
|
||||
(set-buffer (process-buffer process))
|
||||
(while (not (re-search-forward "^\\.\r\n" nil t))
|
||||
(accept-process-output process)
|
||||
;; bill@att.com ... to save wear and tear on the heap
|
||||
(if (> (buffer-size) 20000) (sleep-for 1))
|
||||
(if (> (buffer-size) 50000) (sleep-for 1))
|
||||
(if (> (buffer-size) 100000) (sleep-for 1))
|
||||
(if (> (buffer-size) 200000) (sleep-for 1))
|
||||
(if (> (buffer-size) 500000) (sleep-for 1))
|
||||
;; bill@att.com
|
||||
(goto-char start))
|
||||
(setq pop3-read-point (point-marker))
|
||||
;; this code does not seem to work for some POP servers...
|
||||
;; and I cannot figure out why not.
|
||||
; (goto-char (match-beginning 0))
|
||||
; (backward-char 2)
|
||||
; (if (not (looking-at "\r\n"))
|
||||
; (insert "\r\n"))
|
||||
; (re-search-forward "\\.\r\n")
|
||||
(goto-char (match-beginning 0))
|
||||
(setq end (point-marker))
|
||||
(pop3-clean-region start end)
|
||||
(pop3-munge-message-separator start end)
|
||||
(save-excursion
|
||||
(set-buffer crashbuf)
|
||||
(erase-buffer))
|
||||
(copy-to-buffer crashbuf start end)
|
||||
(delete-region start end)
|
||||
)))
|
||||
|
||||
(defun pop3-dele (process msg)
|
||||
"Mark message-id MSG as deleted."
|
||||
(pop3-send-command process (format "DELE %s" msg))
|
||||
(pop3-read-response process))
|
||||
|
||||
(defun pop3-noop (process msg)
|
||||
"No-operation."
|
||||
(pop3-send-command process "NOOP")
|
||||
(pop3-read-response process))
|
||||
|
||||
(defun pop3-last (process)
|
||||
"Return highest accessed message-id number for the session."
|
||||
(pop3-send-command process "LAST")
|
||||
(let ((response (pop3-read-response process t)))
|
||||
(string-to-int (nth 1 (pop3-string-to-list response)))
|
||||
))
|
||||
|
||||
(defun pop3-rset (process)
|
||||
"Remove all delete marks from current maildrop."
|
||||
(pop3-send-command process "RSET")
|
||||
(pop3-read-response process))
|
||||
|
||||
;; UPDATE
|
||||
|
||||
(defun pop3-quit (process)
|
||||
"Close connection to POP3 server.
|
||||
Tell server to remove all messages marked as deleted, unlock the maildrop,
|
||||
and close the connection."
|
||||
(pop3-send-command process "QUIT")
|
||||
(pop3-read-response process t)
|
||||
(if process
|
||||
(save-excursion
|
||||
(set-buffer (process-buffer process))
|
||||
(goto-char (point-max))
|
||||
(delete-process process))))
|
||||
|
||||
;; Summary of POP3 (Post Office Protocol version 3) commands and responses
|
||||
|
||||
;;; AUTHORIZATION STATE
|
||||
|
||||
;; Initial TCP connection
|
||||
;; Arguments: none
|
||||
;; Restrictions: none
|
||||
;; Possible responses:
|
||||
;; +OK [POP3 server ready]
|
||||
|
||||
;; USER name
|
||||
;; Arguments: a server specific user-id (required)
|
||||
;; Restrictions: authorization state [after unsuccessful USER or PASS
|
||||
;; Possible responses:
|
||||
;; +OK [valid user-id]
|
||||
;; -ERR [invalid user-id]
|
||||
|
||||
;; PASS string
|
||||
;; Arguments: a server/user-id specific password (required)
|
||||
;; Restrictions: authorization state, after successful USER
|
||||
;; Possible responses:
|
||||
;; +OK [maildrop locked and ready]
|
||||
;; -ERR [invalid password]
|
||||
;; -ERR [unable to lock maildrop]
|
||||
|
||||
;;; TRANSACTION STATE
|
||||
|
||||
;; STAT
|
||||
;; Arguments: none
|
||||
;; Restrictions: transaction state
|
||||
;; Possible responses:
|
||||
;; +OK nn mm [# of messages, size of maildrop]
|
||||
|
||||
;; LIST [msg]
|
||||
;; Arguments: a message-id (optional)
|
||||
;; Restrictions: transaction state; msg must not be deleted
|
||||
;; Possible responses:
|
||||
;; +OK [scan listing follows]
|
||||
;; -ERR [no such message]
|
||||
|
||||
;; RETR msg
|
||||
;; Arguments: a message-id (required)
|
||||
;; Restrictions: transaction state; msg must not be deleted
|
||||
;; Possible responses:
|
||||
;; +OK [message contents follow]
|
||||
;; -ERR [no such message]
|
||||
|
||||
;; DELE msg
|
||||
;; Arguments: a message-id (required)
|
||||
;; Restrictions: transaction state; msg must not be deleted
|
||||
;; Possible responses:
|
||||
;; +OK [message deleted]
|
||||
;; -ERR [no such message]
|
||||
|
||||
;; NOOP
|
||||
;; Arguments: none
|
||||
;; Restrictions: transaction state
|
||||
;; Possible responses:
|
||||
;; +OK
|
||||
|
||||
;; LAST
|
||||
;; Arguments: none
|
||||
;; Restrictions: transaction state
|
||||
;; Possible responses:
|
||||
;; +OK nn [highest numbered message accessed]
|
||||
|
||||
;; RSET
|
||||
;; Arguments: none
|
||||
;; Restrictions: transaction state
|
||||
;; Possible responses:
|
||||
;; +OK [all delete marks removed]
|
||||
|
||||
;;; UPDATE STATE
|
||||
|
||||
;; QUIT
|
||||
;; Arguments: none
|
||||
;; Restrictions: none
|
||||
;; Possible responses:
|
||||
;; +OK [TCP connection closed]
|
||||
109
lisp/gnus/score-mode.el
Normal file
109
lisp/gnus/score-mode.el
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
;;; score-mode.el --- mode for editing Gnus score files
|
||||
;; Copyright (C) 1996 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
|
||||
;; Keywords: news, mail
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'easymenu)
|
||||
(require 'timezone)
|
||||
(eval-when-compile (require 'cl))
|
||||
|
||||
(defvar gnus-score-mode-hook nil
|
||||
"*Hook run in score mode buffers.")
|
||||
|
||||
(defvar gnus-score-menu-hook nil
|
||||
"*Hook run after creating the score mode menu.")
|
||||
|
||||
(defvar gnus-score-edit-exit-function nil
|
||||
"Function run on exit from the score buffer.")
|
||||
|
||||
(defvar gnus-score-mode-map nil)
|
||||
(unless gnus-score-mode-map
|
||||
(setq gnus-score-mode-map (copy-keymap emacs-lisp-mode-map))
|
||||
(define-key gnus-score-mode-map "\C-c\C-c" 'gnus-score-edit-exit)
|
||||
(define-key gnus-score-mode-map "\C-c\C-d" 'gnus-score-edit-insert-date)
|
||||
(define-key gnus-score-mode-map "\C-c\C-p" 'gnus-score-pretty-print))
|
||||
|
||||
;;;###autoload
|
||||
(defun gnus-score-mode ()
|
||||
"Mode for editing Gnus score files.
|
||||
This mode is an extended emacs-lisp mode.
|
||||
|
||||
\\{gnus-score-mode-map}"
|
||||
(interactive)
|
||||
(kill-all-local-variables)
|
||||
(use-local-map gnus-score-mode-map)
|
||||
(gnus-score-make-menu-bar)
|
||||
(set-syntax-table emacs-lisp-mode-syntax-table)
|
||||
(setq major-mode 'gnus-score-mode)
|
||||
(setq mode-name "Score")
|
||||
(lisp-mode-variables nil)
|
||||
(make-local-variable 'gnus-score-edit-exit-function)
|
||||
(run-hooks 'emacs-lisp-mode-hook 'gnus-score-mode-hook))
|
||||
|
||||
(defun gnus-score-make-menu-bar ()
|
||||
(unless (boundp 'gnus-score-menu)
|
||||
(easy-menu-define
|
||||
gnus-score-menu gnus-score-mode-map ""
|
||||
'("Score"
|
||||
["Exit" gnus-score-edit-exit t]
|
||||
["Insert date" gnus-score-edit-insert-date t]
|
||||
["Format" gnus-score-pretty-print t]))
|
||||
(run-hooks 'gnus-score-menu-hook)))
|
||||
|
||||
(defun gnus-score-edit-insert-date ()
|
||||
"Insert date in numerical format."
|
||||
(interactive)
|
||||
(princ (gnus-score-day-number (current-time)) (current-buffer)))
|
||||
|
||||
(defun gnus-score-pretty-print ()
|
||||
"Format the current score file."
|
||||
(interactive)
|
||||
(goto-char (point-min))
|
||||
(let ((form (read (current-buffer))))
|
||||
(erase-buffer)
|
||||
(pp form (current-buffer)))
|
||||
(goto-char (point-min)))
|
||||
|
||||
(defun gnus-score-edit-exit ()
|
||||
"Stop editing the score file."
|
||||
(interactive)
|
||||
(unless (file-exists-p (file-name-directory (buffer-file-name)))
|
||||
(make-directory (file-name-directory (buffer-file-name)) t))
|
||||
(save-buffer)
|
||||
(bury-buffer (current-buffer))
|
||||
(let ((buf (current-buffer)))
|
||||
(when gnus-score-edit-exit-function
|
||||
(funcall gnus-score-edit-exit-function))
|
||||
(when (eq buf (current-buffer))
|
||||
(switch-to-buffer (other-buffer (current-buffer))))))
|
||||
|
||||
(defun gnus-score-day-number (time)
|
||||
(let ((dat (decode-time time)))
|
||||
(timezone-absolute-from-gregorian
|
||||
(nth 4 dat) (nth 3 dat) (nth 5 dat))))
|
||||
|
||||
(provide 'score-mode)
|
||||
|
||||
;;; score-mode.el ends here
|
||||
Loading…
Reference in a new issue