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