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