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