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