1 ;;; gnus-agent.el --- unplugged support for Gnus
2 ;; Copyright (C) 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004
3 ;; Free Software Foundation, Inc.
5 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
6 ;; This file is part of GNU Emacs.
8 ;; GNU Emacs is free software; you can redistribute it and/or modify
9 ;; it under the terms of the GNU General Public License as published by
10 ;; the Free Software Foundation; either version 2, or (at your option)
13 ;; GNU Emacs is distributed in the hope that it will be useful,
14 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
15 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 ;; GNU General Public License for more details.
18 ;; You should have received a copy of the GNU General Public License
19 ;; along with GNU Emacs; see the file COPYING. If not, write to the
20 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21 ;; Boston, MA 02111-1307, USA.
36 (if (featurep 'xemacs)
42 (autoload 'gnus-server-update-server "gnus-srvr")
43 (autoload 'gnus-agent-customize-category "gnus-cus")
46 (defcustom gnus-agent-directory (nnheader-concat gnus-directory "agent/")
47 "Where the Gnus agent will store its files."
51 (defcustom gnus-agent-plugged-hook nil
52 "Hook run when plugging into the network."
56 (defcustom gnus-agent-unplugged-hook nil
57 "Hook run when unplugging from the network."
61 (defcustom gnus-agent-fetched-hook nil
62 "Hook run when finished fetching articles."
66 (defcustom gnus-agent-handle-level gnus-level-subscribed
67 "Groups on levels higher than this variable will be ignored by the Agent."
71 (defcustom gnus-agent-expire-days 7
72 "Read articles older than this will be expired.
73 If you wish to disable Agent expiring, see `gnus-agent-enable-expiration'."
75 :type '(number :tag "days"))
77 (defcustom gnus-agent-expire-all nil
78 "If non-nil, also expire unread, ticked and dormant articles.
79 If nil, only read articles will be expired."
83 (defcustom gnus-agent-group-mode-hook nil
84 "Hook run in Agent group minor modes."
88 ;; Extracted from gnus-xmas-redefine in order to preserve user settings
89 (when (featurep 'xemacs)
90 (add-hook 'gnus-agent-group-mode-hook 'gnus-xmas-agent-group-menu-add))
92 (defcustom gnus-agent-summary-mode-hook nil
93 "Hook run in Agent summary minor modes."
97 ;; Extracted from gnus-xmas-redefine in order to preserve user settings
98 (when (featurep 'xemacs)
99 (add-hook 'gnus-agent-summary-mode-hook 'gnus-xmas-agent-summary-menu-add))
101 (defcustom gnus-agent-server-mode-hook nil
102 "Hook run in Agent summary minor modes."
106 ;; Extracted from gnus-xmas-redefine in order to preserve user settings
107 (when (featurep 'xemacs)
108 (add-hook 'gnus-agent-server-mode-hook 'gnus-xmas-agent-server-menu-add))
110 (defcustom gnus-agent-confirmation-function 'y-or-n-p
111 "Function to confirm when error happens."
116 (defcustom gnus-agent-synchronize-flags nil
117 "Indicate if flags are synchronized when you plug in.
118 If this is `ask' the hook will query the user."
120 :type '(choice (const :tag "Always" t)
121 (const :tag "Never" nil)
122 (const :tag "Ask" ask))
125 (defcustom gnus-agent-go-online 'ask
126 "Indicate if offline servers go online when you plug in.
127 If this is `ask' the hook will query the user."
129 :type '(choice (const :tag "Always" t)
130 (const :tag "Never" nil)
131 (const :tag "Ask" ask))
134 (defcustom gnus-agent-mark-unread-after-downloaded t
135 "Indicate whether to mark articles unread after downloaded."
140 (defcustom gnus-agent-download-marks '(download)
141 "Marks for downloading."
143 :type '(repeat (symbol :tag "Mark"))
146 (defcustom gnus-agent-consider-all-articles nil
147 "When non-nil, the agent will let the agent predicate decide
148 whether articles need to be downloaded or not, for all articles. When
149 nil, the default, the agent will only let the predicate decide
150 whether unread articles are downloaded or not. If you enable this,
151 groups with large active ranges may open slower and you may also want
152 to look into the agent expiry settings to block the expiration of
153 read articles as they would just be downloaded again."
158 (defcustom gnus-agent-max-fetch-size 10000000 ;; 10 Mb
159 "Chunk size for `gnus-agent-fetch-session'.
160 The function will split its article fetches into chunks smaller than
165 (defcustom gnus-agent-enable-expiration 'ENABLE
166 "The default expiration state for each group.
167 When set to ENABLE, the default, `gnus-agent-expire' will expire old
168 contents from a group's local storage. This value may be overridden
169 to disable expiration in specific categories, topics, and groups. Of
170 course, you could change gnus-agent-enable-expiration to DISABLE then
171 enable expiration per categories, topics, and groups."
173 :type '(radio (const :format "Enable " ENABLE)
174 (const :format "Disable " DISABLE)))
176 (defcustom gnus-agent-expire-unagentized-dirs t
177 "*Whether expiration should expire in unagentized directories.
178 Have gnus-agent-expire scan the directories under
179 \(gnus-agent-directory) for groups that are no longer agentized.
180 When found, offer to remove them."
184 (defcustom gnus-agent-auto-agentize-methods '(nntp nnimap)
185 "Initially, all servers from these methods are agentized.
186 The user may remove or add servers using the Server buffer.
187 See Info node `(gnus)Server Buffer'."
188 :type '(repeat symbol)
191 (defcustom gnus-agent-queue-mail t
192 "Whether and when outgoing mail should be queued by the agent.
193 When `always', always queue outgoing mail. When nil, never
194 queue. Otherwise, queue if and only if unplugged."
196 :type '(radio (const :format "Always" always)
197 (const :format "Never" nil)
198 (const :format "When plugged" t)))
200 (defcustom gnus-agent-prompt-send-queue nil
201 "If non-nil, `gnus-group-send-queue' will prompt if called when
206 ;;; Internal variables
208 (defvar gnus-agent-history-buffers nil)
209 (defvar gnus-agent-buffer-alist nil)
210 (defvar gnus-agent-article-alist nil
211 "An assoc list identifying the articles whose headers have been fetched.
212 If successfully fetched, these headers will be stored in the group's overview
213 file. The key of each assoc pair is the article ID, the value of each assoc
214 pair is a flag indicating whether the identified article has been downloaded
215 \(gnus-agent-fetch-articles sets the value to the day of the download).
217 1) The last element of this list can not be expired as some
218 routines (for example, get-agent-fetch-headers) use the last
219 value to track which articles have had their headers retrieved.
220 2) The function `gnus-agent-regenerate' may destructively modify the value.")
221 (defvar gnus-agent-group-alist nil)
222 (defvar gnus-category-alist nil)
223 (defvar gnus-agent-current-history nil)
224 (defvar gnus-agent-overview-buffer nil)
225 (defvar gnus-category-predicate-cache nil)
226 (defvar gnus-category-group-cache nil)
227 (defvar gnus-agent-spam-hashtb nil)
228 (defvar gnus-agent-file-name nil)
229 (defvar gnus-agent-send-mail-function nil)
230 (defvar gnus-agent-file-coding-system 'raw-text)
231 (defvar gnus-agent-file-loading-cache nil)
232 (defvar gnus-agent-total-fetched-hashtb nil)
233 (defvar gnus-agent-inhibit-update-total-fetched-for nil)
234 (defvar gnus-agent-need-update-total-fetched-for nil)
237 (defvar gnus-headers)
244 (defun gnus-open-agent ()
246 (gnus-agent-read-servers)
248 (gnus-agent-create-buffer)
249 (add-hook 'gnus-group-mode-hook 'gnus-agent-mode)
250 (add-hook 'gnus-summary-mode-hook 'gnus-agent-mode)
251 (add-hook 'gnus-server-mode-hook 'gnus-agent-mode))
253 (defun gnus-agent-create-buffer ()
254 (if (gnus-buffer-live-p gnus-agent-overview-buffer)
256 (setq gnus-agent-overview-buffer
257 (gnus-get-buffer-create " *Gnus agent overview*"))
258 (with-current-buffer gnus-agent-overview-buffer
259 (mm-enable-multibyte))
262 (gnus-add-shutdown 'gnus-close-agent 'gnus)
264 (defun gnus-close-agent ()
265 (setq gnus-category-predicate-cache nil
266 gnus-category-group-cache nil
267 gnus-agent-spam-hashtb nil)
268 (gnus-kill-buffer gnus-agent-overview-buffer))
271 ;;; Utility functions
274 (defmacro gnus-agent-with-refreshed-group (group &rest body)
275 "Performs the body then updates the group's line in the group
276 buffer. Automatically blocks multiple updates due to recursion."
277 `(prog1 (let ((gnus-agent-inhibit-update-total-fetched-for t)) ,@body)
278 (when (and gnus-agent-need-update-total-fetched-for
279 (not gnus-agent-inhibit-update-total-fetched-for))
281 (set-buffer gnus-group-buffer)
282 (setq gnus-agent-need-update-total-fetched-for nil)
283 (gnus-group-update-group ,group t)))))
285 (defun gnus-agent-read-file (file)
286 "Load FILE and do a `read' there."
289 (nnheader-insert-file-contents file)
290 (goto-char (point-min))
291 (read (current-buffer)))))
293 (defsubst gnus-agent-method ()
294 (concat (symbol-name (car gnus-command-method)) "/"
295 (if (equal (cadr gnus-command-method) "")
297 (cadr gnus-command-method))))
299 (defsubst gnus-agent-directory ()
300 "The name of the Gnus agent directory."
301 (nnheader-concat gnus-agent-directory
302 (nnheader-translate-file-chars (gnus-agent-method)) "/"))
304 (defun gnus-agent-lib-file (file)
305 "The full name of the Gnus agent library FILE."
306 (expand-file-name file
307 (file-name-as-directory
308 (expand-file-name "agent.lib" (gnus-agent-directory)))))
310 (defun gnus-agent-cat-set-property (category property value)
312 (setcdr (or (assq property category)
313 (let ((cell (cons property nil)))
314 (setcdr category (cons cell (cdr category)))
316 (let ((category category))
317 (while (cond ((eq property (caadr category))
318 (setcdr category (cddr category))
321 (setq category (cdr category)))))))
325 (defmacro gnus-agent-cat-defaccessor (name prop-name)
326 "Define accessor and setter methods for manipulating a list of the form
327 \(NAME (PROPERTY1 VALUE1) ... (PROPERTY_N VALUE_N)).
328 Given the call (gnus-agent-cat-defaccessor func PROPERTY1), the list may be
329 manipulated as follows:
330 (func LIST): Returns VALUE1
331 (setf (func LIST) NEW_VALUE1): Replaces VALUE1 with NEW_VALUE1."
332 `(progn (defmacro ,name (category)
333 (list (quote cdr) (list (quote assq)
334 (quote (quote ,prop-name)) category)))
336 (define-setf-method ,name (category)
337 (let* ((--category--temp-- (make-symbol "--category--"))
338 (--value--temp-- (make-symbol "--value--")))
339 (list (list --category--temp--) ; temporary-variables
340 (list category) ; value-forms
341 (list --value--temp--) ; store-variables
342 (let* ((category --category--temp--) ; store-form
343 (value --value--temp--))
344 (list (quote gnus-agent-cat-set-property)
346 (quote (quote ,prop-name))
348 (list (quote ,name) --category--temp--) ; access-form
352 (defmacro gnus-agent-cat-name (category)
355 (gnus-agent-cat-defaccessor
356 gnus-agent-cat-days-until-old agent-days-until-old)
357 (gnus-agent-cat-defaccessor
358 gnus-agent-cat-enable-expiration agent-enable-expiration)
359 (gnus-agent-cat-defaccessor
360 gnus-agent-cat-groups agent-groups)
361 (gnus-agent-cat-defaccessor
362 gnus-agent-cat-high-score agent-high-score)
363 (gnus-agent-cat-defaccessor
364 gnus-agent-cat-length-when-long agent-length-when-long)
365 (gnus-agent-cat-defaccessor
366 gnus-agent-cat-length-when-short agent-length-when-short)
367 (gnus-agent-cat-defaccessor
368 gnus-agent-cat-low-score agent-low-score)
369 (gnus-agent-cat-defaccessor
370 gnus-agent-cat-predicate agent-predicate)
371 (gnus-agent-cat-defaccessor
372 gnus-agent-cat-score-file agent-score-file)
373 (gnus-agent-cat-defaccessor
374 gnus-agent-cat-enable-undownloaded-faces agent-enable-undownloaded-faces)
377 (defsetf gnus-agent-cat-groups (category) (groups)
378 (list 'gnus-agent-set-cat-groups category groups)))
380 (defun gnus-agent-set-cat-groups (category groups)
381 (unless (eq groups 'ignore)
383 (old-g (gnus-agent-cat-groups category)))
384 (cond ((eq new-g old-g)
385 ;; gnus-agent-add-group is fiddling with the group
386 ;; list. Still, Im done.
389 ((eq new-g (cdr old-g))
390 ;; gnus-agent-add-group is fiddling with the group list
391 (setcdr (or (assq 'agent-groups category)
392 (let ((cell (cons 'agent-groups nil)))
393 (setcdr category (cons cell (cdr category)))
396 (let ((groups groups))
398 (let* ((group (pop groups))
399 (old-category (gnus-group-category group)))
400 (if (eq category old-category)
402 (setf (gnus-agent-cat-groups old-category)
403 (delete group (gnus-agent-cat-groups
405 ;; Purge cache as preceeding loop invalidated it.
406 (setq gnus-category-group-cache nil))
408 (setcdr (or (assq 'agent-groups category)
409 (let ((cell (cons 'agent-groups nil)))
410 (setcdr category (cons cell (cdr category)))
413 (defsubst gnus-agent-cat-make (name &optional default-agent-predicate)
414 (list name `(agent-predicate . ,(or default-agent-predicate 'false))))
416 ;;; Fetching setup functions.
418 (defun gnus-agent-start-fetch ()
419 "Initialize data structures for efficient fetching."
420 (gnus-agent-create-buffer))
422 (defun gnus-agent-stop-fetch ()
423 "Save all data structures and clean up."
424 (setq gnus-agent-spam-hashtb nil)
426 (set-buffer nntp-server-buffer)
429 (defmacro gnus-agent-with-fetch (&rest forms)
432 (let ((gnus-agent-fetching t))
433 (gnus-agent-start-fetch)
435 (gnus-agent-stop-fetch)))
437 (put 'gnus-agent-with-fetch 'lisp-indent-function 0)
438 (put 'gnus-agent-with-fetch 'edebug-form-spec '(body))
440 (defmacro gnus-agent-append-to-list (tail value)
441 `(setq ,tail (setcdr ,tail (cons ,value nil))))
443 (defmacro gnus-agent-message (level &rest args)
444 `(if (<= ,level gnus-verbose)
451 (defvar gnus-agent-mode-hook nil
452 "Hook run when installing agent mode.")
454 (defvar gnus-agent-mode nil)
455 (defvar gnus-agent-mode-status '(gnus-agent-mode " Plugged"))
457 (defun gnus-agent-mode ()
458 "Minor mode for providing a agent support in Gnus buffers."
459 (let* ((buffer (progn (string-match "^gnus-\\(.*\\)-mode$"
460 (symbol-name major-mode))
461 (match-string 1 (symbol-name major-mode))))
462 (mode (intern (format "gnus-agent-%s-mode" buffer))))
463 (set (make-local-variable 'gnus-agent-mode) t)
465 (set (make-local-variable mode) t)
467 (when (gnus-visual-p 'agent-menu 'menu)
468 (funcall (intern (format "gnus-agent-%s-make-menu-bar" buffer))))
469 (unless (assq 'gnus-agent-mode minor-mode-alist)
470 (push gnus-agent-mode-status minor-mode-alist))
471 (unless (assq mode minor-mode-map-alist)
472 (push (cons mode (symbol-value (intern (format "gnus-agent-%s-mode-map"
474 minor-mode-map-alist))
475 (when (eq major-mode 'gnus-group-mode)
476 (let ((init-plugged gnus-plugged)
477 (gnus-agent-go-online nil))
478 ;; g-a-t-p does nothing when gnus-plugged isn't changed.
479 ;; Therefore, make certain that the current value does not
480 ;; match the desired initial value.
481 (setq gnus-plugged :unknown)
482 (gnus-agent-toggle-plugged init-plugged)))
483 (gnus-run-hooks 'gnus-agent-mode-hook
484 (intern (format "gnus-agent-%s-mode-hook" buffer)))))
486 (defvar gnus-agent-group-mode-map (make-sparse-keymap))
487 (gnus-define-keys gnus-agent-group-mode-map
488 "Ju" gnus-agent-fetch-groups
489 "Jc" gnus-enter-category-buffer
490 "Jj" gnus-agent-toggle-plugged
491 "Js" gnus-agent-fetch-session
492 "JY" gnus-agent-synchronize-flags
493 "JS" gnus-group-send-queue
494 "Ja" gnus-agent-add-group
495 "Jr" gnus-agent-remove-group
496 "Jo" gnus-agent-toggle-group-plugged)
498 (defun gnus-agent-group-make-menu-bar ()
499 (unless (boundp 'gnus-agent-group-menu)
501 gnus-agent-group-menu gnus-agent-group-mode-map ""
503 ["Toggle plugged" gnus-agent-toggle-plugged t]
504 ["Toggle group plugged" gnus-agent-toggle-group-plugged t]
505 ["List categories" gnus-enter-category-buffer t]
506 ["Add (current) group to category" gnus-agent-add-group t]
507 ["Remove (current) group from category" gnus-agent-remove-group t]
508 ["Send queue" gnus-group-send-queue gnus-plugged]
510 ["All" gnus-agent-fetch-session gnus-plugged]
511 ["Group" gnus-agent-fetch-group gnus-plugged])
512 ["Synchronize flags" gnus-agent-synchronize-flags t]
515 (defvar gnus-agent-summary-mode-map (make-sparse-keymap))
516 (gnus-define-keys gnus-agent-summary-mode-map
517 "Jj" gnus-agent-toggle-plugged
518 "Ju" gnus-agent-summary-fetch-group
519 "JS" gnus-agent-fetch-group
520 "Js" gnus-agent-summary-fetch-series
521 "J#" gnus-agent-mark-article
522 "J\M-#" gnus-agent-unmark-article
523 "@" gnus-agent-toggle-mark
524 "Jc" gnus-agent-catchup)
526 (defun gnus-agent-summary-make-menu-bar ()
527 (unless (boundp 'gnus-agent-summary-menu)
529 gnus-agent-summary-menu gnus-agent-summary-mode-map ""
531 ["Toggle plugged" gnus-agent-toggle-plugged t]
532 ["Mark as downloadable" gnus-agent-mark-article t]
533 ["Unmark as downloadable" gnus-agent-unmark-article t]
534 ["Toggle mark" gnus-agent-toggle-mark t]
535 ["Fetch downloadable" gnus-agent-summary-fetch-group t]
536 ["Catchup undownloaded" gnus-agent-catchup t]))))
538 (defvar gnus-agent-server-mode-map (make-sparse-keymap))
539 (gnus-define-keys gnus-agent-server-mode-map
540 "Jj" gnus-agent-toggle-plugged
541 "Ja" gnus-agent-add-server
542 "Jr" gnus-agent-remove-server)
544 (defun gnus-agent-server-make-menu-bar ()
545 (unless (boundp 'gnus-agent-server-menu)
547 gnus-agent-server-menu gnus-agent-server-mode-map ""
549 ["Toggle plugged" gnus-agent-toggle-plugged t]
550 ["Add" gnus-agent-add-server t]
551 ["Remove" gnus-agent-remove-server t]))))
553 (defun gnus-agent-make-mode-line-string (string mouse-button mouse-func)
554 (if (and (fboundp 'propertize)
555 (fboundp 'make-mode-line-mouse-map))
556 (propertize string 'local-map
557 (make-mode-line-mouse-map mouse-button mouse-func))
560 (defun gnus-agent-toggle-plugged (set-to)
561 "Toggle whether Gnus is unplugged or not."
562 (interactive (list (not gnus-plugged)))
563 (cond ((eq set-to gnus-plugged)
566 (setq gnus-plugged set-to)
567 (gnus-run-hooks 'gnus-agent-plugged-hook)
568 (setcar (cdr gnus-agent-mode-status)
569 (gnus-agent-make-mode-line-string " Plugged"
571 'gnus-agent-toggle-plugged))
572 (gnus-agent-go-online gnus-agent-go-online)
573 (gnus-agent-possibly-synchronize-flags))
575 (gnus-agent-close-connections)
576 (setq gnus-plugged set-to)
577 (gnus-run-hooks 'gnus-agent-unplugged-hook)
578 (setcar (cdr gnus-agent-mode-status)
579 (gnus-agent-make-mode-line-string " Unplugged"
581 'gnus-agent-toggle-plugged))))
582 (set-buffer-modified-p t))
584 (defmacro gnus-agent-while-plugged (&rest body)
585 `(let ((original-gnus-plugged gnus-plugged))
587 (progn (gnus-agent-toggle-plugged t)
589 (gnus-agent-toggle-plugged original-gnus-plugged))))
591 (put 'gnus-agent-while-plugged 'lisp-indent-function 0)
592 (put 'gnus-agent-while-plugged 'edebug-form-spec '(body))
594 (defun gnus-agent-close-connections ()
595 "Close all methods covered by the Gnus agent."
596 (let ((methods (gnus-agent-covered-methods)))
598 (gnus-close-server (pop methods)))))
601 (defun gnus-unplugged ()
602 "Start Gnus unplugged."
604 (setq gnus-plugged nil)
608 (defun gnus-plugged ()
609 "Start Gnus plugged."
611 (setq gnus-plugged t)
615 (defun gnus-slave-unplugged (&optional arg)
616 "Read news as a slave unplugged."
618 (setq gnus-plugged nil)
619 (gnus arg nil 'slave))
622 (defun gnus-agentize ()
623 "Allow Gnus to be an offline newsreader.
625 The gnus-agentize function is now called internally by gnus when
626 gnus-agent is set. If you wish to avoid calling gnus-agentize,
627 customize gnus-agent to nil.
629 This will modify the `gnus-setup-news-hook', and
630 `message-send-mail-real-function' variables, and install the Gnus agent
631 minor mode in all Gnus buffers."
634 (add-hook 'gnus-setup-news-hook 'gnus-agent-queue-setup)
635 (unless gnus-agent-send-mail-function
636 (setq gnus-agent-send-mail-function
637 (or message-send-mail-real-function
638 (function (lambda () (funcall message-send-mail-function))))
639 message-send-mail-real-function 'gnus-agent-send-mail))
641 ;; If the servers file doesn't exist, auto-agentize some servers and
642 ;; save the servers file so this auto-agentizing isn't invoked
644 (unless (file-exists-p (nnheader-concat gnus-agent-directory "lib/servers"))
645 (gnus-message 3 "First time agent user, agentizing remote groups...")
647 (lambda (server-or-method)
648 (let ((method (gnus-server-to-method server-or-method)))
649 (when (memq (car method)
650 gnus-agent-auto-agentize-methods)
651 (push (gnus-method-to-server method)
652 gnus-agent-covered-methods)
653 (setq gnus-agent-method-p-cache nil))))
654 (cons gnus-select-method gnus-secondary-select-methods))
655 (gnus-agent-write-servers)))
657 (defun gnus-agent-queue-setup (&optional group-name)
658 "Make sure the queue group exists.
659 Optional arg GROUP-NAME allows to specify another group."
660 (unless (gnus-gethash (format "nndraft:%s" (or group-name "queue"))
662 (gnus-request-create-group (or group-name "queue") '(nndraft ""))
663 (let ((gnus-level-default-subscribed 1))
664 (gnus-subscribe-group (format "nndraft:%s" (or group-name "queue"))
666 (gnus-group-set-parameter
667 (format "nndraft:%s" (or group-name "queue"))
668 'gnus-dummy '((gnus-draft-mode)))))
670 (defun gnus-agent-send-mail ()
671 (if (or (not gnus-agent-queue-mail)
672 (and gnus-plugged (not (eq gnus-agent-queue-mail 'always))))
673 (funcall gnus-agent-send-mail-function)
674 (goto-char (point-min))
676 (concat "^" (regexp-quote mail-header-separator) "\n"))
678 (gnus-agent-insert-meta-information 'mail)
679 (gnus-request-accept-article "nndraft:queue" nil t t)))
681 (defun gnus-agent-insert-meta-information (type &optional method)
682 "Insert meta-information into the message that says how it's to be posted.
683 TYPE can be either `mail' or `news'. If the latter, then METHOD can
686 (message-remove-header gnus-agent-meta-information-header)
687 (goto-char (point-min))
688 (insert gnus-agent-meta-information-header ": "
689 (symbol-name type) " " (format "%S" method)
692 (while (search-backward "\n" nil t)
693 (replace-match "\\n" t t))))
695 (defun gnus-agent-restore-gcc ()
696 "Restore GCC field from saved header."
698 (goto-char (point-min))
699 (while (re-search-forward (concat gnus-agent-gcc-header ":") nil t)
700 (replace-match "Gcc:" 'fixedcase))))
702 (defun gnus-agent-any-covered-gcc ()
704 (message-narrow-to-headers)
705 (let* ((gcc (mail-fetch-field "gcc" nil t))
707 (mapcar 'gnus-inews-group-method
708 (message-unquote-tokens
709 (message-tokenize-header
712 (while (and (not covered) methods)
713 (setq covered (gnus-agent-method-p (car methods))
714 methods (cdr methods)))
718 (defun gnus-agent-possibly-save-gcc ()
719 "Save GCC if Gnus is unplugged."
720 (when (and (not gnus-plugged) (gnus-agent-any-covered-gcc))
722 (goto-char (point-min))
723 (let ((case-fold-search t))
724 (while (re-search-forward "^gcc:" nil t)
725 (replace-match (concat gnus-agent-gcc-header ":") 'fixedcase))))))
727 (defun gnus-agent-possibly-do-gcc ()
728 "Do GCC if Gnus is plugged."
729 (when (or gnus-plugged (not (gnus-agent-any-covered-gcc)))
730 (gnus-inews-do-gcc)))
733 ;;; Group mode commands
736 (defun gnus-agent-fetch-groups (n)
737 "Put all new articles in the current groups into the Agent."
740 (error "Groups can't be fetched when Gnus is unplugged"))
741 (gnus-group-iterate n 'gnus-agent-fetch-group))
743 (defun gnus-agent-fetch-group (&optional group)
744 "Put all new articles in GROUP into the Agent."
745 (interactive (list (gnus-group-group-name)))
746 (setq group (or group gnus-newsgroup-name))
748 (error "No group on the current line"))
750 (gnus-agent-while-plugged
751 (let ((gnus-command-method (gnus-find-method-for-group group)))
752 (gnus-agent-with-fetch
753 (gnus-agent-fetch-group-1 group gnus-command-method)
754 (gnus-message 5 "Fetching %s...done" group)))))
756 (defun gnus-agent-add-group (category arg)
757 "Add the current group to an agent category."
763 (mapcar (lambda (cat) (list (symbol-name (car cat))))
767 (let ((cat (assq category gnus-category-alist))
769 (gnus-group-iterate arg
771 (when (gnus-agent-cat-groups (setq c (gnus-group-category group)))
772 (setf (gnus-agent-cat-groups c)
773 (delete group (gnus-agent-cat-groups c))))
774 (push group groups)))
775 (setf (gnus-agent-cat-groups cat)
776 (nconc (gnus-agent-cat-groups cat) groups))
777 (gnus-category-write)))
779 (defun gnus-agent-remove-group (arg)
780 "Remove the current group from its agent category, if any."
783 (gnus-group-iterate arg
785 (when (gnus-agent-cat-groups (setq c (gnus-group-category group)))
786 (setf (gnus-agent-cat-groups c)
787 (delete group (gnus-agent-cat-groups c))))))
788 (gnus-category-write)))
790 (defun gnus-agent-synchronize-flags ()
791 "Synchronize unplugged flags with servers."
794 (dolist (gnus-command-method (gnus-agent-covered-methods))
795 (when (file-exists-p (gnus-agent-lib-file "flags"))
796 (gnus-agent-synchronize-flags-server gnus-command-method)))))
798 (defun gnus-agent-possibly-synchronize-flags ()
799 "Synchronize flags according to `gnus-agent-synchronize-flags'."
802 (dolist (gnus-command-method (gnus-agent-covered-methods))
803 (when (file-exists-p (gnus-agent-lib-file "flags"))
804 (gnus-agent-possibly-synchronize-flags-server gnus-command-method)))))
806 (defun gnus-agent-synchronize-flags-server (method)
807 "Synchronize flags set when unplugged for server."
808 (let ((gnus-command-method method))
809 (when (file-exists-p (gnus-agent-lib-file "flags"))
810 (set-buffer (get-buffer-create " *Gnus Agent flag synchronize*"))
812 (nnheader-insert-file-contents (gnus-agent-lib-file "flags"))
813 (if (null (gnus-check-server gnus-command-method))
814 (gnus-message 1 "Couldn't open server %s" (nth 1 gnus-command-method))
816 (if (null (eval (read (current-buffer))))
818 (write-file (gnus-agent-lib-file "flags"))
819 (error "Couldn't set flags from file %s"
820 (gnus-agent-lib-file "flags"))))
821 (delete-file (gnus-agent-lib-file "flags")))
824 (defun gnus-agent-possibly-synchronize-flags-server (method)
825 "Synchronize flags for server according to `gnus-agent-synchronize-flags'."
826 (when (or (and gnus-agent-synchronize-flags
827 (not (eq gnus-agent-synchronize-flags 'ask)))
828 (and (eq gnus-agent-synchronize-flags 'ask)
829 (gnus-y-or-n-p (format "Synchronize flags on server `%s'? "
831 (gnus-agent-synchronize-flags-server method)))
834 (defun gnus-agent-rename-group (old-group new-group)
835 "Rename fully-qualified OLD-GROUP as NEW-GROUP. Always updates the agent, even when
836 disabled, as the old agent files would corrupt gnus when the agent was
837 next enabled. Depends upon the caller to determine whether group renaming is supported."
838 (let* ((old-command-method (gnus-find-method-for-group old-group))
839 (old-path (directory-file-name
840 (let (gnus-command-method old-command-method)
841 (gnus-agent-group-pathname old-group))))
842 (new-command-method (gnus-find-method-for-group new-group))
843 (new-path (directory-file-name
844 (let (gnus-command-method new-command-method)
845 (gnus-agent-group-pathname new-group)))))
846 (gnus-rename-file old-path new-path t)
848 (let* ((old-real-group (gnus-group-real-name old-group))
849 (new-real-group (gnus-group-real-name new-group))
850 (old-active (gnus-agent-get-group-info old-command-method old-real-group)))
851 (gnus-agent-save-group-info old-command-method old-real-group nil)
852 (gnus-agent-save-group-info new-command-method new-real-group old-active)
854 (let ((old-local (gnus-agent-get-local old-group
855 old-real-group old-command-method)))
856 (gnus-agent-set-local old-group
858 old-real-group old-command-method)
859 (gnus-agent-set-local new-group
860 (car old-local) (cdr old-local)
861 new-real-group new-command-method)))))
864 (defun gnus-agent-delete-group (group)
865 "Delete fully-qualified GROUP. Always updates the agent, even when
866 disabled, as the old agent files would corrupt gnus when the agent was
867 next enabled. Depends upon the caller to determine whether group deletion is supported."
868 (let* ((command-method (gnus-find-method-for-group group))
869 (path (directory-file-name
870 (let (gnus-command-method command-method)
871 (gnus-agent-group-pathname group)))))
872 (gnus-delete-file path)
874 (let* ((real-group (gnus-group-real-name group)))
875 (gnus-agent-save-group-info command-method real-group nil)
877 (let ((local (gnus-agent-get-local group
878 real-group command-method)))
879 (gnus-agent-set-local group
881 real-group command-method)))))
884 ;;; Server mode commands
887 (defun gnus-agent-add-server ()
888 "Enroll SERVER in the agent program."
890 (let* ((server (gnus-server-server-name))
891 (named-server (gnus-server-named-server))
893 (gnus-server-get-method nil server))))
895 (error "No server on the current line"))
897 (when (gnus-agent-method-p method)
898 (error "Server already in the agent program"))
900 (push named-server gnus-agent-covered-methods)
902 (setq gnus-agent-method-p-cache nil)
903 (gnus-server-update-server server)
904 (gnus-agent-write-servers)
905 (gnus-message 1 "Entered %s into the Agent" server)))
907 (defun gnus-agent-remove-server ()
908 "Remove SERVER from the agent program."
910 (let* ((server (gnus-server-server-name))
911 (named-server (gnus-server-named-server)))
913 (error "No server on the current line"))
915 (unless (member named-server gnus-agent-covered-methods)
916 (error "Server not in the agent program"))
918 (setq gnus-agent-covered-methods
919 (delete named-server gnus-agent-covered-methods)
920 gnus-agent-method-p-cache nil)
922 (gnus-server-update-server server)
923 (gnus-agent-write-servers)
924 (gnus-message 1 "Removed %s from the agent" server)))
926 (defun gnus-agent-read-servers ()
927 "Read the alist of covered servers."
928 (setq gnus-agent-covered-methods
929 (gnus-agent-read-file
930 (nnheader-concat gnus-agent-directory "lib/servers"))
931 gnus-agent-method-p-cache nil)
933 ;; I am called so early in start-up that I can not validate server
934 ;; names. When that is the case, I skip the validation. That is
935 ;; alright as the gnus startup code calls the validate methods
937 (if gnus-server-alist
938 (gnus-agent-read-servers-validate)))
940 (defun gnus-agent-read-servers-validate ()
941 (mapcar (lambda (server-or-method)
942 (let* ((server (if (stringp server-or-method)
944 (gnus-method-to-server server-or-method)))
945 (method (gnus-server-to-method server)))
947 (unless (member server gnus-agent-covered-methods)
948 (push server gnus-agent-covered-methods)
949 (setq gnus-agent-method-p-cache nil))
950 (gnus-message 1 "Ignoring disappeared server `%s'" server))))
951 (prog1 gnus-agent-covered-methods
952 (setq gnus-agent-covered-methods nil))))
954 (defun gnus-agent-read-servers-validate-native (native-method)
955 (setq gnus-agent-covered-methods
956 (mapcar (lambda (method)
958 (equal method native-method))
960 method)) gnus-agent-covered-methods)))
962 (defun gnus-agent-write-servers ()
963 "Write the alist of covered servers."
964 (gnus-make-directory (nnheader-concat gnus-agent-directory "lib"))
965 (let ((coding-system-for-write nnheader-file-coding-system)
966 (file-name-coding-system nnmail-pathname-coding-system))
967 (with-temp-file (nnheader-concat gnus-agent-directory "lib/servers")
968 (prin1 gnus-agent-covered-methods
975 (defun gnus-agent-mark-article (n &optional unmark)
976 "Mark the next N articles as downloadable.
977 If N is negative, mark backward instead. If UNMARK is non-nil, remove
978 the mark instead. The difference between N and the actual number of
979 articles marked is returned."
981 (let ((backward (< n 0))
986 (gnus-summary-set-agent-mark
987 (gnus-summary-article-number) unmark)
988 (zerop (gnus-summary-next-subject (if backward -1 1) nil t))))
991 (gnus-message 7 "No more articles"))
992 (gnus-summary-recenter)
993 (gnus-summary-position-point)
996 (defun gnus-agent-unmark-article (n)
997 "Remove the downloadable mark from the next N articles.
998 If N is negative, unmark backward instead. The difference between N and
999 the actual number of articles unmarked is returned."
1001 (gnus-agent-mark-article n t))
1003 (defun gnus-agent-toggle-mark (n)
1004 "Toggle the downloadable mark from the next N articles.
1005 If N is negative, toggle backward instead. The difference between N and
1006 the actual number of articles toggled is returned."
1008 (gnus-agent-mark-article n 'toggle))
1010 (defun gnus-summary-set-agent-mark (article &optional unmark)
1011 "Mark ARTICLE as downloadable. If UNMARK is nil, article is marked.
1012 When UNMARK is t, the article is unmarked. For any other value, the
1013 article's mark is toggled."
1014 (let ((unmark (cond ((eq nil unmark)
1019 (memq article gnus-newsgroup-downloadable)))))
1020 (when (gnus-summary-goto-subject article nil t)
1021 (gnus-summary-update-mark
1024 (setq gnus-newsgroup-downloadable
1025 (delq article gnus-newsgroup-downloadable))
1026 (gnus-article-mark article))
1027 (setq gnus-newsgroup-downloadable
1028 (gnus-add-to-sorted-list gnus-newsgroup-downloadable article))
1029 gnus-downloadable-mark)
1032 (defun gnus-agent-get-undownloaded-list ()
1033 "Construct list of articles that have not been downloaded."
1034 (let ((gnus-command-method (gnus-find-method-for-group gnus-newsgroup-name)))
1035 (when (set (make-local-variable 'gnus-newsgroup-agentized)
1036 (gnus-agent-method-p gnus-command-method))
1037 (let* ((alist (gnus-agent-load-alist gnus-newsgroup-name))
1038 (headers (sort (mapcar (lambda (h)
1039 (mail-header-number h))
1040 gnus-newsgroup-headers) '<))
1041 (cached (and gnus-use-cache gnus-newsgroup-cached))
1042 (undownloaded (list nil))
1043 (tail-undownloaded undownloaded)
1044 (unfetched (list nil))
1045 (tail-unfetched unfetched))
1046 (while (and alist headers)
1047 (let ((a (caar alist))
1050 ;; Ignore IDs in the alist that are not being
1051 ;; displayed in the summary.
1052 (setq alist (cdr alist)))
1054 ;; Headers that are not in the alist should be
1055 ;; fictious (see nnagent-retrieve-headers); they
1056 ;; imply that this article isn't in the agent.
1057 (gnus-agent-append-to-list tail-undownloaded h)
1058 (gnus-agent-append-to-list tail-unfetched h)
1059 (setq headers (cdr headers)))
1061 (setq alist (cdr alist))
1062 (setq headers (cdr headers))
1063 nil ; ignore already downloaded
1066 (setq alist (cdr alist))
1067 (setq headers (cdr headers))
1069 ;; This article isn't in the agent. Check to see
1070 ;; if it is in the cache. If it is, it's been
1072 (while (and cached (< (car cached) a))
1073 (setq cached (cdr cached)))
1074 (unless (equal a (car cached))
1075 (gnus-agent-append-to-list tail-undownloaded a))))))
1078 (let ((num (pop headers)))
1079 (gnus-agent-append-to-list tail-undownloaded num)
1080 (gnus-agent-append-to-list tail-unfetched num)))
1082 (setq gnus-newsgroup-undownloaded (cdr undownloaded)
1083 gnus-newsgroup-unfetched (cdr unfetched))))))
1085 (defun gnus-agent-catchup ()
1086 "Mark as read all unhandled articles.
1087 An article is unhandled if it is neither cached, nor downloaded, nor
1091 (let ((articles gnus-newsgroup-undownloaded))
1092 (when (or gnus-newsgroup-downloadable
1093 gnus-newsgroup-cached)
1094 (setq articles (gnus-sorted-ndifference
1095 (gnus-sorted-ndifference
1096 (gnus-copy-sequence articles)
1097 gnus-newsgroup-downloadable)
1098 gnus-newsgroup-cached)))
1101 (gnus-summary-mark-article
1102 (pop articles) gnus-catchup-mark)))
1103 (gnus-summary-position-point)))
1105 (defun gnus-agent-summary-fetch-series ()
1107 (when gnus-newsgroup-processable
1108 (setq gnus-newsgroup-downloadable
1109 (let* ((dl gnus-newsgroup-downloadable)
1110 (gnus-newsgroup-downloadable
1111 (sort (gnus-copy-sequence gnus-newsgroup-processable) '<))
1112 (fetched-articles (gnus-agent-summary-fetch-group)))
1113 ;; The preceeding call to (gnus-agent-summary-fetch-group)
1114 ;; updated gnus-newsgroup-downloadable to remove each
1115 ;; article successfully fetched.
1117 ;; For each article that I processed, remove its
1118 ;; processable mark IF the article is no longer
1119 ;; downloadable (i.e. it's already downloaded)
1120 (dolist (article gnus-newsgroup-processable)
1121 (unless (memq article gnus-newsgroup-downloadable)
1122 (gnus-summary-remove-process-mark article)))
1123 (gnus-sorted-ndifference dl fetched-articles)))))
1125 (defun gnus-agent-summary-fetch-group (&optional all)
1126 "Fetch the downloadable articles in the group.
1127 Optional arg ALL, if non-nil, means to fetch all articles."
1130 (if all gnus-newsgroup-articles
1131 gnus-newsgroup-downloadable))
1132 (gnus-command-method (gnus-find-method-for-group gnus-newsgroup-name))
1134 (gnus-agent-while-plugged
1136 (error "No articles to download"))
1137 (gnus-agent-with-fetch
1138 (setq gnus-newsgroup-undownloaded
1139 (gnus-sorted-ndifference
1140 gnus-newsgroup-undownloaded
1141 (setq fetched-articles
1142 (gnus-agent-fetch-articles
1143 gnus-newsgroup-name articles)))))
1145 (dolist (article articles)
1146 (let ((was-marked-downloadable
1147 (memq article gnus-newsgroup-downloadable)))
1148 (cond (gnus-agent-mark-unread-after-downloaded
1149 (setq gnus-newsgroup-downloadable
1150 (delq article gnus-newsgroup-downloadable))
1152 (gnus-summary-mark-article article gnus-unread-mark))
1153 (was-marked-downloadable
1154 (gnus-summary-set-agent-mark article t)))
1155 (when (gnus-summary-goto-subject article nil t)
1156 (gnus-summary-update-download-mark article))))))
1159 (defun gnus-agent-fetch-selected-article ()
1160 "Fetch the current article as it is selected.
1161 This can be added to `gnus-select-article-hook' or
1162 `gnus-mark-article-hook'."
1163 (let ((gnus-command-method gnus-current-select-method))
1164 (when (and gnus-plugged (gnus-agent-method-p gnus-command-method))
1165 (when (gnus-agent-fetch-articles
1167 (list gnus-current-article))
1168 (setq gnus-newsgroup-undownloaded
1169 (delq gnus-current-article gnus-newsgroup-undownloaded))
1170 (gnus-summary-update-download-mark gnus-current-article)))))
1173 ;;; Internal functions
1176 (defun gnus-agent-save-active (method)
1177 (when (gnus-agent-method-p method)
1178 (let* ((gnus-command-method method)
1179 (new (gnus-make-hashtable (count-lines (point-min) (point-max))))
1180 (file (gnus-agent-lib-file "active")))
1181 (gnus-active-to-gnus-format nil new)
1182 (gnus-agent-write-active file new)
1184 (nnheader-insert-file-contents file))))
1186 (defun gnus-agent-write-active (file new)
1187 (gnus-make-directory (file-name-directory file))
1188 (let ((nnmail-active-file-coding-system gnus-agent-file-coding-system))
1189 ;; The hashtable contains real names of groups. However, do NOT
1190 ;; add the foreign server prefix as gnus-active-to-gnus-format
1191 ;; will add it while reading the file.
1192 (gnus-write-active-file file new nil)))
1194 (defun gnus-agent-possibly-alter-active (group active &optional info)
1195 "Possibly expand a group's active range to include articles
1196 downloaded into the agent."
1197 (let* ((gnus-command-method (or gnus-command-method
1198 (gnus-find-method-for-group group))))
1199 (when (gnus-agent-method-p gnus-command-method)
1200 (let* ((local (gnus-agent-get-local group))
1201 (active-min (or (car active) 0))
1202 (active-max (or (cdr active) 0))
1203 (agent-min (or (car local) active-min))
1204 (agent-max (or (cdr local) active-max)))
1206 (when (< agent-min active-min)
1207 (setcar active agent-min))
1209 (when (> agent-max active-max)
1210 (setcdr active agent-max))
1212 (when (and info (< agent-max (- active-min 100)))
1213 ;; I'm expanding the active range by such a large amount
1214 ;; that there is a gap of more than 100 articles between the
1215 ;; last article known to the agent and the first article
1216 ;; currently available on the server. This gap contains
1217 ;; articles that have been lost, mark them as read so that
1218 ;; gnus doesn't waste resources trying to fetch them.
1220 ;; NOTE: I don't do this for smaller gaps (< 100) as I don't
1221 ;; want to modify the local file everytime someone restarts
1222 ;; gnus. The small gap will cause a tiny performance hit
1223 ;; when gnus tries, and fails, to retrieve the articles.
1224 ;; Still that should be smaller than opening a buffer,
1225 ;; printing this list to the buffer, and then writing it to a
1228 (let ((read (gnus-info-read info)))
1233 (list (cons (1+ agent-max)
1234 (1- active-min))))))
1236 ;; Lie about the agent's local range for this group to
1237 ;; disable the set read each time this server is opened.
1238 ;; NOTE: Opening this group will restore the valid local
1239 ;; range but it will also expand the local range to
1240 ;; incompass the new active range.
1241 (gnus-agent-set-local group agent-min (1- active-min)))))))
1243 (defun gnus-agent-save-group-info (method group active)
1244 "Update a single group's active range in the agent's copy of the server's active file."
1245 (when (gnus-agent-method-p method)
1246 (let* ((gnus-command-method (or method gnus-command-method))
1247 (coding-system-for-write nnheader-file-coding-system)
1248 (file-name-coding-system nnmail-pathname-coding-system)
1249 (file (gnus-agent-lib-file "active"))
1250 oactive-min oactive-max)
1251 (gnus-make-directory (file-name-directory file))
1252 (with-temp-file file
1253 ;; Emacs got problem to match non-ASCII group in multibyte buffer.
1254 (mm-disable-multibyte)
1255 (when (file-exists-p file)
1256 (nnheader-insert-file-contents file)
1258 (goto-char (point-min))
1259 (when (re-search-forward
1260 (concat "^" (regexp-quote group) " ") nil t)
1262 (setq oactive-max (read (current-buffer)) ;; max
1263 oactive-min (read (current-buffer)))) ;; min
1264 (gnus-delete-line)))
1266 (insert (format "%S %d %d y\n" (intern group)
1267 (max (or oactive-max (cdr active)) (cdr active))
1268 (min (or oactive-min (car active)) (car active))))
1269 (goto-char (point-max))
1270 (while (search-backward "\\." nil t)
1271 (delete-char 1)))))))
1273 (defun gnus-agent-get-group-info (method group)
1274 "Get a single group's active range in the agent's copy of the server's active file."
1275 (when (gnus-agent-method-p method)
1276 (let* ((gnus-command-method (or method gnus-command-method))
1277 (coding-system-for-write nnheader-file-coding-system)
1278 (file-name-coding-system nnmail-pathname-coding-system)
1279 (file (gnus-agent-lib-file "active"))
1280 oactive-min oactive-max)
1281 (gnus-make-directory (file-name-directory file))
1283 ;; Emacs got problem to match non-ASCII group in multibyte buffer.
1284 (mm-disable-multibyte)
1285 (when (file-exists-p file)
1286 (nnheader-insert-file-contents file)
1288 (goto-char (point-min))
1289 (when (re-search-forward
1290 (concat "^" (regexp-quote group) " ") nil t)
1292 (setq oactive-max (read (current-buffer)) ;; max
1293 oactive-min (read (current-buffer))) ;; min
1294 (cons oactive-min oactive-max))))))))
1296 (defun gnus-agent-group-path (group)
1297 "Translate GROUP into a file name."
1299 ;; NOTE: This is what nnmail-group-pathname does as of Apr 2003.
1300 ;; The two methods must be kept synchronized, which is why
1301 ;; gnus-agent-group-pathname was added.
1304 (nnheader-translate-file-chars
1305 (nnheader-replace-duplicate-chars-in-string
1306 (nnheader-replace-chars-in-string
1307 (gnus-group-real-name group)
1310 (if (or nnmail-use-long-file-names
1311 (file-directory-p (expand-file-name group (gnus-agent-directory))))
1313 (mm-encode-coding-string
1314 (nnheader-replace-chars-in-string group ?. ?/)
1315 nnmail-pathname-coding-system)))
1317 (defun gnus-agent-group-pathname (group)
1318 "Translate GROUP into a file name."
1319 ;; nnagent uses nnmail-group-pathname to read articles while
1320 ;; unplugged. The agent must, therefore, use the same directory
1322 (let ((gnus-command-method (or gnus-command-method
1323 (gnus-find-method-for-group group))))
1324 (nnmail-group-pathname (gnus-group-real-name group) (gnus-agent-directory))))
1326 (defun gnus-agent-get-function (method)
1327 (if (gnus-online method)
1332 (defun gnus-agent-covered-methods ()
1333 "Return the subset of methods that are covered by the agent."
1334 (delq nil (mapcar #'gnus-server-to-method gnus-agent-covered-methods)))
1336 ;;; History functions
1338 (defun gnus-agent-history-buffer ()
1339 (cdr (assoc (gnus-agent-method) gnus-agent-history-buffers)))
1341 (defun gnus-agent-open-history ()
1343 (push (cons (gnus-agent-method)
1344 (set-buffer (gnus-get-buffer-create
1345 (format " *Gnus agent %s history*"
1346 (gnus-agent-method)))))
1347 gnus-agent-history-buffers)
1348 (mm-disable-multibyte) ;; everything is binary
1351 (let ((file (gnus-agent-lib-file "history")))
1352 (when (file-exists-p file)
1353 (nnheader-insert-file-contents file))
1354 (set (make-local-variable 'gnus-agent-file-name) file))))
1356 (defun gnus-agent-close-history ()
1357 (when (gnus-buffer-live-p gnus-agent-current-history)
1358 (kill-buffer gnus-agent-current-history)
1359 (setq gnus-agent-history-buffers
1360 (delq (assoc (gnus-agent-method) gnus-agent-history-buffers)
1361 gnus-agent-history-buffers))))
1367 (defun gnus-agent-fetch-articles (group articles)
1368 "Fetch ARTICLES from GROUP and put them into the Agent."
1370 (gnus-agent-load-alist group)
1371 (let* ((alist gnus-agent-article-alist)
1372 (headers (if (< (length articles) 2) nil gnus-newsgroup-headers))
1373 (selected-sets (list nil))
1374 (current-set-size 0)
1377 ;; Check each article
1378 (while (setq article (pop articles))
1379 ;; Skip alist entries preceeding this article
1380 (while (> article (or (caar alist) (1+ article)))
1381 (setq alist (cdr alist)))
1383 ;; Prune off articles that we have already fetched.
1384 (unless (and (eq article (caar alist))
1386 ;; Skip headers preceeding this article
1389 (let* ((header (car headers)))
1391 (mail-header-number header)
1393 (setq headers (cdr headers)))
1395 ;; Add this article to the current set
1396 (setcar selected-sets (cons article (car selected-sets)))
1398 ;; Update the set size, when the set is too large start a
1399 ;; new one. I do this after adding the article as I want at
1400 ;; least one article in each set.
1401 (when (< gnus-agent-max-fetch-size
1402 (setq current-set-size
1404 (if (= header-number article)
1405 (let ((char-size (mail-header-chars
1407 (if (<= char-size 0)
1408 ;; The char size was missing/invalid,
1409 ;; assume a worst-case situation of
1410 ;; 65 char/line. If the line count
1411 ;; is missing, arbitrarily assume a
1412 ;; size of 1000 characters.
1413 (max (* 65 (mail-header-lines
1418 (setcar selected-sets (nreverse (car selected-sets)))
1419 (setq selected-sets (cons nil selected-sets)
1420 current-set-size 0))))
1422 (when (or (cdr selected-sets) (car selected-sets))
1423 (let* ((fetched-articles (list nil))
1424 (tail-fetched-articles fetched-articles)
1425 (dir (gnus-agent-group-pathname group))
1426 (date (time-to-days (current-time)))
1427 (case-fold-search t)
1430 (setcar selected-sets (nreverse (car selected-sets)))
1431 (setq selected-sets (nreverse selected-sets))
1433 (gnus-make-directory dir)
1434 (gnus-message 7 "Fetching articles for %s..." group)
1437 (while (setq articles (pop selected-sets))
1438 ;; Fetch the articles from the backend.
1439 (if (gnus-check-backend-function 'retrieve-articles group)
1440 (setq pos (gnus-retrieve-articles articles group))
1443 (while (setq article (pop articles))
1444 (gnus-message 10 "Fetching article %s for %s..."
1447 (gnus-backlog-request-article group article
1449 (gnus-request-article article group))
1450 (goto-char (point-max))
1451 (push (cons article (point)) pos)
1452 (insert-buffer-substring nntp-server-buffer)))
1454 nntp-server-buffer (point-min) (point-max))
1455 (setq pos (nreverse pos)))))
1456 ;; Then save these articles into the Agent.
1458 (set-buffer nntp-server-buffer)
1460 (narrow-to-region (cdar pos) (or (cdadr pos) (point-max)))
1461 (goto-char (point-min))
1462 (unless (eobp) ;; Don't save empty articles.
1463 (when (search-forward "\n\n" nil t)
1464 (when (search-backward "\nXrefs: " nil t)
1465 ;; Handle cross posting.
1466 (goto-char (match-end 0)) ; move to end of header name
1467 (skip-chars-forward "^ ") ; skip server name
1468 (skip-chars-forward " ")
1470 (while (looking-at "\\([^: \n]+\\):\\([0-9]+\\) *")
1471 (push (cons (buffer-substring (match-beginning 1)
1474 (buffer-substring (match-beginning 2)
1477 (goto-char (match-end 0)))
1478 (gnus-agent-crosspost crosses (caar pos) date)))
1479 (goto-char (point-min))
1480 (if (not (re-search-forward
1481 "^Message-ID: *<\\([^>\n]+\\)>" nil t))
1482 (setq id "No-Message-ID-in-article")
1483 (setq id (buffer-substring
1484 (match-beginning 1) (match-end 1))))
1485 (let ((coding-system-for-write
1486 gnus-agent-file-coding-system))
1487 (write-region (point-min) (point-max)
1488 (concat dir (number-to-string (caar pos)))
1491 (gnus-agent-append-to-list
1492 tail-fetched-articles (caar pos)))
1494 (setq pos (cdr pos)))))
1496 (gnus-agent-save-alist group (cdr fetched-articles) date)
1497 (gnus-agent-update-files-total-fetched-for group (cdr fetched-articles))
1499 (gnus-message 7 ""))
1500 (cdr fetched-articles))))))
1502 (defun gnus-agent-crosspost (crosses article &optional date)
1503 (setq date (or date t))
1505 (let (gnus-agent-article-alist group alist beg end)
1507 (set-buffer gnus-agent-overview-buffer)
1508 (when (nnheader-find-nov-line article)
1511 (setq end (progn (forward-line 1) (point)))))
1513 (setq group (caar crosses))
1514 (unless (setq alist (assoc group gnus-agent-group-alist))
1515 (push (setq alist (list group (gnus-agent-load-alist (caar crosses))))
1516 gnus-agent-group-alist))
1517 (setcdr alist (cons (cons (cdar crosses) date) (cdr alist)))
1519 (set-buffer (gnus-get-buffer-create (format " *Gnus agent overview %s*"
1521 (when (= (point-max) (point-min))
1522 (push (cons group (current-buffer)) gnus-agent-buffer-alist)
1524 (nnheader-insert-file-contents
1525 (gnus-agent-article-name ".overview" group))))
1526 (nnheader-find-nov-line (string-to-number (cdar crosses)))
1527 (insert (string-to-number (cdar crosses)))
1528 (insert-buffer-substring gnus-agent-overview-buffer beg end)
1529 (gnus-agent-check-overview-buffer))
1530 (setq crosses (cdr crosses)))))
1532 (defun gnus-agent-backup-overview-buffer ()
1533 (when gnus-newsgroup-name
1534 (let ((root (gnus-agent-article-name ".overview" gnus-newsgroup-name))
1537 (while (file-exists-p
1538 (setq name (concat root "~"
1539 (int-to-string (setq cnt (1+ cnt))) "~"))))
1540 (write-region (point-min) (point-max) name nil 'no-msg)
1541 (gnus-message 1 "Created backup copy of overview in %s." name)))
1544 (defun gnus-agent-check-overview-buffer (&optional buffer)
1545 "Check the overview file given for sanity.
1546 In particular, checks that the file is sorted by article number
1547 and that there are no duplicates."
1552 (set-buffer buffer))
1555 (goto-char (point-min))
1557 (while (< (point) (point-max))
1559 (cur (condition-case nil
1560 (read (current-buffer))
1563 ((or (not (integerp cur))
1564 (not (eq (char-after) ?\t)))
1566 (setq backed-up (gnus-agent-backup-overview-buffer)))
1568 "Overview buffer contains garbage '%s'."
1573 (setq backed-up (gnus-agent-backup-overview-buffer)))
1575 "Duplicate overview line for %d" cur)
1576 (delete-region (point) (progn (forward-line 1) (point))))
1579 (setq backed-up (gnus-agent-backup-overview-buffer)))
1580 (gnus-message 1 "Overview buffer not sorted!")
1581 (sort-numeric-fields 1 (point-min) (point-max))
1582 (goto-char (point-min))
1585 (setq prev-num cur)))
1586 (forward-line 1)))))))
1588 (defun gnus-agent-flush-cache ()
1590 (while gnus-agent-buffer-alist
1591 (set-buffer (cdar gnus-agent-buffer-alist))
1592 (let ((coding-system-for-write
1593 gnus-agent-file-coding-system))
1594 (write-region (point-min) (point-max)
1595 (gnus-agent-article-name ".overview"
1596 (caar gnus-agent-buffer-alist))
1598 (setq gnus-agent-buffer-alist (cdr gnus-agent-buffer-alist)))
1599 (while gnus-agent-group-alist
1600 (with-temp-file (gnus-agent-article-name
1601 ".agentview" (caar gnus-agent-group-alist))
1602 (princ (cdar gnus-agent-group-alist))
1604 (princ 1 (current-buffer))
1606 (setq gnus-agent-group-alist (cdr gnus-agent-group-alist)))))
1608 (defun gnus-agent-find-parameter (group symbol)
1609 "Search for GROUPs SYMBOL in the group's parameters, the group's
1610 topic parameters, the group's category, or the customizable
1611 variables. Returns the first non-nil value found."
1612 (or (gnus-group-find-parameter group symbol t)
1613 (gnus-group-parameter-value (cdr (gnus-group-category group)) symbol t)
1617 '((agent-short-article . gnus-agent-short-article)
1618 (agent-long-article . gnus-agent-long-article)
1619 (agent-low-score . gnus-agent-low-score)
1620 (agent-high-score . gnus-agent-high-score)
1621 (agent-days-until-old . gnus-agent-expire-days)
1622 (agent-enable-expiration
1623 . gnus-agent-enable-expiration)
1624 (agent-predicate . gnus-agent-predicate)))))))
1626 (defun gnus-agent-fetch-headers (group &optional force)
1627 "Fetch interesting headers into the agent. The group's overview
1628 file will be updated to include the headers while a list of available
1629 article numbers will be returned."
1630 (let* ((fetch-all (and gnus-agent-consider-all-articles
1631 ;; Do not fetch all headers if the predicate
1632 ;; implies that we only consider unread articles.
1633 (not (gnus-predicate-implies-unread
1634 (gnus-agent-find-parameter group
1635 'agent-predicate)))))
1636 (articles (if fetch-all
1637 (gnus-uncompress-range (gnus-active group))
1638 (gnus-list-of-unread-articles group)))
1639 (gnus-decode-encoded-word-function 'identity)
1640 (file (gnus-agent-article-name ".overview" group)))
1643 ;; Add articles with marks to the list of article headers we want to
1644 ;; fetch. Don't fetch articles solely on the basis of a recent or seen
1645 ;; mark, but do fetch recent or seen articles if they have other, more
1646 ;; interesting marks. (We have to fetch articles with boring marks
1647 ;; because otherwise the agent will remove their marks.)
1648 (dolist (arts (gnus-info-marks (gnus-get-info group)))
1649 (unless (memq (car arts) '(seen recent killed cache))
1650 (setq articles (gnus-range-add articles (cdr arts)))))
1651 (setq articles (sort (gnus-uncompress-sequence articles) '<)))
1653 ;; At this point, I have the list of articles to consider for
1654 ;; fetching. This is the list that I'll return to my caller. Some
1655 ;; of these articles may have already been fetched. That's OK as
1656 ;; the fetch article code will filter those out. Internally, I'll
1657 ;; filter this list to just those articles whose headers need to
1659 (let ((articles articles))
1660 ;; Remove known articles.
1661 (when (and (or gnus-agent-cache
1663 (gnus-agent-load-alist group))
1664 ;; Remove articles marked as downloaded.
1666 ;; I want to fetch all headers in the active range.
1667 ;; Therefore, exclude only those headers that are in the
1669 ;; NOTE: This is probably NOT what I want to do after
1670 ;; agent expiration in this group.
1671 (setq articles (gnus-agent-uncached-articles articles group))
1673 ;; I want to only fetch those headers that have never been
1674 ;; fetched. Therefore, exclude all headers that are, or
1675 ;; WERE, in the article alist.
1676 (let ((low (1+ (caar (last gnus-agent-article-alist))))
1677 (high (cdr (gnus-active group))))
1678 ;; Low can be greater than High when the same group is
1679 ;; fetched twice in the same session {The first fetch will
1680 ;; fill the article alist such that (last
1681 ;; gnus-agent-article-alist) equals (cdr (gnus-active
1682 ;; group))}. The addition of one(the 1+ above) then
1683 ;; forces Low to be greater than High. When this happens,
1684 ;; gnus-list-range-intersection returns nil which
1685 ;; indicates that no headers need to be fetched. -- Kevin
1686 (setq articles (gnus-list-range-intersection
1687 articles (list (cons low high)))))))
1690 10 "gnus-agent-fetch-headers: undownloaded articles are '%s'"
1691 (gnus-compress-sequence articles t))
1694 (set-buffer nntp-server-buffer)
1698 (gnus-message 7 "Fetching headers for %s..." group)
1701 (gnus-make-directory (nnheader-translate-file-chars
1702 (file-name-directory file) t))
1704 (unless (eq 'nov (gnus-retrieve-headers articles group))
1705 (nnvirtual-convert-headers))
1706 (gnus-agent-check-overview-buffer)
1707 ;; Move these headers to the overview buffer so that
1708 ;; gnus-agent-braid-nov can merge them with the contents
1711 gnus-agent-overview-buffer (point-min) (point-max))
1712 (when (file-exists-p file)
1713 (gnus-agent-braid-nov group articles file))
1714 (let ((coding-system-for-write
1715 gnus-agent-file-coding-system))
1716 (gnus-agent-check-overview-buffer)
1717 (write-region (point-min) (point-max) file nil 'silent))
1718 (gnus-agent-update-view-total-fetched-for group t)
1719 (gnus-agent-save-alist group articles nil)
1723 (nnheader-insert-file-contents file)))))
1726 (defsubst gnus-agent-copy-nov-line (article)
1728 (set-buffer gnus-agent-overview-buffer)
1729 (while (and (not (eobp))
1730 (< (setq art (read (current-buffer))) article))
1734 (not (eq article art)))
1735 (set-buffer nntp-server-buffer)
1737 (setq e (progn (forward-line 1) (point)))
1738 (set-buffer nntp-server-buffer)
1739 (insert-buffer-substring gnus-agent-overview-buffer b e))))
1741 (defun gnus-agent-braid-nov (group articles file)
1742 "Merge agent overview data with given file.
1743 Takes headers for ARTICLES from `gnus-agent-overview-buffer' and the given
1744 FILE and places the combined headers into `nntp-server-buffer'."
1746 (set-buffer gnus-agent-overview-buffer)
1747 (goto-char (point-min))
1748 (set-buffer nntp-server-buffer)
1750 (nnheader-insert-file-contents file)
1751 (goto-char (point-max))
1753 (unless (looking-at "[0-9]+\t")
1754 ;; Remove corrupted lines
1756 1 "Overview %s is corrupted. Removing corrupted lines..." file)
1757 (goto-char (point-min))
1759 (if (looking-at "[0-9]+\t")
1761 (delete-region (point) (progn (forward-line 1) (point)))))
1763 (unless (or (= (point-min) (point-max))
1764 (< (setq last (read (current-buffer))) (car articles)))
1765 ;; We do it the hard way.
1766 (when (nnheader-find-nov-line (car articles))
1767 ;; Replacing existing NOV entry
1768 (delete-region (point) (progn (forward-line 1) (point))))
1769 (gnus-agent-copy-nov-line (pop articles))
1773 (while (let ((art (read (current-buffer))))
1774 (cond ((< art (car articles))
1777 ((= art (car articles))
1780 (point) (progn (forward-line 1) (point)))
1786 (gnus-agent-copy-nov-line (pop articles)))))
1788 ;; Copy the rest lines
1789 (set-buffer nntp-server-buffer)
1790 (goto-char (point-max))
1793 (set-buffer gnus-agent-overview-buffer)
1795 (while (<= (read (current-buffer)) last)
1798 (setq start (point))
1799 (set-buffer nntp-server-buffer))
1800 (insert-buffer-substring gnus-agent-overview-buffer start))))
1802 ;; Keeps the compiler from warning about the free variable in
1803 ;; gnus-agent-read-agentview.
1805 (defvar gnus-agent-read-agentview))
1807 (defun gnus-agent-load-alist (group)
1808 "Load the article-state alist for GROUP."
1809 ;; Bind free variable that's used in `gnus-agent-read-agentview'.
1810 (let ((gnus-agent-read-agentview group))
1811 (setq gnus-agent-article-alist
1812 (gnus-cache-file-contents
1813 (gnus-agent-article-name ".agentview" group)
1814 'gnus-agent-file-loading-cache
1815 'gnus-agent-read-agentview))))
1817 ;; Save format may be either 1 or 2. Two is the new, compressed
1818 ;; format that is still being tested. Format 1 is uncompressed but
1819 ;; known to be reliable.
1820 (defconst gnus-agent-article-alist-save-format 2)
1822 (defun gnus-agent-read-agentview (file)
1823 "Load FILE and do a `read' there."
1827 (nnheader-insert-file-contents file)
1828 (goto-char (point-min))
1829 (let ((alist (read (current-buffer)))
1830 (version (condition-case nil (read (current-buffer))
1836 (error "gnus-agent-read-agentview no longer supports version %d. Stop gnus, manually evaluate gnus-agent-convert-to-compressed-agentview, then restart gnus." version))
1838 (let ((inhibit-quit t)
1840 (gnus-agent-open-history)
1841 (set-buffer (gnus-agent-history-buffer))
1842 (goto-char (point-min))
1844 (if (and (looking-at
1845 "[^\t\n]+\t\\([0-9]+\\)\t\\([^ \n]+\\) \\([0-9]+\\)")
1846 (string= (match-string 2)
1847 gnus-agent-read-agentview)
1848 (setq entry (assoc (string-to-number (match-string 3)) alist)))
1849 (setcdr entry (string-to-number (match-string 1))))
1851 (gnus-agent-close-history)
1852 (setq changed-version t)))
1854 (setq changed-version (not (= 1 gnus-agent-article-alist-save-format))))
1859 (let ((state (car comp-list))
1861 (gnus-uncompress-range
1863 (mapcar (lambda (article-id)
1864 (setq uncomp (cons (cons article-id state) uncomp)))
1867 (setq alist (sort uncomp 'car-less-than-car)))))
1868 (when changed-version
1869 (let ((gnus-agent-article-alist alist))
1870 (gnus-agent-save-alist gnus-agent-read-agentview)))
1874 (defun gnus-agent-save-alist (group &optional articles state)
1875 "Save the article-state alist for GROUP."
1876 (let* ((file-name-coding-system nnmail-pathname-coding-system)
1877 (prev (cons nil gnus-agent-article-alist))
1879 print-level print-length item article)
1880 (while (setq article (pop articles))
1881 (while (and (cdr prev)
1882 (< (caadr prev) article))
1883 (setq prev (cdr prev)))
1886 (setcdr prev (list (cons article state))))
1887 ((> (caadr prev) article)
1888 (setcdr prev (cons (cons article state) (cdr prev))))
1889 ((= (caadr prev) article)
1890 (setcdr (cadr prev) state)))
1891 (setq prev (cdr prev)))
1892 (setq gnus-agent-article-alist (cdr all))
1894 (gnus-agent-set-local group
1895 (caar gnus-agent-article-alist)
1896 (caar (last gnus-agent-article-alist)))
1898 (gnus-make-directory (gnus-agent-article-name "" group))
1899 (with-temp-file (gnus-agent-article-name ".agentview" group)
1900 (cond ((eq gnus-agent-article-alist-save-format 1)
1901 (princ gnus-agent-article-alist (current-buffer)))
1902 ((eq gnus-agent-article-alist-save-format 2)
1903 (let ((compressed nil))
1904 (mapcar (lambda (pair)
1905 (let* ((article-id (car pair))
1906 (day-of-download (cdr pair))
1907 (comp-list (assq day-of-download compressed)))
1910 (cons article-id (cdr comp-list)))
1912 (cons (list day-of-download article-id)
1914 nil)) gnus-agent-article-alist)
1915 (mapcar (lambda (comp-list)
1917 (gnus-compress-sequence
1918 (nreverse (cdr comp-list)))))
1920 (princ compressed (current-buffer)))))
1922 (princ gnus-agent-article-alist-save-format (current-buffer))
1925 (gnus-agent-update-view-total-fetched-for group nil)))
1927 (defvar gnus-agent-article-local nil)
1928 (defvar gnus-agent-file-loading-local nil)
1930 (defun gnus-agent-load-local (&optional method)
1931 "Load the METHOD'S local file. The local file contains min/max
1932 article counts for each of the method's subscribed groups."
1933 (let ((gnus-command-method (or method gnus-command-method)))
1934 (setq gnus-agent-article-local
1935 (gnus-cache-file-contents
1936 (gnus-agent-lib-file "local")
1937 'gnus-agent-file-loading-local
1938 'gnus-agent-read-and-cache-local))))
1940 (defun gnus-agent-read-and-cache-local (file)
1941 "Load and read FILE then bind its contents to
1942 gnus-agent-article-local. If that variable had `dirty' (also known as
1943 modified) original contents, they are first saved to their own file."
1945 (if (and gnus-agent-article-local
1946 (symbol-value (intern "+dirty" gnus-agent-article-local)))
1947 (gnus-agent-save-local))
1948 (gnus-agent-read-local file))
1950 (defun gnus-agent-read-local (file)
1951 "Load FILE and do a `read' there."
1952 (let ((my-obarray (gnus-make-hashtable (count-lines (point-min)
1957 (let ((nnheader-file-coding-system gnus-agent-file-coding-system))
1958 (nnheader-insert-file-contents file))
1961 (goto-char (point-min))
1962 ;; Skip any comments at the beginning of the file (the only place where they may appear)
1963 (while (= (following-char) ?\;)
1965 (setq line (1+ line)))
1972 (cur (current-buffer)))
1973 (setq group (read cur)
1977 (when (stringp group)
1978 (setq group (intern group my-obarray)))
1980 ;; NOTE: The '+ 0' ensure that min and max are both numerics.
1981 (set group (cons (+ 0 min) (+ 0 max))))
1983 (gnus-message 3 "Warning - invalid agent local: %s on line %d: "
1984 file line (error-message-string err))))
1986 (setq line (1+ line))))
1988 (set (intern "+dirty" my-obarray) nil)
1989 (set (intern "+method" my-obarray) gnus-command-method)
1992 (defun gnus-agent-save-local (&optional force)
1993 "Save gnus-agent-article-local under it method's agent.lib directory."
1994 (let ((my-obarray gnus-agent-article-local))
1995 (when (and my-obarray
1996 (or force (symbol-value (intern "+dirty" my-obarray))))
1997 (let* ((gnus-command-method (symbol-value (intern "+method" my-obarray)))
1998 ;; NOTE: gnus-command-method is used within gnus-agent-lib-file.
1999 (dest (gnus-agent-lib-file "local")))
2000 (gnus-make-directory (gnus-agent-lib-file ""))
2002 (let ((buffer-file-coding-system gnus-agent-file-coding-system))
2003 (with-temp-file dest
2004 (let ((gnus-command-method (symbol-value (intern "+method" my-obarray)))
2005 (file-name-coding-system nnmail-pathname-coding-system)
2006 print-level print-length item article
2007 (standard-output (current-buffer)))
2008 (mapatoms (lambda (symbol)
2009 (cond ((not (boundp symbol))
2011 ((member (symbol-name symbol) '("+dirty" "+method"))
2015 (let ((range (symbol-value symbol)))
2023 (defun gnus-agent-get-local (group &optional gmane method)
2024 (let* ((gmane (or gmane (gnus-group-real-name group)))
2025 (gnus-command-method (or method (gnus-find-method-for-group group)))
2026 (local (gnus-agent-load-local))
2027 (symb (intern gmane local))
2028 (minmax (and (boundp symb) (symbol-value symb))))
2030 ;; Bind these so that gnus-agent-load-alist doesn't change the
2031 ;; current alist (i.e. gnus-agent-article-alist)
2032 (let* ((gnus-agent-article-alist gnus-agent-article-alist)
2033 (gnus-agent-file-loading-cache gnus-agent-file-loading-cache)
2034 (alist (gnus-agent-load-alist group)))
2038 (caar (last alist))))
2039 (gnus-agent-set-local group (car minmax) (cdr minmax)
2040 gmane gnus-command-method local))))
2043 (defun gnus-agent-set-local (group min max &optional gmane method local)
2044 (let* ((gmane (or gmane (gnus-group-real-name group)))
2045 (gnus-command-method (or method (gnus-find-method-for-group group)))
2046 (local (or local (gnus-agent-load-local)))
2047 (symb (intern gmane local))
2048 (minmax (and (boundp symb) (symbol-value symb))))
2050 (if (cond ((and minmax
2051 (or (not (eq min (car minmax)))
2052 (not (eq max (cdr minmax)))))
2059 (set symb (cons min max))
2062 (unintern symb local)))
2063 (set (intern "+dirty" local) t))))
2065 (defun gnus-agent-article-name (article group)
2066 (expand-file-name article
2067 (file-name-as-directory
2068 (gnus-agent-group-pathname group))))
2070 (defun gnus-agent-batch-confirmation (msg)
2071 "Show error message and return t."
2072 (gnus-message 1 msg)
2076 (defun gnus-agent-batch-fetch ()
2077 "Start Gnus and fetch session."
2080 (let ((gnus-agent-confirmation-function 'gnus-agent-batch-confirmation))
2081 (gnus-agent-fetch-session))
2084 (defun gnus-agent-fetch-session ()
2085 "Fetch all articles and headers that are eligible for fetching."
2087 (unless gnus-agent-covered-methods
2088 (error "No servers are covered by the Gnus agent"))
2089 (unless gnus-plugged
2090 (error "Can't fetch articles while Gnus is unplugged"))
2091 (let ((methods (gnus-agent-covered-methods))
2092 groups group gnus-command-method)
2095 (setq gnus-command-method (car methods))
2096 (when (and (or (gnus-server-opened gnus-command-method)
2097 (gnus-open-server gnus-command-method))
2098 (gnus-online gnus-command-method))
2099 (setq groups (gnus-groups-from-server (car methods)))
2100 (gnus-agent-with-fetch
2101 (while (setq group (pop groups))
2102 (when (<= (gnus-group-level group)
2103 gnus-agent-handle-level)
2104 (if (or debug-on-error debug-on-quit)
2105 (gnus-agent-fetch-group-1
2106 group gnus-command-method)
2108 (gnus-agent-fetch-group-1
2109 group gnus-command-method)
2111 (unless (funcall gnus-agent-confirmation-function
2112 (format "Error %s while fetching session. Should gnus continue? "
2113 (error-message-string err)))
2114 (error "Cannot fetch articles into the Gnus agent")))
2116 (gnus-agent-regenerate-group group)
2117 (unless (funcall gnus-agent-confirmation-function
2119 "%s while fetching session. Should gnus continue? "
2120 (error-message-string err)))
2122 "Cannot fetch articles into the Gnus agent")))))))))
2123 (setq methods (cdr methods)))
2124 (gnus-run-hooks 'gnus-agent-fetched-hook)
2125 (gnus-message 6 "Finished fetching articles into the Gnus agent"))))
2127 (defun gnus-agent-fetch-group-1 (group method)
2129 (let ((gnus-command-method method)
2130 (gnus-newsgroup-name group)
2131 (gnus-newsgroup-dependencies gnus-newsgroup-dependencies)
2132 (gnus-newsgroup-headers gnus-newsgroup-headers)
2133 (gnus-newsgroup-scored gnus-newsgroup-scored)
2134 (gnus-use-cache gnus-use-cache)
2135 (gnus-summary-expunge-below gnus-summary-expunge-below)
2136 (gnus-summary-mark-below gnus-summary-mark-below)