mirror of
https://git.savannah.gnu.org/git/emacs.git
synced 2024-12-28 10:56:36 +00:00
410 lines
14 KiB
EmacsLisp
410 lines
14 KiB
EmacsLisp
;;; nnvirtual.el --- virtual newsgroups access for Gnus
|
||
;; Copyright (C) 1994,95,96 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:
|
||
|
||
;; 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)
|
||
(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.")
|
||
|
||
|
||
|
||
(defconst nnvirtual-version "nnvirtual 1.0")
|
||
|
||
(defvoo nnvirtual-current-group nil)
|
||
(defvoo nnvirtual-component-groups nil)
|
||
(defvoo nnvirtual-mapping nil)
|
||
|
||
(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*")))
|
||
(unfetched (mapcar (lambda (g) (list g))
|
||
nnvirtual-component-groups))
|
||
(system-name (system-name))
|
||
cgroup article result prefix)
|
||
(while articles
|
||
(setq article (assq (pop articles) nnvirtual-mapping))
|
||
(when (and (setq cgroup (cadr article))
|
||
(gnus-check-server
|
||
(gnus-find-method-for-group cgroup) t)
|
||
(gnus-request-group cgroup t))
|
||
(setq prefix (gnus-group-real-prefix cgroup))
|
||
(when (setq result (gnus-retrieve-headers
|
||
(list (caddr article)) cgroup nil))
|
||
(set-buffer nntp-server-buffer)
|
||
(if (zerop (buffer-size))
|
||
(nconc (assq cgroup unfetched) (list (caddr article)))
|
||
;; 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 (read nntp-server-buffer) (point)))
|
||
(princ (car article) (current-buffer))
|
||
(beginning-of-line)
|
||
(looking-at
|
||
"[^\t]*\t[^\t]*\t[^\t]*\t[^\t]*\t[^\t]*\t[^\t]*\t[^\t]*\t")
|
||
(goto-char (match-end 0))
|
||
(or (search-forward
|
||
"\t" (save-excursion (end-of-line) (point)) t)
|
||
(end-of-line))
|
||
(while (= (char-after (1- (point))) ? )
|
||
(forward-char -1)
|
||
(delete-char 1))
|
||
(if (eolp)
|
||
(progn
|
||
(end-of-line)
|
||
(or (= (char-after (1- (point))) ?\t)
|
||
(insert ?\t))
|
||
(insert "Xref: " system-name " " cgroup ":")
|
||
(princ (caddr article) (current-buffer))
|
||
(insert "\t"))
|
||
(insert "Xref: " system-name " " cgroup ":")
|
||
(princ (caddr article) (current-buffer))
|
||
(insert " ")
|
||
(if (not (string= "" prefix))
|
||
(while (re-search-forward
|
||
"[^ ]+:[0-9]+"
|
||
(save-excursion (end-of-line) (point)) t)
|
||
(save-excursion
|
||
(goto-char (match-beginning 0))
|
||
(insert prefix))))
|
||
(end-of-line)
|
||
(or (= (char-after (1- (point))) ?\t)
|
||
(insert ?\t)))
|
||
(forward-line 1))
|
||
(set-buffer vbuf)
|
||
(goto-char (point-max))
|
||
(insert-buffer-substring nntp-server-buffer)))))
|
||
|
||
;; In case some of the articles have expired or been
|
||
;; cancelled, we have to mark them as read in the
|
||
;; component group.
|
||
(while unfetched
|
||
(when (cdar unfetched)
|
||
(gnus-group-make-articles-read
|
||
(caar unfetched) (sort (cdar unfetched) '<)))
|
||
(setq unfetched (cdr unfetched)))
|
||
|
||
;; 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)
|
||
'nov)
|
||
(kill-buffer vbuf)))))))
|
||
|
||
(deffoo nnvirtual-request-article (article &optional group server buffer)
|
||
(when (and (nnvirtual-possibly-change-server server)
|
||
(numberp article))
|
||
(let* ((amap (assq article nnvirtual-mapping))
|
||
(cgroup (cadr 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
|
||
(if buffer
|
||
(save-excursion
|
||
(set-buffer buffer)
|
||
(gnus-request-article-this-buffer (caddr amap) cgroup))
|
||
(gnus-request-article (caddr 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 nil)
|
||
;; 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
|
||
(unless dont-check
|
||
(nnvirtual-create-mapping))
|
||
(setq nnvirtual-current-group group)
|
||
(let ((len (length nnvirtual-mapping)))
|
||
(nnheader-insert "211 %d 1 %d %s\n" len len group)))))
|
||
|
||
(deffoo nnvirtual-request-type (group &optional article)
|
||
(if (not article)
|
||
'unknown
|
||
(let ((mart (assq article nnvirtual-mapping)))
|
||
(when mart
|
||
(gnus-request-type (cadr mart) (car mart))))))
|
||
|
||
(deffoo nnvirtual-request-update-mark (group article mark)
|
||
(let* ((nart (assq article nnvirtual-mapping))
|
||
(cgroup (cadr nart))
|
||
;; The component group might be a virtual group.
|
||
(nmark (gnus-request-update-mark cgroup (caddr 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 (nnvirtual-possibly-change-server server)
|
||
;; Copy (un)read articles.
|
||
(nnvirtual-update-reads)
|
||
;; We copy the marks from this group to the component
|
||
;; groups here.
|
||
(nnvirtual-update-marked))
|
||
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 (nnvirtual-possibly-change-server server)
|
||
(let ((map nnvirtual-mapping)
|
||
(marks (mapcar (lambda (m) (list (cdr m))) gnus-article-mark-lists))
|
||
reads mr m op)
|
||
;; Go through the mapping.
|
||
(while map
|
||
(unless (nth 3 (setq m (pop map)))
|
||
;; Read article.
|
||
(push (car m) reads))
|
||
;; Copy marks.
|
||
(when (setq mr (nth 4 m))
|
||
(while mr
|
||
(setcdr (setq op (assq (pop mr) marks)) (cons (car m) (cdr op))))))
|
||
;; Compress the marks and the reads.
|
||
(setq mr marks)
|
||
(while mr
|
||
(setcdr (car mr) (gnus-compress-sequence (sort (cdr (pop mr)) '<))))
|
||
(setcar (cddr info) (gnus-compress-sequence (nreverse reads)))
|
||
;; Remove empty marks lists.
|
||
(while (and marks (not (cdar marks)))
|
||
(setq marks (cdr marks)))
|
||
(setq mr marks)
|
||
(while (cdr mr)
|
||
(if (cdadr mr)
|
||
(setq mr (cdr mr))
|
||
(setcdr mr (cddr mr))))
|
||
|
||
;; Enter these new marks into the info of the group.
|
||
(if (nthcdr 3 info)
|
||
(setcar (nthcdr 3 info) marks)
|
||
;; Add the marks lists to the end of the info.
|
||
(when marks
|
||
(setcdr (nthcdr 2 info) (list marks))))
|
||
t)))
|
||
|
||
(deffoo nnvirtual-catchup-group (group &optional server all)
|
||
(nnvirtual-possibly-change-server server)
|
||
(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."
|
||
(let ((mart (assq article nnvirtual-mapping)))
|
||
(when mart
|
||
(cons (cadr mart) (caddr mart)))))
|
||
|
||
|
||
;;; 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-possibly-change-server (server)
|
||
(or (not server)
|
||
(nnoo-current-server-p 'nnvirtual server)
|
||
(nnvirtual-open-server server)))
|
||
|
||
(defun nnvirtual-update-marked ()
|
||
"Copy marks from the virtual group to the component groups."
|
||
(let ((mark-lists gnus-article-mark-lists)
|
||
(marks (gnus-info-marks (gnus-get-info (nnvirtual-current-group))))
|
||
type list mart cgroups)
|
||
(while (setq type (cdr (pop mark-lists)))
|
||
(setq list (gnus-uncompress-range (cdr (assq type marks))))
|
||
(setq cgroups
|
||
(mapcar (lambda (g) (list g)) nnvirtual-component-groups))
|
||
(while list
|
||
(nconc (assoc (cadr (setq mart (assq (pop list) nnvirtual-mapping)))
|
||
cgroups)
|
||
(list (caddr mart))))
|
||
(while cgroups
|
||
(gnus-add-marked-articles
|
||
(caar cgroups) type (cdar cgroups) nil t)
|
||
(gnus-group-update-group (car (pop cgroups)) t)))))
|
||
|
||
(defun nnvirtual-update-reads ()
|
||
"Copy (un)reads from the current group to the component groups."
|
||
(let ((groups (mapcar (lambda (g) (list g)) nnvirtual-component-groups))
|
||
(articles (gnus-list-of-unread-articles
|
||
(nnvirtual-current-group)))
|
||
m)
|
||
(while articles
|
||
(setq m (assq (pop articles) nnvirtual-mapping))
|
||
(nconc (assoc (nth 1 m) groups) (list (nth 2 m))))
|
||
(while groups
|
||
(gnus-update-read-articles (caar groups) (cdr (pop groups))))))
|
||
|
||
(defun nnvirtual-current-group ()
|
||
"Return the prefixed name of the current nnvirtual group."
|
||
(concat "nnvirtual:" nnvirtual-current-group))
|
||
|
||
(defsubst nnvirtual-marks (article marks)
|
||
"Return a list of mark types for ARTICLE."
|
||
(let (out)
|
||
(while marks
|
||
(when (memq article (cdar marks))
|
||
(push (caar marks) out))
|
||
(setq marks (cdr marks)))
|
||
out))
|
||
|
||
(defun nnvirtual-create-mapping ()
|
||
"Create an article mapping for the current group."
|
||
(let* ((div nil)
|
||
m marks list article unreads marks active
|
||
(map (sort
|
||
(apply
|
||
'nconc
|
||
(mapcar
|
||
(lambda (g)
|
||
(when (and (setq active (gnus-activate-group g))
|
||
(> (cdr active) (car active)))
|
||
(setq unreads (gnus-list-of-unread-articles g)
|
||
marks (gnus-uncompress-marks
|
||
(gnus-info-marks (gnus-get-info g))))
|
||
(when gnus-use-cache
|
||
(push (cons 'cache (gnus-cache-articles-in-group g))
|
||
marks))
|
||
(setq div (/ (float (car active))
|
||
(if (zerop (cdr active))
|
||
1 (cdr active))))
|
||
(mapcar (lambda (n)
|
||
(list (* div (- n (car active)))
|
||
g n (and (memq n unreads) t)
|
||
(inline (nnvirtual-marks n marks))))
|
||
(gnus-uncompress-range active))))
|
||
nnvirtual-component-groups))
|
||
(lambda (m1 m2)
|
||
(< (car m1) (car m2)))))
|
||
(i 0))
|
||
(setq nnvirtual-mapping map)
|
||
;; Set the virtual article numbers.
|
||
(while (setq m (pop map))
|
||
(setcar m (setq article (incf i))))))
|
||
|
||
(provide 'nnvirtual)
|
||
|
||
;;; nnvirtual.el ends here
|