* pop3.el (pop3-md5): Treat a given string as binary.
[elisp/gnus.git-] / lisp / gnus-agent.el
1 ;;; gnus-agent.el --- unplugged support for Semi-gnus
2 ;; Copyright (C) 1997, 1998, 1999, 2000, 2001, 2002
3 ;;        Free Software Foundation, Inc.
4
5 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
6 ;;      Tatsuya Ichikawa <t-ichi@po.shiojiri.ne.jp>
7 ;; This file is part of GNU Emacs.
8
9 ;; GNU Emacs is free software; you can redistribute it and/or modify
10 ;; it under the terms of the GNU General Public License as published by
11 ;; the Free Software Foundation; either version 2, or (at your option)
12 ;; any later version.
13
14 ;; GNU Emacs is distributed in the hope that it will be useful,
15 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 ;; GNU General Public License for more details.
18
19 ;; You should have received a copy of the GNU General Public License
20 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
21 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22 ;; Boston, MA 02111-1307, USA.
23
24 ;;; Commentary:
25
26 ;;; Code:
27
28 (eval-when-compile (require 'cl))
29 (eval-when-compile (require 'gnus-clfns))
30
31 (require 'gnus)
32 (require 'gnus-cache)
33 (require 'nnvirtual)
34 (require 'gnus-sum)
35 (require 'gnus-score)
36 (require 'gnus-srvr)
37 (eval-when-compile
38   (if (featurep 'xemacs)
39       (require 'itimer)
40     (require 'timer))
41   (require 'gnus-group))
42
43 (eval-and-compile
44   (autoload 'gnus-server-update-server "gnus-srvr"))
45
46 (defcustom gnus-agent-directory (nnheader-concat gnus-directory "agent/")
47   "Where the Gnus agent will store its files."
48   :group 'gnus-agent
49   :type 'directory)
50
51 (defcustom gnus-agent-plugged-hook nil
52   "Hook run when plugging into the network."
53   :group 'gnus-agent
54   :type 'hook)
55
56 (defcustom gnus-agent-unplugged-hook nil
57   "Hook run when unplugging from the network."
58   :group 'gnus-agent
59   :type 'hook)
60
61 (defcustom gnus-agent-fetched-hook nil
62   "Hook run after finishing fetching articles."
63   :group 'gnus-agent
64   :type 'hook)
65
66 (defcustom gnus-agent-handle-level gnus-level-subscribed
67   "Groups on levels higher than this variable will be ignored by the Agent."
68   :group 'gnus-agent
69   :type 'integer)
70
71 (defcustom gnus-agent-expire-days 7
72   "Read articles older than this will be expired.
73 This can also be a list of regexp/day pairs.  The regexps will
74 be matched against group names."
75   :group 'gnus-agent
76   :type 'integer)
77
78 (defcustom gnus-agent-expire-all nil
79   "If non-nil, also expire unread, ticked and dormant articles.
80 If nil, only read articles will be expired."
81   :group 'gnus-agent
82   :type 'boolean)
83
84 (defcustom gnus-agent-group-mode-hook nil
85   "Hook run in Agent group minor modes."
86   :group 'gnus-agent
87   :type 'hook)
88
89 ;; Extracted from gnus-xmas-redefine in order to preserve user settings
90 (when (featurep 'xemacs)
91   (add-hook 'gnus-agent-group-mode-hook 'gnus-xmas-agent-group-menu-add))
92
93 (defcustom gnus-agent-summary-mode-hook nil
94   "Hook run in Agent summary minor modes."
95   :group 'gnus-agent
96   :type 'hook)
97
98 ;; Extracted from gnus-xmas-redefine in order to preserve user settings
99 (when (featurep 'xemacs)
100   (add-hook 'gnus-agent-summary-mode-hook 'gnus-xmas-agent-summary-menu-add))
101
102 (defcustom gnus-agent-server-mode-hook nil
103   "Hook run in Agent summary minor modes."
104   :group 'gnus-agent
105   :type 'hook)
106
107 ;; Extracted from gnus-xmas-redefine in order to preserve user settings
108 (when (featurep 'xemacs)
109   (add-hook 'gnus-agent-server-mode-hook 'gnus-xmas-agent-server-menu-add))
110
111 (defcustom gnus-agent-confirmation-function 'y-or-n-p
112   "Function to confirm when error happens."
113   :version "21.1"
114   :group 'gnus-agent
115   :type 'function)
116
117 (defcustom gnus-agent-large-newsgroup nil
118   "*The number of articles which indicates a large newsgroup.
119 If the number of unread articles exceeds it, The number of articles to be
120 fetched will be limited to it. If not a positive integer, never consider it."
121   :group 'gnus-agent
122   :type '(choice (const nil)
123                  (integer :tag "Number")))
124
125 (defcustom gnus-agent-synchronize-flags 'ask
126   "Indicate if flags are synchronized when you plug in.
127 If this is `ask' the hook will query the user."
128   :version "21.1"
129   :type '(choice (const :tag "Always" t)
130                  (const :tag "Never" nil)
131                  (const :tag "Ask" ask))
132   :group 'gnus-agent)
133
134 (defcustom gnus-agent-go-online 'ask
135   "Indicate if offline servers go online when you plug in.
136 If this is `ask' the hook will query the user."
137   :version "21.1"
138   :type '(choice (const :tag "Always" t)
139                  (const :tag "Never" nil)
140                  (const :tag "Ask" ask))
141   :group 'gnus-agent)
142
143 ;;; Internal variables
144
145 (defvar gnus-agent-history-buffers nil)
146 (defvar gnus-agent-buffer-alist nil)
147 (defvar gnus-agent-article-alist nil)
148 (defvar gnus-agent-group-alist nil)
149 (defvar gnus-category-alist nil)
150 (defvar gnus-agent-current-history nil)
151 (defvar gnus-agent-overview-buffer nil)
152 (defvar gnus-category-predicate-cache nil)
153 (defvar gnus-category-group-cache nil)
154 (defvar gnus-agent-spam-hashtb nil)
155 (defvar gnus-agent-file-name nil)
156 (defvar gnus-agent-send-mail-function nil)
157 (defvar gnus-agent-file-coding-system 'raw-text)
158
159 ;; Dynamic variables
160 (defvar gnus-headers)
161 (defvar gnus-score)
162
163 ;;;
164 ;;; Setup
165 ;;;
166
167 (defun gnus-open-agent ()
168   (setq gnus-agent t)
169   (gnus-agent-read-servers)
170   (gnus-category-read)
171   (gnus-agent-create-buffer)
172   (add-hook 'gnus-group-mode-hook 'gnus-agent-mode)
173   (add-hook 'gnus-summary-mode-hook 'gnus-agent-mode)
174   (add-hook 'gnus-server-mode-hook 'gnus-agent-mode))
175
176 (defun gnus-agent-create-buffer ()
177   (if (gnus-buffer-live-p gnus-agent-overview-buffer)
178       t
179     (setq gnus-agent-overview-buffer
180           (gnus-get-buffer-create " *Gnus agent overview*"))
181     (with-current-buffer gnus-agent-overview-buffer
182       (set-buffer-multibyte t))
183     nil))
184
185 (gnus-add-shutdown 'gnus-close-agent 'gnus)
186
187 (defun gnus-close-agent ()
188   (setq gnus-agent-covered-methods nil
189         gnus-category-predicate-cache nil
190         gnus-category-group-cache nil
191         gnus-agent-spam-hashtb nil)
192   (gnus-kill-buffer gnus-agent-overview-buffer))
193
194 ;;;
195 ;;; Utility functions
196 ;;;
197
198 (defun gnus-agent-read-file (file)
199   "Load FILE and do a `read' there."
200   (with-temp-buffer
201     (ignore-errors
202       (nnheader-insert-file-contents file)
203       (goto-char (point-min))
204       (read (current-buffer)))))
205
206 (defsubst gnus-agent-method ()
207   (concat (symbol-name (car gnus-command-method)) "/"
208           (if (equal (cadr gnus-command-method) "")
209               "unnamed"
210             (cadr gnus-command-method))))
211
212 (defsubst gnus-agent-directory ()
213   "Path of the Gnus agent directory."
214   (nnheader-concat gnus-agent-directory
215                    (nnheader-translate-file-chars (gnus-agent-method)) "/"))
216
217 (defun gnus-agent-lib-file (file)
218   "The full path of the Gnus agent library FILE."
219   (expand-file-name file
220                     (file-name-as-directory
221                      (expand-file-name "agent.lib" (gnus-agent-directory)))))
222
223 ;;; Fetching setup functions.
224
225 (defun gnus-agent-start-fetch ()
226   "Initialize data structures for efficient fetching."
227   (gnus-agent-open-history)
228   (setq gnus-agent-current-history (gnus-agent-history-buffer))
229   (gnus-agent-create-buffer))
230
231 (defun gnus-agent-stop-fetch ()
232   "Save all data structures and clean up."
233   (gnus-agent-save-history)
234   (gnus-agent-close-history)
235   (setq gnus-agent-spam-hashtb nil)
236   (save-excursion
237     (set-buffer nntp-server-buffer)
238     (widen)))
239
240 (defmacro gnus-agent-with-fetch (&rest forms)
241   "Do FORMS safely."
242   `(unwind-protect
243        (let ((gnus-agent-fetching t))
244          (gnus-agent-start-fetch)
245          ,@forms)
246      (gnus-agent-stop-fetch)))
247
248 (put 'gnus-agent-with-fetch 'lisp-indent-function 0)
249 (put 'gnus-agent-with-fetch 'edebug-form-spec '(body))
250
251 ;;;
252 ;;; Mode infestation
253 ;;;
254
255 (defvar gnus-agent-mode-hook nil
256   "Hook run when installing agent mode.")
257
258 (defvar gnus-agent-mode nil)
259 (defvar gnus-agent-mode-status '(gnus-agent-mode " Plugged"))
260
261 (defun gnus-agent-mode ()
262   "Minor mode for providing a agent support in Gnus buffers."
263   (let* ((buffer (progn (string-match "^gnus-\\(.*\\)-mode$"
264                                       (symbol-name major-mode))
265                         (match-string 1 (symbol-name major-mode))))
266          (mode (intern (format "gnus-agent-%s-mode" buffer))))
267     (set (make-local-variable 'gnus-agent-mode) t)
268     (set mode nil)
269     (set (make-local-variable mode) t)
270     ;; Set up the menu.
271     (when (gnus-visual-p 'agent-menu 'menu)
272       (funcall (intern (format "gnus-agent-%s-make-menu-bar" buffer))))
273     (unless (assq 'gnus-agent-mode minor-mode-alist)
274       (push gnus-agent-mode-status minor-mode-alist))
275     (unless (assq mode minor-mode-map-alist)
276       (push (cons mode (symbol-value (intern (format "gnus-agent-%s-mode-map"
277                                                      buffer))))
278             minor-mode-map-alist))
279     (when (eq major-mode 'gnus-group-mode)
280       (gnus-agent-toggle-plugged gnus-plugged))
281     (gnus-run-hooks 'gnus-agent-mode-hook
282                     (intern (format "gnus-agent-%s-mode-hook" buffer)))))
283
284 (defvar gnus-agent-group-mode-map (make-sparse-keymap))
285 (gnus-define-keys gnus-agent-group-mode-map
286   "Ju" gnus-agent-fetch-groups
287   "Jc" gnus-enter-category-buffer
288   "Jj" gnus-agent-toggle-plugged
289   "Js" gnus-agent-fetch-session
290   "JY" gnus-agent-synchronize-flags
291   "JS" gnus-group-send-queue
292   "Ja" gnus-agent-add-group
293   "Jr" gnus-agent-remove-group
294   "Jo" gnus-agent-toggle-group-plugged)
295
296 (defun gnus-agent-group-make-menu-bar ()
297   (unless (boundp 'gnus-agent-group-menu)
298     (easy-menu-define
299      gnus-agent-group-menu gnus-agent-group-mode-map ""
300      '("Agent"
301        ["Toggle plugged" gnus-agent-toggle-plugged t]
302        ["Toggle group plugged" gnus-agent-toggle-group-plugged t]
303        ["List categories" gnus-enter-category-buffer t]
304        ["Send queue" gnus-group-send-queue gnus-plugged]
305        ("Fetch"
306         ["All" gnus-agent-fetch-session gnus-plugged]
307         ["Group" gnus-agent-fetch-group gnus-plugged])))))
308
309 (defvar gnus-agent-summary-mode-map (make-sparse-keymap))
310 (gnus-define-keys gnus-agent-summary-mode-map
311   "Jj" gnus-agent-toggle-plugged
312   "Ju" gnus-agent-summary-fetch-group
313   "J#" gnus-agent-mark-article
314   "J\M-#" gnus-agent-unmark-article
315   "@" gnus-agent-toggle-mark
316   "Jc" gnus-agent-catchup)
317
318 (defun gnus-agent-summary-make-menu-bar ()
319   (unless (boundp 'gnus-agent-summary-menu)
320     (easy-menu-define
321      gnus-agent-summary-menu gnus-agent-summary-mode-map ""
322      '("Agent"
323        ["Toggle plugged" gnus-agent-toggle-plugged t]
324        ["Mark as downloadable" gnus-agent-mark-article t]
325        ["Unmark as downloadable" gnus-agent-unmark-article t]
326        ["Toggle mark" gnus-agent-toggle-mark t]
327        ["Fetch downloadable" gnus-agent-summary-fetch-group t]
328        ["Catchup undownloaded" gnus-agent-catchup t]))))
329
330 (defvar gnus-agent-server-mode-map (make-sparse-keymap))
331 (gnus-define-keys gnus-agent-server-mode-map
332   "Jj" gnus-agent-toggle-plugged
333   "Ja" gnus-agent-add-server
334   "Jr" gnus-agent-remove-server)
335
336 (defun gnus-agent-server-make-menu-bar ()
337   (unless (boundp 'gnus-agent-server-menu)
338     (easy-menu-define
339      gnus-agent-server-menu gnus-agent-server-mode-map ""
340      '("Agent"
341        ["Toggle plugged" gnus-agent-toggle-plugged t]
342        ["Add" gnus-agent-add-server t]
343        ["Remove" gnus-agent-remove-server t]))))
344
345 (defun gnus-agent-make-mode-line-string (string mouse-button mouse-func)
346   (if (and (fboundp 'propertize)
347            (fboundp 'make-mode-line-mouse-map))
348       (propertize string 'local-map
349                   (make-mode-line-mouse-map mouse-button mouse-func))
350     string))
351
352 (defun gnus-agent-toggle-plugged (plugged)
353   "Toggle whether Gnus is unplugged or not."
354   (interactive (list (not gnus-plugged)))
355   (if plugged
356       (progn
357         (setq gnus-plugged plugged)
358         (gnus-run-hooks 'gnus-agent-plugged-hook)
359         (setcar (cdr gnus-agent-mode-status)
360                 (gnus-agent-make-mode-line-string " Plugged"
361                                                   'mouse-2
362                                                   'gnus-agent-toggle-plugged))
363         (gnus-agent-go-online gnus-agent-go-online)
364         (gnus-agent-possibly-synchronize-flags))
365     (gnus-agent-close-connections)
366     (setq gnus-plugged plugged)
367     (gnus-run-hooks 'gnus-agent-unplugged-hook)
368     (setcar (cdr gnus-agent-mode-status)
369             (gnus-agent-make-mode-line-string " Unplugged"
370                                               'mouse-2
371                                               'gnus-agent-toggle-plugged)))
372   (force-mode-line-update))
373
374 (defun gnus-agent-close-connections ()
375   "Close all methods covered by the Gnus agent."
376   (let ((methods gnus-agent-covered-methods))
377     (while methods
378       (gnus-close-server (pop methods)))))
379
380 ;;;###autoload
381 (defun gnus-unplugged ()
382   "Start Gnus unplugged."
383   (interactive)
384   (setq gnus-plugged nil)
385   (gnus))
386
387 ;;;###autoload
388 (defun gnus-plugged ()
389   "Start Gnus plugged."
390   (interactive)
391   (setq gnus-plugged t)
392   (gnus))
393
394 ;;;###autoload
395 (defun gnus-agentize ()
396   "Allow Gnus to be an offline newsreader.
397 The normal usage of this command is to put the following as the
398 last form in your `.gnus.el' file:
399
400 \(gnus-agentize)
401
402 This will modify the `gnus-setup-news-hook', and
403 `message-send-mail-real-function' variables, and install the Gnus agent
404 minor mode in all Gnus buffers."
405   (interactive)
406   (gnus-open-agent)
407   (add-hook 'gnus-setup-news-hook 'gnus-agent-queue-setup)
408   (unless gnus-agent-send-mail-function
409     (setq gnus-agent-send-mail-function (or
410                                          message-send-mail-real-function
411                                          message-send-mail-function)
412           message-send-mail-real-function 'gnus-agent-send-mail))
413   (unless gnus-agent-covered-methods
414     (setq gnus-agent-covered-methods (list gnus-select-method))))
415
416 (defun gnus-agent-queue-setup ()
417   "Make sure the queue group exists."
418   (unless (gnus-gethash "nndraft:queue" gnus-newsrc-hashtb)
419     (gnus-request-create-group "queue" '(nndraft ""))
420     (let ((gnus-level-default-subscribed 1))
421       (gnus-subscribe-group "nndraft:queue" nil '(nndraft "")))
422     (gnus-group-set-parameter
423      "nndraft:queue" 'gnus-dummy '((gnus-draft-mode)))))
424
425 (defun gnus-agent-send-mail ()
426   (if gnus-plugged
427       (funcall gnus-agent-send-mail-function)
428     (goto-char (point-min))
429     (re-search-forward
430      (concat "^" (regexp-quote mail-header-separator) "\n"))
431     (replace-match "\n")
432     (gnus-agent-insert-meta-information 'mail)
433     (gnus-request-accept-article "nndraft:queue" nil t t)))
434
435 (defun gnus-agent-insert-meta-information (type &optional method)
436   "Insert meta-information into the message that says how it's to be posted.
437 TYPE can be either `mail' or `news'.  If the latter, then METHOD can
438 be a select method."
439   (save-excursion
440     (message-remove-header gnus-agent-meta-information-header)
441     (goto-char (point-min))
442     (insert gnus-agent-meta-information-header ": "
443             (symbol-name type) " " (format "%S" method)
444             "\n")
445     (forward-char -1)
446     (while (search-backward "\n" nil t)
447       (replace-match "\\n" t t))))
448
449 (defun gnus-agent-restore-gcc ()
450   "Restore GCC field from saved header."
451   (save-excursion
452     (goto-char (point-min))
453     (while (re-search-forward (concat gnus-agent-gcc-header ":") nil t)
454       (replace-match "Gcc:" 'fixedcase))))
455
456 (defun gnus-agent-any-covered-gcc ()
457   (save-restriction
458     (message-narrow-to-headers)
459     (let* ((gcc (mail-fetch-field "gcc" nil t))
460            (methods (and gcc
461                          (mapcar 'gnus-inews-group-method
462                                  (message-unquote-tokens
463                                   (message-tokenize-header
464                                    gcc " ,")))))
465            covered)
466       (while (and (not covered) methods)
467         (setq covered (gnus-agent-method-p (car methods))
468               methods (cdr methods)))
469       covered)))
470
471 (defun gnus-agent-possibly-save-gcc ()
472   "Save GCC if Gnus is unplugged."
473   (when (and (not gnus-plugged) (gnus-agent-any-covered-gcc))
474     (save-excursion
475       (goto-char (point-min))
476       (let ((case-fold-search t))
477         (while (re-search-forward "^gcc:" nil t)
478           (replace-match (concat gnus-agent-gcc-header ":") 'fixedcase))))))
479
480 (defun gnus-agent-possibly-do-gcc ()
481   "Do GCC if Gnus is plugged."
482   (when (or gnus-plugged (not (gnus-agent-any-covered-gcc)))
483     (gnus-inews-do-gcc)))
484
485 ;;;
486 ;;; Group mode commands
487 ;;;
488
489 (defun gnus-agent-fetch-groups (n)
490   "Put all new articles in the current groups into the Agent."
491   (interactive "P")
492   (unless gnus-plugged
493     (error "Groups can't be fetched when Gnus is unplugged"))
494   (gnus-group-iterate n 'gnus-agent-fetch-group))
495
496 (defun gnus-agent-fetch-group (group)
497   "Put all new articles in GROUP into the Agent."
498   (interactive (list (gnus-group-group-name)))
499   (let ((state gnus-plugged))
500     (unwind-protect
501         (progn
502           (unless group
503             (error "No group on the current line"))
504           (unless state
505             (gnus-agent-toggle-plugged gnus-plugged))
506           (let ((gnus-command-method (gnus-find-method-for-group group)))
507             (gnus-agent-with-fetch
508               (gnus-agent-fetch-group-1 group gnus-command-method)
509               (gnus-message 5 "Fetching %s...done" group))))
510       (when (and (not state)
511                  gnus-plugged)
512         (gnus-agent-toggle-plugged gnus-plugged)))))
513
514 (defun gnus-agent-add-group (category arg)
515   "Add the current group to an agent category."
516   (interactive
517    (list
518     (intern
519      (completing-read
520       "Add to category: "
521       (mapcar (lambda (cat) (list (symbol-name (car cat))))
522               gnus-category-alist)
523       nil t))
524     current-prefix-arg))
525   (let ((cat (assq category gnus-category-alist))
526         c groups)
527     (gnus-group-iterate arg
528       (lambda (group)
529         (when (cadddr (setq c (gnus-group-category group)))
530           (setf (cadddr c) (delete group (cadddr c))))
531         (push group groups)))
532     (setf (cadddr cat) (nconc (cadddr cat) groups))
533     (gnus-category-write)))
534
535 (defun gnus-agent-remove-group (arg)
536   "Remove the current group from its agent category, if any."
537   (interactive "P")
538   (let (c)
539     (gnus-group-iterate arg
540       (lambda (group)
541         (when (cadddr (setq c (gnus-group-category group)))
542           (setf (cadddr c) (delete group (cadddr c))))))
543     (gnus-category-write)))
544
545 (defun gnus-agent-synchronize-flags ()
546   "Synchronize unplugged flags with servers."
547   (interactive)
548   (save-excursion
549     (dolist (gnus-command-method gnus-agent-covered-methods)
550       (when (file-exists-p (gnus-agent-lib-file "flags"))
551         (gnus-agent-synchronize-flags-server gnus-command-method)))))
552
553 (defun gnus-agent-possibly-synchronize-flags ()
554   "Synchronize flags according to `gnus-agent-synchronize-flags'."
555   (interactive)
556   (save-excursion
557     (dolist (gnus-command-method gnus-agent-covered-methods)
558       (when (file-exists-p (gnus-agent-lib-file "flags"))
559         (gnus-agent-possibly-synchronize-flags-server gnus-command-method)))))
560
561 (defun gnus-agent-synchronize-flags-server (method)
562   "Synchronize flags set when unplugged for server."
563   (let ((gnus-command-method method))
564     (when (file-exists-p (gnus-agent-lib-file "flags"))
565       (set-buffer (get-buffer-create " *Gnus Agent flag synchronize*"))
566       (erase-buffer)
567       (nnheader-insert-file-contents (gnus-agent-lib-file "flags"))
568       (if (null (gnus-check-server gnus-command-method))
569           (message "Couldn't open server %s" (nth 1 gnus-command-method))
570         (while (not (eobp))
571           (if (null (eval (read (current-buffer))))
572               (progn (forward-line)
573                      (kill-line -1))
574             (write-file (gnus-agent-lib-file "flags"))
575             (error "Couldn't set flags from file %s"
576                    (gnus-agent-lib-file "flags"))))
577         (delete-file (gnus-agent-lib-file "flags")))
578       (kill-buffer nil))))
579
580 (defun gnus-agent-possibly-synchronize-flags-server (method)
581   "Synchronize flags for server according to `gnus-agent-synchronize-flags'."
582   (when (or (and gnus-agent-synchronize-flags
583                  (not (eq gnus-agent-synchronize-flags 'ask)))
584             (and (eq gnus-agent-synchronize-flags 'ask)
585                  (gnus-y-or-n-p (format "Synchronize flags on server `%s'? "
586                                         (cadr method)))))
587     (gnus-agent-synchronize-flags-server method)))
588
589 ;;;
590 ;;; Server mode commands
591 ;;;
592
593 (defun gnus-agent-add-server (server)
594   "Enroll SERVER in the agent program."
595   (interactive (list (gnus-server-server-name)))
596   (unless server
597     (error "No server on the current line"))
598   (let ((method (gnus-server-get-method nil (gnus-server-server-name))))
599     (when (gnus-agent-method-p method)
600       (error "Server already in the agent program"))
601     (push method gnus-agent-covered-methods)
602     (gnus-server-update-server server)
603     (gnus-agent-write-servers)
604     (message "Entered %s into the Agent" server)))
605
606 (defun gnus-agent-remove-server (server)
607   "Remove SERVER from the agent program."
608   (interactive (list (gnus-server-server-name)))
609   (unless server
610     (error "No server on the current line"))
611   (let ((method (gnus-server-get-method nil (gnus-server-server-name))))
612     (unless (gnus-agent-method-p method)
613       (error "Server not in the agent program"))
614     (setq gnus-agent-covered-methods
615           (delete method gnus-agent-covered-methods))
616     (gnus-server-update-server server)
617     (gnus-agent-write-servers)
618     (message "Removed %s from the agent" server)))
619
620 (defun gnus-agent-read-servers ()
621   "Read the alist of covered servers."
622   (setq gnus-agent-covered-methods
623         (gnus-agent-read-file
624          (nnheader-concat gnus-agent-directory "lib/servers"))))
625
626 (defun gnus-agent-write-servers ()
627   "Write the alist of covered servers."
628   (gnus-make-directory (nnheader-concat gnus-agent-directory "lib"))
629   (let ((coding-system-for-write nnheader-file-coding-system)
630         (output-coding-system nnheader-file-coding-system)
631         (file-name-coding-system nnmail-pathname-coding-system)
632         (pathname-coding-system nnmail-pathname-coding-system))
633     (with-temp-file (nnheader-concat gnus-agent-directory "lib/servers")
634       (prin1 gnus-agent-covered-methods (current-buffer)))))
635
636 ;;;
637 ;;; Summary commands
638 ;;;
639
640 (defun gnus-agent-mark-article (n &optional unmark)
641   "Mark the next N articles as downloadable.
642 If N is negative, mark backward instead.  If UNMARK is non-nil, remove
643 the mark instead.  The difference between N and the actual number of
644 articles marked is returned."
645   (interactive "p")
646   (let ((backward (< n 0))
647         (n (abs n)))
648     (while (and
649             (> n 0)
650             (progn
651               (gnus-summary-set-agent-mark
652                (gnus-summary-article-number) unmark)
653               (zerop (gnus-summary-next-subject (if backward -1 1) nil t))))
654       (setq n (1- n)))
655     (when (/= 0 n)
656       (gnus-message 7 "No more articles"))
657     (gnus-summary-recenter)
658     (gnus-summary-position-point)
659     n))
660
661 (defun gnus-agent-unmark-article (n)
662   "Remove the downloadable mark from the next N articles.
663 If N is negative, unmark backward instead.  The difference between N and
664 the actual number of articles unmarked is returned."
665   (interactive "p")
666   (gnus-agent-mark-article n t))
667
668 (defun gnus-agent-toggle-mark (n)
669   "Toggle the downloadable mark from the next N articles.
670 If N is negative, toggle backward instead.  The difference between N and
671 the actual number of articles toggled is returned."
672   (interactive "p")
673   (gnus-agent-mark-article n 'toggle))
674
675 (defun gnus-summary-set-agent-mark (article &optional unmark)
676   "Mark ARTICLE as downloadable."
677   (let ((unmark (if (and (not (null unmark)) (not (eq t unmark)))
678                     (memq article gnus-newsgroup-downloadable)
679                   unmark)))
680     (if unmark
681         (progn
682           (setq gnus-newsgroup-downloadable
683                 (delq article gnus-newsgroup-downloadable))
684           (push article gnus-newsgroup-undownloaded))
685       (setq gnus-newsgroup-undownloaded
686             (delq article gnus-newsgroup-undownloaded))
687       (push article gnus-newsgroup-downloadable))
688     (gnus-summary-update-mark
689      (if unmark gnus-undownloaded-mark gnus-downloadable-mark)
690      'unread)))
691
692 (defun gnus-agent-get-undownloaded-list ()
693   "Mark all unfetched articles as read."
694   (let ((gnus-command-method (gnus-find-method-for-group gnus-newsgroup-name)))
695     (when (and (not (gnus-online gnus-command-method))
696                (gnus-agent-method-p gnus-command-method))
697       (gnus-agent-load-alist gnus-newsgroup-name)
698       ;; First mark all undownloaded articles as undownloaded.
699       (dolist (article (mapcar (lambda (header) (mail-header-number header))
700                                gnus-newsgroup-headers))
701         (unless (or (cdr (assq article gnus-agent-article-alist))
702                     (memq article gnus-newsgroup-downloadable)
703                     (memq article gnus-newsgroup-cached))
704           (push article gnus-newsgroup-undownloaded)))
705       ;; Then mark downloaded downloadable as not-downloadable,
706       ;; if you get my drift.
707       (dolist (article gnus-newsgroup-downloadable)
708         (when (cdr (assq article gnus-agent-article-alist))
709           (setq gnus-newsgroup-downloadable
710                 (delq article gnus-newsgroup-downloadable)))))))
711
712 (defun gnus-agent-catchup ()
713   "Mark all undownloaded articles as read."
714   (interactive)
715   (save-excursion
716     (while gnus-newsgroup-undownloaded
717       (gnus-summary-mark-article
718        (pop gnus-newsgroup-undownloaded) gnus-catchup-mark)))
719   (gnus-summary-position-point))
720
721 (defun gnus-agent-summary-fetch-group ()
722   "Fetch the downloadable articles in the group."
723   (interactive)
724   (let ((articles gnus-newsgroup-downloadable)
725         (gnus-command-method (gnus-find-method-for-group gnus-newsgroup-name))
726         (state gnus-plugged))
727     (unwind-protect
728         (progn
729           (unless state
730             (gnus-agent-toggle-plugged t))
731           (unless articles
732             (error "No articles to download"))
733           (gnus-agent-with-fetch
734             (gnus-agent-fetch-articles gnus-newsgroup-name articles))
735           (save-excursion
736             (dolist (article articles)
737               (setq gnus-newsgroup-downloadable
738                     (delq article gnus-newsgroup-downloadable))
739               (gnus-summary-mark-article article gnus-unread-mark))))
740       (when (and (not state)
741                  gnus-plugged)
742         (gnus-agent-toggle-plugged nil)))))
743
744 ;;;
745 ;;; Internal functions
746 ;;;
747
748 (defun gnus-agent-save-active (method)
749   (gnus-agent-save-active-1 method 'gnus-active-to-gnus-format))
750
751 (defun gnus-agent-save-active-1 (method function)
752   (when (gnus-agent-method-p method)
753     (let* ((gnus-command-method method)
754            (new (gnus-make-hashtable (count-lines (point-min) (point-max))))
755            (file (gnus-agent-lib-file "active")))
756       (funcall function nil new)
757       (gnus-agent-write-active file new)
758       (erase-buffer)
759       (insert-file-contents-as-coding-system gnus-agent-file-coding-system
760                                              file))))
761
762 (defun gnus-agent-write-active (file new)
763   (let ((orig (gnus-make-hashtable (count-lines (point-min) (point-max))))
764         (file (gnus-agent-lib-file "active"))
765         elem osym)
766     (when (file-exists-p file)
767       (with-temp-buffer
768         (insert-file-contents-as-coding-system gnus-agent-file-coding-system
769                                                file)
770         (gnus-active-to-gnus-format nil orig))
771       (mapatoms
772        (lambda (sym)
773          (when (and sym (boundp sym))
774            (if (and (boundp (setq osym (intern (symbol-name sym) orig)))
775                     (setq elem (symbol-value osym)))
776                (progn
777                  (if (and (integerp (car (symbol-value sym)))
778                           (> (car elem) (car (symbol-value sym))))
779                      (setcar elem (car (symbol-value sym))))
780                  (if (integerp (cdr (symbol-value sym)))
781                      (setcdr elem (cdr (symbol-value sym)))))
782              (set (intern (symbol-name sym) orig) (symbol-value sym)))))
783        new))
784     (gnus-make-directory (file-name-directory file))
785     (let ((nnmail-active-file-coding-system gnus-agent-file-coding-system))
786       ;; The hashtable contains real names of groups,  no more prefix
787       ;; removing, so set `full' to `t'.
788       (gnus-write-active-file file orig t))))
789
790 (defun gnus-agent-save-groups (method)
791   (gnus-agent-save-active-1 method 'gnus-groups-to-gnus-format))
792
793 (defun gnus-agent-save-group-info (method group active)
794   (when (gnus-agent-method-p method)
795     (let* ((gnus-command-method method)
796            (coding-system-for-write nnheader-file-coding-system)
797            (output-coding-system nnheader-file-coding-system)
798            (file-name-coding-system nnmail-pathname-coding-system)
799            (pathname-coding-system nnmail-pathname-coding-system)
800            (file (gnus-agent-lib-file "active"))
801            oactive-min)
802       (gnus-make-directory (file-name-directory file))
803       (with-temp-file file
804         (when (file-exists-p file)
805           (nnheader-insert-file-contents file))
806         (goto-char (point-min))
807         (when (re-search-forward
808                (concat "^" (regexp-quote group) " ") nil t)
809           (save-excursion
810             (read (current-buffer))                      ;; max
811             (setq oactive-min (read (current-buffer))))  ;; min
812           (gnus-delete-line))
813         (insert (format "%S %d %d y\n" (intern group)
814                         (cdr active)
815                         (or oactive-min (car active))))
816         (goto-char (point-max))
817         (while (search-backward "\\." nil t)
818           (delete-char 1))))))
819
820 (defun gnus-agent-group-path (group)
821   "Translate GROUP into a path."
822   (if nnmail-use-long-file-names
823       (gnus-group-real-name group)
824     (nnheader-translate-file-chars
825      (nnheader-replace-chars-in-string
826       (nnheader-replace-duplicate-chars-in-string
827        (nnheader-replace-chars-in-string
828         (gnus-group-real-name group)
829         ?/ ?_)
830        ?. ?_)
831       ?. ?/))))
832
833 \f
834
835 (defun gnus-agent-get-function (method)
836   (if (gnus-online method)
837       (car method)
838     (require 'nnagent)
839     'nnagent))
840
841 ;;; History functions
842
843 (defun gnus-agent-history-buffer ()
844   (cdr (assoc (gnus-agent-method) gnus-agent-history-buffers)))
845
846 (defun gnus-agent-open-history ()
847   (save-excursion
848     (push (cons (gnus-agent-method)
849                 (set-buffer (gnus-get-buffer-create
850                              (format " *Gnus agent %s history*"
851                                      (gnus-agent-method)))))
852           gnus-agent-history-buffers)
853     (erase-buffer)
854     (insert "\n")
855     (let ((file (gnus-agent-lib-file "history")))
856       (when (file-exists-p file)
857         (nnheader-insert-file-contents file))
858       (set (make-local-variable 'gnus-agent-file-name) file))))
859
860 (defun gnus-agent-save-history ()
861   (save-excursion
862     (set-buffer gnus-agent-current-history)
863     (gnus-make-directory (file-name-directory gnus-agent-file-name))
864     (write-region-as-coding-system
865      gnus-agent-file-coding-system
866      (1+ (point-min)) (point-max) gnus-agent-file-name nil 'silent)))
867
868 (defun gnus-agent-close-history ()
869   (when (gnus-buffer-live-p gnus-agent-current-history)
870     (kill-buffer gnus-agent-current-history)
871     (setq gnus-agent-history-buffers
872           (delq (assoc (gnus-agent-method) gnus-agent-history-buffers)
873                 gnus-agent-history-buffers))))
874
875 (defun gnus-agent-enter-history (id group-arts date)
876   (save-excursion
877     (set-buffer gnus-agent-current-history)
878     (goto-char (point-max))
879     (let ((p (point)))
880       (insert id "\t" (number-to-string date) "\t")
881       (while group-arts
882         (insert (format "%S" (intern (caar group-arts)))
883                 " " (number-to-string (cdr (pop group-arts)))
884                 " "))
885       (insert "\n")
886       (while (search-backward "\\." p t)
887         (delete-char 1)))))
888
889 (defun gnus-agent-article-in-history-p (id)
890   (save-excursion
891     (set-buffer (gnus-agent-history-buffer))
892     (goto-char (point-min))
893     (search-forward (concat "\n" id "\t") nil t)))
894
895 (defun gnus-agent-history-path (id)
896   (save-excursion
897     (set-buffer (gnus-agent-history-buffer))
898     (goto-char (point-min))
899     (when (search-forward (concat "\n" id "\t") nil t)
900       (let ((method (gnus-agent-method)))
901         (let (paths group)
902           (while (not (numberp (setq group (read (current-buffer)))))
903             (push (concat method "/" group) paths))
904           (nreverse paths))))))
905
906 ;;;
907 ;;; Fetching
908 ;;;
909
910 (defun gnus-agent-fetch-articles (group articles)
911   "Fetch ARTICLES from GROUP and put them into the Agent."
912   (when articles
913     ;; Prune off articles that we have already fetched.
914     (while (and articles
915                 (cdr (assq (car articles) gnus-agent-article-alist)))
916       (pop articles))
917     (let ((arts articles))
918       (while (cdr arts)
919         (if (cdr (assq (cadr arts) gnus-agent-article-alist))
920             (setcdr arts (cddr arts))
921           (setq arts (cdr arts)))))
922     (when articles
923       (let ((dir (concat
924                   (gnus-agent-directory)
925                   (gnus-agent-group-path group) "/"))
926             (date (time-to-days (current-time)))
927             (case-fold-search t)
928             pos crosses id elem)
929         (gnus-make-directory dir)
930         (gnus-message 7 "Fetching articles for %s..." group)
931         ;; Fetch the articles from the backend.
932         (if (gnus-check-backend-function 'retrieve-articles group)
933             (setq pos (gnus-retrieve-articles articles group))
934           (with-temp-buffer
935             (let (article)
936               (while (setq article (pop articles))
937                 (when (or
938                        (gnus-backlog-request-article group article
939                                                      nntp-server-buffer)
940                        (gnus-request-article article group))
941                   (goto-char (point-max))
942                   (push (cons article (point)) pos)
943                   (insert-buffer-substring nntp-server-buffer)))
944               (copy-to-buffer nntp-server-buffer (point-min) (point-max))
945               (setq pos (nreverse pos)))))
946         ;; Then save these articles into the Agent.
947         (save-excursion
948           (set-buffer nntp-server-buffer)
949           (while pos
950             (narrow-to-region (cdar pos) (or (cdadr pos) (point-max)))
951             (goto-char (point-min))
952             (when (search-forward "\n\n" nil t)
953               (when (search-backward "\nXrefs: " nil t)
954                 ;; Handle crossposting.
955                 (skip-chars-forward "^ ")
956                 (skip-chars-forward " ")
957                 (setq crosses nil)
958                 (while (looking-at "\\([^: \n]+\\):\\([0-9]+\\) +")
959                   (push (cons (buffer-substring (match-beginning 1)
960                                                 (match-end 1))
961                               (buffer-substring (match-beginning 2)
962                                                 (match-end 2)))
963                         crosses)
964                   (goto-char (match-end 0)))
965                 (gnus-agent-crosspost crosses (caar pos))))
966             (goto-char (point-min))
967             (if (not (re-search-forward "^Message-ID: *<\\([^>\n]+\\)>" nil t))
968                 (setq id "No-Message-ID-in-article")
969               (setq id (buffer-substring (match-beginning 1) (match-end 1))))
970             (write-region-as-coding-system
971              gnus-agent-file-coding-system
972              (point-min) (point-max)
973              (concat dir (number-to-string (caar pos))) nil 'silent)
974             (when (setq elem (assq (caar pos) gnus-agent-article-alist))
975               (setcdr elem t))
976             (gnus-agent-enter-history
977              id (or crosses (list (cons group (caar pos)))) date)
978             (widen)
979             (pop pos)))
980         (gnus-agent-save-alist group)))))
981
982 (defun gnus-agent-crosspost (crosses article)
983   (let (gnus-agent-article-alist group alist beg end)
984     (save-excursion
985       (set-buffer gnus-agent-overview-buffer)
986       (when (nnheader-find-nov-line article)
987         (forward-word 1)
988         (setq beg (point))
989         (setq end (progn (forward-line 1) (point)))))
990     (while crosses
991       (setq group (caar crosses))
992       (unless (setq alist (assoc group gnus-agent-group-alist))
993         (push (setq alist (list group (gnus-agent-load-alist (caar crosses))))
994               gnus-agent-group-alist))
995       (setcdr alist (cons (cons (cdar crosses) t) (cdr alist)))
996       (save-excursion
997         (set-buffer (gnus-get-buffer-create (format " *Gnus agent overview %s*"
998                                                     group)))
999         (when (= (point-max) (point-min))
1000           (push (cons group (current-buffer)) gnus-agent-buffer-alist)
1001           (ignore-errors
1002             (nnheader-insert-file-contents
1003              (gnus-agent-article-name ".overview" group))))
1004         (nnheader-find-nov-line (string-to-number (cdar crosses)))
1005         (insert (string-to-number (cdar crosses)))
1006         (insert-buffer-substring gnus-agent-overview-buffer beg end))
1007       (pop crosses))))
1008
1009 (defun gnus-agent-flush-cache ()
1010   (save-excursion
1011     (while gnus-agent-buffer-alist
1012       (set-buffer (cdar gnus-agent-buffer-alist))
1013       (write-region-as-coding-system
1014        gnus-agent-file-coding-system
1015        (point-min) (point-max)
1016        (gnus-agent-article-name ".overview"
1017                                 (caar gnus-agent-buffer-alist))
1018        nil 'silent)
1019       (pop gnus-agent-buffer-alist))
1020     (while gnus-agent-group-alist
1021       (with-temp-file (caar gnus-agent-group-alist)
1022         (princ (cdar gnus-agent-group-alist))
1023         (insert "\n"))
1024       (pop gnus-agent-group-alist))))
1025
1026 (defun gnus-agent-fetch-headers (group &optional force)
1027   (let* ((articles (gnus-list-of-unread-articles group))
1028          (len (length articles))
1029          (gnus-decode-encoded-word-function 'identity)
1030          (file (gnus-agent-article-name ".overview" group))
1031          i gnus-agent-cache)
1032     ;; Check the number of articles is not too large.
1033     (when (and (integerp gnus-agent-large-newsgroup)
1034                (< 0 gnus-agent-large-newsgroup))
1035       (and (< 0 (setq i (- len gnus-agent-large-newsgroup)))
1036            (setq articles (nthcdr i articles))))
1037     ;; add article with marks to list of article headers we want to fetch.
1038     (dolist (arts (gnus-info-marks (gnus-get-info group)))
1039       (setq articles (gnus-range-add articles (cdr arts))))
1040     (setq articles (sort (gnus-uncompress-sequence articles) '<))
1041     ;; Remove known articles.
1042     (when (gnus-agent-load-alist group)
1043       (setq articles (gnus-sorted-intersection
1044                       articles
1045                       (gnus-uncompress-range
1046                        (cons (1+ (caar (last gnus-agent-article-alist)))
1047                              (cdr (gnus-active group)))))))
1048     ;; Fetch them.
1049     (gnus-make-directory (nnheader-translate-file-chars
1050                           (file-name-directory file) t))
1051     (when articles
1052       (gnus-message 7 "Fetching headers for %s..." group)
1053       (save-excursion
1054         (set-buffer nntp-server-buffer)
1055         (unless (eq 'nov (gnus-retrieve-headers articles group))
1056           (nnvirtual-convert-headers))
1057         ;; Save these headers for later processing.
1058         (copy-to-buffer gnus-agent-overview-buffer (point-min) (point-max))
1059         (when (file-exists-p file)
1060           (gnus-agent-braid-nov group articles file))
1061         (write-region-as-coding-system
1062          gnus-agent-file-coding-system
1063          (point-min) (point-max) file nil 'silent)
1064         (gnus-agent-save-alist group articles nil)
1065         (gnus-agent-enter-history
1066          "last-header-fetched-for-session"
1067          (list (cons group (nth (- (length  articles) 1) articles)))
1068          (time-to-days (current-time)))
1069         articles))))
1070
1071 (defsubst gnus-agent-copy-nov-line (article)
1072   (let (b e)
1073     (set-buffer gnus-agent-overview-buffer)
1074     (unless (eobp)
1075       (setq b (point))
1076       (if (eq article (read (current-buffer)))
1077           (setq e (progn (forward-line 1) (point)))
1078         (progn
1079           (beginning-of-line)
1080           (setq e b)))
1081       (set-buffer nntp-server-buffer)
1082       (insert-buffer-substring gnus-agent-overview-buffer b e))))
1083
1084 (defun gnus-agent-braid-nov (group articles file)
1085   (set-buffer gnus-agent-overview-buffer)
1086   (goto-char (point-min))
1087   (set-buffer nntp-server-buffer)
1088   (erase-buffer)
1089   (nnheader-insert-file-contents file)
1090   (goto-char (point-max))
1091   (if (or (= (point-min) (point-max))
1092           (progn
1093             (forward-line -1)
1094             (< (read (current-buffer)) (car articles))))
1095       ;; We have only headers that are after the older headers,
1096       ;; so we just append them.
1097       (progn
1098         (goto-char (point-max))
1099         (insert-buffer-substring gnus-agent-overview-buffer))
1100     ;; We do it the hard way.
1101     (nnheader-find-nov-line (car articles))
1102     (gnus-agent-copy-nov-line (car articles))
1103     (pop articles)
1104     (while (and articles
1105                 (not (eobp)))
1106       (while (and (not (eobp))
1107                   (< (read (current-buffer)) (car articles)))
1108         (forward-line 1))
1109       (beginning-of-line)
1110       (unless (eobp)
1111         (gnus-agent-copy-nov-line (car articles))
1112         (setq articles (cdr articles))))
1113     (set-buffer nntp-server-buffer)
1114     (when articles
1115       (let (b e)
1116         (set-buffer gnus-agent-overview-buffer)
1117         (setq b (point)
1118               e (point-max))
1119         (while (and (not (eobp))
1120                     (<= (read (current-buffer)) (car articles)))
1121           (forward-line 1)
1122           (setq b (point)))
1123         (set-buffer nntp-server-buffer)
1124         (insert-buffer-substring gnus-agent-overview-buffer b e)))))
1125
1126 (defun gnus-agent-load-alist (group &optional dir)
1127   "Load the article-state alist for GROUP."
1128   (setq gnus-agent-article-alist
1129         (gnus-agent-read-file
1130          (if dir
1131              (expand-file-name ".agentview" dir)
1132            (gnus-agent-article-name ".agentview" group)))))
1133
1134 (defun gnus-agent-save-alist (group &optional articles state dir)
1135   "Save the article-state alist for GROUP."
1136   (let ((file-name-coding-system nnmail-pathname-coding-system)
1137         (pathname-coding-system nnmail-pathname-coding-system)
1138         print-level print-length item)
1139     (dolist (art articles)
1140       (if (setq item (memq art gnus-agent-article-alist))
1141           (setcdr item state)
1142         (push  (cons art state) gnus-agent-article-alist)))
1143     (setq gnus-agent-article-alist
1144           (sort gnus-agent-article-alist 'car-less-than-car))
1145     (with-temp-file (if dir
1146                         (expand-file-name ".agentview" dir)
1147                       (gnus-agent-article-name ".agentview" group))
1148       (princ gnus-agent-article-alist (current-buffer))
1149       (insert "\n"))))
1150
1151 (defun gnus-agent-article-name (article group)
1152   (expand-file-name (if (stringp article) article (string-to-number article))
1153                     (file-name-as-directory
1154                      (expand-file-name (gnus-agent-group-path group)
1155                                        (gnus-agent-directory)))))
1156
1157 ;;;###autoload
1158 (defun gnus-agent-batch-fetch ()
1159   "Start Gnus and fetch session."
1160   (interactive)
1161   (gnus)
1162   (let ((gnus-agent-confirmation-function 'gnus-agent-batch-confirmation))
1163     (gnus-agent-fetch-session))
1164   (gnus-group-exit))
1165
1166 (defun gnus-agent-fetch-session ()
1167   "Fetch all articles and headers that are eligible for fetching."
1168   (interactive)
1169   (unless gnus-agent-covered-methods
1170     (error "No servers are covered by the Gnus agent"))
1171   (unless gnus-plugged
1172     (error "Can't fetch articles while Gnus is unplugged"))
1173   (let ((methods gnus-agent-covered-methods)
1174         groups group gnus-command-method)
1175     (save-excursion
1176       (while methods
1177         (condition-case err
1178             (progn
1179               (setq gnus-command-method (car methods))
1180               (when (and (or (gnus-server-opened gnus-command-method)
1181                              (gnus-open-server gnus-command-method))
1182                          (gnus-online gnus-command-method))
1183                 (setq groups (gnus-groups-from-server (car methods)))
1184                 (gnus-agent-with-fetch
1185                   (while (setq group (pop groups))
1186                     (when (<= (gnus-group-level group) gnus-agent-handle-level)
1187                       (gnus-agent-fetch-group-1 group gnus-command-method))))))
1188           (error
1189            (unless (funcall gnus-agent-confirmation-function
1190                             (format "Error (%s).  Continue? " err))
1191              (error "Cannot fetch articles into the Gnus agent")))
1192           (quit
1193            (unless (funcall gnus-agent-confirmation-function
1194                             (format "Quit fetching session (%s).  Continue? "
1195                                     err))
1196              (signal 'quit "Cannot fetch articles into the Gnus agent"))))
1197         (pop methods))
1198       (run-hooks 'gnus-agent-fetch-hook)
1199       (gnus-message 6 "Finished fetching articles into the Gnus agent"))))
1200
1201 (defun gnus-agent-fetch-group-1 (group method)
1202   "Fetch GROUP."
1203   (let ((gnus-command-method method)
1204         (gnus-newsgroup-name group)
1205         gnus-newsgroup-dependencies gnus-newsgroup-headers
1206         gnus-newsgroup-scored gnus-headers gnus-score
1207         gnus-use-cache articles arts
1208         category predicate info marks score-param
1209         (gnus-summary-expunge-below gnus-summary-expunge-below)
1210         (gnus-summary-mark-below gnus-summary-mark-below)
1211         (gnus-orphan-score gnus-orphan-score)
1212         ;; Maybe some other gnus-summary local variables should also
1213         ;; be put here.
1214         )
1215     (unless (gnus-check-group group)
1216       (error "Can't open server for %s" group))
1217     ;; Fetch headers.
1218     (when (and (or (gnus-active group)
1219                    (gnus-activate-group group))
1220                (setq articles (gnus-agent-fetch-headers group))
1221                (let ((nntp-server-buffer gnus-agent-overview-buffer))
1222                  ;; Parse them and see which articles we want to fetch.
1223                  (setq gnus-newsgroup-dependencies
1224                        (make-vector (length articles) 0))
1225                  (setq gnus-newsgroup-headers
1226                        (gnus-get-newsgroup-headers-xover articles nil nil
1227                                                          group))
1228                  ;; `gnus-agent-overview-buffer' may be killed for
1229                  ;; timeout reason.  If so, recreate it.
1230                  (gnus-agent-create-buffer)))
1231       (setq category (gnus-group-category group))
1232       (setq predicate
1233             (gnus-get-predicate
1234              (or (gnus-group-find-parameter group 'agent-predicate t)
1235                  (cadr category))))
1236       (if (memq predicate '(gnus-agent-true gnus-agent-false))
1237           ;; Simple implementation
1238           (setq arts (and (eq predicate 'gnus-agent-true) articles))
1239         (setq arts nil)
1240         (setq score-param
1241               (or (gnus-group-get-parameter group 'agent-score t)
1242                   (caddr category)))
1243         ;; Translate score-param into real one
1244         (cond
1245          ((not score-param))
1246          ((eq score-param 'file)
1247           (setq score-param (gnus-all-score-files group)))
1248          ((stringp (car score-param)))
1249          (t
1250           (setq score-param (list (list score-param)))))
1251         (when score-param
1252           (gnus-score-headers score-param))
1253         (while (setq gnus-headers (pop gnus-newsgroup-headers))
1254           (setq gnus-score
1255                 (or (cdr (assq (mail-header-number gnus-headers)
1256                                gnus-newsgroup-scored))
1257                     gnus-summary-default-score))
1258           (when (funcall predicate)
1259             (push (mail-header-number gnus-headers)
1260                   arts))))
1261       ;; Fetch the articles.
1262       (when arts
1263         (gnus-agent-fetch-articles group arts)))
1264     ;; Perhaps we have some additional articles to fetch.
1265     (setq arts (assq 'download (gnus-info-marks
1266                                 (setq info (gnus-get-info group)))))
1267     (when (cdr arts)
1268       (gnus-message 8 "Agent is downloading marked articles...")
1269       (gnus-agent-fetch-articles
1270        group (gnus-uncompress-range (cdr arts)))
1271       (setq marks (delq arts (gnus-info-marks info)))
1272       (gnus-info-set-marks info marks)
1273       (gnus-dribble-enter
1274        (concat "(gnus-group-set-info '"
1275                (gnus-prin1-to-string info)
1276                ")")))))
1277
1278 ;;;
1279 ;;; Agent Category Mode
1280 ;;;
1281
1282 (defvar gnus-category-mode-hook nil
1283   "Hook run in `gnus-category-mode' buffers.")
1284
1285 (defvar gnus-category-line-format "     %(%20c%): %g\n"
1286   "Format of category lines.
1287
1288 Valid specifiers include:
1289 %c  Topic name (string)
1290 %g  The number of groups in the topic (integer)
1291
1292 General format specifiers can also be used.  See
1293 (gnus)Formatting Variables.")
1294
1295 (defvar gnus-category-mode-line-format "Gnus: %%b"
1296   "The format specification for the category mode line.")
1297
1298 (defvar gnus-agent-short-article 100
1299   "Articles that have fewer lines than this are short.")
1300
1301 (defvar gnus-agent-long-article 200
1302   "Articles that have more lines than this are long.")
1303
1304 (defvar gnus-agent-low-score 0
1305   "Articles that have a score lower than this have a low score.")
1306
1307 (defvar gnus-agent-high-score 0
1308   "Articles that have a score higher than this have a high score.")
1309
1310
1311 ;;; Internal variables.
1312
1313 (defvar gnus-category-buffer "*Agent Category*")
1314
1315 (defvar gnus-category-line-format-alist
1316   `((?c gnus-tmp-name ?s)
1317     (?g gnus-tmp-groups ?d)))
1318
1319 (defvar gnus-category-mode-line-format-alist
1320   `((?u user-defined ?s)))
1321
1322 (defvar gnus-category-line-format-spec nil)
1323 (defvar gnus-category-mode-line-format-spec nil)
1324
1325 (defvar gnus-category-mode-map nil)
1326 (put 'gnus-category-mode 'mode-class 'special)
1327
1328 (unless gnus-category-mode-map
1329   (setq gnus-category-mode-map (make-sparse-keymap))
1330   (suppress-keymap gnus-category-mode-map)
1331
1332   (gnus-define-keys gnus-category-mode-map
1333     "q" gnus-category-exit
1334     "k" gnus-category-kill
1335     "c" gnus-category-copy
1336     "a" gnus-category-add
1337     "p" gnus-category-edit-predicate
1338     "g" gnus-category-edit-groups
1339     "s" gnus-category-edit-score
1340     "l" gnus-category-list
1341
1342     "\C-c\C-i" gnus-info-find-node
1343     "\C-c\C-b" gnus-bug))
1344
1345 (defvar gnus-category-menu-hook nil
1346   "*Hook run after the creation of the menu.")
1347
1348 (defun gnus-category-make-menu-bar ()
1349   (gnus-turn-off-edit-menu 'category)
1350   (unless (boundp 'gnus-category-menu)
1351     (easy-menu-define
1352      gnus-category-menu gnus-category-mode-map ""
1353      '("Categories"
1354        ["Add" gnus-category-add t]
1355        ["Kill" gnus-category-kill t]
1356        ["Copy" gnus-category-copy t]
1357        ["Edit predicate" gnus-category-edit-predicate t]
1358        ["Edit score" gnus-category-edit-score t]
1359        ["Edit groups" gnus-category-edit-groups t]
1360        ["Exit" gnus-category-exit t]))
1361
1362     (gnus-run-hooks 'gnus-category-menu-hook)))
1363
1364 (defun gnus-category-mode ()
1365   "Major mode for listing and editing agent categories.
1366
1367 All normal editing commands are switched off.
1368 \\<gnus-category-mode-map>
1369 For more in-depth information on this mode, read the manual
1370 (`\\[gnus-info-find-node]').
1371
1372 The following commands are available:
1373
1374 \\{gnus-category-mode-map}"
1375   (interactive)
1376   (when (gnus-visual-p 'category-menu 'menu)
1377     (gnus-category-make-menu-bar))
1378   (kill-all-local-variables)
1379   (gnus-simplify-mode-line)
1380   (setq major-mode 'gnus-category-mode)
1381   (setq mode-name "Category")
1382   (gnus-set-default-directory)
1383   (setq mode-line-process nil)
1384   (use-local-map gnus-category-mode-map)
1385   (buffer-disable-undo)
1386   (setq truncate-lines t)
1387   (setq buffer-read-only t)
1388   (gnus-run-hooks 'gnus-category-mode-hook))
1389
1390 (defalias 'gnus-category-position-point 'gnus-goto-colon)
1391
1392 (defun gnus-category-insert-line (category)
1393   (let* ((gnus-tmp-name (format "%s" (car category)))
1394          (gnus-tmp-groups (length (cadddr category))))
1395     (beginning-of-line)
1396     (gnus-add-text-properties
1397      (point)
1398      (prog1 (1+ (point))
1399        ;; Insert the text.
1400        (eval gnus-category-line-format-spec))
1401      (list 'gnus-category gnus-tmp-name))))
1402
1403 (defun gnus-enter-category-buffer ()
1404   "Go to the Category buffer."
1405   (interactive)
1406   (gnus-category-setup-buffer)
1407   (gnus-configure-windows 'category)
1408   (gnus-category-prepare))
1409
1410 (defun gnus-category-setup-buffer ()
1411   (unless (get-buffer gnus-category-buffer)
1412     (save-excursion
1413       (set-buffer (gnus-get-buffer-create gnus-category-buffer))
1414       (gnus-category-mode))))
1415
1416 (defun gnus-category-prepare ()
1417   (gnus-set-format 'category-mode)
1418   (gnus-set-format 'category t)
1419   (let ((alist gnus-category-alist)
1420         (buffer-read-only nil))
1421     (erase-buffer)
1422     (while alist
1423       (gnus-category-insert-line (pop alist)))
1424     (goto-char (point-min))
1425     (gnus-category-position-point)))
1426
1427 (defun gnus-category-name ()
1428   (or (intern (get-text-property (gnus-point-at-bol) 'gnus-category))
1429       (error "No category on the current line")))
1430
1431 (defun gnus-category-read ()
1432   "Read the category alist."
1433   (setq gnus-category-alist
1434         (or (gnus-agent-read-file
1435              (nnheader-concat gnus-agent-directory "lib/categories"))
1436             (list (list 'default 'short nil nil)))))
1437
1438 (defun gnus-category-write ()
1439   "Write the category alist."
1440   (setq gnus-category-predicate-cache nil
1441         gnus-category-group-cache nil)
1442   (gnus-make-directory (nnheader-concat gnus-agent-directory "lib"))
1443   (with-temp-file (nnheader-concat gnus-agent-directory "lib/categories")
1444     (prin1 gnus-category-alist (current-buffer))))
1445
1446 (defun gnus-category-edit-predicate (category)
1447   "Edit the predicate for CATEGORY."
1448   (interactive (list (gnus-category-name)))
1449   (let ((info (assq category gnus-category-alist)))
1450     (gnus-edit-form
1451      (cadr info) (format "Editing the predicate for category %s" category)
1452      `(lambda (predicate)
1453         (setcar (cdr (assq ',category gnus-category-alist)) predicate)
1454         (gnus-category-write)
1455         (gnus-category-list)))))
1456
1457 (defun gnus-category-edit-score (category)
1458   "Edit the score expression for CATEGORY."
1459   (interactive (list (gnus-category-name)))
1460   (let ((info (assq category gnus-category-alist)))
1461     (gnus-edit-form
1462      (caddr info)
1463      (format "Editing the score expression for category %s" category)
1464      `(lambda (groups)
1465         (setcar (nthcdr 2 (assq ',category gnus-category-alist)) groups)
1466         (gnus-category-write)
1467         (gnus-category-list)))))
1468
1469 (defun gnus-category-edit-groups (category)
1470   "Edit the group list for CATEGORY."
1471   (interactive (list (gnus-category-name)))
1472   (let ((info (assq category gnus-category-alist)))
1473     (gnus-edit-form
1474      (cadddr info) (format "Editing the group list for category %s" category)
1475      `(lambda (groups)
1476         (setcar (nthcdr 3 (assq ',category gnus-category-alist)) groups)
1477         (gnus-category-write)
1478         (gnus-category-list)))))
1479
1480 (defun gnus-category-kill (category)
1481   "Kill the current category."
1482   (interactive (list (gnus-category-name)))
1483   (let ((info (assq category gnus-category-alist))
1484         (buffer-read-only nil))
1485     (gnus-delete-line)
1486     (setq gnus-category-alist (delq info gnus-category-alist))
1487     (gnus-category-write)))
1488
1489 (defun gnus-category-copy (category to)
1490   "Copy the current category."
1491   (interactive (list (gnus-category-name) (intern (read-string "New name: "))))
1492   (let ((info (assq category gnus-category-alist)))
1493     (push (list to (gnus-copy-sequence (cadr info))
1494                 (gnus-copy-sequence (caddr info)) nil)
1495           gnus-category-alist)
1496     (gnus-category-write)
1497     (gnus-category-list)))
1498
1499 (defun gnus-category-add (category)
1500   "Create a new category."
1501   (interactive "SCategory name: ")
1502   (when (assq category gnus-category-alist)
1503     (error "Category %s already exists" category))
1504   (push (list category 'false nil nil)
1505         gnus-category-alist)
1506   (gnus-category-write)
1507   (gnus-category-list))
1508
1509 (defun gnus-category-list ()
1510   "List all categories."
1511   (interactive)
1512   (gnus-category-prepare))
1513
1514 (defun gnus-category-exit ()
1515   "Return to the group buffer."
1516   (interactive)
1517   (kill-buffer (current-buffer))
1518   (gnus-configure-windows 'group t))
1519
1520 ;; To avoid having 8-bit characters in the source file.
1521 (defvar gnus-category-not (list '! 'not (intern (format "%c" 172))))
1522
1523 (defvar gnus-category-predicate-alist
1524   '((spam . gnus-agent-spam-p)
1525     (short . gnus-agent-short-p)
1526     (long . gnus-agent-long-p)
1527     (low . gnus-agent-low-scored-p)
1528     (high . gnus-agent-high-scored-p)
1529     (true . gnus-agent-true)
1530     (false . gnus-agent-false))
1531   "Mapping from short score predicate symbols to predicate functions.")
1532
1533 (defun gnus-agent-spam-p ()
1534   "Say whether an article is spam or not."
1535   (unless gnus-agent-spam-hashtb
1536     (setq gnus-agent-spam-hashtb (gnus-make-hashtable 1000)))
1537   (if (not (equal (mail-header-references gnus-headers) ""))
1538       nil
1539     (let ((string (gnus-simplify-subject (mail-header-subject gnus-headers))))
1540       (prog1
1541           (gnus-gethash string gnus-agent-spam-hashtb)
1542         (gnus-sethash string t gnus-agent-spam-hashtb)))))
1543
1544 (defun gnus-agent-short-p ()
1545   "Say whether an article is short or not."
1546   (< (mail-header-lines gnus-headers) gnus-agent-short-article))
1547
1548 (defun gnus-agent-long-p ()
1549   "Say whether an article is long or not."
1550   (> (mail-header-lines gnus-headers) gnus-agent-long-article))
1551
1552 (defun gnus-agent-low-scored-p ()
1553   "Say whether an article has a low score or not."
1554   (< gnus-score gnus-agent-low-score))
1555
1556 (defun gnus-agent-high-scored-p ()
1557   "Say whether an article has a high score or not."
1558   (> gnus-score gnus-agent-high-score))
1559
1560 (defun gnus-category-make-function (cat)
1561   "Make a function from category CAT."
1562   (let ((func (gnus-category-make-function-1 cat)))
1563     (if (and (= (length func) 1)
1564              (symbolp (car func)))
1565         (car func)
1566       (gnus-byte-compile `(lambda () ,func)))))
1567
1568 (defun gnus-agent-true ()
1569   "Return t."
1570   t)
1571
1572 (defun gnus-agent-false ()
1573   "Return nil."
1574   nil)
1575
1576 (defun gnus-category-make-function-1 (cat)
1577   "Make a function from category CAT."
1578   (cond
1579    ;; Functions are just returned as is.
1580    ((or (symbolp cat)
1581         (gnus-functionp cat))
1582     `(,(or (cdr (assq cat gnus-category-predicate-alist))
1583            cat)))
1584    ;; More complex category.
1585    ((consp cat)
1586     `(,(cond
1587         ((memq (car cat) '(& and))
1588          'and)
1589         ((memq (car cat) '(| or))
1590          'or)
1591         ((memq (car cat) gnus-category-not)
1592          'not))
1593       ,@(mapcar 'gnus-category-make-function-1 (cdr cat))))
1594    (t
1595     (error "Unknown category type: %s" cat))))
1596
1597 (defun gnus-get-predicate (predicate)
1598   "Return the predicate for CATEGORY."
1599   (or (cdr (assoc predicate gnus-category-predicate-cache))
1600       (cdar (push (cons predicate
1601                         (gnus-category-make-function predicate))
1602                   gnus-category-predicate-cache))))
1603
1604 (defun gnus-group-category (group)
1605   "Return the category GROUP belongs to."
1606   (unless gnus-category-group-cache
1607     (setq gnus-category-group-cache (gnus-make-hashtable 1000))
1608     (let ((cs gnus-category-alist)
1609           groups cat)
1610       (while (setq cat (pop cs))
1611         (setq groups (cadddr cat))
1612         (while groups
1613           (gnus-sethash (pop groups) cat gnus-category-group-cache)))))
1614   (or (gnus-gethash group gnus-category-group-cache)
1615       (assq 'default gnus-category-alist)))
1616
1617 (defun gnus-agent-expire ()
1618   "Expire all old articles."
1619   (interactive)
1620   (let ((methods gnus-agent-covered-methods)
1621         (day (if (numberp gnus-agent-expire-days)
1622                  (- (time-to-days (current-time)) gnus-agent-expire-days)
1623                nil))
1624         (current-day (time-to-days (current-time)))
1625         gnus-command-method sym group articles
1626         history overview file histories elem art nov-file low info
1627         unreads marked article orig lowest highest found days)
1628     (save-excursion
1629       (setq overview (gnus-get-buffer-create " *expire overview*"))
1630       (while (setq gnus-command-method (pop methods))
1631         (when (file-exists-p (gnus-agent-lib-file "active"))
1632           (with-temp-buffer
1633             (insert-file-contents-as-coding-system
1634              gnus-agent-file-coding-system (gnus-agent-lib-file "active"))
1635             (gnus-active-to-gnus-format
1636              gnus-command-method
1637              (setq orig (gnus-make-hashtable
1638                          (count-lines (point-min) (point-max))))))
1639           (let ((expiry-hashtb (gnus-make-hashtable 1023)))
1640             (gnus-agent-open-history)
1641             (set-buffer
1642              (setq gnus-agent-current-history
1643                    (setq history (gnus-agent-history-buffer))))
1644             (goto-char (point-min))
1645             (when (> (buffer-size) 1)
1646               (goto-char (point-min))
1647               (while (not (eobp))
1648                 (skip-chars-forward "^\t")
1649                 (if (let ((fetch-date (read (current-buffer))))
1650                       (if (numberp fetch-date)
1651                           ;; We now have the arrival day, so we see
1652                           ;; whether it's old enough to be expired.
1653                           (if (numberp day)
1654                               (> fetch-date day)
1655                             (skip-chars-forward "\t")
1656                             (setq found nil
1657                                   days gnus-agent-expire-days)
1658                             (while (and (not found)
1659                                         days)
1660                               (when (looking-at (caar days))
1661                                 (setq found (cadar days)))
1662                               (pop days))
1663                             (> fetch-date (- current-day found)))
1664                         ;; History file is corrupted.
1665                         (gnus-message
1666                          5
1667                          (format "File %s is corrupted!"
1668                                  (gnus-agent-lib-file "history")))
1669                         (sit-for 1)
1670                         ;; Ignore it
1671                         t))
1672                     ;; New article; we don't expire it.
1673                     (forward-line 1)
1674                   ;; Old article.  Schedule it for possible nuking.
1675                   (while (not (eolp))
1676                     (setq sym (let ((obarray expiry-hashtb) s)
1677                                 (setq s (read (current-buffer)))
1678                                 (if (stringp s) (intern s) s)))
1679                     (if (boundp sym)
1680                         (set sym (cons (cons (read (current-buffer)) (point))
1681                                        (symbol-value sym)))
1682                       (set sym (list (cons (read (current-buffer)) (point)))))
1683                     (skip-chars-forward " "))
1684                   (forward-line 1)))
1685               ;; We now have all articles that can possibly be expired.
1686               (mapatoms
1687                (lambda (sym)
1688                  (setq group (symbol-name sym)
1689                        articles (sort (symbol-value sym) 'car-less-than-car)
1690                        low (car (gnus-active group))
1691                        info (gnus-get-info group)
1692                        unreads (ignore-errors
1693                                  (gnus-list-of-unread-articles group))
1694                        marked (nconc
1695                                (gnus-uncompress-range
1696                                 (cdr (assq 'tick (gnus-info-marks info))))
1697                                (gnus-uncompress-range
1698                                 (cdr (assq 'dormant (gnus-info-marks info))))
1699                                (gnus-uncompress-range
1700                                 (cdr (assq 'save (gnus-info-marks info))))
1701                                (gnus-uncompress-range
1702                                 (cdr (assq 'reply (gnus-info-marks info)))))
1703                        nov-file (gnus-agent-article-name ".overview" group)
1704                        lowest nil
1705                        highest nil)
1706                  (gnus-agent-load-alist group)
1707                  (gnus-message 5 "Expiring articles in %s" group)
1708                  (set-buffer overview)
1709                  (erase-buffer)
1710                  (when (file-exists-p nov-file)
1711                    (nnheader-insert-file-contents nov-file))
1712                  (goto-char (point-min))
1713                  (setq article 0)
1714                  (while (setq elem (pop articles))
1715                    (setq article (car elem))
1716                    (when (or (null low)
1717                              (< article low)
1718                              gnus-agent-expire-all
1719                              (and (not (memq article unreads))
1720                                   (not (memq article marked))))
1721                      ;; Find and nuke the NOV line.
1722                      (while (and (not (eobp))
1723                                  (or (not (numberp
1724                                            (setq art (read (current-buffer)))))
1725                                      (< art article)))
1726                        (if (and (numberp art)
1727                                 (file-exists-p
1728                                  (gnus-agent-article-name
1729                                   (number-to-string art) group)))
1730                            (progn
1731                              (unless lowest
1732                                (setq lowest art))
1733                              (setq highest art)
1734                              (forward-line 1))
1735                          ;; Remove old NOV lines that have no articles.
1736                          (gnus-delete-line)))
1737                      (if (or (eobp)
1738                              (/= art article))
1739                          (beginning-of-line)
1740                        (gnus-delete-line))
1741                      ;; Nuke the article.
1742                      (when (file-exists-p
1743                             (setq file (gnus-agent-article-name
1744                                         (number-to-string article)
1745                                         group)))
1746                        (delete-file file))
1747                      ;; Schedule the history line for nuking.
1748                      (push (cdr elem) histories)))
1749                  (gnus-make-directory (file-name-directory nov-file))
1750                  (write-region-as-coding-system
1751                   gnus-agent-file-coding-system
1752                   (point-min) (point-max) nov-file nil 'silent)
1753                  ;; Delete the unwanted entries in the alist.
1754                  (setq gnus-agent-article-alist
1755                        (sort gnus-agent-article-alist 'car-less-than-car))
1756                  (let* ((alist gnus-agent-article-alist)
1757                         (prev (cons nil alist))
1758                         (first prev)
1759                         expired)
1760                    (while (and alist
1761                                (<= (caar alist) article))
1762                      (if (or (not (cdar alist))
1763                              (not (file-exists-p
1764                                    (gnus-agent-article-name
1765                                     (number-to-string
1766                                      (caar alist))
1767                                     group))))
1768                          (progn
1769                            (push (caar alist) expired)
1770                            (setcdr prev (setq alist (cdr alist))))
1771                        (setq prev alist
1772                              alist (cdr alist))))
1773                    (setq gnus-agent-article-alist (cdr first))
1774                    (gnus-agent-save-alist group)
1775                    ;; Mark all articles up to the first article
1776                    ;; in `gnus-article-alist' as read.
1777                    (when (and info (caar gnus-agent-article-alist))
1778                      (setcar (nthcdr 2 info)
1779                              (gnus-range-add
1780                               (nth 2 info)
1781                               (cons 1 (- (caar gnus-agent-article-alist) 1)))))
1782                    ;; Maybe everything has been expired from
1783                    ;; `gnus-article-alist' and so the above marking as
1784                    ;; read could not be conducted, or there are
1785                    ;; expired article within the range of the alist.
1786                    (when (and info
1787                               expired
1788                               (or (not (caar gnus-agent-article-alist))
1789                                   (> (car expired)
1790                                      (caar gnus-agent-article-alist))))
1791                      (setcar (nthcdr 2 info)
1792                              (gnus-add-to-range
1793                               (nth 2 info)
1794                               (nreverse expired))))
1795                    (gnus-dribble-enter
1796                     (concat "(gnus-group-set-info '"
1797                             (gnus-prin1-to-string info)
1798                             ")")))
1799                  (when lowest
1800                    (if (gnus-gethash group orig)
1801                        (setcar (gnus-gethash group orig) lowest)
1802                      (gnus-sethash group (cons lowest highest) orig))))
1803                expiry-hashtb)
1804               (set-buffer history)
1805               (setq histories (nreverse (sort histories '<)))
1806               (while histories
1807                 (goto-char (pop histories))
1808                 (gnus-delete-line))
1809               (gnus-agent-save-history)
1810               (gnus-agent-close-history)
1811               (gnus-write-active-file (gnus-agent-lib-file "active") orig))
1812             (gnus-message 4 "Expiry...done")))))))
1813
1814 ;;;###autoload
1815 (defun gnus-agent-batch ()
1816   (interactive)
1817   (let ((init-file-user "")
1818         (gnus-always-read-dribble-file t))
1819     (gnus))
1820   (let ((gnus-agent-confirmation-function 'gnus-agent-batch-confirmation))
1821     (gnus-group-send-queue)
1822     (gnus-agent-fetch-session)))
1823
1824 (defun gnus-agent-retrieve-headers (articles group &optional fetch-old)
1825   (save-excursion
1826     (gnus-agent-create-buffer)
1827     (let ((gnus-decode-encoded-word-function 'identity)
1828           (file (gnus-agent-article-name ".overview" group))
1829           cached-articles uncached-articles)
1830       (gnus-make-directory (nnheader-translate-file-chars
1831                             (file-name-directory file) t))
1832       (when (file-exists-p file)
1833         (with-current-buffer gnus-agent-overview-buffer
1834           (erase-buffer)
1835           (let ((nnheader-file-coding-system
1836                  gnus-agent-file-coding-system))
1837             (nnheader-insert-file-contents file))
1838           (goto-char (point-min))
1839           (while (not (eobp))
1840             (when (looking-at "[0-9]")
1841               (push (read (current-buffer)) cached-articles))
1842             (forward-line 1))
1843           (setq cached-articles (sort cached-articles '<))))
1844       (if (setq uncached-articles
1845                 (gnus-set-difference articles cached-articles))
1846           (progn
1847             (set-buffer nntp-server-buffer)
1848             (erase-buffer)
1849             (let (gnus-agent-cache)
1850               (unless (eq 'nov
1851                           (gnus-retrieve-headers
1852                            uncached-articles group fetch-old))
1853                 (nnvirtual-convert-headers)))
1854             (set-buffer gnus-agent-overview-buffer)
1855             (erase-buffer)
1856             (set-buffer nntp-server-buffer)
1857             (copy-to-buffer gnus-agent-overview-buffer (point-min) (point-max))
1858             (when (and uncached-articles (file-exists-p file))
1859               (gnus-agent-braid-nov group uncached-articles file))
1860             (set-buffer nntp-server-buffer)
1861             (write-region-as-coding-system gnus-agent-file-coding-system
1862                                            (point-min) (point-max)
1863                                            file nil 'silent)
1864             (gnus-agent-load-alist group)
1865             (gnus-agent-save-alist group uncached-articles nil)
1866             (gnus-agent-open-history)
1867             (setq gnus-agent-current-history (gnus-agent-history-buffer))
1868             (gnus-agent-enter-history
1869              "last-header-fetched-for-session"
1870              (list (cons group (nth (- (length  articles) 1) articles)))
1871              (time-to-days (current-time)))
1872             (gnus-agent-save-history))
1873         (set-buffer nntp-server-buffer)
1874         (erase-buffer)
1875         (insert-buffer-substring gnus-agent-overview-buffer)))
1876     (if (and fetch-old
1877              (not (numberp fetch-old)))
1878         t                               ; Don't remove anything.
1879       (nnheader-nov-delete-outside-range
1880        (if fetch-old (max 1 (- (car articles) fetch-old))
1881          (car articles))
1882        (car (last articles)))
1883       t)
1884     'nov))
1885
1886 (defun gnus-agent-request-article (article group)
1887   "Retrieve ARTICLE in GROUP from the agent cache."
1888   (let* ((gnus-command-method (gnus-find-method-for-group group))
1889          (file (concat
1890                   (gnus-agent-directory)
1891                   (gnus-agent-group-path group) "/"
1892                   (number-to-string article)))
1893         (buffer-read-only nil))
1894     (when (file-exists-p file)
1895       (erase-buffer)
1896       (gnus-kill-all-overlays)
1897       (insert-file-contents-as-coding-system gnus-cache-coding-system file)
1898       t)))
1899
1900 (defun gnus-agent-regenerate-group (group &optional clean)
1901   "Regenerate GROUP."
1902   (let ((dir (concat (gnus-agent-directory)
1903                      (gnus-agent-group-path group) "/"))
1904         (file (gnus-agent-article-name ".overview" group))
1905         n point arts alist header new-alist changed)
1906     (when (file-exists-p dir)
1907       (setq arts
1908             (sort (mapcar (lambda (name) (string-to-int name))
1909                           (directory-files dir nil "^[0-9]+$" t))
1910                   '<)))
1911     (gnus-make-directory (nnheader-translate-file-chars
1912                           (file-name-directory file) t))
1913     (mm-with-unibyte-buffer
1914       (if (file-exists-p file)
1915           (let ((nnheader-file-coding-system
1916                  gnus-agent-file-coding-system))
1917             (nnheader-insert-file-contents file)))
1918       (goto-char (point-min))
1919       (while (not (eobp))
1920         (while (not (or (eobp) (looking-at "[0-9]")))
1921           (setq point (point))
1922           (forward-line 1)
1923           (delete-region point (point)))
1924         (unless (eobp)
1925           (setq n (read (current-buffer)))
1926           (when (and arts (> n (car arts)))
1927             (beginning-of-line)
1928             (while (and arts (> n (car arts)))
1929               (message "Regenerating NOV %s %d..." group (car arts))
1930               (mm-with-unibyte-buffer
1931                 (nnheader-insert-file-contents
1932                  (concat dir (number-to-string (car arts))))
1933                 (goto-char (point-min))
1934                 (if (search-forward "\n\n" nil t)
1935                     (delete-region (point) (point-max))
1936                   (goto-char (point-max)))
1937                 (setq header (nnheader-parse-head t)))
1938               (mail-header-set-number header (car arts))
1939               (nnheader-insert-nov header)
1940               (setq changed t)
1941               (push (cons (car arts) t) alist)
1942               (pop arts)))
1943           (if (and arts (= n (car arts)))
1944               (progn
1945                 (push (cons n t) alist)
1946                 (pop arts))
1947             (push (cons n nil) alist))
1948           (forward-line 1)))
1949       (if changed
1950           (write-region-as-coding-system gnus-agent-file-coding-system
1951                                          (point-min) (point-max)
1952                                          file nil 'silent)))
1953     (setq gnus-agent-article-alist nil)
1954     (unless clean
1955       (gnus-agent-load-alist group))
1956     (setq alist (sort alist 'car-less-than-car))
1957     (setq gnus-agent-article-alist (sort gnus-agent-article-alist
1958                                          'car-less-than-car))
1959     (while (and alist gnus-agent-article-alist)
1960       (cond
1961        ((< (caar alist) (caar gnus-agent-article-alist))
1962         (push (pop alist) new-alist))
1963        ((> (caar alist) (caar gnus-agent-article-alist))
1964         (push (list (car (pop gnus-agent-article-alist))) new-alist))
1965        (t
1966         (pop gnus-agent-article-alist)
1967         (while (and gnus-agent-article-alist
1968                     (= (caar alist) (caar gnus-agent-article-alist)))
1969           (pop gnus-agent-article-alist))
1970         (push (pop alist) new-alist))))
1971     (while alist
1972       (push (pop alist) new-alist))
1973     (while gnus-agent-article-alist
1974       (push (list (car (pop gnus-agent-article-alist))) new-alist))
1975     (setq gnus-agent-article-alist (nreverse new-alist))
1976     (gnus-agent-save-alist group)))
1977
1978 (defun gnus-agent-regenerate-history (group article)
1979   (let ((file (concat (gnus-agent-directory)
1980                       (gnus-agent-group-path group) "/"
1981                       (number-to-string article))) id)
1982     (mm-with-unibyte-buffer
1983       (nnheader-insert-file-contents file)
1984       (message-narrow-to-head)
1985       (goto-char (point-min))
1986       (if (not (re-search-forward "^Message-ID: *<\\([^>\n]+\\)>" nil t))
1987           (setq id "No-Message-ID-in-article")
1988         (setq id (buffer-substring (match-beginning 1) (match-end 1))))
1989       (gnus-agent-enter-history
1990        id (list (cons group article))
1991        (time-to-days (nth 5 (file-attributes file)))))))
1992
1993 ;;;###autoload
1994 (defun gnus-agent-regenerate (&optional clean)
1995   "Regenerate all agent covered files.
1996 If CLEAN, don't read existing active and agentview files."
1997   (interactive "P")
1998   (message "Regenerating Gnus agent files...")
1999   (dolist (gnus-command-method gnus-agent-covered-methods)
2000     (let ((active-file (gnus-agent-lib-file "active"))
2001           history-hashtb active-hashtb active-changed
2002           history-changed point)
2003       (gnus-make-directory (file-name-directory active-file))
2004       (if clean
2005           (setq active-hashtb (gnus-make-hashtable 1000))
2006         (mm-with-unibyte-buffer
2007           (if (file-exists-p active-file)
2008               (let ((nnheader-file-coding-system
2009                      gnus-agent-file-coding-system))
2010                 (nnheader-insert-file-contents active-file))
2011             (setq active-changed t))
2012           (gnus-active-to-gnus-format
2013            nil (setq active-hashtb
2014                      (gnus-make-hashtable
2015                       (count-lines (point-min) (point-max)))))))
2016       (gnus-agent-open-history)
2017       (setq history-hashtb (gnus-make-hashtable 1000))
2018       (with-current-buffer
2019           (setq gnus-agent-current-history (gnus-agent-history-buffer))
2020         (goto-char (point-min))
2021         (forward-line 1)
2022         (while (not (eobp))
2023           (if (looking-at
2024                "\\([^\t\n]+\\)\t[0-9]+\t\\([^ \n]+\\) \\([0-9]+\\)")
2025               (progn
2026                 (unless (string= (match-string 1)
2027                                  "last-header-fetched-for-session")
2028                   (gnus-sethash (match-string 2)
2029                                 (cons (string-to-number (match-string 3))
2030                                       (gnus-gethash-safe (match-string 2)
2031                                                          history-hashtb))
2032                                 history-hashtb))
2033                 (forward-line 1))
2034             (setq point (point))
2035             (forward-line 1)
2036             (delete-region point (point))
2037             (setq history-changed t))))
2038       (dolist (group (gnus-groups-from-server gnus-command-method))
2039         (gnus-agent-regenerate-group group clean)
2040         (let ((min (or (caar gnus-agent-article-alist) 1))
2041               (max (or (caar (last gnus-agent-article-alist)) 0))
2042               (active (gnus-gethash-safe (gnus-group-real-name group)
2043                                          active-hashtb)))
2044           (if (not active)
2045               (progn
2046                 (setq active (cons min max)
2047                       active-changed t)
2048                 (gnus-sethash group active active-hashtb))
2049             (when (> (car active) min)
2050               (setcar active min)
2051               (setq active-changed t))
2052             (when (< (cdr active) max)
2053               (setcdr active max)
2054               (setq active-changed t))))
2055         (let ((arts (sort (gnus-gethash-safe group history-hashtb) '<))
2056               n)
2057           (gnus-sethash group arts history-hashtb)
2058           (while (and arts gnus-agent-article-alist)
2059             (cond
2060              ((> (car arts) (caar gnus-agent-article-alist))
2061               (when (cdar gnus-agent-article-alist)
2062                 (gnus-agent-regenerate-history
2063                  group (caar gnus-agent-article-alist))
2064                 (setq history-changed t))
2065               (setq n (car (pop gnus-agent-article-alist)))
2066               (while (and gnus-agent-article-alist
2067                           (= n (caar gnus-agent-article-alist)))
2068                 (pop gnus-agent-article-alist)))
2069              ((< (car arts) (caar gnus-agent-article-alist))
2070               (setq n (pop arts))
2071               (while (and arts (= n (car arts)))
2072                 (pop arts)))
2073              (t
2074               (setq n (car (pop gnus-agent-article-alist)))
2075               (while (and gnus-agent-article-alist
2076                           (= n (caar gnus-agent-article-alist)))
2077                 (pop gnus-agent-article-alist))
2078               (setq n (pop arts))
2079               (while (and arts (= n (car arts)))
2080                 (pop arts)))))
2081           (while gnus-agent-article-alist
2082             (when (cdar gnus-agent-article-alist)
2083               (gnus-agent-regenerate-history
2084                group (caar gnus-agent-article-alist))
2085               (setq history-changed t))
2086             (pop gnus-agent-article-alist))))
2087       (when history-changed
2088         (message "Regenerate the history file of %s:%s"
2089                  (car gnus-command-method)
2090                  (cadr gnus-command-method))
2091         (gnus-agent-save-history))
2092       (gnus-agent-close-history)
2093       (when active-changed
2094         (message "Regenerate %s" active-file)
2095         (let ((nnmail-active-file-coding-system gnus-agent-file-coding-system))
2096           (gnus-write-active-file active-file active-hashtb)))))
2097   (message "Regenerating Gnus agent files...done"))
2098
2099 (defun gnus-agent-go-online (&optional force)
2100   "Switch servers into online status."
2101   (interactive (list t))
2102   (dolist (server gnus-opened-servers)
2103     (when (eq (nth 1 server) 'offline)
2104       (if (if (eq force 'ask)
2105               (gnus-y-or-n-p
2106                (format "Switch %s:%s into online status? "
2107                        (caar server) (cadar server)))
2108             force)
2109           (setcar (nthcdr 1 server) 'close)))))
2110
2111 (defun gnus-agent-toggle-group-plugged (group)
2112   "Toggle the status of the server of the current group."
2113   (interactive (list (gnus-group-group-name)))
2114   (let* ((method (gnus-find-method-for-group group))
2115          (status (cadr (assoc method gnus-opened-servers))))
2116     (if (eq status 'offline)
2117         (gnus-server-set-status method 'closed)
2118       (gnus-close-server method)
2119       (gnus-server-set-status method 'offline))
2120     (message "Turn %s:%s from %s to %s." (car method) (cadr method)
2121              (if (eq status 'offline) 'offline 'online)
2122              (if (eq status 'offline) 'online 'offline))))
2123
2124 (provide 'gnus-agent)
2125
2126 ;;; gnus-agent.el ends here