*** empty log message ***
[gnus] / lisp / gnus-agent.el
1 ;;; gnus-agent.el --- unplugged support for Gnus
2 ;; Copyright (C) 1997,98 Free Software Foundation, Inc.
3
4 ;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
5 ;; This file is part of GNU Emacs.
6
7 ;; GNU Emacs is free software; you can redistribute it and/or modify
8 ;; it under the terms of the GNU General Public License as published by
9 ;; the Free Software Foundation; either version 2, or (at your option)
10 ;; any later version.
11
12 ;; GNU Emacs is distributed in the hope that it will be useful,
13 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
14 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 ;; GNU General Public License for more details.
16
17 ;; You should have received a copy of the GNU General Public License
18 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
19 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20 ;; Boston, MA 02111-1307, USA.
21
22 ;;; Commentary:
23
24 ;;; Code:
25
26 (require 'gnus)
27 (require 'gnus-cache)
28 (require 'nnvirtual)
29 (require 'gnus-sum)
30 (eval-when-compile (require 'cl))
31
32 (defcustom gnus-agent-directory (nnheader-concat gnus-directory "agent/")
33   "*Where the Gnus agent will store its files."
34   :group 'gnus-agent
35   :type 'directory)
36
37 (defcustom gnus-agent-plugged-hook nil
38   "*Hook run when plugging into the network."
39   :group 'gnus-agent
40   :type 'hook)
41
42 (defcustom gnus-agent-unplugged-hook nil
43   "*Hook run when unplugging from the network."
44   :group 'gnus-agent
45   :type 'hook)
46
47 (defcustom gnus-agent-handle-level gnus-level-subscribed
48   "*Groups on levels higher than this variable will be ignored by the Agent."
49   :group 'gnus-agent
50   :type 'integer)
51
52 ;;; Internal variables
53
54 (defvar gnus-agent-history-buffers nil)
55 (defvar gnus-agent-buffer-alist nil)
56 (defvar gnus-agent-article-alist nil)
57 (defvar gnus-agent-group-alist nil)
58 (defvar gnus-agent-covered-methods nil)
59 (defvar gnus-category-alist nil)
60 (defvar gnus-agent-current-history nil)
61 (defvar gnus-agent-overview-buffer nil)
62 (defvar gnus-category-predicate-cache nil)
63 (defvar gnus-category-group-cache nil)
64 (defvar gnus-agent-spam-hashtb nil)
65 (defvar gnus-agent-file-name nil)
66 (defvar gnus-agent-send-mail-function nil)
67 (defvar gnus-agent-article-file-coding-system 'no-conversion)
68
69 ;; Dynamic variables
70 (defvar gnus-headers)
71 (defvar gnus-score)
72
73 ;;;
74 ;;; Setup
75 ;;;
76
77 (defun gnus-open-agent ()
78   (setq gnus-agent t)
79   (gnus-agent-read-servers)
80   (gnus-category-read)
81   (setq gnus-agent-overview-buffer
82         (get-buffer-create " *Gnus agent overview*"))
83   (add-hook 'gnus-group-mode-hook 'gnus-agent-mode)
84   (add-hook 'gnus-summary-mode-hook 'gnus-agent-mode)
85   (add-hook 'gnus-server-mode-hook 'gnus-agent-mode))
86
87 (gnus-add-shutdown 'gnus-close-agent 'gnus)
88
89 (defun gnus-close-agent ()
90   (setq gnus-agent-covered-methods nil
91         gnus-category-predicate-cache nil
92         gnus-category-group-cache nil
93         gnus-agent-spam-hashtb nil)
94   (gnus-kill-buffer gnus-agent-overview-buffer))
95
96 ;;;
97 ;;; Utility functions
98 ;;;
99
100 (defun gnus-agent-read-file (file)
101   "Load FILE and do a `read' there."
102   (nnheader-temp-write nil
103     (ignore-errors
104       (insert-file-contents file)
105       (goto-char (point-min))
106       (read (current-buffer)))))
107
108 (defsubst gnus-agent-method ()
109   (concat (symbol-name (car gnus-command-method)) "/"
110           (if (equal (cadr gnus-command-method) "")
111               "unnamed"
112             (cadr gnus-command-method))))
113
114 (defsubst gnus-agent-directory ()
115   "Path of the Gnus agent directory."
116   (nnheader-concat gnus-agent-directory
117                    (nnheader-translate-file-chars (gnus-agent-method)) "/"))
118
119 (defun gnus-agent-lib-file (file)
120   "The full path of the Gnus agent library FILE."
121   (concat (gnus-agent-directory) "agent.lib/" file))
122
123 ;;; Fetching setup functions.
124
125 (defun gnus-agent-start-fetch ()
126   "Initialize data structures for efficient fetching."
127   (gnus-agent-open-history)
128   (setq gnus-agent-current-history (gnus-agent-history-buffer)))
129
130 (defun gnus-agent-stop-fetch ()
131   "Save all data structures and clean up."
132   (gnus-agent-save-history)
133   (gnus-agent-close-history)
134   (setq gnus-agent-spam-hashtb nil)
135   (save-excursion
136     (set-buffer nntp-server-buffer)
137     (widen)))
138
139 (defmacro gnus-agent-with-fetch (&rest forms)
140   "Do FORMS safely."
141   `(unwind-protect
142        (progn
143          (gnus-agent-start-fetch)
144          ,@forms)
145      (gnus-agent-stop-fetch)))
146
147 (put 'gnus-agent-with-fetch 'lisp-indent-function 0)
148 (put 'gnus-agent-with-fetch 'edebug-form-spec '(body))
149
150 ;;;
151 ;;; Mode infestation
152 ;;;
153
154 (defvar gnus-agent-mode-hook nil
155   "Hook run when installing agent mode.")
156
157 (defvar gnus-agent-mode nil)
158 (defvar gnus-agent-mode-status '(gnus-agent-mode " Plugged"))
159
160 (defun gnus-agent-mode ()
161   "Minor mode for providing a agent support in Gnus buffers."
162   (let* ((buffer (progn (string-match "^gnus-\\(.*\\)-mode$"
163                                       (symbol-name major-mode))
164                         (match-string 1 (symbol-name major-mode))))
165          (mode (intern (format "gnus-agent-%s-mode" buffer))))
166     (set (make-local-variable 'gnus-agent-mode) t)
167     (set mode nil)
168     (set (make-local-variable mode) t)
169     ;; Set up the menu.
170     (when (gnus-visual-p 'agent-menu 'menu)
171       (funcall (intern (format "gnus-agent-%s-make-menu-bar" buffer))))
172     (unless (assq 'gnus-agent-mode minor-mode-alist)
173       (push gnus-agent-mode-status minor-mode-alist))
174     (unless (assq mode minor-mode-map-alist)
175       (push (cons mode (symbol-value (intern (format "gnus-agent-%s-mode-map"
176                                                      buffer))))
177             minor-mode-map-alist))
178     (gnus-agent-toggle-plugged gnus-plugged)
179     (gnus-run-hooks 'gnus-agent-mode-hook)))
180
181 (defvar gnus-agent-group-mode-map (make-sparse-keymap))
182 (gnus-define-keys gnus-agent-group-mode-map
183   "Ju" gnus-agent-fetch-groups
184   "Jc" gnus-enter-category-buffer
185   "Jj" gnus-agent-toggle-plugged
186   "Js" gnus-agent-fetch-session
187   "JS" gnus-group-send-drafts
188   "Ja" gnus-agent-add-group)
189
190 (defun gnus-agent-group-make-menu-bar ()
191   (unless (boundp 'gnus-agent-group-menu)
192     (easy-menu-define
193      gnus-agent-group-menu gnus-agent-group-mode-map ""
194      '("Agent"
195        ["Toggle plugged" gnus-agent-toggle-plugged t]
196        ["List categories" gnus-enter-category-buffer t]
197        ["Send drafts" gnus-group-send-drafts gnus-plugged]
198        ("Fetch"
199         ["All" gnus-agent-fetch-session gnus-plugged]
200         ["Group" gnus-agent-fetch-group gnus-plugged])))))
201
202 (defvar gnus-agent-summary-mode-map (make-sparse-keymap))
203 (gnus-define-keys gnus-agent-summary-mode-map
204   "Jj" gnus-agent-toggle-plugged
205   "J#" gnus-agent-mark-article
206   "J\M-#" gnus-agent-unmark-article
207   "@" gnus-agent-toggle-mark
208   "Jc" gnus-agent-catchup)
209
210 (defun gnus-agent-summary-make-menu-bar ()
211   (unless (boundp 'gnus-agent-summary-menu)
212     (easy-menu-define
213      gnus-agent-summary-menu gnus-agent-summary-mode-map ""
214      '("Agent"
215        ["Toggle plugged" gnus-agent-toggle-plugged t]
216        ["Mark as downloadable" gnus-agent-mark-article t]
217        ["Unmark as downloadable" gnus-agent-unmark-article t]
218        ["Toggle mark" gnus-agent-toggle-mark t]
219        ["Catchup undownloaded" gnus-agent-catchup t]))))
220
221 (defvar gnus-agent-server-mode-map (make-sparse-keymap))
222 (gnus-define-keys gnus-agent-server-mode-map
223   "Jj" gnus-agent-toggle-plugged
224   "Ja" gnus-agent-add-server
225   "Jr" gnus-agent-remove-server)
226
227 (defun gnus-agent-server-make-menu-bar ()
228   (unless (boundp 'gnus-agent-server-menu)
229     (easy-menu-define
230      gnus-agent-server-menu gnus-agent-server-mode-map ""
231      '("Agent"
232        ["Toggle plugged" gnus-agent-toggle-plugged t]
233        ["Add" gnus-agent-add-server t]
234        ["Remove" gnus-agent-remove-server t]))))
235
236 (defun gnus-agent-toggle-plugged (plugged)
237   "Toggle whether Gnus is unplugged or not."
238   (interactive (list (not gnus-plugged)))
239   (setq gnus-plugged plugged)
240   (if plugged
241       (progn
242         (gnus-run-hooks 'gnus-agent-plugged-hook)
243         (setcar (cdr gnus-agent-mode-status) " Plugged"))
244     (gnus-agent-close-connections)
245     (gnus-run-hooks 'gnus-agent-unplugged-hook)
246     (setcar (cdr gnus-agent-mode-status) " Unplugged"))
247   (set-buffer-modified-p t))
248
249 (defun gnus-agent-close-connections ()
250   "Close all methods covered by the Gnus agent."
251   (let ((methods gnus-agent-covered-methods))
252     (while methods
253       (gnus-close-server (pop methods)))))
254
255 ;;;###autoload
256 (defun gnus-unplugged ()
257   "Start Gnus unplugged."
258   (interactive)
259   (setq gnus-plugged nil)
260   (gnus))
261
262 ;;;###autoload
263 (defun gnus-agentize ()
264   "Allow Gnus to be an offline newsreader.
265 The normal usage of this command is to put the following as the
266 last form in your `.gnus.el' file:
267
268 \(gnus-agentize)
269
270 This will modify the `gnus-before-startup-hook', `gnus-post-method',
271 and `message-send-mail-function' variables, and install the Gnus
272 agent minor mode in all Gnus buffers."
273   (interactive)
274   (gnus-open-agent)
275   (add-hook 'gnus-setup-news-hook 'gnus-agent-queue-setup)
276   (unless gnus-agent-send-mail-function 
277     (setq gnus-agent-send-mail-function message-send-mail-function
278           message-send-mail-function 'gnus-agent-send-mail))
279   (unless gnus-agent-covered-methods
280     (setq gnus-agent-covered-methods (list gnus-select-method))))
281
282 (defun gnus-agent-queue-setup ()
283   "Make sure the queue group exists."
284   (unless (gnus-gethash "nndraft:queue" gnus-newsrc-hashtb)
285     (gnus-request-create-group "queue" '(nndraft ""))
286     (let ((gnus-level-default-subscribed 1))
287       (gnus-subscribe-group "nndraft:queue" nil '(nndraft "")))
288     (gnus-group-set-parameter
289      "nndraft:queue" 'gnus-dummy '((gnus-draft-mode)))))
290
291 (defun gnus-agent-send-mail ()
292   (if gnus-plugged
293       (funcall gnus-agent-send-mail-function)
294     (goto-char (point-min))
295     (re-search-forward
296      (concat "^" (regexp-quote mail-header-separator) "\n"))
297     (replace-match "\n")
298     (gnus-request-accept-article "nndraft:queue")))
299
300 ;;;
301 ;;; Group mode commands
302 ;;;
303
304 (defun gnus-agent-fetch-groups (n)
305   "Put all new articles in the current groups into the agent."
306   (interactive "P")
307   (gnus-group-iterate n 'gnus-agent-fetch-group))
308
309 (defun gnus-agent-fetch-group (group)
310   "Put all new articles in GROUP into the agent."
311   (interactive (list (gnus-group-group-name)))
312   (unless group
313     (error "No group on the current line"))
314   (let ((gnus-command-method (gnus-find-method-for-group group)))
315     (gnus-agent-with-fetch
316       (gnus-agent-fetch-group-1 group gnus-command-method)
317       (gnus-message 5 "Fetching %s...done" group))))
318
319 (defun gnus-agent-add-group (category arg)
320   "Add the current group to an agent category."
321   (interactive
322    (list
323     (intern
324      (completing-read
325       "Add to category: "
326       (mapcar (lambda (cat) (list (symbol-name (car cat))))
327               gnus-category-alist)
328       nil t))
329     current-prefix-arg))
330   (let ((cat (assq category gnus-category-alist))
331         c groups)
332     (gnus-group-iterate arg
333       (lambda (group)
334         (when (cadddr (setq c (gnus-group-category group)))
335           (setf (cadddr c) (delete group (cadddr c))))
336         (push group groups)))
337     (setf (cadddr cat) (nconc (cadddr cat) groups))
338     (gnus-category-write)))
339
340 ;;;
341 ;;; Server mode commands
342 ;;;
343
344 (defun gnus-agent-add-server (server)
345   "Enroll SERVER in the agent program."
346   (interactive (list (gnus-server-server-name)))
347   (unless server
348     (error "No server on the current line"))
349   (let ((method (gnus-server-get-method nil (gnus-server-server-name))))
350     (when (member method gnus-agent-covered-methods)
351       (error "Server already in the agent program"))
352     (push method gnus-agent-covered-methods)
353     (gnus-agent-write-servers)
354     (message "Entered %s into the agent" server)))
355
356 (defun gnus-agent-remove-server (server)
357   "Remove SERVER from the agent program."
358   (interactive (list (gnus-server-server-name)))
359   (unless server
360     (error "No server on the current line"))
361   (let ((method (gnus-server-get-method nil (gnus-server-server-name))))
362     (unless (member method gnus-agent-covered-methods)
363       (error "Server not in the agent program"))
364     (setq gnus-agent-covered-methods
365           (delete method gnus-agent-covered-methods))
366     (gnus-agent-write-servers)
367     (message "Removed %s from the agent" server)))
368
369 (defun gnus-agent-read-servers ()
370   "Read the alist of covered servers."
371   (setq gnus-agent-covered-methods
372         (gnus-agent-read-file
373          (nnheader-concat gnus-agent-directory "lib/servers"))))
374
375 (defun gnus-agent-write-servers ()
376   "Write the alist of covered servers."
377   (nnheader-temp-write (nnheader-concat gnus-agent-directory "lib/servers")
378     (prin1 gnus-agent-covered-methods (current-buffer))))
379
380 ;;;
381 ;;; Summary commands
382 ;;;
383
384 (defun gnus-agent-mark-article (n &optional unmark)
385   "Mark the next N articles as downloadable.
386 If N is negative, mark backward instead.  If UNMARK is non-nil, remove
387 the mark instead.  The difference between N and the actual number of
388 articles marked is returned."
389   (interactive "p")
390   (let ((backward (< n 0))
391         (n (abs n)))
392     (while (and
393             (> n 0)
394             (progn
395               (gnus-summary-set-agent-mark
396                (gnus-summary-article-number) unmark)
397               (zerop (gnus-summary-next-subject (if backward -1 1) nil t))))
398       (setq n (1- n)))
399     (when (/= 0 n)
400       (gnus-message 7 "No more articles"))
401     (gnus-summary-recenter)
402     (gnus-summary-position-point)
403     n))
404
405 (defun gnus-agent-unmark-article (n)
406   "Remove the downloadable mark from the next N articles.
407 If N is negative, unmark backward instead.  The difference between N and
408 the actual number of articles unmarked is returned."
409   (interactive "p")
410   (gnus-agent-mark-article n t))
411
412 (defun gnus-agent-toggle-mark (n)
413   "Toggle the downloadable mark from the next N articles.
414 If N is negative, toggle backward instead.  The difference between N and
415 the actual number of articles toggled is returned."
416   (interactive "p")
417   (gnus-agent-mark-article n 'toggle))
418
419 (defun gnus-summary-set-agent-mark (article &optional unmark)
420   "Mark ARTICLE as downloadable."
421   (let ((unmark (if (and (not (null unmark)) (not (eq t unmark)))
422                     (memq article gnus-newsgroup-downloadable)
423                   unmark)))
424     (if unmark
425         (progn
426           (setq gnus-newsgroup-downloadable
427                 (delq article gnus-newsgroup-downloadable))
428           (push article gnus-newsgroup-undownloaded))
429       (setq gnus-newsgroup-undownloaded
430             (delq article gnus-newsgroup-undownloaded))
431       (push article gnus-newsgroup-downloadable))
432     (gnus-summary-update-mark
433      (if unmark gnus-undownloaded-mark gnus-downloadable-mark)
434      'unread)))
435
436 (defun gnus-agent-get-undownloaded-list ()
437   "Mark all unfetched articles as read."
438   (let ((gnus-command-method (gnus-find-method-for-group gnus-newsgroup-name)))
439     (when (and (not gnus-plugged)
440                (gnus-agent-method-p gnus-command-method))
441       (gnus-agent-load-alist gnus-newsgroup-name)
442       (let ((articles gnus-newsgroup-unreads)
443             article)
444         (while (setq article (pop articles))
445           (unless (or (cdr (assq article gnus-agent-article-alist))
446                   (memq article gnus-newsgroup-downloadable))
447             (push article gnus-newsgroup-undownloaded)))))))
448
449 (defun gnus-agent-catchup ()
450   "Mark all undownloaded articles as read."
451   (interactive)
452   (save-excursion
453     (while gnus-newsgroup-undownloaded
454       (gnus-summary-mark-article
455        (pop gnus-newsgroup-undownloaded) gnus-catchup-mark)))
456   (gnus-summary-position-point))
457
458 ;;;
459 ;;; Internal functions
460 ;;;
461
462 (defun gnus-agent-save-active (method)
463   (when (gnus-agent-method-p method)
464     (let* ((gnus-command-method method)
465            (file (gnus-agent-lib-file "active")))
466       (gnus-make-directory (file-name-directory file))
467       (let ((coding-system-for-write gnus-agent-article-file-coding-system))
468         (write-region (point-min) (point-max) file nil 'silent))
469       (when (file-exists-p (gnus-agent-lib-file "groups"))
470         (delete-file (gnus-agent-lib-file "groups"))))))
471
472 (defun gnus-agent-save-groups (method)
473   (let* ((gnus-command-method method)
474          (file (gnus-agent-lib-file "groups")))
475     (gnus-make-directory (file-name-directory file))
476     (write-region (point-min) (point-max) file nil 'silent))
477     (when (file-exists-p (gnus-agent-lib-file "active"))
478       (delete-file (gnus-agent-lib-file "active"))))
479
480 (defun gnus-agent-group-path (group)
481   "Translate GROUP into a path."
482   (if nnmail-use-long-file-names
483       group
484     (nnheader-translate-file-chars
485      (nnheader-replace-chars-in-string group ?. ?/))))
486
487 \f
488
489 (defun gnus-agent-method-p (method)
490   "Say whether METHOD is covered by the agent."
491   (member method gnus-agent-covered-methods))
492
493 (defun gnus-agent-get-function (method)
494   (if (and (not gnus-plugged)
495            (gnus-agent-method-p method))
496       (progn
497         (require 'nnagent)
498         'nnagent)
499     (car method)))
500
501 ;;; History functions
502
503 (defun gnus-agent-history-buffer ()
504   (cdr (assoc (gnus-agent-method) gnus-agent-history-buffers)))
505
506 (defun gnus-agent-open-history ()
507   (save-excursion
508     (push (cons (gnus-agent-method)
509                 (set-buffer (get-buffer-create
510                              (format " *Gnus agent %s history*"
511                                      (gnus-agent-method)))))
512           gnus-agent-history-buffers)
513     (erase-buffer)
514     (insert "\n")
515     (let ((file (gnus-agent-lib-file "history")))
516       (when (file-exists-p file)
517         (insert-file file))
518       (set (make-local-variable 'gnus-agent-file-name) file))))
519
520 (defun gnus-agent-save-history ()
521   (save-excursion
522     (set-buffer gnus-agent-current-history)
523     (gnus-make-directory (file-name-directory gnus-agent-file-name))
524     (write-region (1+ (point-min)) (point-max)
525                   gnus-agent-file-name nil 'silent)))
526
527 (defun gnus-agent-close-history ()
528   (when (gnus-buffer-live-p gnus-agent-current-history)
529     (kill-buffer gnus-agent-current-history)
530     (setq gnus-agent-history-buffers
531           (delq (assoc (gnus-agent-method) gnus-agent-history-buffers)
532                 gnus-agent-history-buffers))))
533
534 (defun gnus-agent-enter-history (id group-arts date)
535   (save-excursion
536     (set-buffer gnus-agent-current-history)
537     (goto-char (point-max))
538     (insert id "\t" (number-to-string date) "\t")
539     (while group-arts
540       (insert (caar group-arts) "/" (number-to-string (cdr (pop group-arts)))
541               " "))
542     (insert "\n")))
543
544 (defun gnus-agent-article-in-history-p (id)
545   (save-excursion
546     (set-buffer (gnus-agent-history-buffer))
547     (goto-char (point-min))
548     (search-forward (concat "\n" id "\t") nil t)))
549
550 (defun gnus-agent-history-path (id)
551   (save-excursion
552     (set-buffer (gnus-agent-history-buffer))
553     (goto-char (point-min))
554     (when (search-forward (concat "\n" id "\t") nil t)
555       (let ((method (gnus-agent-method)))
556         (let (paths group)
557           (while (not (numberp (setq group (read (current-buffer)))))
558             (push (concat method "/" group) paths))
559           (nreverse paths))))))
560
561 ;;;
562 ;;; Fetching
563 ;;;
564
565 (defun gnus-agent-fetch-articles (group articles)
566   "Fetch ARTICLES from GROUP and put them into the agent."
567   (when articles
568     ;; Prune off articles that we have already fetched.
569     (while (and articles
570                 (cdr (assq (car articles) gnus-agent-article-alist)))
571       (pop articles))
572     (let ((arts articles))
573       (while (cdr arts)
574         (if (cdr (assq (cadr arts) gnus-agent-article-alist))
575             (setcdr arts (cddr arts))
576           (setq arts (cdr arts)))))
577     (when articles
578       (let ((dir (concat
579                   (gnus-agent-directory)
580                   (gnus-agent-group-path group) "/"))
581             (date (gnus-time-to-day (current-time)))
582             (case-fold-search t)
583             pos alists crosses id elem)
584         (gnus-make-directory dir)
585         (gnus-message 7 "Fetching articles for %s..." group)
586         ;; Fetch the articles from the backend.
587         (if (gnus-check-backend-function 'retrieve-articles group)
588             (setq pos (gnus-retrieve-articles articles group))
589           (nnheader-temp-write nil
590             (let ((buf (current-buffer))
591                   article)
592               (while (setq article (pop articles))
593                 (when (gnus-request-article article group)
594                   (goto-char (point-max))
595                   (push (cons article (point)) pos)
596                   (insert-buffer-substring nntp-server-buffer)))
597               (copy-to-buffer nntp-server-buffer (point-min) (point-max))
598               (setq pos (nreverse pos)))))
599         ;; Then save these articles into the agent.
600         (save-excursion
601           (set-buffer nntp-server-buffer)
602           (while pos
603             (narrow-to-region (cdar pos) (or (cdadr pos) (point-max)))
604             (goto-char (point-min))
605             (when (search-forward "\n\n" nil t)
606               (when (search-backward "\nXrefs: " nil t)
607                 ;; Handle crossposting.
608                 (skip-chars-forward "^ ")
609                 (skip-chars-forward " ")
610                 (setq crosses nil)
611                 (while (looking-at "\\([^: \n]+\\):\\([0-9]+\\) +")
612                   (push (cons (buffer-substring (match-beginning 1)
613                                                 (match-end 1))
614                               (buffer-substring (match-beginning 2)
615                                                 (match-end 2)))
616                         crosses)
617                   (goto-char (match-end 0)))
618                 (gnus-agent-crosspost crosses (caar pos))))
619             (goto-char (point-min))
620             (if (not (re-search-forward "^Message-ID: *<\\([^>\n]+\\)>" nil t))
621                 (setq id "No-Message-ID-in-article")
622               (setq id (buffer-substring (match-beginning 1) (match-end 1))))
623             (let ((coding-system-for-write gnus-agent-article-file-coding-system))
624               (write-region (point-min) (point-max)
625                             (concat dir (number-to-string (caar pos)))
626                             nil 'silent))
627             (when (setq elem (assq (caar pos) gnus-agent-article-alist))
628               (setcdr elem t))
629             (gnus-agent-enter-history
630              id (or crosses (list (cons group (caar pos)))) date)
631             (widen)
632             (pop pos)))
633         (gnus-agent-save-alist group)))))
634
635 (defun gnus-agent-crosspost (crosses article)
636   (let (gnus-agent-article-alist group alist beg end)
637     (save-excursion
638       (set-buffer gnus-agent-overview-buffer)
639       (when (nnheader-find-nov-line article)
640         (forward-word 1)
641         (setq beg (point))
642         (setq end (progn (forward-line 1) (point)))))
643     (while crosses
644       (setq group (caar crosses))
645       (unless (setq alist (assoc group gnus-agent-group-alist))
646         (push (setq alist (list group (gnus-agent-load-alist (caar crosses))))
647               gnus-agent-group-alist))
648       (setcdr alist (cons (cons (cdar crosses) t) (cdr alist)))
649       (save-excursion
650         (set-buffer (get-buffer-create (format " *Gnus agent overview %s*"
651                                                group)))
652         (when (= (point-max) (point-min))
653           (push (cons group (current-buffer)) gnus-agent-buffer-alist)
654           (ignore-errors
655             (insert-file-contents
656              (gnus-agent-article-name ".overview" group))))
657         (nnheader-find-nov-line (string-to-number (cdar crosses)))
658         (insert (string-to-number (cdar crosses)))
659         (insert-buffer-substring gnus-agent-overview-buffer beg end))
660       (pop crosses))))
661
662 (defun gnus-agent-flush-cache ()
663   (save-excursion
664     (while gnus-agent-buffer-alist
665       (set-buffer (cdar gnus-agent-buffer-alist))
666       (write-region (point-min) (point-max)
667                     (gnus-agent-article-name ".overview"
668                                              (caar gnus-agent-buffer-alist))
669                      nil 'silent)
670       (pop gnus-agent-buffer-alist))
671     (while gnus-agent-group-alist
672       (nnheader-temp-write (caar gnus-agent-group-alist)
673         (princ (cdar gnus-agent-group-alist))
674         (insert "\n"))
675       (pop gnus-agent-group-alist))))
676
677 (defun gnus-agent-fetch-headers (group articles &optional force)
678   (gnus-agent-load-alist group)
679   ;; Find out what headers we need to retrieve.
680   (when articles
681     (while (and articles
682                 (assq (car articles) gnus-agent-article-alist))
683       (pop articles))
684     (let ((arts articles))
685       (while (cdr arts)
686         (if (assq (cadr arts) gnus-agent-article-alist)
687             (setcdr arts (cddr arts))
688           (setq arts (cdr arts)))))
689     ;; Fetch them.
690     (when articles
691       (gnus-message 7 "Fetching headers for %s..." group)
692       (save-excursion
693         (set-buffer nntp-server-buffer)
694         (unless (eq 'nov (gnus-retrieve-headers articles group))
695           (nnvirtual-convert-headers))
696         ;; Save these headers for later processing.
697         (copy-to-buffer gnus-agent-overview-buffer (point-min) (point-max))
698         (let (file)
699           (when (file-exists-p
700                  (setq file (gnus-agent-article-name ".overview" group)))
701             (gnus-agent-braid-nov group articles file))
702           (gnus-make-directory (nnheader-translate-file-chars
703                                 (file-name-directory file)))
704           (write-region (point-min) (point-max) file nil 'silent)
705           (gnus-agent-save-alist group articles nil))
706         t))))
707
708 (defsubst gnus-agent-copy-nov-line (article)
709   (let (b e)
710     (set-buffer gnus-agent-overview-buffer)
711     (setq b (point))
712     (if (eq article (read (current-buffer)))
713         (setq e (progn (forward-line 1) (point)))
714       (setq e b))
715     (set-buffer nntp-server-buffer)
716     (insert-buffer-substring gnus-agent-overview-buffer b e)))
717
718 (defun gnus-agent-braid-nov (group articles file)
719   (let (beg end)
720     (set-buffer gnus-agent-overview-buffer)
721     (goto-char (point-min))
722     (set-buffer nntp-server-buffer)
723     (erase-buffer)
724     (insert-file-contents file)
725     (goto-char (point-min))
726     (if (or (= (point-min) (point-max))
727             (progn
728               (forward-line -1)
729               (< (read (current-buffer)) (car articles))))
730         ;; We have only headers that are after the older headers,
731         ;; so we just append them.
732         (progn
733           (goto-char (point-max))
734           (insert-buffer-substring gnus-agent-overview-buffer))
735       ;; We do it the hard way.
736       (nnheader-find-nov-line (car articles))
737       (gnus-agent-copy-nov-line (car articles))
738       (pop articles)
739       (while (and articles
740                   (not (eobp)))
741         (while (and (not (eobp))
742                     (< (read (current-buffer)) (car articles)))
743           (forward-line 1))
744         (beginning-of-line)
745         (unless (eobp)
746           (gnus-agent-copy-nov-line (car articles))
747           (setq articles (cdr articles))))
748       (when articles
749         (let (b e)
750           (set-buffer gnus-agent-overview-buffer)
751           (setq b (point)
752                 e (point-max))
753           (set-buffer nntp-server-buffer)
754           (insert-buffer-substring gnus-agent-overview-buffer b e))))))
755
756 (defun gnus-agent-load-alist (group &optional dir)
757   "Load the article-state alist for GROUP."
758   (setq gnus-agent-article-alist
759         (gnus-agent-read-file
760          (if dir
761              (concat dir ".agentview")
762            (gnus-agent-article-name ".agentview" group)))))
763
764 (defun gnus-agent-save-alist (group &optional articles state dir)
765   "Load the article-state alist for GROUP."
766   (nnheader-temp-write (if dir
767                            (concat dir ".agentview")
768                          (gnus-agent-article-name ".agentview" group))
769     (princ (setq gnus-agent-article-alist
770                  (nconc gnus-agent-article-alist
771                         (mapcar (lambda (article) (cons article state))
772                                 articles)))
773            (current-buffer))
774     (insert "\n")))
775
776 (defun gnus-agent-article-name (article group)
777   (concat (gnus-agent-directory) (gnus-agent-group-path group) "/"
778           (if (stringp article) article (string-to-number article))))
779
780 ;;;###autoload
781 (defun gnus-agent-batch-fetch ()
782   "Start Gnus and fetch session."
783   (interactive)
784   (gnus)
785   (gnus-agent-fetch-session)
786   (gnus-group-exit))
787
788 (defun gnus-agent-fetch-session ()
789   "Fetch all articles and headers that are eligible for fetching."
790   (interactive)
791   (unless gnus-agent-covered-methods
792     (error "No servers are covered by the Gnus agent"))
793   (unless gnus-plugged
794     (error "Can't fetch articles while Gnus is unplugged"))
795   (let ((methods gnus-agent-covered-methods)
796         groups group gnus-command-method)
797     (save-excursion
798       (while methods
799         (setq gnus-command-method (car methods)
800               groups (gnus-groups-from-server (pop methods)))
801         (gnus-agent-with-fetch
802           (while (setq group (pop groups))
803             (when (<= (gnus-group-level group) gnus-agent-handle-level)
804               (gnus-agent-fetch-group-1 group gnus-command-method)))))
805       (gnus-message 6 "Finished fetching articles into the Gnus agent"))))
806
807 (defun gnus-agent-fetch-group-1 (group method)
808   "Fetch GROUP."
809   (let ((gnus-command-method method)
810         gnus-newsgroup-dependencies gnus-newsgroup-headers
811         gnus-newsgroup-scored gnus-headers gnus-score
812         gnus-use-cache articles score arts
813         category predicate info marks score-param)
814     ;; Fetch headers.
815     (when (and (setq articles (gnus-list-of-unread-articles group))
816                (gnus-agent-fetch-headers group articles))
817       ;; Parse them and see which articles we want to fetch.
818       (setq gnus-newsgroup-dependencies
819             (make-vector (length articles) 0))
820       (setq gnus-newsgroup-headers
821             (gnus-get-newsgroup-headers-xover articles nil nil group))
822       (setq category (gnus-group-category group))
823       (setq predicate
824             (gnus-get-predicate 
825              (or (gnus-group-get-parameter group 'agent-predicate)
826                  (cadr category))))
827       (setq score-param
828             (or (gnus-group-get-parameter group 'agent-score)
829                 (caddr category)))
830       (when score-param
831         (gnus-score-headers (list (list score-param))))
832       (setq arts nil)
833       (while (setq gnus-headers (pop gnus-newsgroup-headers))
834         (setq gnus-score
835               (or (cdr (assq (mail-header-number gnus-headers)
836                              gnus-newsgroup-scored))
837                   gnus-summary-default-score))
838         (when (funcall predicate)
839           (push (mail-header-number gnus-headers)
840                 arts)))
841       ;; Fetch the articles.
842       (when arts
843         (gnus-agent-fetch-articles group arts)))
844     ;; Perhaps we have some additional articles to fetch.
845     (setq arts (assq 'download (gnus-info-marks
846                                 (setq info (gnus-get-info group)))))
847     (when (cdr arts)
848       (gnus-agent-fetch-articles
849        group (gnus-uncompress-range (cdr arts)))
850       (setq marks (delq arts (gnus-info-marks info)))
851       (gnus-info-set-marks info marks))))
852
853 ;;;
854 ;;; Agent Category Mode
855 ;;;
856
857 (defvar gnus-category-mode-hook nil
858   "Hook run in `gnus-category-mode' buffers.")
859
860 (defvar gnus-category-line-format "     %(%20c%): %g\n"
861   "Format of category lines.")
862
863 (defvar gnus-category-mode-line-format "Gnus: %%b"
864   "The format specification for the category mode line.")
865
866 (defvar gnus-agent-short-article 100
867   "Articles that have fewer lines than this are short.")
868
869 (defvar gnus-agent-long-article 200
870   "Articles that have more lines than this are long.")
871
872 (defvar gnus-agent-low-score 0
873   "Articles that have a score lower than this have a low score.")
874
875 (defvar gnus-agent-high-score 0
876   "Articles that have a score higher than this have a high score.")
877
878
879 ;;; Internal variables.
880
881 (defvar gnus-category-buffer "*Agent Category*")
882
883 (defvar gnus-category-line-format-alist
884   `((?c name ?s)
885     (?g groups ?d)))
886
887 (defvar gnus-category-mode-line-format-alist
888   `((?u user-defined ?s)))
889
890 (defvar gnus-category-line-format-spec nil)
891 (defvar gnus-category-mode-line-format-spec nil)
892
893 (defvar gnus-category-mode-map nil)
894 (put 'gnus-category-mode 'mode-class 'special)
895
896 (unless gnus-category-mode-map
897   (setq gnus-category-mode-map (make-sparse-keymap))
898   (suppress-keymap gnus-category-mode-map)
899
900   (gnus-define-keys gnus-category-mode-map
901     "q" gnus-category-exit
902     "k" gnus-category-kill
903     "c" gnus-category-copy
904     "a" gnus-category-add
905     "p" gnus-category-edit-predicate
906     "g" gnus-category-edit-groups
907     "s" gnus-category-edit-score
908     "l" gnus-category-list
909
910     "\C-c\C-i" gnus-info-find-node
911     "\C-c\C-b" gnus-bug))
912
913 (defvar gnus-category-menu-hook nil
914   "*Hook run after the creation of the menu.")
915
916 (defun gnus-category-make-menu-bar ()
917   (gnus-turn-off-edit-menu 'category)
918   (unless (boundp 'gnus-category-menu)
919     (easy-menu-define
920      gnus-category-menu gnus-category-mode-map ""
921      '("Categories"
922        ["Add" gnus-category-add t]
923        ["Kill" gnus-category-kill t]
924        ["Copy" gnus-category-copy t]
925        ["Edit predicate" gnus-category-edit-predicate t]
926        ["Edit score" gnus-category-edit-score t]
927        ["Edit groups" gnus-category-edit-groups t]
928        ["Exit" gnus-category-exit t]))
929
930     (gnus-run-hooks 'gnus-category-menu-hook)))
931
932 (defun gnus-category-mode ()
933   "Major mode for listing and editing agent categories.
934
935 All normal editing commands are switched off.
936 \\<gnus-category-mode-map>
937 For more in-depth information on this mode, read the manual
938 (`\\[gnus-info-find-node]').
939
940 The following commands are available:
941
942 \\{gnus-category-mode-map}"
943   (interactive)
944   (when (gnus-visual-p 'category-menu 'menu)
945     (gnus-category-make-menu-bar))
946   (kill-all-local-variables)
947   (gnus-simplify-mode-line)
948   (setq major-mode 'gnus-category-mode)
949   (setq mode-name "Category")
950   (gnus-set-default-directory)
951   (setq mode-line-process nil)
952   (use-local-map gnus-category-mode-map)
953   (buffer-disable-undo (current-buffer))
954   (setq truncate-lines t)
955   (setq buffer-read-only t)
956   (gnus-run-hooks 'gnus-category-mode-hook))
957
958 (defalias 'gnus-category-position-point 'gnus-goto-colon)
959
960 (defun gnus-category-insert-line (category)
961   (let* ((name (car category))
962          (groups (length (cadddr category))))
963     (beginning-of-line)
964     (gnus-add-text-properties
965      (point)
966      (prog1 (1+ (point))
967        ;; Insert the text.
968        (eval gnus-category-line-format-spec))
969      (list 'gnus-category name))))
970
971 (defun gnus-enter-category-buffer ()
972   "Go to the Category buffer."
973   (interactive)
974   (gnus-category-setup-buffer)
975   (gnus-configure-windows 'category)
976   (gnus-category-prepare))
977
978 (defun gnus-category-setup-buffer ()
979   (unless (get-buffer gnus-category-buffer)
980     (save-excursion
981       (set-buffer (get-buffer-create gnus-category-buffer))
982       (gnus-add-current-to-buffer-list)
983       (gnus-category-mode))))
984
985 (defun gnus-category-prepare ()
986   (gnus-set-format 'category-mode)
987   (gnus-set-format 'category t)
988   (let ((alist gnus-category-alist)
989         (buffer-read-only nil))
990     (erase-buffer)
991     (while alist
992       (gnus-category-insert-line (pop alist)))
993     (goto-char (point-min))
994     (gnus-category-position-point)))
995
996 (defun gnus-category-name ()
997   (or (get-text-property (gnus-point-at-bol) 'gnus-category)
998       (error "No category on the current line")))
999
1000 (defun gnus-category-read ()
1001   "Read the category alist."
1002   (setq gnus-category-alist
1003         (or (gnus-agent-read-file
1004              (nnheader-concat gnus-agent-directory "lib/categories"))
1005             (list (list 'default 'short nil nil)))))
1006     
1007 (defun gnus-category-write ()
1008   "Write the category alist."
1009   (setq gnus-category-predicate-cache nil
1010         gnus-category-group-cache nil)
1011   (nnheader-temp-write (nnheader-concat gnus-agent-directory "lib/categories")
1012     (prin1 gnus-category-alist (current-buffer))))
1013
1014 (defun gnus-category-edit-predicate (category)
1015   "Edit the predicate for CATEGORY."
1016   (interactive (list (gnus-category-name)))
1017   (let ((info (assq category gnus-category-alist)))
1018     (gnus-edit-form
1019      (cadr info) (format "Editing the predicate for category %s" category)
1020      `(lambda (predicate)
1021         (setf (cadr (assq ',category gnus-category-alist)) predicate)
1022         (gnus-category-write)
1023         (gnus-category-list)))))
1024   
1025 (defun gnus-category-edit-score (category)
1026   "Edit the score expression for CATEGORY."
1027   (interactive (list (gnus-category-name)))
1028   (let ((info (assq category gnus-category-alist)))
1029     (gnus-edit-form
1030      (caddr info)
1031      (format "Editing the score expression for category %s" category)
1032      `(lambda (groups)
1033         (setf (caddr (assq ',category gnus-category-alist)) groups)
1034         (gnus-category-write)
1035         (gnus-category-list)))))
1036
1037 (defun gnus-category-edit-groups (category)
1038   "Edit the group list for CATEGORY."
1039   (interactive (list (gnus-category-name)))
1040   (let ((info (assq category gnus-category-alist)))
1041     (gnus-edit-form
1042      (cadddr info) (format "Editing the group list for category %s" category)
1043      `(lambda (groups)
1044         (setf (cadddr (assq ',category gnus-category-alist)) groups)
1045         (gnus-category-write)
1046         (gnus-category-list)))))
1047
1048 (defun gnus-category-kill (category)
1049   "Kill the current category."
1050   (interactive (list (gnus-category-name)))
1051   (let ((info (assq category gnus-category-alist))
1052         (buffer-read-only nil))
1053     (gnus-delete-line)
1054     (gnus-category-write)
1055     (setq gnus-category-alist (delq info gnus-category-alist))))
1056
1057 (defun gnus-category-copy (category to)
1058   "Copy the current category."
1059   (interactive (list (gnus-category-name) (intern (read-string "New name: "))))
1060   (let ((info (assq category gnus-category-alist)))
1061     (push (list to (gnus-copy-sequence (cadr info))
1062                 (gnus-copy-sequence (caddr info)) nil)
1063           gnus-category-alist)
1064     (gnus-category-write)
1065     (gnus-category-list)))
1066
1067 (defun gnus-category-add (category)
1068   "Create a new category."
1069   (interactive "SCategory name: ")
1070   (when (assq category gnus-category-alist)
1071     (error "Category %s already exists" category))
1072   (push (list category 'true nil nil)
1073         gnus-category-alist)
1074   (gnus-category-write)
1075   (gnus-category-list))
1076
1077 (defun gnus-category-list ()
1078   "List all categories."
1079   (interactive)
1080   (gnus-category-prepare))
1081
1082 (defun gnus-category-exit ()
1083   "Return to the group buffer."
1084   (interactive)
1085   (kill-buffer (current-buffer))
1086   (gnus-configure-windows 'group t))
1087
1088 ;; To avoid having 8-bit characters in the source file.
1089 (defvar gnus-category-not (list '! 'not (intern (format "%c" 172))))
1090
1091 (defvar gnus-category-predicate-alist
1092   '((spam . gnus-agent-spam-p)
1093     (short . gnus-agent-short-p)
1094     (long . gnus-agent-long-p)
1095     (low . gnus-agent-low-scored-p)
1096     (high . gnus-agent-high-scored-p)
1097     (true . gnus-agent-true)
1098     (false . gnus-agent-false))
1099   "Mapping from short score predicate symbols to predicate functions.")
1100
1101 (defun gnus-agent-spam-p ()
1102   "Say whether an article is spam or not."
1103   (unless gnus-agent-spam-hashtb
1104     (setq gnus-agent-spam-hashtb (gnus-make-hashtable 1000)))
1105   (if (not (equal (mail-header-references gnus-headers) ""))
1106       nil
1107     (let ((string (gnus-simplify-subject (mail-header-subject gnus-headers))))
1108       (prog1
1109           (gnus-gethash string gnus-agent-spam-hashtb)
1110         (gnus-sethash string t gnus-agent-spam-hashtb)))))
1111
1112 (defun gnus-agent-short-p ()
1113   "Say whether an article is short or not."
1114   (< (mail-header-lines gnus-headers) gnus-agent-short-article))
1115
1116 (defun gnus-agent-long-p ()
1117   "Say whether an article is long or not."
1118   (> (mail-header-lines gnus-headers) gnus-agent-long-article))
1119
1120 (defun gnus-agent-low-scored-p ()
1121   "Say whether an article has a low score or not."
1122   (< gnus-score gnus-agent-low-score))
1123
1124 (defun gnus-agent-high-scored-p ()
1125   "Say whether an article has a high score or not."
1126   (> gnus-score gnus-agent-low-score))
1127
1128 (defun gnus-category-make-function (cat)
1129   "Make a function from category CAT."
1130   `(lambda () ,(gnus-category-make-function-1 cat)))
1131
1132 (defun gnus-agent-true ()
1133   "Return t."
1134   t)
1135
1136 (defun gnus-agent-false ()
1137   "Return nil."
1138   nil)
1139   
1140 (defun gnus-category-make-function-1 (cat)
1141   "Make a function from category CAT."
1142   (cond
1143    ;; Functions are just returned as is.
1144    ((or (symbolp cat)
1145         (gnus-functionp cat))
1146     `(,(or (cdr (assq cat gnus-category-predicate-alist))
1147            cat)))
1148    ;; More complex category.
1149    ((consp cat)
1150     `(,(cond
1151         ((memq (car cat) '(& and))
1152          'and)
1153         ((memq (car cat) '(| or))
1154          'or)
1155         ((memq (car cat) gnus-category-not)
1156          'not))
1157       ,@(mapcar 'gnus-category-make-function-1 (cdr cat))))
1158    (t
1159     (error "Unknown category type: %s" cat))))
1160
1161 (defun gnus-get-predicate (predicate)
1162   "Return the predicate for CATEGORY."
1163   (or (cdr (assoc predicate gnus-category-predicate-cache))
1164       (cdar (push (cons predicate
1165                         (gnus-category-make-function predicate))
1166                   gnus-category-predicate-cache))))
1167
1168 (defun gnus-group-category (group)
1169   "Return the category GROUP belongs to."
1170   (unless gnus-category-group-cache
1171     (setq gnus-category-group-cache (gnus-make-hashtable 1000))
1172     (let ((cs gnus-category-alist)
1173           groups cat)
1174       (while (setq cat (pop cs))
1175         (setq groups (cadddr cat))
1176         (while groups
1177           (gnus-sethash (pop groups) cat gnus-category-group-cache)))))
1178   (or (gnus-gethash group gnus-category-group-cache)
1179       (assq 'default gnus-category-alist)))
1180
1181 (defun gnus-agent-expire ()
1182   "Expire all old articles."
1183   (interactive)
1184   (let ((methods gnus-agent-covered-methods)
1185         (alist (cdr gnus-newsrc-alist))
1186         gnus-command-method ofiles info method file group)
1187     (while (setq gnus-command-method (pop methods))
1188       (setq ofiles (nconc ofiles (gnus-agent-expire-directory
1189                                   (gnus-agent-directory)))))
1190     (while (setq info (pop alist))
1191       (when (and (gnus-agent-method-p
1192                   (setq gnus-command-method
1193                         (gnus-find-method-for-group
1194                          (setq group (gnus-info-group info)))))
1195                  (member
1196                   (setq file
1197                         (concat
1198                          (gnus-agent-directory)
1199                          (gnus-agent-group-path group) "/.overview"))
1200                   ofiles))
1201         (setq ofiles (delete file ofiles))
1202         (gnus-agent-expire-group file group)))
1203     (while ofiles
1204       (gnus-agent-expire-group (pop ofiles)))))
1205
1206 (defun gnus-agent-expire-directory (dir)
1207   "Expire all groups in DIR recursively."
1208   (when (file-directory-p dir)
1209     (let ((files (directory-files dir t))
1210           file ofiles)
1211       (while (setq file (pop files))
1212         (cond
1213          ((member (file-name-nondirectory file) '("." ".."))
1214           ;; Do nothing.
1215           )
1216          ((file-directory-p file)
1217           ;; Recurse.
1218           (setq ofiles (nconc ofiles (gnus-agent-expire-directory file))))
1219          ((string-match "\\.overview$" file)
1220           ;; Expire group.
1221           (push file ofiles))))
1222       ofiles)))
1223
1224 (defun gnus-agent-expire-group (overview &optional group)
1225   "Expire articles in OVERVIEW."
1226   (gnus-message 5 "Expiring %s..." overview)
1227   (let ((odate (- (gnus-time-to-day (current-time)) 4))
1228         (dir (file-name-directory overview))
1229         (info (when group (gnus-get-info group)))
1230         headers article file point unreads)
1231     (gnus-agent-load-alist nil dir)
1232     (when info
1233       (setq unreads
1234             (nconc
1235              (gnus-list-of-unread-articles group)
1236              (gnus-uncompress-range
1237               (cdr (assq 'tick (gnus-info-marks info))))
1238              (gnus-uncompress-range
1239               (cdr (assq 'dormant (gnus-info-marks info)))))))
1240     (nnheader-temp-write overview
1241       (insert-file-contents overview)
1242       (goto-char (point-min))
1243       (while (not (eobp))
1244         (setq point (point))
1245         (condition-case ()
1246             (setq headers (inline (nnheader-parse-nov)))
1247           (error
1248            (goto-char point)
1249            (gnus-delete-line)
1250            (setq headers nil)))
1251         (when headers
1252           (unless (memq (setq article (mail-header-number headers)) unreads)
1253             (if (not (< (inline
1254                           (gnus-time-to-day
1255                            (inline (nnmail-date-to-time
1256                                     (mail-header-date headers)))))
1257                         odate))
1258                 (forward-line 1)              
1259               (gnus-delete-line)
1260               (setq gnus-agent-article-alist
1261                     (delq (assq article gnus-agent-article-alist)
1262                           gnus-agent-article-alist))
1263               (when (file-exists-p
1264                      (setq file (concat dir (number-to-string article))))
1265                 (delete-file file))))))
1266       (gnus-agent-save-alist nil nil nil dir))))
1267
1268 ;;;###autoload
1269 (defun gnus-agent-batch ()
1270   (interactive)
1271   (let ((init-file-user "")
1272         (gnus-always-read-dribble-file t))
1273     (gnus))
1274   (gnus-group-send-drafts)
1275   (gnus-agent-fetch-session))
1276
1277 (provide 'gnus-agent)
1278
1279 ;;; gnus-agent.el ends here