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