*** 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
624                    gnus-agent-article-file-coding-system))
625               (write-region (point-min) (point-max)
626                             (concat dir (number-to-string (caar pos)))
627                             nil 'silent))
628             (when (setq elem (assq (caar pos) gnus-agent-article-alist))
629               (setcdr elem t))
630             (gnus-agent-enter-history
631              id (or crosses (list (cons group (caar pos)))) date)
632             (widen)
633             (pop pos)))
634         (gnus-agent-save-alist group)))))
635
636 (defun gnus-agent-crosspost (crosses article)
637   (let (gnus-agent-article-alist group alist beg end)
638     (save-excursion
639       (set-buffer gnus-agent-overview-buffer)
640       (when (nnheader-find-nov-line article)
641         (forward-word 1)
642         (setq beg (point))
643         (setq end (progn (forward-line 1) (point)))))
644     (while crosses
645       (setq group (caar crosses))
646       (unless (setq alist (assoc group gnus-agent-group-alist))
647         (push (setq alist (list group (gnus-agent-load-alist (caar crosses))))
648               gnus-agent-group-alist))
649       (setcdr alist (cons (cons (cdar crosses) t) (cdr alist)))
650       (save-excursion
651         (set-buffer (get-buffer-create (format " *Gnus agent overview %s*"
652                                                group)))
653         (when (= (point-max) (point-min))
654           (push (cons group (current-buffer)) gnus-agent-buffer-alist)
655           (ignore-errors
656             (insert-file-contents
657              (gnus-agent-article-name ".overview" group))))
658         (nnheader-find-nov-line (string-to-number (cdar crosses)))
659         (insert (string-to-number (cdar crosses)))
660         (insert-buffer-substring gnus-agent-overview-buffer beg end))
661       (pop crosses))))
662
663 (defun gnus-agent-flush-cache ()
664   (save-excursion
665     (while gnus-agent-buffer-alist
666       (set-buffer (cdar gnus-agent-buffer-alist))
667       (write-region (point-min) (point-max)
668                     (gnus-agent-article-name ".overview"
669                                              (caar gnus-agent-buffer-alist))
670                      nil 'silent)
671       (pop gnus-agent-buffer-alist))
672     (while gnus-agent-group-alist
673       (nnheader-temp-write (caar gnus-agent-group-alist)
674         (princ (cdar gnus-agent-group-alist))
675         (insert "\n"))
676       (pop gnus-agent-group-alist))))
677
678 (defun gnus-agent-fetch-headers (group articles &optional force)
679   (gnus-agent-load-alist group)
680   ;; Find out what headers we need to retrieve.
681   (when articles
682     (while (and articles
683                 (assq (car articles) gnus-agent-article-alist))
684       (pop articles))
685     (let ((arts articles))
686       (while (cdr arts)
687         (if (assq (cadr arts) gnus-agent-article-alist)
688             (setcdr arts (cddr arts))
689           (setq arts (cdr arts)))))
690     ;; Fetch them.
691     (when articles
692       (gnus-message 7 "Fetching headers for %s..." group)
693       (save-excursion
694         (set-buffer nntp-server-buffer)
695         (unless (eq 'nov (gnus-retrieve-headers articles group))
696           (nnvirtual-convert-headers))
697         ;; Save these headers for later processing.
698         (copy-to-buffer gnus-agent-overview-buffer (point-min) (point-max))
699         (let (file)
700           (when (file-exists-p
701                  (setq file (gnus-agent-article-name ".overview" group)))
702             (gnus-agent-braid-nov group articles file))
703           (gnus-make-directory (nnheader-translate-file-chars
704                                 (file-name-directory file)))
705           (write-region (point-min) (point-max) file nil 'silent)
706           (gnus-agent-save-alist group articles nil))
707         t))))
708
709 (defsubst gnus-agent-copy-nov-line (article)
710   (let (b e)
711     (set-buffer gnus-agent-overview-buffer)
712     (setq b (point))
713     (if (eq article (read (current-buffer)))
714         (setq e (progn (forward-line 1) (point)))
715       (setq e b))
716     (set-buffer nntp-server-buffer)
717     (insert-buffer-substring gnus-agent-overview-buffer b e)))
718
719 (defun gnus-agent-braid-nov (group articles file)
720   (let (beg end)
721     (set-buffer gnus-agent-overview-buffer)
722     (goto-char (point-min))
723     (set-buffer nntp-server-buffer)
724     (erase-buffer)
725     (insert-file-contents file)
726     (goto-char (point-min))
727     (if (or (= (point-min) (point-max))
728             (progn
729               (forward-line -1)
730               (< (read (current-buffer)) (car articles))))
731         ;; We have only headers that are after the older headers,
732         ;; so we just append them.
733         (progn
734           (goto-char (point-max))
735           (insert-buffer-substring gnus-agent-overview-buffer))
736       ;; We do it the hard way.
737       (nnheader-find-nov-line (car articles))
738       (gnus-agent-copy-nov-line (car articles))
739       (pop articles)
740       (while (and articles
741                   (not (eobp)))
742         (while (and (not (eobp))
743                     (< (read (current-buffer)) (car articles)))
744           (forward-line 1))
745         (beginning-of-line)
746         (unless (eobp)
747           (gnus-agent-copy-nov-line (car articles))
748           (setq articles (cdr articles))))
749       (when articles
750         (let (b e)
751           (set-buffer gnus-agent-overview-buffer)
752           (setq b (point)
753                 e (point-max))
754           (set-buffer nntp-server-buffer)
755           (insert-buffer-substring gnus-agent-overview-buffer b e))))))
756
757 (defun gnus-agent-load-alist (group &optional dir)
758   "Load the article-state alist for GROUP."
759   (setq gnus-agent-article-alist
760         (gnus-agent-read-file
761          (if dir
762              (concat dir ".agentview")
763            (gnus-agent-article-name ".agentview" group)))))
764
765 (defun gnus-agent-save-alist (group &optional articles state dir)
766   "Load the article-state alist for GROUP."
767   (nnheader-temp-write (if dir
768                            (concat dir ".agentview")
769                          (gnus-agent-article-name ".agentview" group))
770     (princ (setq gnus-agent-article-alist
771                  (nconc gnus-agent-article-alist
772                         (mapcar (lambda (article) (cons article state))
773                                 articles)))
774            (current-buffer))
775     (insert "\n")))
776
777 (defun gnus-agent-article-name (article group)
778   (concat (gnus-agent-directory) (gnus-agent-group-path group) "/"
779           (if (stringp article) article (string-to-number article))))
780
781 ;;;###autoload
782 (defun gnus-agent-batch-fetch ()
783   "Start Gnus and fetch session."
784   (interactive)
785   (gnus)
786   (gnus-agent-fetch-session)
787   (gnus-group-exit))
788
789 (defun gnus-agent-fetch-session ()
790   "Fetch all articles and headers that are eligible for fetching."
791   (interactive)
792   (unless gnus-agent-covered-methods
793     (error "No servers are covered by the Gnus agent"))
794   (unless gnus-plugged
795     (error "Can't fetch articles while Gnus is unplugged"))
796   (let ((methods gnus-agent-covered-methods)
797         groups group gnus-command-method)
798     (save-excursion
799       (while methods
800         (setq gnus-command-method (car methods)
801               groups (gnus-groups-from-server (pop methods)))
802         (gnus-agent-with-fetch
803           (while (setq group (pop groups))
804             (when (<= (gnus-group-level group) gnus-agent-handle-level)
805               (gnus-agent-fetch-group-1 group gnus-command-method)))))
806       (gnus-message 6 "Finished fetching articles into the Gnus agent"))))
807
808 (defun gnus-agent-fetch-group-1 (group method)
809   "Fetch GROUP."
810   (let ((gnus-command-method method)
811         gnus-newsgroup-dependencies gnus-newsgroup-headers
812         gnus-newsgroup-scored gnus-headers gnus-score
813         gnus-use-cache articles score arts
814         category predicate info marks score-param)
815     ;; Fetch headers.
816     (when (and (setq articles (gnus-list-of-unread-articles group))
817                (gnus-agent-fetch-headers group articles))
818       ;; Parse them and see which articles we want to fetch.
819       (setq gnus-newsgroup-dependencies
820             (make-vector (length articles) 0))
821       (setq gnus-newsgroup-headers
822             (gnus-get-newsgroup-headers-xover articles nil nil group))
823       (setq category (gnus-group-category group))
824       (setq predicate
825             (gnus-get-predicate 
826              (or (gnus-group-get-parameter group 'agent-predicate)
827                  (cadr category))))
828       (setq score-param
829             (or (gnus-group-get-parameter group 'agent-score)
830                 (caddr category)))
831       (when score-param
832         (gnus-score-headers (list (list score-param))))
833       (setq arts nil)
834       (while (setq gnus-headers (pop gnus-newsgroup-headers))
835         (setq gnus-score
836               (or (cdr (assq (mail-header-number gnus-headers)
837                              gnus-newsgroup-scored))
838                   gnus-summary-default-score))
839         (when (funcall predicate)
840           (push (mail-header-number gnus-headers)
841                 arts)))
842       ;; Fetch the articles.
843       (when arts
844         (gnus-agent-fetch-articles group arts)))
845     ;; Perhaps we have some additional articles to fetch.
846     (setq arts (assq 'download (gnus-info-marks
847                                 (setq info (gnus-get-info group)))))
848     (when (cdr arts)
849       (gnus-agent-fetch-articles
850        group (gnus-uncompress-range (cdr arts)))
851       (setq marks (delq arts (gnus-info-marks info)))
852       (gnus-info-set-marks info marks))))
853
854 ;;;
855 ;;; Agent Category Mode
856 ;;;
857
858 (defvar gnus-category-mode-hook nil
859   "Hook run in `gnus-category-mode' buffers.")
860
861 (defvar gnus-category-line-format "     %(%20c%): %g\n"
862   "Format of category lines.")
863
864 (defvar gnus-category-mode-line-format "Gnus: %%b"
865   "The format specification for the category mode line.")
866
867 (defvar gnus-agent-short-article 100
868   "Articles that have fewer lines than this are short.")
869
870 (defvar gnus-agent-long-article 200
871   "Articles that have more lines than this are long.")
872
873 (defvar gnus-agent-low-score 0
874   "Articles that have a score lower than this have a low score.")
875
876 (defvar gnus-agent-high-score 0
877   "Articles that have a score higher than this have a high score.")
878
879
880 ;;; Internal variables.
881
882 (defvar gnus-category-buffer "*Agent Category*")
883
884 (defvar gnus-category-line-format-alist
885   `((?c name ?s)
886     (?g groups ?d)))
887
888 (defvar gnus-category-mode-line-format-alist
889   `((?u user-defined ?s)))
890
891 (defvar gnus-category-line-format-spec nil)
892 (defvar gnus-category-mode-line-format-spec nil)
893
894 (defvar gnus-category-mode-map nil)
895 (put 'gnus-category-mode 'mode-class 'special)
896
897 (unless gnus-category-mode-map
898   (setq gnus-category-mode-map (make-sparse-keymap))
899   (suppress-keymap gnus-category-mode-map)
900
901   (gnus-define-keys gnus-category-mode-map
902     "q" gnus-category-exit
903     "k" gnus-category-kill
904     "c" gnus-category-copy
905     "a" gnus-category-add
906     "p" gnus-category-edit-predicate
907     "g" gnus-category-edit-groups
908     "s" gnus-category-edit-score
909     "l" gnus-category-list
910
911     "\C-c\C-i" gnus-info-find-node
912     "\C-c\C-b" gnus-bug))
913
914 (defvar gnus-category-menu-hook nil
915   "*Hook run after the creation of the menu.")
916
917 (defun gnus-category-make-menu-bar ()
918   (gnus-turn-off-edit-menu 'category)
919   (unless (boundp 'gnus-category-menu)
920     (easy-menu-define
921      gnus-category-menu gnus-category-mode-map ""
922      '("Categories"
923        ["Add" gnus-category-add t]
924        ["Kill" gnus-category-kill t]
925        ["Copy" gnus-category-copy t]
926        ["Edit predicate" gnus-category-edit-predicate t]
927        ["Edit score" gnus-category-edit-score t]
928        ["Edit groups" gnus-category-edit-groups t]
929        ["Exit" gnus-category-exit t]))
930
931     (gnus-run-hooks 'gnus-category-menu-hook)))
932
933 (defun gnus-category-mode ()
934   "Major mode for listing and editing agent categories.
935
936 All normal editing commands are switched off.
937 \\<gnus-category-mode-map>
938 For more in-depth information on this mode, read the manual
939 (`\\[gnus-info-find-node]').
940
941 The following commands are available:
942
943 \\{gnus-category-mode-map}"
944   (interactive)
945   (when (gnus-visual-p 'category-menu 'menu)
946     (gnus-category-make-menu-bar))
947   (kill-all-local-variables)
948   (gnus-simplify-mode-line)
949   (setq major-mode 'gnus-category-mode)
950   (setq mode-name "Category")
951   (gnus-set-default-directory)
952   (setq mode-line-process nil)
953   (use-local-map gnus-category-mode-map)
954   (buffer-disable-undo (current-buffer))
955   (setq truncate-lines t)
956   (setq buffer-read-only t)
957   (gnus-run-hooks 'gnus-category-mode-hook))
958
959 (defalias 'gnus-category-position-point 'gnus-goto-colon)
960
961 (defun gnus-category-insert-line (category)
962   (let* ((name (car category))
963          (groups (length (cadddr category))))
964     (beginning-of-line)
965     (gnus-add-text-properties
966      (point)
967      (prog1 (1+ (point))
968        ;; Insert the text.
969        (eval gnus-category-line-format-spec))
970      (list 'gnus-category name))))
971
972 (defun gnus-enter-category-buffer ()
973   "Go to the Category buffer."
974   (interactive)
975   (gnus-category-setup-buffer)
976   (gnus-configure-windows 'category)
977   (gnus-category-prepare))
978
979 (defun gnus-category-setup-buffer ()
980   (unless (get-buffer gnus-category-buffer)
981     (save-excursion
982       (set-buffer (get-buffer-create gnus-category-buffer))
983       (gnus-add-current-to-buffer-list)
984       (gnus-category-mode))))
985
986 (defun gnus-category-prepare ()
987   (gnus-set-format 'category-mode)
988   (gnus-set-format 'category t)
989   (let ((alist gnus-category-alist)
990         (buffer-read-only nil))
991     (erase-buffer)
992     (while alist
993       (gnus-category-insert-line (pop alist)))
994     (goto-char (point-min))
995     (gnus-category-position-point)))
996
997 (defun gnus-category-name ()
998   (or (get-text-property (gnus-point-at-bol) 'gnus-category)
999       (error "No category on the current line")))
1000
1001 (defun gnus-category-read ()
1002   "Read the category alist."
1003   (setq gnus-category-alist
1004         (or (gnus-agent-read-file
1005              (nnheader-concat gnus-agent-directory "lib/categories"))
1006             (list (list 'default 'short nil nil)))))
1007     
1008 (defun gnus-category-write ()
1009   "Write the category alist."
1010   (setq gnus-category-predicate-cache nil
1011         gnus-category-group-cache nil)
1012   (nnheader-temp-write (nnheader-concat gnus-agent-directory "lib/categories")
1013     (prin1 gnus-category-alist (current-buffer))))
1014
1015 (defun gnus-category-edit-predicate (category)
1016   "Edit the predicate for CATEGORY."
1017   (interactive (list (gnus-category-name)))
1018   (let ((info (assq category gnus-category-alist)))
1019     (gnus-edit-form
1020      (cadr info) (format "Editing the predicate for category %s" category)
1021      `(lambda (predicate)
1022         (setf (cadr (assq ',category gnus-category-alist)) predicate)
1023         (gnus-category-write)
1024         (gnus-category-list)))))
1025   
1026 (defun gnus-category-edit-score (category)
1027   "Edit the score expression for CATEGORY."
1028   (interactive (list (gnus-category-name)))
1029   (let ((info (assq category gnus-category-alist)))
1030     (gnus-edit-form
1031      (caddr info)
1032      (format "Editing the score expression for category %s" category)
1033      `(lambda (groups)
1034         (setf (caddr (assq ',category gnus-category-alist)) groups)
1035         (gnus-category-write)
1036         (gnus-category-list)))))
1037
1038 (defun gnus-category-edit-groups (category)
1039   "Edit the group list for CATEGORY."
1040   (interactive (list (gnus-category-name)))
1041   (let ((info (assq category gnus-category-alist)))
1042     (gnus-edit-form
1043      (cadddr info) (format "Editing the group list for category %s" category)
1044      `(lambda (groups)
1045         (setf (cadddr (assq ',category gnus-category-alist)) groups)
1046         (gnus-category-write)
1047         (gnus-category-list)))))
1048
1049 (defun gnus-category-kill (category)
1050   "Kill the current category."
1051   (interactive (list (gnus-category-name)))
1052   (let ((info (assq category gnus-category-alist))
1053         (buffer-read-only nil))
1054     (gnus-delete-line)
1055     (gnus-category-write)
1056     (setq gnus-category-alist (delq info gnus-category-alist))))
1057
1058 (defun gnus-category-copy (category to)
1059   "Copy the current category."
1060   (interactive (list (gnus-category-name) (intern (read-string "New name: "))))
1061   (let ((info (assq category gnus-category-alist)))
1062     (push (list to (gnus-copy-sequence (cadr info))
1063                 (gnus-copy-sequence (caddr info)) nil)
1064           gnus-category-alist)
1065     (gnus-category-write)
1066     (gnus-category-list)))
1067
1068 (defun gnus-category-add (category)
1069   "Create a new category."
1070   (interactive "SCategory name: ")
1071   (when (assq category gnus-category-alist)
1072     (error "Category %s already exists" category))
1073   (push (list category 'true nil nil)
1074         gnus-category-alist)
1075   (gnus-category-write)
1076   (gnus-category-list))
1077
1078 (defun gnus-category-list ()
1079   "List all categories."
1080   (interactive)
1081   (gnus-category-prepare))
1082
1083 (defun gnus-category-exit ()
1084   "Return to the group buffer."
1085   (interactive)
1086   (kill-buffer (current-buffer))
1087   (gnus-configure-windows 'group t))
1088
1089 ;; To avoid having 8-bit characters in the source file.
1090 (defvar gnus-category-not (list '! 'not (intern (format "%c" 172))))
1091
1092 (defvar gnus-category-predicate-alist
1093   '((spam . gnus-agent-spam-p)
1094     (short . gnus-agent-short-p)
1095     (long . gnus-agent-long-p)
1096     (low . gnus-agent-low-scored-p)
1097     (high . gnus-agent-high-scored-p)
1098     (true . gnus-agent-true)
1099     (false . gnus-agent-false))
1100   "Mapping from short score predicate symbols to predicate functions.")
1101
1102 (defun gnus-agent-spam-p ()
1103   "Say whether an article is spam or not."
1104   (unless gnus-agent-spam-hashtb
1105     (setq gnus-agent-spam-hashtb (gnus-make-hashtable 1000)))
1106   (if (not (equal (mail-header-references gnus-headers) ""))
1107       nil
1108     (let ((string (gnus-simplify-subject (mail-header-subject gnus-headers))))
1109       (prog1
1110           (gnus-gethash string gnus-agent-spam-hashtb)
1111         (gnus-sethash string t gnus-agent-spam-hashtb)))))
1112
1113 (defun gnus-agent-short-p ()
1114   "Say whether an article is short or not."
1115   (< (mail-header-lines gnus-headers) gnus-agent-short-article))
1116
1117 (defun gnus-agent-long-p ()
1118   "Say whether an article is long or not."
1119   (> (mail-header-lines gnus-headers) gnus-agent-long-article))
1120
1121 (defun gnus-agent-low-scored-p ()
1122   "Say whether an article has a low score or not."
1123   (< gnus-score gnus-agent-low-score))
1124
1125 (defun gnus-agent-high-scored-p ()
1126   "Say whether an article has a high score or not."
1127   (> gnus-score gnus-agent-low-score))
1128
1129 (defun gnus-category-make-function (cat)
1130   "Make a function from category CAT."
1131   `(lambda () ,(gnus-category-make-function-1 cat)))
1132
1133 (defun gnus-agent-true ()
1134   "Return t."
1135   t)
1136
1137 (defun gnus-agent-false ()
1138   "Return nil."
1139   nil)
1140   
1141 (defun gnus-category-make-function-1 (cat)
1142   "Make a function from category CAT."
1143   (cond
1144    ;; Functions are just returned as is.
1145    ((or (symbolp cat)
1146         (gnus-functionp cat))
1147     `(,(or (cdr (assq cat gnus-category-predicate-alist))
1148            cat)))
1149    ;; More complex category.
1150    ((consp cat)
1151     `(,(cond
1152         ((memq (car cat) '(& and))
1153          'and)
1154         ((memq (car cat) '(| or))
1155          'or)
1156         ((memq (car cat) gnus-category-not)
1157          'not))
1158       ,@(mapcar 'gnus-category-make-function-1 (cdr cat))))
1159    (t
1160     (error "Unknown category type: %s" cat))))
1161
1162 (defun gnus-get-predicate (predicate)
1163   "Return the predicate for CATEGORY."
1164   (or (cdr (assoc predicate gnus-category-predicate-cache))
1165       (cdar (push (cons predicate
1166                         (gnus-category-make-function predicate))
1167                   gnus-category-predicate-cache))))
1168
1169 (defun gnus-group-category (group)
1170   "Return the category GROUP belongs to."
1171   (unless gnus-category-group-cache
1172     (setq gnus-category-group-cache (gnus-make-hashtable 1000))
1173     (let ((cs gnus-category-alist)
1174           groups cat)
1175       (while (setq cat (pop cs))
1176         (setq groups (cadddr cat))
1177         (while groups
1178           (gnus-sethash (pop groups) cat gnus-category-group-cache)))))
1179   (or (gnus-gethash group gnus-category-group-cache)
1180       (assq 'default gnus-category-alist)))
1181
1182 (defun gnus-agent-expire ()
1183   "Expire all old articles."
1184   (interactive)
1185   (let ((methods gnus-agent-covered-methods)
1186         (alist (cdr gnus-newsrc-alist))
1187         gnus-command-method ofiles info method file group)
1188     (while (setq gnus-command-method (pop methods))
1189       (setq ofiles (nconc ofiles (gnus-agent-expire-directory
1190                                   (gnus-agent-directory)))))
1191     (while (setq info (pop alist))
1192       (when (and (gnus-agent-method-p
1193                   (setq gnus-command-method
1194                         (gnus-find-method-for-group
1195                          (setq group (gnus-info-group info)))))
1196                  (member
1197                   (setq file
1198                         (concat
1199                          (gnus-agent-directory)
1200                          (gnus-agent-group-path group) "/.overview"))
1201                   ofiles))
1202         (setq ofiles (delete file ofiles))
1203         (gnus-agent-expire-group file group)))
1204     (while ofiles
1205       (gnus-agent-expire-group (pop ofiles)))))
1206
1207 (defun gnus-agent-expire-directory (dir)
1208   "Expire all groups in DIR recursively."
1209   (when (file-directory-p dir)
1210     (let ((files (directory-files dir t))
1211           file ofiles)
1212       (while (setq file (pop files))
1213         (cond
1214          ((member (file-name-nondirectory file) '("." ".."))
1215           ;; Do nothing.
1216           )
1217          ((file-directory-p file)
1218           ;; Recurse.
1219           (setq ofiles (nconc ofiles (gnus-agent-expire-directory file))))
1220          ((string-match "\\.overview$" file)
1221           ;; Expire group.
1222           (push file ofiles))))
1223       ofiles)))
1224
1225 (defun gnus-agent-expire-group (overview &optional group)
1226   "Expire articles in OVERVIEW."
1227   (gnus-message 5 "Expiring %s..." overview)
1228   (let ((odate (- (gnus-time-to-day (current-time)) 4))
1229         (dir (file-name-directory overview))
1230         (info (when group (gnus-get-info group)))
1231         headers article file point unreads)
1232     (gnus-agent-load-alist nil dir)
1233     (when info
1234       (setq unreads
1235             (nconc
1236              (gnus-list-of-unread-articles group)
1237              (gnus-uncompress-range
1238               (cdr (assq 'tick (gnus-info-marks info))))
1239              (gnus-uncompress-range
1240               (cdr (assq 'dormant (gnus-info-marks info)))))))
1241     (nnheader-temp-write overview
1242       (insert-file-contents overview)
1243       (goto-char (point-min))
1244       (while (not (eobp))
1245         (setq point (point))
1246         (condition-case ()
1247             (setq headers (inline (nnheader-parse-nov)))
1248           (error
1249            (goto-char point)
1250            (gnus-delete-line)
1251            (setq headers nil)))
1252         (when headers
1253           (unless (memq (setq article (mail-header-number headers)) unreads)
1254             (if (not (< (inline
1255                           (gnus-time-to-day
1256                            (inline (nnmail-date-to-time
1257                                     (mail-header-date headers)))))
1258                         odate))
1259                 (forward-line 1)              
1260               (gnus-delete-line)
1261               (setq gnus-agent-article-alist
1262                     (delq (assq article gnus-agent-article-alist)
1263                           gnus-agent-article-alist))
1264               (when (file-exists-p
1265                      (setq file (concat dir (number-to-string article))))
1266                 (delete-file file))))))
1267       (gnus-agent-save-alist nil nil nil dir))))
1268
1269 ;;;###autoload
1270 (defun gnus-agent-batch ()
1271   (interactive)
1272   (let ((init-file-user "")
1273         (gnus-always-read-dribble-file t))
1274     (gnus))
1275   (gnus-group-send-drafts)
1276   (gnus-agent-fetch-session))
1277
1278 (provide 'gnus-agent)
1279
1280 ;;; gnus-agent.el ends here