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