;;; gnus-agent.el --- unplugged support for Gnus
-;; Copyright (C) 1997,98,99 Free Software Foundation, Inc.
+;; Copyright (C) 1997, 1998, 1999, 2000, 2001
+;; Free Software Foundation, Inc.
;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
;; This file is part of GNU Emacs.
(require 'gnus-cache)
(require 'nnvirtual)
(require 'gnus-sum)
+(require 'gnus-score)
(eval-when-compile
- (require 'cl)
- (require 'gnus-score))
+ (if (featurep 'xemacs)
+ (require 'itimer)
+ (require 'timer))
+ (require 'cl))
+
+(eval-and-compile
+ (autoload 'gnus-server-update-server "gnus-srvr"))
(defcustom gnus-agent-directory (nnheader-concat gnus-directory "agent/")
"Where the Gnus agent will store its files."
:group 'gnus-agent
:type 'hook)
+;; Extracted from gnus-xmas-redefine in order to preserve user settings
+(when (featurep 'xemacs)
+ (add-hook 'gnus-agent-group-mode-hook 'gnus-xmas-agent-group-menu-add))
+
(defcustom gnus-agent-summary-mode-hook nil
"Hook run in Agent summary minor modes."
:group 'gnus-agent
:type 'hook)
+;; Extracted from gnus-xmas-redefine in order to preserve user settings
+(when (featurep 'xemacs)
+ (add-hook 'gnus-agent-summary-mode-hook 'gnus-xmas-agent-summary-menu-add))
+
(defcustom gnus-agent-server-mode-hook nil
"Hook run in Agent summary minor modes."
:group 'gnus-agent
:type 'hook)
+;; Extracted from gnus-xmas-redefine in order to preserve user settings
+(when (featurep 'xemacs)
+ (add-hook 'gnus-agent-server-mode-hook 'gnus-xmas-agent-server-menu-add))
+
+(defcustom gnus-agent-confirmation-function 'y-or-n-p
+ "Function to confirm when error happens."
+ :version "21.1"
+ :group 'gnus-agent
+ :type 'function)
+
+(defcustom gnus-agent-synchronize-flags 'ask
+ "Indicate if flags are synchronized when you plug in.
+If this is `ask' the hook will query the user."
+ :version "21.1"
+ :type '(choice (const :tag "Always" t)
+ (const :tag "Never" nil)
+ (const :tag "Ask" ask))
+ :group 'gnus-agent)
+
;;; Internal variables
(defvar gnus-agent-history-buffers nil)
(defvar gnus-agent-send-mail-function nil)
(defvar gnus-agent-file-coding-system 'raw-text)
-(defconst gnus-agent-scoreable-headers
- '("subject" "from" "date" "message-id" "references" "chars" "lines" "xref")
- "Headers that are considered when scoring articles for download via the Agent.")
-
;; Dynamic variables
(defvar gnus-headers)
(defvar gnus-score)
(setq gnus-agent t)
(gnus-agent-read-servers)
(gnus-category-read)
- (setq gnus-agent-overview-buffer
- (gnus-get-buffer-create " *Gnus agent overview*"))
- (with-current-buffer gnus-agent-overview-buffer
- (mm-enable-multibyte))
+ (gnus-agent-create-buffer)
(add-hook 'gnus-group-mode-hook 'gnus-agent-mode)
(add-hook 'gnus-summary-mode-hook 'gnus-agent-mode)
(add-hook 'gnus-server-mode-hook 'gnus-agent-mode))
+(defun gnus-agent-create-buffer ()
+ (if (gnus-buffer-live-p gnus-agent-overview-buffer)
+ t
+ (setq gnus-agent-overview-buffer
+ (gnus-get-buffer-create " *Gnus agent overview*"))
+ (with-current-buffer gnus-agent-overview-buffer
+ (mm-enable-multibyte))
+ nil))
+
(gnus-add-shutdown 'gnus-close-agent 'gnus)
(defun gnus-close-agent ()
"Load FILE and do a `read' there."
(with-temp-buffer
(ignore-errors
- (mm-insert-file-contents file)
+ (nnheader-insert-file-contents file)
(goto-char (point-min))
(read (current-buffer)))))
(defun gnus-agent-lib-file (file)
"The full path of the Gnus agent library FILE."
- (concat (gnus-agent-directory) "agent.lib/" file))
+ (expand-file-name file
+ (file-name-as-directory
+ (expand-file-name "agent.lib" (gnus-agent-directory)))))
;;; Fetching setup functions.
(defun gnus-agent-start-fetch ()
"Initialize data structures for efficient fetching."
(gnus-agent-open-history)
- (setq gnus-agent-current-history (gnus-agent-history-buffer)))
+ (setq gnus-agent-current-history (gnus-agent-history-buffer))
+ (gnus-agent-create-buffer))
(defun gnus-agent-stop-fetch ()
"Save all data structures and clean up."
(defmacro gnus-agent-with-fetch (&rest forms)
"Do FORMS safely."
`(unwind-protect
- (progn
+ (let ((gnus-agent-fetching t))
(gnus-agent-start-fetch)
,@forms)
(gnus-agent-stop-fetch)))
"Jc" gnus-enter-category-buffer
"Jj" gnus-agent-toggle-plugged
"Js" gnus-agent-fetch-session
+ "JY" gnus-agent-synchronize-flags
"JS" gnus-group-send-drafts
"Ja" gnus-agent-add-group
"Jr" gnus-agent-remove-group)
(if plugged
(progn
(setq gnus-plugged plugged)
+ (gnus-agent-possibly-synchronize-flags)
(gnus-run-hooks 'gnus-agent-plugged-hook)
(setcar (cdr gnus-agent-mode-status) " Plugged"))
(gnus-agent-close-connections)
\(gnus-agentize)
-This will modify the `gnus-before-startup-hook', `gnus-post-method',
-and `message-send-mail-function' variables, and install the Gnus
-agent minor mode in all Gnus buffers."
+This will modify the `gnus-setup-news-hook', and
+`message-send-mail-function' variables, and install the Gnus agent
+minor mode in all Gnus buffers."
(interactive)
(gnus-open-agent)
(add-hook 'gnus-setup-news-hook 'gnus-agent-queue-setup)
(defun gnus-agent-insert-meta-information (type &optional method)
"Insert meta-information into the message that says how it's to be posted.
-TYPE can be either `mail' or `news'. If the latter METHOD can
+TYPE can be either `mail' or `news'. If the latter, then METHOD can
be a select method."
(save-excursion
(message-remove-header gnus-agent-meta-information-header)
(while (search-backward "\n" nil t)
(replace-match "\\n" t t))))
+(defun gnus-agent-restore-gcc ()
+ "Restore GCC field from saved header."
+ (save-excursion
+ (goto-char (point-min))
+ (while (re-search-forward (concat gnus-agent-gcc-header ":") nil t)
+ (replace-match "Gcc:" 'fixedcase))))
+
+(defun gnus-agent-any-covered-gcc ()
+ (save-restriction
+ (message-narrow-to-headers)
+ (let* ((gcc (mail-fetch-field "gcc" nil t))
+ (methods (and gcc
+ (mapcar 'gnus-inews-group-method
+ (message-unquote-tokens
+ (message-tokenize-header
+ gcc " ,")))))
+ covered)
+ (while (and (not covered) methods)
+ (setq covered
+ (member (car methods) gnus-agent-covered-methods)
+ methods (cdr methods)))
+ covered)))
+
+(defun gnus-agent-possibly-save-gcc ()
+ "Save GCC if Gnus is unplugged."
+ (when (and (not gnus-plugged) (gnus-agent-any-covered-gcc))
+ (save-excursion
+ (goto-char (point-min))
+ (let ((case-fold-search t))
+ (while (re-search-forward "^gcc:" nil t)
+ (replace-match (concat gnus-agent-gcc-header ":") 'fixedcase))))))
+
+(defun gnus-agent-possibly-do-gcc ()
+ "Do GCC if Gnus is plugged."
+ (when (or gnus-plugged (not (gnus-agent-any-covered-gcc)))
+ (gnus-inews-do-gcc)))
+
;;;
;;; Group mode commands
;;;
(setf (cadddr c) (delete group (cadddr c))))))
(gnus-category-write)))
+(defun gnus-agent-synchronize-flags ()
+ "Synchronize unplugged flags with servers."
+ (interactive)
+ (save-excursion
+ (dolist (gnus-command-method gnus-agent-covered-methods)
+ (when (file-exists-p (gnus-agent-lib-file "flags"))
+ (gnus-agent-synchronize-flags-server gnus-command-method)))))
+
+(defun gnus-agent-possibly-synchronize-flags ()
+ "Synchronize flags according to `gnus-agent-synchronize-flags'."
+ (interactive)
+ (save-excursion
+ (dolist (gnus-command-method gnus-agent-covered-methods)
+ (when (file-exists-p (gnus-agent-lib-file "flags"))
+ (gnus-agent-possibly-synchronize-flags-server gnus-command-method)))))
+
+(defun gnus-agent-synchronize-flags-server (method)
+ "Synchronize flags set when unplugged for server."
+ (let ((gnus-command-method method))
+ (when (file-exists-p (gnus-agent-lib-file "flags"))
+ (set-buffer (get-buffer-create " *Gnus Agent flag synchronize*"))
+ (erase-buffer)
+ (nnheader-insert-file-contents (gnus-agent-lib-file "flags"))
+ (if (null (gnus-check-server gnus-command-method))
+ (message "Couldn't open server %s" (nth 1 gnus-command-method))
+ (while (not (eobp))
+ (if (null (eval (read (current-buffer))))
+ (progn (forward-line)
+ (kill-line -1))
+ (write-file (gnus-agent-lib-file "flags"))
+ (error "Couldn't set flags from file %s"
+ (gnus-agent-lib-file "flags"))))
+ (delete-file (gnus-agent-lib-file "flags")))
+ (kill-buffer nil))))
+
+(defun gnus-agent-possibly-synchronize-flags-server (method)
+ "Synchronize flags for server according to `gnus-agent-synchronize-flags'."
+ (when (or (and gnus-agent-synchronize-flags
+ (not (eq gnus-agent-synchronize-flags 'ask)))
+ (and (eq gnus-agent-synchronize-flags 'ask)
+ (gnus-y-or-n-p (format "Synchronize flags on server `%s'? "
+ (cadr method)))))
+ (gnus-agent-synchronize-flags-server method)))
+
;;;
;;; Server mode commands
;;;
(when (member method gnus-agent-covered-methods)
(error "Server already in the agent program"))
(push method gnus-agent-covered-methods)
+ (gnus-server-update-server server)
(gnus-agent-write-servers)
(message "Entered %s into the Agent" server)))
(error "Server not in the agent program"))
(setq gnus-agent-covered-methods
(delete method gnus-agent-covered-methods))
+ (gnus-server-update-server server)
(gnus-agent-write-servers)
(message "Removed %s from the agent" server)))
(defun gnus-agent-write-servers ()
"Write the alist of covered servers."
(gnus-make-directory (nnheader-concat gnus-agent-directory "lib"))
- (with-temp-file (nnheader-concat gnus-agent-directory "lib/servers")
- (prin1 gnus-agent-covered-methods (current-buffer))))
+ (let ((coding-system-for-write nnheader-file-coding-system)
+ (file-name-coding-system nnmail-pathname-coding-system))
+ (with-temp-file (nnheader-concat gnus-agent-directory "lib/servers")
+ (prin1 gnus-agent-covered-methods (current-buffer)))))
;;;
;;; Summary commands
(gnus-agent-method-p gnus-command-method))
(gnus-agent-load-alist gnus-newsgroup-name)
;; First mark all undownloaded articles as undownloaded.
- (let ((articles (append gnus-newsgroup-unreads
- gnus-newsgroup-marked
- gnus-newsgroup-dormant))
- article)
- (while (setq article (pop articles))
- (unless (or (cdr (assq article gnus-agent-article-alist))
- (memq article gnus-newsgroup-downloadable))
- (push article gnus-newsgroup-undownloaded))))
+ (dolist (article (mapcar (lambda (header) (mail-header-number header))
+ gnus-newsgroup-headers))
+ (unless (or (cdr (assq article gnus-agent-article-alist))
+ (memq article gnus-newsgroup-downloadable)
+ (memq article gnus-newsgroup-cached))
+ (push article gnus-newsgroup-undownloaded)))
;; Then mark downloaded downloadable as not-downloadable,
;; if you get my drift.
- (let ((articles gnus-newsgroup-downloadable)
- article)
- (while (setq article (pop articles))
- (when (cdr (assq article gnus-agent-article-alist))
- (setq gnus-newsgroup-downloadable
- (delq article gnus-newsgroup-downloadable))))))))
+ (dolist (article gnus-newsgroup-downloadable)
+ (when (cdr (assq article gnus-agent-article-alist))
+ (setq gnus-newsgroup-downloadable
+ (delq article gnus-newsgroup-downloadable)))))))
(defun gnus-agent-catchup ()
"Mark all undownloaded articles as read."
(funcall function nil new)
(gnus-agent-write-active file new)
(erase-buffer)
- (insert-file-contents-literally file))))
+ (nnheader-insert-file-contents file))))
(defun gnus-agent-write-active (file new)
(let ((orig (gnus-make-hashtable (count-lines (point-min) (point-max))))
(file (gnus-agent-lib-file "active"))
- elem)
+ elem osym)
(when (file-exists-p file)
(with-temp-buffer
- (insert-file-contents file)
+ (nnheader-insert-file-contents file)
(gnus-active-to-gnus-format nil orig))
(mapatoms
(lambda (sym)
(when (and sym (boundp sym))
- (if (setq elem (symbol-value (intern (symbol-name sym) orig)))
+ (if (and (boundp (setq osym (intern (symbol-name sym) orig)))
+ (setq elem (symbol-value osym)))
(setcdr elem (cdr (symbol-value sym)))
(set (intern (symbol-name sym) orig) (symbol-value sym)))))
new))
(gnus-make-directory (file-name-directory file))
(let ((coding-system-for-write gnus-agent-file-coding-system))
- (gnus-write-active-file file orig))))
+ ;; The hashtable contains real names of groups, no more prefix
+ ;; removing, so set `full' to `t'.
+ (gnus-write-active-file file orig t))))
(defun gnus-agent-save-groups (method)
(gnus-agent-save-active-1 method 'gnus-groups-to-gnus-format))
(defun gnus-agent-save-group-info (method group active)
(when (gnus-agent-method-p method)
(let* ((gnus-command-method method)
- (file (gnus-agent-lib-file "active")))
+ (coding-system-for-write nnheader-file-coding-system)
+ (file-name-coding-system nnmail-pathname-coding-system)
+ (file (gnus-agent-lib-file "active"))
+ oactive)
(gnus-make-directory (file-name-directory file))
(with-temp-file file
+ ;; Emacs got problem to match non-ASCII group in multibyte buffer.
+ (mm-disable-multibyte)
(when (file-exists-p file)
- (mm-insert-file-contents file))
+ (nnheader-insert-file-contents file))
(goto-char (point-min))
(when (re-search-forward
(concat "^" (regexp-quote group) " ") nil t)
+ (save-excursion
+ (save-restriction
+ (narrow-to-region (match-beginning 0)
+ (progn
+ (forward-line 1)
+ (point)))
+ (setq oactive (car (nnmail-parse-active)))))
(gnus-delete-line))
- (insert group " " (number-to-string (cdr active)) " "
- (number-to-string (car active)) " y\n")))))
+ (insert (format "%S %d %d y\n" (intern group)
+ (cdr active)
+ (or (car oactive) (car active))))
+ (goto-char (point-max))
+ (while (search-backward "\\." nil t)
+ (delete-char 1))))))
(defun gnus-agent-group-path (group)
"Translate GROUP into a path."
(if nnmail-use-long-file-names
(gnus-group-real-name group)
- (nnheader-replace-chars-in-string
- (nnheader-translate-file-chars (gnus-group-real-name group))
- ?. ?/)))
+ (nnheader-translate-file-chars
+ (nnheader-replace-chars-in-string
+ (nnheader-replace-duplicate-chars-in-string
+ (nnheader-replace-chars-in-string
+ (gnus-group-real-name group)
+ ?/ ?_)
+ ?. ?_)
+ ?. ?/))))
\f