Mercurial > emacs
comparison lisp/gnus/gnus-agent.el @ 88155:d7ddb3e565de
sync with trunk
author | Henrik Enberg <henrik.enberg@telia.com> |
---|---|
date | Mon, 16 Jan 2006 00:03:54 +0000 |
parents | 0d8b17d428b5 |
children |
comparison
equal
deleted
inserted
replaced
88154:8ce476d3ba36 | 88155:d7ddb3e565de |
---|---|
1 ;;; gnus-agent.el --- unplugged support for Gnus | 1 ;;; gnus-agent.el --- unplugged support for Gnus |
2 ;; Copyright (C) 1997, 1998, 1999, 2000 Free Software Foundation, Inc. | 2 |
3 ;; Copyright (C) 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, | |
4 ;; 2005 Free Software Foundation, Inc. | |
3 | 5 |
4 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org> | 6 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org> |
5 ;; This file is part of GNU Emacs. | 7 ;; This file is part of GNU Emacs. |
6 | 8 |
7 ;; GNU Emacs is free software; you can redistribute it and/or modify | 9 ;; GNU Emacs is free software; you can redistribute it and/or modify |
14 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
15 ;; GNU General Public License for more details. | 17 ;; GNU General Public License for more details. |
16 | 18 |
17 ;; You should have received a copy of the GNU General Public License | 19 ;; 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 | 20 ;; along with GNU Emacs; see the file COPYING. If not, write to the |
19 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, | 21 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
20 ;; Boston, MA 02111-1307, USA. | 22 ;; Boston, MA 02110-1301, USA. |
21 | 23 |
22 ;;; Commentary: | 24 ;;; Commentary: |
23 | 25 |
24 ;;; Code: | 26 ;;; Code: |
25 | 27 |
26 (require 'gnus) | 28 (require 'gnus) |
27 (require 'gnus-cache) | 29 (require 'gnus-cache) |
30 (require 'nnmail) | |
28 (require 'nnvirtual) | 31 (require 'nnvirtual) |
29 (require 'gnus-sum) | 32 (require 'gnus-sum) |
30 (require 'gnus-score) | 33 (require 'gnus-score) |
34 (require 'gnus-srvr) | |
35 (require 'gnus-util) | |
31 (eval-when-compile | 36 (eval-when-compile |
32 (if (featurep 'xemacs) | 37 (if (featurep 'xemacs) |
33 (require 'itimer) | 38 (require 'itimer) |
34 (require 'timer)) | 39 (require 'timer)) |
35 (require 'cl)) | 40 (require 'cl)) |
36 | 41 |
42 (eval-and-compile | |
43 (autoload 'gnus-server-update-server "gnus-srvr") | |
44 (autoload 'gnus-agent-customize-category "gnus-cus") | |
45 ) | |
46 | |
37 (defcustom gnus-agent-directory (nnheader-concat gnus-directory "agent/") | 47 (defcustom gnus-agent-directory (nnheader-concat gnus-directory "agent/") |
38 "Where the Gnus agent will store its files." | 48 "Where the Gnus agent will store its files." |
39 :group 'gnus-agent | 49 :group 'gnus-agent |
40 :type 'directory) | 50 :type 'directory) |
41 | 51 |
47 (defcustom gnus-agent-unplugged-hook nil | 57 (defcustom gnus-agent-unplugged-hook nil |
48 "Hook run when unplugging from the network." | 58 "Hook run when unplugging from the network." |
49 :group 'gnus-agent | 59 :group 'gnus-agent |
50 :type 'hook) | 60 :type 'hook) |
51 | 61 |
62 (defcustom gnus-agent-fetched-hook nil | |
63 "Hook run when finished fetching articles." | |
64 :version "22.1" | |
65 :group 'gnus-agent | |
66 :type 'hook) | |
67 | |
52 (defcustom gnus-agent-handle-level gnus-level-subscribed | 68 (defcustom gnus-agent-handle-level gnus-level-subscribed |
53 "Groups on levels higher than this variable will be ignored by the Agent." | 69 "Groups on levels higher than this variable will be ignored by the Agent." |
54 :group 'gnus-agent | 70 :group 'gnus-agent |
55 :type 'integer) | 71 :type 'integer) |
56 | 72 |
57 (defcustom gnus-agent-expire-days 7 | 73 (defcustom gnus-agent-expire-days 7 |
58 "Read articles older than this will be expired." | 74 "Read articles older than this will be expired. |
75 If you wish to disable Agent expiring, see `gnus-agent-enable-expiration'." | |
59 :group 'gnus-agent | 76 :group 'gnus-agent |
60 :type 'integer) | 77 :type '(number :tag "days")) |
61 | 78 |
62 (defcustom gnus-agent-expire-all nil | 79 (defcustom gnus-agent-expire-all nil |
63 "If non-nil, also expire unread, ticked and dormant articles. | 80 "If non-nil, also expire unread, ticked and dormant articles. |
64 If nil, only read articles will be expired." | 81 If nil, only read articles will be expired." |
65 :group 'gnus-agent | 82 :group 'gnus-agent |
68 (defcustom gnus-agent-group-mode-hook nil | 85 (defcustom gnus-agent-group-mode-hook nil |
69 "Hook run in Agent group minor modes." | 86 "Hook run in Agent group minor modes." |
70 :group 'gnus-agent | 87 :group 'gnus-agent |
71 :type 'hook) | 88 :type 'hook) |
72 | 89 |
90 ;; Extracted from gnus-xmas-redefine in order to preserve user settings | |
91 (when (featurep 'xemacs) | |
92 (add-hook 'gnus-agent-group-mode-hook 'gnus-xmas-agent-group-menu-add)) | |
93 | |
73 (defcustom gnus-agent-summary-mode-hook nil | 94 (defcustom gnus-agent-summary-mode-hook nil |
74 "Hook run in Agent summary minor modes." | 95 "Hook run in Agent summary minor modes." |
75 :group 'gnus-agent | 96 :group 'gnus-agent |
76 :type 'hook) | 97 :type 'hook) |
77 | 98 |
99 ;; Extracted from gnus-xmas-redefine in order to preserve user settings | |
100 (when (featurep 'xemacs) | |
101 (add-hook 'gnus-agent-summary-mode-hook 'gnus-xmas-agent-summary-menu-add)) | |
102 | |
78 (defcustom gnus-agent-server-mode-hook nil | 103 (defcustom gnus-agent-server-mode-hook nil |
79 "Hook run in Agent summary minor modes." | 104 "Hook run in Agent summary minor modes." |
80 :group 'gnus-agent | 105 :group 'gnus-agent |
81 :type 'hook) | 106 :type 'hook) |
107 | |
108 ;; Extracted from gnus-xmas-redefine in order to preserve user settings | |
109 (when (featurep 'xemacs) | |
110 (add-hook 'gnus-agent-server-mode-hook 'gnus-xmas-agent-server-menu-add)) | |
82 | 111 |
83 (defcustom gnus-agent-confirmation-function 'y-or-n-p | 112 (defcustom gnus-agent-confirmation-function 'y-or-n-p |
84 "Function to confirm when error happens." | 113 "Function to confirm when error happens." |
85 :version "21.1" | 114 :version "21.1" |
86 :group 'gnus-agent | 115 :group 'gnus-agent |
87 :type 'function) | 116 :type 'function) |
88 | 117 |
89 (defcustom gnus-agent-synchronize-flags 'ask | 118 (defcustom gnus-agent-synchronize-flags t |
90 "Indicate if flags are synchronized when you plug in. | 119 "Indicate if flags are synchronized when you plug in. |
91 If this is `ask' the hook will query the user." | 120 If this is `ask' the hook will query the user." |
121 ;; If the default switches to something else than nil, then the function | |
122 ;; should be fixed not be exceedingly slow. See 2005-09-20 ChangeLog entry. | |
92 :version "21.1" | 123 :version "21.1" |
93 :type '(choice (const :tag "Always" t) | 124 :type '(choice (const :tag "Always" t) |
94 (const :tag "Never" nil) | 125 (const :tag "Never" nil) |
95 (const :tag "Ask" ask)) | 126 (const :tag "Ask" ask)) |
96 :group 'gnus-agent) | 127 :group 'gnus-agent) |
97 | 128 |
129 (defcustom gnus-agent-go-online 'ask | |
130 "Indicate if offline servers go online when you plug in. | |
131 If this is `ask' the hook will query the user." | |
132 :version "21.3" | |
133 :type '(choice (const :tag "Always" t) | |
134 (const :tag "Never" nil) | |
135 (const :tag "Ask" ask)) | |
136 :group 'gnus-agent) | |
137 | |
138 (defcustom gnus-agent-mark-unread-after-downloaded t | |
139 "Indicate whether to mark articles unread after downloaded." | |
140 :version "21.1" | |
141 :type 'boolean | |
142 :group 'gnus-agent) | |
143 | |
144 (defcustom gnus-agent-download-marks '(download) | |
145 "Marks for downloading." | |
146 :version "21.1" | |
147 :type '(repeat (symbol :tag "Mark")) | |
148 :group 'gnus-agent) | |
149 | |
150 (defcustom gnus-agent-consider-all-articles nil | |
151 "When non-nil, the agent will let the agent predicate decide | |
152 whether articles need to be downloaded or not, for all articles. When | |
153 nil, the default, the agent will only let the predicate decide | |
154 whether unread articles are downloaded or not. If you enable this, | |
155 groups with large active ranges may open slower and you may also want | |
156 to look into the agent expiry settings to block the expiration of | |
157 read articles as they would just be downloaded again." | |
158 :version "22.1" | |
159 :type 'boolean | |
160 :group 'gnus-agent) | |
161 | |
162 (defcustom gnus-agent-max-fetch-size 10000000 ;; 10 Mb | |
163 "Chunk size for `gnus-agent-fetch-session'. | |
164 The function will split its article fetches into chunks smaller than | |
165 this limit." | |
166 :version "22.1" | |
167 :group 'gnus-agent | |
168 :type 'integer) | |
169 | |
170 (defcustom gnus-agent-enable-expiration 'ENABLE | |
171 "The default expiration state for each group. | |
172 When set to ENABLE, the default, `gnus-agent-expire' will expire old | |
173 contents from a group's local storage. This value may be overridden | |
174 to disable expiration in specific categories, topics, and groups. Of | |
175 course, you could change gnus-agent-enable-expiration to DISABLE then | |
176 enable expiration per categories, topics, and groups." | |
177 :version "22.1" | |
178 :group 'gnus-agent | |
179 :type '(radio (const :format "Enable " ENABLE) | |
180 (const :format "Disable " DISABLE))) | |
181 | |
182 (defcustom gnus-agent-expire-unagentized-dirs t | |
183 "*Whether expiration should expire in unagentized directories. | |
184 Have gnus-agent-expire scan the directories under | |
185 \(gnus-agent-directory) for groups that are no longer agentized. | |
186 When found, offer to remove them." | |
187 :version "22.1" | |
188 :type 'boolean | |
189 :group 'gnus-agent) | |
190 | |
191 (defcustom gnus-agent-auto-agentize-methods '(nntp nnimap) | |
192 "Initially, all servers from these methods are agentized. | |
193 The user may remove or add servers using the Server buffer. | |
194 See Info node `(gnus)Server Buffer'." | |
195 :version "22.1" | |
196 :type '(repeat symbol) | |
197 :group 'gnus-agent) | |
198 | |
199 (defcustom gnus-agent-queue-mail t | |
200 "Whether and when outgoing mail should be queued by the agent. | |
201 When `always', always queue outgoing mail. When nil, never | |
202 queue. Otherwise, queue if and only if unplugged." | |
203 :version "22.1" | |
204 :group 'gnus-agent | |
205 :type '(radio (const :format "Always" always) | |
206 (const :format "Never" nil) | |
207 (const :format "When plugged" t))) | |
208 | |
209 (defcustom gnus-agent-prompt-send-queue nil | |
210 "If non-nil, `gnus-group-send-queue' will prompt if called when | |
211 unplugged." | |
212 :version "22.1" | |
213 :group 'gnus-agent | |
214 :type 'boolean) | |
215 | |
216 (defcustom gnus-agent-article-alist-save-format 1 | |
217 "Indicates whether to use compression(2), versus no | |
218 compression(1), when writing agentview files. The compressed | |
219 files do save space but load times are 6-7 times higher. A group | |
220 must be opened then closed for the agentview to be updated using | |
221 the new format." | |
222 ;; Wouldn't symbols instead numbers be nicer? --rsteib | |
223 :version "22.1" | |
224 :group 'gnus-agent | |
225 :type '(radio (const :format "Compressed" 2) | |
226 (const :format "Uncompressed" 1))) | |
227 | |
98 ;;; Internal variables | 228 ;;; Internal variables |
99 | 229 |
100 (defvar gnus-agent-history-buffers nil) | 230 (defvar gnus-agent-history-buffers nil) |
101 (defvar gnus-agent-buffer-alist nil) | 231 (defvar gnus-agent-buffer-alist nil) |
102 (defvar gnus-agent-article-alist nil) | 232 (defvar gnus-agent-article-alist nil |
233 "An assoc list identifying the articles whose headers have been fetched. | |
234 If successfully fetched, these headers will be stored in the group's overview | |
235 file. The key of each assoc pair is the article ID, the value of each assoc | |
236 pair is a flag indicating whether the identified article has been downloaded | |
237 \(gnus-agent-fetch-articles sets the value to the day of the download). | |
238 NOTES: | |
239 1) The last element of this list can not be expired as some | |
240 routines (for example, get-agent-fetch-headers) use the last | |
241 value to track which articles have had their headers retrieved. | |
242 2) The function `gnus-agent-regenerate' may destructively modify the value.") | |
103 (defvar gnus-agent-group-alist nil) | 243 (defvar gnus-agent-group-alist nil) |
104 (defvar gnus-agent-covered-methods nil) | |
105 (defvar gnus-category-alist nil) | 244 (defvar gnus-category-alist nil) |
106 (defvar gnus-agent-current-history nil) | 245 (defvar gnus-agent-current-history nil) |
107 (defvar gnus-agent-overview-buffer nil) | 246 (defvar gnus-agent-overview-buffer nil) |
108 (defvar gnus-category-predicate-cache nil) | 247 (defvar gnus-category-predicate-cache nil) |
109 (defvar gnus-category-group-cache nil) | 248 (defvar gnus-category-group-cache nil) |
110 (defvar gnus-agent-spam-hashtb nil) | 249 (defvar gnus-agent-spam-hashtb nil) |
111 (defvar gnus-agent-file-name nil) | 250 (defvar gnus-agent-file-name nil) |
112 (defvar gnus-agent-send-mail-function nil) | 251 (defvar gnus-agent-send-mail-function nil) |
113 (defvar gnus-agent-file-coding-system 'raw-text) | 252 (defvar gnus-agent-file-coding-system 'raw-text) |
253 (defvar gnus-agent-file-loading-cache nil) | |
114 | 254 |
115 ;; Dynamic variables | 255 ;; Dynamic variables |
116 (defvar gnus-headers) | 256 (defvar gnus-headers) |
117 (defvar gnus-score) | 257 (defvar gnus-score) |
118 | 258 |
139 nil)) | 279 nil)) |
140 | 280 |
141 (gnus-add-shutdown 'gnus-close-agent 'gnus) | 281 (gnus-add-shutdown 'gnus-close-agent 'gnus) |
142 | 282 |
143 (defun gnus-close-agent () | 283 (defun gnus-close-agent () |
144 (setq gnus-agent-covered-methods nil | 284 (setq gnus-category-predicate-cache nil |
145 gnus-category-predicate-cache nil | |
146 gnus-category-group-cache nil | 285 gnus-category-group-cache nil |
147 gnus-agent-spam-hashtb nil) | 286 gnus-agent-spam-hashtb nil) |
148 (gnus-kill-buffer gnus-agent-overview-buffer)) | 287 (gnus-kill-buffer gnus-agent-overview-buffer)) |
149 | 288 |
150 ;;; | 289 ;;; |
174 "The full name of the Gnus agent library FILE." | 313 "The full name of the Gnus agent library FILE." |
175 (expand-file-name file | 314 (expand-file-name file |
176 (file-name-as-directory | 315 (file-name-as-directory |
177 (expand-file-name "agent.lib" (gnus-agent-directory))))) | 316 (expand-file-name "agent.lib" (gnus-agent-directory))))) |
178 | 317 |
318 (defun gnus-agent-cat-set-property (category property value) | |
319 (if value | |
320 (setcdr (or (assq property category) | |
321 (let ((cell (cons property nil))) | |
322 (setcdr category (cons cell (cdr category))) | |
323 cell)) value) | |
324 (let ((category category)) | |
325 (while (cond ((eq property (caadr category)) | |
326 (setcdr category (cddr category)) | |
327 nil) | |
328 (t | |
329 (setq category (cdr category))))))) | |
330 category) | |
331 | |
332 (eval-when-compile | |
333 (defmacro gnus-agent-cat-defaccessor (name prop-name) | |
334 "Define accessor and setter methods for manipulating a list of the form | |
335 \(NAME (PROPERTY1 VALUE1) ... (PROPERTY_N VALUE_N)). | |
336 Given the call (gnus-agent-cat-defaccessor func PROPERTY1), the list may be | |
337 manipulated as follows: | |
338 (func LIST): Returns VALUE1 | |
339 (setf (func LIST) NEW_VALUE1): Replaces VALUE1 with NEW_VALUE1." | |
340 `(progn (defmacro ,name (category) | |
341 (list (quote cdr) (list (quote assq) | |
342 (quote (quote ,prop-name)) category))) | |
343 | |
344 (define-setf-method ,name (category) | |
345 (let* ((--category--temp-- (make-symbol "--category--")) | |
346 (--value--temp-- (make-symbol "--value--"))) | |
347 (list (list --category--temp--) ; temporary-variables | |
348 (list category) ; value-forms | |
349 (list --value--temp--) ; store-variables | |
350 (let* ((category --category--temp--) ; store-form | |
351 (value --value--temp--)) | |
352 (list (quote gnus-agent-cat-set-property) | |
353 category | |
354 (quote (quote ,prop-name)) | |
355 value)) | |
356 (list (quote ,name) --category--temp--) ; access-form | |
357 ))))) | |
358 ) | |
359 | |
360 (defmacro gnus-agent-cat-name (category) | |
361 `(car ,category)) | |
362 | |
363 (gnus-agent-cat-defaccessor | |
364 gnus-agent-cat-days-until-old agent-days-until-old) | |
365 (gnus-agent-cat-defaccessor | |
366 gnus-agent-cat-enable-expiration agent-enable-expiration) | |
367 (gnus-agent-cat-defaccessor | |
368 gnus-agent-cat-groups agent-groups) | |
369 (gnus-agent-cat-defaccessor | |
370 gnus-agent-cat-high-score agent-high-score) | |
371 (gnus-agent-cat-defaccessor | |
372 gnus-agent-cat-length-when-long agent-long-article) | |
373 (gnus-agent-cat-defaccessor | |
374 gnus-agent-cat-length-when-short agent-short-article) | |
375 (gnus-agent-cat-defaccessor | |
376 gnus-agent-cat-low-score agent-low-score) | |
377 (gnus-agent-cat-defaccessor | |
378 gnus-agent-cat-predicate agent-predicate) | |
379 (gnus-agent-cat-defaccessor | |
380 gnus-agent-cat-score-file agent-score) | |
381 (gnus-agent-cat-defaccessor | |
382 gnus-agent-cat-enable-undownloaded-faces agent-enable-undownloaded-faces) | |
383 | |
384 | |
385 ;; This form is equivalent to defsetf except that it calls make-symbol | |
386 ;; whereas defsetf calls gensym (Using gensym creates a run-time | |
387 ;; dependency on the CL library). | |
388 | |
389 (eval-and-compile | |
390 (define-setf-method gnus-agent-cat-groups (category) | |
391 (let* ((--category--temp-- (make-symbol "--category--")) | |
392 (--groups--temp-- (make-symbol "--groups--"))) | |
393 (list (list --category--temp--) | |
394 (list category) | |
395 (list --groups--temp--) | |
396 (let* ((category --category--temp--) | |
397 (groups --groups--temp--)) | |
398 (list (quote gnus-agent-set-cat-groups) category groups)) | |
399 (list (quote gnus-agent-cat-groups) --category--temp--)))) | |
400 ) | |
401 | |
402 (defun gnus-agent-set-cat-groups (category groups) | |
403 (unless (eq groups 'ignore) | |
404 (let ((new-g groups) | |
405 (old-g (gnus-agent-cat-groups category))) | |
406 (cond ((eq new-g old-g) | |
407 ;; gnus-agent-add-group is fiddling with the group | |
408 ;; list. Still, Im done. | |
409 nil | |
410 ) | |
411 ((eq new-g (cdr old-g)) | |
412 ;; gnus-agent-add-group is fiddling with the group list | |
413 (setcdr (or (assq 'agent-groups category) | |
414 (let ((cell (cons 'agent-groups nil))) | |
415 (setcdr category (cons cell (cdr category))) | |
416 cell)) new-g)) | |
417 (t | |
418 (let ((groups groups)) | |
419 (while groups | |
420 (let* ((group (pop groups)) | |
421 (old-category (gnus-group-category group))) | |
422 (if (eq category old-category) | |
423 nil | |
424 (setf (gnus-agent-cat-groups old-category) | |
425 (delete group (gnus-agent-cat-groups | |
426 old-category)))))) | |
427 ;; Purge cache as preceeding loop invalidated it. | |
428 (setq gnus-category-group-cache nil)) | |
429 | |
430 (setcdr (or (assq 'agent-groups category) | |
431 (let ((cell (cons 'agent-groups nil))) | |
432 (setcdr category (cons cell (cdr category))) | |
433 cell)) groups)))))) | |
434 | |
435 (defsubst gnus-agent-cat-make (name &optional default-agent-predicate) | |
436 (list name `(agent-predicate . ,(or default-agent-predicate 'false)))) | |
437 | |
179 ;;; Fetching setup functions. | 438 ;;; Fetching setup functions. |
180 | 439 |
181 (defun gnus-agent-start-fetch () | 440 (defun gnus-agent-start-fetch () |
182 "Initialize data structures for efficient fetching." | 441 "Initialize data structures for efficient fetching." |
183 (gnus-agent-open-history) | |
184 (setq gnus-agent-current-history (gnus-agent-history-buffer)) | |
185 (gnus-agent-create-buffer)) | 442 (gnus-agent-create-buffer)) |
186 | 443 |
187 (defun gnus-agent-stop-fetch () | 444 (defun gnus-agent-stop-fetch () |
188 "Save all data structures and clean up." | 445 "Save all data structures and clean up." |
189 (gnus-agent-save-history) | |
190 (gnus-agent-close-history) | |
191 (setq gnus-agent-spam-hashtb nil) | 446 (setq gnus-agent-spam-hashtb nil) |
192 (save-excursion | 447 (save-excursion |
193 (set-buffer nntp-server-buffer) | 448 (set-buffer nntp-server-buffer) |
194 (widen))) | 449 (widen))) |
195 | 450 |
201 ,@forms) | 456 ,@forms) |
202 (gnus-agent-stop-fetch))) | 457 (gnus-agent-stop-fetch))) |
203 | 458 |
204 (put 'gnus-agent-with-fetch 'lisp-indent-function 0) | 459 (put 'gnus-agent-with-fetch 'lisp-indent-function 0) |
205 (put 'gnus-agent-with-fetch 'edebug-form-spec '(body)) | 460 (put 'gnus-agent-with-fetch 'edebug-form-spec '(body)) |
461 | |
462 (defmacro gnus-agent-append-to-list (tail value) | |
463 `(setq ,tail (setcdr ,tail (cons ,value nil)))) | |
464 | |
465 (defmacro gnus-agent-message (level &rest args) | |
466 `(if (<= ,level gnus-verbose) | |
467 (message ,@args))) | |
206 | 468 |
207 ;;; | 469 ;;; |
208 ;;; Mode infestation | 470 ;;; Mode infestation |
209 ;;; | 471 ;;; |
210 | 472 |
231 (unless (assq mode minor-mode-map-alist) | 493 (unless (assq mode minor-mode-map-alist) |
232 (push (cons mode (symbol-value (intern (format "gnus-agent-%s-mode-map" | 494 (push (cons mode (symbol-value (intern (format "gnus-agent-%s-mode-map" |
233 buffer)))) | 495 buffer)))) |
234 minor-mode-map-alist)) | 496 minor-mode-map-alist)) |
235 (when (eq major-mode 'gnus-group-mode) | 497 (when (eq major-mode 'gnus-group-mode) |
236 (gnus-agent-toggle-plugged gnus-plugged)) | 498 (let ((init-plugged gnus-plugged) |
499 (gnus-agent-go-online nil)) | |
500 ;; g-a-t-p does nothing when gnus-plugged isn't changed. | |
501 ;; Therefore, make certain that the current value does not | |
502 ;; match the desired initial value. | |
503 (setq gnus-plugged :unknown) | |
504 (gnus-agent-toggle-plugged init-plugged))) | |
237 (gnus-run-hooks 'gnus-agent-mode-hook | 505 (gnus-run-hooks 'gnus-agent-mode-hook |
238 (intern (format "gnus-agent-%s-mode-hook" buffer))))) | 506 (intern (format "gnus-agent-%s-mode-hook" buffer))))) |
239 | 507 |
240 (defvar gnus-agent-group-mode-map (make-sparse-keymap)) | 508 (defvar gnus-agent-group-mode-map (make-sparse-keymap)) |
241 (gnus-define-keys gnus-agent-group-mode-map | 509 (gnus-define-keys gnus-agent-group-mode-map |
242 "Ju" gnus-agent-fetch-groups | 510 "Ju" gnus-agent-fetch-groups |
243 "Jc" gnus-enter-category-buffer | 511 "Jc" gnus-enter-category-buffer |
244 "Jj" gnus-agent-toggle-plugged | 512 "Jj" gnus-agent-toggle-plugged |
245 "Js" gnus-agent-fetch-session | 513 "Js" gnus-agent-fetch-session |
246 "JY" gnus-agent-synchronize-flags | 514 "JY" gnus-agent-synchronize-flags |
247 "JS" gnus-group-send-drafts | 515 "JS" gnus-group-send-queue |
248 "Ja" gnus-agent-add-group | 516 "Ja" gnus-agent-add-group |
249 "Jr" gnus-agent-remove-group) | 517 "Jr" gnus-agent-remove-group |
518 "Jo" gnus-agent-toggle-group-plugged) | |
250 | 519 |
251 (defun gnus-agent-group-make-menu-bar () | 520 (defun gnus-agent-group-make-menu-bar () |
252 (unless (boundp 'gnus-agent-group-menu) | 521 (unless (boundp 'gnus-agent-group-menu) |
253 (easy-menu-define | 522 (easy-menu-define |
254 gnus-agent-group-menu gnus-agent-group-mode-map "" | 523 gnus-agent-group-menu gnus-agent-group-mode-map "" |
255 '("Agent" | 524 '("Agent" |
256 ["Toggle plugged" gnus-agent-toggle-plugged t] | 525 ["Toggle plugged" gnus-agent-toggle-plugged t] |
526 ["Toggle group plugged" gnus-agent-toggle-group-plugged t] | |
257 ["List categories" gnus-enter-category-buffer t] | 527 ["List categories" gnus-enter-category-buffer t] |
258 ["Send drafts" gnus-group-send-drafts gnus-plugged] | 528 ["Add (current) group to category" gnus-agent-add-group t] |
529 ["Remove (current) group from category" gnus-agent-remove-group t] | |
530 ["Send queue" gnus-group-send-queue gnus-plugged] | |
259 ("Fetch" | 531 ("Fetch" |
260 ["All" gnus-agent-fetch-session gnus-plugged] | 532 ["All" gnus-agent-fetch-session gnus-plugged] |
261 ["Group" gnus-agent-fetch-group gnus-plugged]))))) | 533 ["Group" gnus-agent-fetch-group gnus-plugged]) |
534 ["Synchronize flags" gnus-agent-synchronize-flags t] | |
535 )))) | |
262 | 536 |
263 (defvar gnus-agent-summary-mode-map (make-sparse-keymap)) | 537 (defvar gnus-agent-summary-mode-map (make-sparse-keymap)) |
264 (gnus-define-keys gnus-agent-summary-mode-map | 538 (gnus-define-keys gnus-agent-summary-mode-map |
265 "Jj" gnus-agent-toggle-plugged | 539 "Jj" gnus-agent-toggle-plugged |
540 "Ju" gnus-agent-summary-fetch-group | |
541 "JS" gnus-agent-fetch-group | |
542 "Js" gnus-agent-summary-fetch-series | |
266 "J#" gnus-agent-mark-article | 543 "J#" gnus-agent-mark-article |
267 "J\M-#" gnus-agent-unmark-article | 544 "J\M-#" gnus-agent-unmark-article |
268 "@" gnus-agent-toggle-mark | 545 "@" gnus-agent-toggle-mark |
269 "Jc" gnus-agent-catchup) | 546 "Jc" gnus-agent-catchup) |
270 | 547 |
275 '("Agent" | 552 '("Agent" |
276 ["Toggle plugged" gnus-agent-toggle-plugged t] | 553 ["Toggle plugged" gnus-agent-toggle-plugged t] |
277 ["Mark as downloadable" gnus-agent-mark-article t] | 554 ["Mark as downloadable" gnus-agent-mark-article t] |
278 ["Unmark as downloadable" gnus-agent-unmark-article t] | 555 ["Unmark as downloadable" gnus-agent-unmark-article t] |
279 ["Toggle mark" gnus-agent-toggle-mark t] | 556 ["Toggle mark" gnus-agent-toggle-mark t] |
557 ["Fetch downloadable" gnus-agent-summary-fetch-group t] | |
280 ["Catchup undownloaded" gnus-agent-catchup t])))) | 558 ["Catchup undownloaded" gnus-agent-catchup t])))) |
281 | 559 |
282 (defvar gnus-agent-server-mode-map (make-sparse-keymap)) | 560 (defvar gnus-agent-server-mode-map (make-sparse-keymap)) |
283 (gnus-define-keys gnus-agent-server-mode-map | 561 (gnus-define-keys gnus-agent-server-mode-map |
284 "Jj" gnus-agent-toggle-plugged | 562 "Jj" gnus-agent-toggle-plugged |
292 '("Agent" | 570 '("Agent" |
293 ["Toggle plugged" gnus-agent-toggle-plugged t] | 571 ["Toggle plugged" gnus-agent-toggle-plugged t] |
294 ["Add" gnus-agent-add-server t] | 572 ["Add" gnus-agent-add-server t] |
295 ["Remove" gnus-agent-remove-server t])))) | 573 ["Remove" gnus-agent-remove-server t])))) |
296 | 574 |
297 (defun gnus-agent-toggle-plugged (plugged) | 575 (defun gnus-agent-make-mode-line-string (string mouse-button mouse-func) |
576 (if (and (fboundp 'propertize) | |
577 (fboundp 'make-mode-line-mouse-map)) | |
578 (propertize string 'local-map | |
579 (make-mode-line-mouse-map mouse-button mouse-func) | |
580 'mouse-face 'mode-line-highlight) | |
581 string)) | |
582 | |
583 (defun gnus-agent-toggle-plugged (set-to) | |
298 "Toggle whether Gnus is unplugged or not." | 584 "Toggle whether Gnus is unplugged or not." |
299 (interactive (list (not gnus-plugged))) | 585 (interactive (list (not gnus-plugged))) |
300 (if plugged | 586 (cond ((eq set-to gnus-plugged) |
301 (progn | 587 nil) |
302 (setq gnus-plugged plugged) | 588 (set-to |
303 (gnus-agent-possibly-synchronize-flags) | 589 (setq gnus-plugged set-to) |
304 (gnus-run-hooks 'gnus-agent-plugged-hook) | 590 (gnus-run-hooks 'gnus-agent-plugged-hook) |
305 (setcar (cdr gnus-agent-mode-status) " Plugged")) | 591 (setcar (cdr gnus-agent-mode-status) |
306 (gnus-agent-close-connections) | 592 (gnus-agent-make-mode-line-string " Plugged" |
307 (setq gnus-plugged plugged) | 593 'mouse-2 |
308 (gnus-run-hooks 'gnus-agent-unplugged-hook) | 594 'gnus-agent-toggle-plugged)) |
309 (setcar (cdr gnus-agent-mode-status) " Unplugged")) | 595 (gnus-agent-go-online gnus-agent-go-online) |
596 (gnus-agent-possibly-synchronize-flags)) | |
597 (t | |
598 (gnus-agent-close-connections) | |
599 (setq gnus-plugged set-to) | |
600 (gnus-run-hooks 'gnus-agent-unplugged-hook) | |
601 (setcar (cdr gnus-agent-mode-status) | |
602 (gnus-agent-make-mode-line-string " Unplugged" | |
603 'mouse-2 | |
604 'gnus-agent-toggle-plugged)))) | |
310 (set-buffer-modified-p t)) | 605 (set-buffer-modified-p t)) |
606 | |
607 (defmacro gnus-agent-while-plugged (&rest body) | |
608 `(let ((original-gnus-plugged gnus-plugged)) | |
609 (unwind-protect | |
610 (progn (gnus-agent-toggle-plugged t) | |
611 ,@body) | |
612 (gnus-agent-toggle-plugged original-gnus-plugged)))) | |
613 | |
614 (put 'gnus-agent-while-plugged 'lisp-indent-function 0) | |
615 (put 'gnus-agent-while-plugged 'edebug-form-spec '(body)) | |
311 | 616 |
312 (defun gnus-agent-close-connections () | 617 (defun gnus-agent-close-connections () |
313 "Close all methods covered by the Gnus agent." | 618 "Close all methods covered by the Gnus agent." |
314 (let ((methods gnus-agent-covered-methods)) | 619 (let ((methods (gnus-agent-covered-methods))) |
315 (while methods | 620 (while methods |
316 (gnus-close-server (pop methods))))) | 621 (gnus-close-server (pop methods))))) |
317 | 622 |
318 ;;;###autoload | 623 ;;;###autoload |
319 (defun gnus-unplugged () | 624 (defun gnus-unplugged () |
328 (interactive) | 633 (interactive) |
329 (setq gnus-plugged t) | 634 (setq gnus-plugged t) |
330 (gnus)) | 635 (gnus)) |
331 | 636 |
332 ;;;###autoload | 637 ;;;###autoload |
638 (defun gnus-slave-unplugged (&optional arg) | |
639 "Read news as a slave unplugged." | |
640 (interactive "P") | |
641 (setq gnus-plugged nil) | |
642 (gnus arg nil 'slave)) | |
643 | |
644 ;;;###autoload | |
333 (defun gnus-agentize () | 645 (defun gnus-agentize () |
334 "Allow Gnus to be an offline newsreader. | 646 "Allow Gnus to be an offline newsreader. |
335 The normal usage of this command is to put the following as the | 647 |
336 last form in your `.gnus.el' file: | 648 The gnus-agentize function is now called internally by gnus when |
337 | 649 gnus-agent is set. If you wish to avoid calling gnus-agentize, |
338 \(gnus-agentize) | 650 customize gnus-agent to nil. |
339 | 651 |
340 This will modify the `gnus-before-startup-hook', `gnus-post-method', | 652 This will modify the `gnus-setup-news-hook', and |
341 and `message-send-mail-function' variables, and install the Gnus | 653 `message-send-mail-real-function' variables, and install the Gnus agent |
342 agent minor mode in all Gnus buffers." | 654 minor mode in all Gnus buffers." |
343 (interactive) | 655 (interactive) |
344 (gnus-open-agent) | 656 (gnus-open-agent) |
345 (add-hook 'gnus-setup-news-hook 'gnus-agent-queue-setup) | 657 (add-hook 'gnus-setup-news-hook 'gnus-agent-queue-setup) |
346 (unless gnus-agent-send-mail-function | 658 (unless gnus-agent-send-mail-function |
347 (setq gnus-agent-send-mail-function message-send-mail-function | 659 (setq gnus-agent-send-mail-function |
348 message-send-mail-function 'gnus-agent-send-mail)) | 660 (or message-send-mail-real-function |
349 (unless gnus-agent-covered-methods | 661 (function (lambda () (funcall message-send-mail-function)))) |
350 (setq gnus-agent-covered-methods (list gnus-select-method)))) | 662 message-send-mail-real-function 'gnus-agent-send-mail)) |
351 | 663 |
352 (defun gnus-agent-queue-setup () | 664 ;; If the servers file doesn't exist, auto-agentize some servers and |
353 "Make sure the queue group exists." | 665 ;; save the servers file so this auto-agentizing isn't invoked |
354 (unless (gnus-gethash "nndraft:queue" gnus-newsrc-hashtb) | 666 ;; again. |
355 (gnus-request-create-group "queue" '(nndraft "")) | 667 (unless (file-exists-p (nnheader-concat gnus-agent-directory "lib/servers")) |
668 (gnus-message 3 "First time agent user, agentizing remote groups...") | |
669 (mapc | |
670 (lambda (server-or-method) | |
671 (let ((method (gnus-server-to-method server-or-method))) | |
672 (when (memq (car method) | |
673 gnus-agent-auto-agentize-methods) | |
674 (push (gnus-method-to-server method) | |
675 gnus-agent-covered-methods) | |
676 (setq gnus-agent-method-p-cache nil)))) | |
677 (cons gnus-select-method gnus-secondary-select-methods)) | |
678 (gnus-agent-write-servers))) | |
679 | |
680 (defun gnus-agent-queue-setup (&optional group-name) | |
681 "Make sure the queue group exists. | |
682 Optional arg GROUP-NAME allows to specify another group." | |
683 (unless (gnus-gethash (format "nndraft:%s" (or group-name "queue")) | |
684 gnus-newsrc-hashtb) | |
685 (gnus-request-create-group (or group-name "queue") '(nndraft "")) | |
356 (let ((gnus-level-default-subscribed 1)) | 686 (let ((gnus-level-default-subscribed 1)) |
357 (gnus-subscribe-group "nndraft:queue" nil '(nndraft ""))) | 687 (gnus-subscribe-group (format "nndraft:%s" (or group-name "queue")) |
688 nil '(nndraft ""))) | |
358 (gnus-group-set-parameter | 689 (gnus-group-set-parameter |
359 "nndraft:queue" 'gnus-dummy '((gnus-draft-mode))))) | 690 (format "nndraft:%s" (or group-name "queue")) |
691 'gnus-dummy '((gnus-draft-mode))))) | |
360 | 692 |
361 (defun gnus-agent-send-mail () | 693 (defun gnus-agent-send-mail () |
362 (if gnus-plugged | 694 (if (or (not gnus-agent-queue-mail) |
695 (and gnus-plugged (not (eq gnus-agent-queue-mail 'always)))) | |
363 (funcall gnus-agent-send-mail-function) | 696 (funcall gnus-agent-send-mail-function) |
364 (goto-char (point-min)) | 697 (goto-char (point-min)) |
365 (re-search-forward | 698 (re-search-forward |
366 (concat "^" (regexp-quote mail-header-separator) "\n")) | 699 (concat "^" (regexp-quote mail-header-separator) "\n")) |
367 (replace-match "\n") | 700 (replace-match "\n") |
368 (gnus-agent-insert-meta-information 'mail) | 701 (gnus-agent-insert-meta-information 'mail) |
369 (gnus-request-accept-article "nndraft:queue" nil t t))) | 702 (gnus-request-accept-article "nndraft:queue" nil t t))) |
370 | 703 |
371 (defun gnus-agent-insert-meta-information (type &optional method) | 704 (defun gnus-agent-insert-meta-information (type &optional method) |
372 "Insert meta-information into the message that says how it's to be posted. | 705 "Insert meta-information into the message that says how it's to be posted. |
373 TYPE can be either `mail' or `news'. If the latter METHOD can | 706 TYPE can be either `mail' or `news'. If the latter, then METHOD can |
374 be a select method." | 707 be a select method." |
375 (save-excursion | 708 (save-excursion |
376 (message-remove-header gnus-agent-meta-information-header) | 709 (message-remove-header gnus-agent-meta-information-header) |
377 (goto-char (point-min)) | 710 (goto-char (point-min)) |
378 (insert gnus-agent-meta-information-header ": " | 711 (insert gnus-agent-meta-information-header ": " |
384 | 717 |
385 (defun gnus-agent-restore-gcc () | 718 (defun gnus-agent-restore-gcc () |
386 "Restore GCC field from saved header." | 719 "Restore GCC field from saved header." |
387 (save-excursion | 720 (save-excursion |
388 (goto-char (point-min)) | 721 (goto-char (point-min)) |
389 (while (re-search-forward (concat gnus-agent-gcc-header ":") nil t) | 722 (while (re-search-forward |
723 (concat "^" (regexp-quote gnus-agent-gcc-header) ":") nil t) | |
390 (replace-match "Gcc:" 'fixedcase)))) | 724 (replace-match "Gcc:" 'fixedcase)))) |
391 | 725 |
392 (defun gnus-agent-any-covered-gcc () | 726 (defun gnus-agent-any-covered-gcc () |
393 (save-restriction | 727 (save-restriction |
394 (message-narrow-to-headers) | 728 (message-narrow-to-headers) |
398 (message-unquote-tokens | 732 (message-unquote-tokens |
399 (message-tokenize-header | 733 (message-tokenize-header |
400 gcc " ,"))))) | 734 gcc " ,"))))) |
401 covered) | 735 covered) |
402 (while (and (not covered) methods) | 736 (while (and (not covered) methods) |
403 (setq covered | 737 (setq covered (gnus-agent-method-p (car methods)) |
404 (member (car methods) gnus-agent-covered-methods) | |
405 methods (cdr methods))) | 738 methods (cdr methods))) |
406 covered))) | 739 covered))) |
407 | 740 |
741 ;;;###autoload | |
408 (defun gnus-agent-possibly-save-gcc () | 742 (defun gnus-agent-possibly-save-gcc () |
409 "Save GCC if Gnus is unplugged." | 743 "Save GCC if Gnus is unplugged." |
410 (when (and (not gnus-plugged) (gnus-agent-any-covered-gcc)) | 744 (when (and (not gnus-plugged) (gnus-agent-any-covered-gcc)) |
411 (save-excursion | 745 (save-excursion |
412 (goto-char (point-min)) | 746 (goto-char (point-min)) |
428 (interactive "P") | 762 (interactive "P") |
429 (unless gnus-plugged | 763 (unless gnus-plugged |
430 (error "Groups can't be fetched when Gnus is unplugged")) | 764 (error "Groups can't be fetched when Gnus is unplugged")) |
431 (gnus-group-iterate n 'gnus-agent-fetch-group)) | 765 (gnus-group-iterate n 'gnus-agent-fetch-group)) |
432 | 766 |
433 (defun gnus-agent-fetch-group (group) | 767 (defun gnus-agent-fetch-group (&optional group) |
434 "Put all new articles in GROUP into the Agent." | 768 "Put all new articles in GROUP into the Agent." |
435 (interactive (list (gnus-group-group-name))) | 769 (interactive (list (gnus-group-group-name))) |
436 (unless gnus-plugged | 770 (setq group (or group gnus-newsgroup-name)) |
437 (error "Groups can't be fetched when Gnus is unplugged")) | |
438 (unless group | 771 (unless group |
439 (error "No group on the current line")) | 772 (error "No group on the current line")) |
440 (let ((gnus-command-method (gnus-find-method-for-group group))) | 773 |
441 (gnus-agent-with-fetch | 774 (gnus-agent-while-plugged |
442 (gnus-agent-fetch-group-1 group gnus-command-method) | 775 (let ((gnus-command-method (gnus-find-method-for-group group))) |
443 (gnus-message 5 "Fetching %s...done" group)))) | 776 (gnus-agent-with-fetch |
777 (gnus-agent-fetch-group-1 group gnus-command-method) | |
778 (gnus-message 5 "Fetching %s...done" group))))) | |
444 | 779 |
445 (defun gnus-agent-add-group (category arg) | 780 (defun gnus-agent-add-group (category arg) |
446 "Add the current group to an agent category." | 781 "Add the current group to an agent category." |
447 (interactive | 782 (interactive |
448 (list | 783 (list |
455 current-prefix-arg)) | 790 current-prefix-arg)) |
456 (let ((cat (assq category gnus-category-alist)) | 791 (let ((cat (assq category gnus-category-alist)) |
457 c groups) | 792 c groups) |
458 (gnus-group-iterate arg | 793 (gnus-group-iterate arg |
459 (lambda (group) | 794 (lambda (group) |
460 (when (cadddr (setq c (gnus-group-category group))) | 795 (when (gnus-agent-cat-groups (setq c (gnus-group-category group))) |
461 (setf (cadddr c) (delete group (cadddr c)))) | 796 (setf (gnus-agent-cat-groups c) |
797 (delete group (gnus-agent-cat-groups c)))) | |
462 (push group groups))) | 798 (push group groups))) |
463 (setf (cadddr cat) (nconc (cadddr cat) groups)) | 799 (setf (gnus-agent-cat-groups cat) |
800 (nconc (gnus-agent-cat-groups cat) groups)) | |
464 (gnus-category-write))) | 801 (gnus-category-write))) |
465 | 802 |
466 (defun gnus-agent-remove-group (arg) | 803 (defun gnus-agent-remove-group (arg) |
467 "Remove the current group from its agent category, if any." | 804 "Remove the current group from its agent category, if any." |
468 (interactive "P") | 805 (interactive "P") |
469 (let (c) | 806 (let (c) |
470 (gnus-group-iterate arg | 807 (gnus-group-iterate arg |
471 (lambda (group) | 808 (lambda (group) |
472 (when (cadddr (setq c (gnus-group-category group))) | 809 (when (gnus-agent-cat-groups (setq c (gnus-group-category group))) |
473 (setf (cadddr c) (delete group (cadddr c)))))) | 810 (setf (gnus-agent-cat-groups c) |
811 (delete group (gnus-agent-cat-groups c)))))) | |
474 (gnus-category-write))) | 812 (gnus-category-write))) |
475 | 813 |
476 (defun gnus-agent-synchronize-flags () | 814 (defun gnus-agent-synchronize-flags () |
477 "Synchronize unplugged flags with servers." | 815 "Synchronize unplugged flags with servers." |
478 (interactive) | 816 (interactive) |
479 (save-excursion | 817 (save-excursion |
480 (dolist (gnus-command-method gnus-agent-covered-methods) | 818 (dolist (gnus-command-method (gnus-agent-covered-methods)) |
481 (when (file-exists-p (gnus-agent-lib-file "flags")) | 819 (when (file-exists-p (gnus-agent-lib-file "flags")) |
482 (gnus-agent-synchronize-flags-server gnus-command-method))))) | 820 (gnus-agent-synchronize-flags-server gnus-command-method))))) |
483 | 821 |
484 (defun gnus-agent-possibly-synchronize-flags () | 822 (defun gnus-agent-possibly-synchronize-flags () |
485 "Synchronize flags according to `gnus-agent-synchronize-flags'." | 823 "Synchronize flags according to `gnus-agent-synchronize-flags'." |
486 (interactive) | 824 (interactive) |
487 (save-excursion | 825 (save-excursion |
488 (dolist (gnus-command-method gnus-agent-covered-methods) | 826 (dolist (gnus-command-method (gnus-agent-covered-methods)) |
489 (when (file-exists-p (gnus-agent-lib-file "flags")) | 827 (when (and (file-exists-p (gnus-agent-lib-file "flags")) |
828 (not (eq (gnus-server-status gnus-command-method) 'offline))) | |
490 (gnus-agent-possibly-synchronize-flags-server gnus-command-method))))) | 829 (gnus-agent-possibly-synchronize-flags-server gnus-command-method))))) |
491 | 830 |
492 (defun gnus-agent-synchronize-flags-server (method) | 831 (defun gnus-agent-synchronize-flags-server (method) |
493 "Synchronize flags set when unplugged for server." | 832 "Synchronize flags set when unplugged for server." |
494 (let ((gnus-command-method method)) | 833 (let ((gnus-command-method method) |
834 (gnus-agent nil)) | |
495 (when (file-exists-p (gnus-agent-lib-file "flags")) | 835 (when (file-exists-p (gnus-agent-lib-file "flags")) |
496 (set-buffer (get-buffer-create " *Gnus Agent flag synchronize*")) | 836 (set-buffer (get-buffer-create " *Gnus Agent flag synchronize*")) |
497 (erase-buffer) | 837 (erase-buffer) |
498 (nnheader-insert-file-contents (gnus-agent-lib-file "flags")) | 838 (nnheader-insert-file-contents (gnus-agent-lib-file "flags")) |
499 (if (null (gnus-check-server gnus-command-method)) | 839 (cond ((null gnus-plugged) |
500 (message "Couldn't open server %s" (nth 1 gnus-command-method)) | 840 (gnus-message |
501 (while (not (eobp)) | 841 1 "You must be plugged to synchronize flags with server %s" |
502 (if (null (eval (read (current-buffer)))) | 842 (nth 1 gnus-command-method))) |
503 (progn (forward-line) | 843 ((null (gnus-check-server gnus-command-method)) |
504 (kill-line -1)) | 844 (gnus-message |
505 (write-file (gnus-agent-lib-file "flags")) | 845 1 "Couldn't open server %s" (nth 1 gnus-command-method))) |
506 (error "Couldn't set flags from file %s" | 846 (t |
507 (gnus-agent-lib-file "flags")))) | 847 (condition-case err |
508 (delete-file (gnus-agent-lib-file "flags"))) | 848 (while t |
849 (let ((bgn (point))) | |
850 (eval (read (current-buffer))) | |
851 (delete-region bgn (point)))) | |
852 (end-of-file | |
853 (delete-file (gnus-agent-lib-file "flags"))) | |
854 (error | |
855 (let ((file (gnus-agent-lib-file "flags"))) | |
856 (write-region (point-min) (point-max) | |
857 (gnus-agent-lib-file "flags") nil 'silent) | |
858 (error "Couldn't set flags from file %s due to %s" | |
859 file (error-message-string err))))))) | |
509 (kill-buffer nil)))) | 860 (kill-buffer nil)))) |
510 | 861 |
511 (defun gnus-agent-possibly-synchronize-flags-server (method) | 862 (defun gnus-agent-possibly-synchronize-flags-server (method) |
512 "Synchronize flags for server according to `gnus-agent-synchronize-flags'." | 863 "Synchronize flags for server according to `gnus-agent-synchronize-flags'." |
513 (when (or (and gnus-agent-synchronize-flags | 864 (when (or (and gnus-agent-synchronize-flags |
515 (and (eq gnus-agent-synchronize-flags 'ask) | 866 (and (eq gnus-agent-synchronize-flags 'ask) |
516 (gnus-y-or-n-p (format "Synchronize flags on server `%s'? " | 867 (gnus-y-or-n-p (format "Synchronize flags on server `%s'? " |
517 (cadr method))))) | 868 (cadr method))))) |
518 (gnus-agent-synchronize-flags-server method))) | 869 (gnus-agent-synchronize-flags-server method))) |
519 | 870 |
871 ;;;###autoload | |
872 (defun gnus-agent-rename-group (old-group new-group) | |
873 "Rename fully-qualified OLD-GROUP as NEW-GROUP. | |
874 Always updates the agent, even when disabled, as the old agent | |
875 files would corrupt gnus when the agent was next enabled. | |
876 Depends upon the caller to determine whether group renaming is | |
877 supported." | |
878 (let* ((old-command-method (gnus-find-method-for-group old-group)) | |
879 (old-path (directory-file-name | |
880 (let (gnus-command-method old-command-method) | |
881 (gnus-agent-group-pathname old-group)))) | |
882 (new-command-method (gnus-find-method-for-group new-group)) | |
883 (new-path (directory-file-name | |
884 (let (gnus-command-method new-command-method) | |
885 (gnus-agent-group-pathname new-group))))) | |
886 (gnus-rename-file old-path new-path t) | |
887 | |
888 (let* ((old-real-group (gnus-group-real-name old-group)) | |
889 (new-real-group (gnus-group-real-name new-group)) | |
890 (old-active (gnus-agent-get-group-info old-command-method old-real-group))) | |
891 (gnus-agent-save-group-info old-command-method old-real-group nil) | |
892 (gnus-agent-save-group-info new-command-method new-real-group old-active) | |
893 | |
894 (let ((old-local (gnus-agent-get-local old-group | |
895 old-real-group old-command-method))) | |
896 (gnus-agent-set-local old-group | |
897 nil nil | |
898 old-real-group old-command-method) | |
899 (gnus-agent-set-local new-group | |
900 (car old-local) (cdr old-local) | |
901 new-real-group new-command-method))))) | |
902 | |
903 ;;;###autoload | |
904 (defun gnus-agent-delete-group (group) | |
905 "Delete fully-qualified GROUP. | |
906 Always updates the agent, even when disabled, as the old agent | |
907 files would corrupt gnus when the agent was next enabled. | |
908 Depends upon the caller to determine whether group deletion is | |
909 supported." | |
910 (let* ((command-method (gnus-find-method-for-group group)) | |
911 (path (directory-file-name | |
912 (let (gnus-command-method command-method) | |
913 (gnus-agent-group-pathname group))))) | |
914 (gnus-delete-directory path) | |
915 | |
916 (let* ((real-group (gnus-group-real-name group))) | |
917 (gnus-agent-save-group-info command-method real-group nil) | |
918 | |
919 (let ((local (gnus-agent-get-local group | |
920 real-group command-method))) | |
921 (gnus-agent-set-local group | |
922 nil nil | |
923 real-group command-method))))) | |
924 | |
520 ;;; | 925 ;;; |
521 ;;; Server mode commands | 926 ;;; Server mode commands |
522 ;;; | 927 ;;; |
523 | 928 |
524 (defun gnus-agent-add-server (server) | 929 (defun gnus-agent-add-server () |
525 "Enroll SERVER in the agent program." | 930 "Enroll SERVER in the agent program." |
526 (interactive (list (gnus-server-server-name))) | 931 (interactive) |
527 (unless server | 932 (let* ((server (gnus-server-server-name)) |
528 (error "No server on the current line")) | 933 (named-server (gnus-server-named-server)) |
529 (let ((method (gnus-server-get-method nil (gnus-server-server-name)))) | 934 (method (and server |
530 (when (member method gnus-agent-covered-methods) | 935 (gnus-server-get-method nil server)))) |
936 (unless server | |
937 (error "No server on the current line")) | |
938 | |
939 (when (gnus-agent-method-p method) | |
531 (error "Server already in the agent program")) | 940 (error "Server already in the agent program")) |
532 (push method gnus-agent-covered-methods) | 941 |
942 (push named-server gnus-agent-covered-methods) | |
943 | |
944 (setq gnus-agent-method-p-cache nil) | |
945 (gnus-server-update-server server) | |
533 (gnus-agent-write-servers) | 946 (gnus-agent-write-servers) |
534 (message "Entered %s into the Agent" server))) | 947 (gnus-message 1 "Entered %s into the Agent" server))) |
535 | 948 |
536 (defun gnus-agent-remove-server (server) | 949 (defun gnus-agent-remove-server () |
537 "Remove SERVER from the agent program." | 950 "Remove SERVER from the agent program." |
538 (interactive (list (gnus-server-server-name))) | 951 (interactive) |
539 (unless server | 952 (let* ((server (gnus-server-server-name)) |
540 (error "No server on the current line")) | 953 (named-server (gnus-server-named-server))) |
541 (let ((method (gnus-server-get-method nil (gnus-server-server-name)))) | 954 (unless server |
542 (unless (member method gnus-agent-covered-methods) | 955 (error "No server on the current line")) |
956 | |
957 (unless (member named-server gnus-agent-covered-methods) | |
543 (error "Server not in the agent program")) | 958 (error "Server not in the agent program")) |
959 | |
544 (setq gnus-agent-covered-methods | 960 (setq gnus-agent-covered-methods |
545 (delete method gnus-agent-covered-methods)) | 961 (delete named-server gnus-agent-covered-methods) |
962 gnus-agent-method-p-cache nil) | |
963 | |
964 (gnus-server-update-server server) | |
546 (gnus-agent-write-servers) | 965 (gnus-agent-write-servers) |
547 (message "Removed %s from the agent" server))) | 966 (gnus-message 1 "Removed %s from the agent" server))) |
548 | 967 |
549 (defun gnus-agent-read-servers () | 968 (defun gnus-agent-read-servers () |
550 "Read the alist of covered servers." | 969 "Read the alist of covered servers." |
551 (setq gnus-agent-covered-methods | 970 (setq gnus-agent-covered-methods |
552 (gnus-agent-read-file | 971 (gnus-agent-read-file |
553 (nnheader-concat gnus-agent-directory "lib/servers")))) | 972 (nnheader-concat gnus-agent-directory "lib/servers")) |
973 gnus-agent-method-p-cache nil) | |
974 | |
975 ;; I am called so early in start-up that I can not validate server | |
976 ;; names. When that is the case, I skip the validation. That is | |
977 ;; alright as the gnus startup code calls the validate methods | |
978 ;; directly. | |
979 (if gnus-server-alist | |
980 (gnus-agent-read-servers-validate))) | |
981 | |
982 (defun gnus-agent-read-servers-validate () | |
983 (mapcar (lambda (server-or-method) | |
984 (let* ((server (if (stringp server-or-method) | |
985 server-or-method | |
986 (gnus-method-to-server server-or-method))) | |
987 (method (gnus-server-to-method server))) | |
988 (if method | |
989 (unless (member server gnus-agent-covered-methods) | |
990 (push server gnus-agent-covered-methods) | |
991 (setq gnus-agent-method-p-cache nil)) | |
992 (gnus-message 1 "Ignoring disappeared server `%s'" server)))) | |
993 (prog1 gnus-agent-covered-methods | |
994 (setq gnus-agent-covered-methods nil)))) | |
995 | |
996 (defun gnus-agent-read-servers-validate-native (native-method) | |
997 (setq gnus-agent-covered-methods | |
998 (mapcar (lambda (method) | |
999 (if (or (not method) | |
1000 (equal method native-method)) | |
1001 "native" | |
1002 method)) gnus-agent-covered-methods))) | |
554 | 1003 |
555 (defun gnus-agent-write-servers () | 1004 (defun gnus-agent-write-servers () |
556 "Write the alist of covered servers." | 1005 "Write the alist of covered servers." |
557 (gnus-make-directory (nnheader-concat gnus-agent-directory "lib")) | 1006 (gnus-make-directory (nnheader-concat gnus-agent-directory "lib")) |
558 (let ((coding-system-for-write nnheader-file-coding-system) | 1007 (let ((coding-system-for-write nnheader-file-coding-system) |
559 (file-name-coding-system nnmail-pathname-coding-system)) | 1008 (file-name-coding-system nnmail-pathname-coding-system)) |
560 (with-temp-file (nnheader-concat gnus-agent-directory "lib/servers") | 1009 (with-temp-file (nnheader-concat gnus-agent-directory "lib/servers") |
561 (prin1 gnus-agent-covered-methods (current-buffer))))) | 1010 (prin1 gnus-agent-covered-methods |
1011 (current-buffer))))) | |
562 | 1012 |
563 ;;; | 1013 ;;; |
564 ;;; Summary commands | 1014 ;;; Summary commands |
565 ;;; | 1015 ;;; |
566 | 1016 |
598 the actual number of articles toggled is returned." | 1048 the actual number of articles toggled is returned." |
599 (interactive "p") | 1049 (interactive "p") |
600 (gnus-agent-mark-article n 'toggle)) | 1050 (gnus-agent-mark-article n 'toggle)) |
601 | 1051 |
602 (defun gnus-summary-set-agent-mark (article &optional unmark) | 1052 (defun gnus-summary-set-agent-mark (article &optional unmark) |
603 "Mark ARTICLE as downloadable." | 1053 "Mark ARTICLE as downloadable. If UNMARK is nil, article is marked. |
604 (let ((unmark (if (and (not (null unmark)) (not (eq t unmark))) | 1054 When UNMARK is t, the article is unmarked. For any other value, the |
605 (memq article gnus-newsgroup-downloadable) | 1055 article's mark is toggled." |
606 unmark))) | 1056 (let ((unmark (cond ((eq nil unmark) |
607 (if unmark | 1057 nil) |
608 (progn | 1058 ((eq t unmark) |
609 (setq gnus-newsgroup-downloadable | 1059 t) |
610 (delq article gnus-newsgroup-downloadable)) | 1060 (t |
611 (push article gnus-newsgroup-undownloaded)) | 1061 (memq article gnus-newsgroup-downloadable))))) |
612 (setq gnus-newsgroup-undownloaded | 1062 (when (gnus-summary-goto-subject article nil t) |
613 (delq article gnus-newsgroup-undownloaded)) | 1063 (gnus-summary-update-mark |
614 (push article gnus-newsgroup-downloadable)) | 1064 (if unmark |
615 (gnus-summary-update-mark | 1065 (progn |
616 (if unmark gnus-undownloaded-mark gnus-downloadable-mark) | 1066 (setq gnus-newsgroup-downloadable |
617 'unread))) | 1067 (delq article gnus-newsgroup-downloadable)) |
618 | 1068 (gnus-article-mark article)) |
1069 (setq gnus-newsgroup-downloadable | |
1070 (gnus-add-to-sorted-list gnus-newsgroup-downloadable article)) | |
1071 gnus-downloadable-mark) | |
1072 'unread)))) | |
1073 | |
1074 ;;;###autoload | |
619 (defun gnus-agent-get-undownloaded-list () | 1075 (defun gnus-agent-get-undownloaded-list () |
620 "Mark all unfetched articles as read." | 1076 "Construct list of articles that have not been downloaded." |
621 (let ((gnus-command-method (gnus-find-method-for-group gnus-newsgroup-name))) | 1077 (let ((gnus-command-method (gnus-find-method-for-group gnus-newsgroup-name))) |
622 (when (and (not gnus-plugged) | 1078 (when (set (make-local-variable 'gnus-newsgroup-agentized) |
623 (gnus-agent-method-p gnus-command-method)) | 1079 (gnus-agent-method-p gnus-command-method)) |
624 (gnus-agent-load-alist gnus-newsgroup-name) | 1080 (let* ((alist (gnus-agent-load-alist gnus-newsgroup-name)) |
625 ;; First mark all undownloaded articles as undownloaded. | 1081 (headers (sort (mapcar (lambda (h) |
626 (let ((articles (append gnus-newsgroup-unreads | 1082 (mail-header-number h)) |
627 gnus-newsgroup-marked | 1083 gnus-newsgroup-headers) '<)) |
628 gnus-newsgroup-dormant)) | 1084 (cached (and gnus-use-cache gnus-newsgroup-cached)) |
629 article) | 1085 (undownloaded (list nil)) |
630 (while (setq article (pop articles)) | 1086 (tail-undownloaded undownloaded) |
631 (unless (or (cdr (assq article gnus-agent-article-alist)) | 1087 (unfetched (list nil)) |
632 (memq article gnus-newsgroup-downloadable) | 1088 (tail-unfetched unfetched)) |
633 (memq article gnus-newsgroup-cached)) | 1089 (while (and alist headers) |
634 (push article gnus-newsgroup-undownloaded)))) | 1090 (let ((a (caar alist)) |
635 ;; Then mark downloaded downloadable as not-downloadable, | 1091 (h (car headers))) |
636 ;; if you get my drift. | 1092 (cond ((< a h) |
637 (let ((articles gnus-newsgroup-downloadable) | 1093 ;; Ignore IDs in the alist that are not being |
638 article) | 1094 ;; displayed in the summary. |
639 (while (setq article (pop articles)) | 1095 (setq alist (cdr alist))) |
640 (when (cdr (assq article gnus-agent-article-alist)) | 1096 ((> a h) |
641 (setq gnus-newsgroup-downloadable | 1097 ;; Headers that are not in the alist should be |
642 (delq article gnus-newsgroup-downloadable)))))))) | 1098 ;; fictious (see nnagent-retrieve-headers); they |
1099 ;; imply that this article isn't in the agent. | |
1100 (gnus-agent-append-to-list tail-undownloaded h) | |
1101 (gnus-agent-append-to-list tail-unfetched h) | |
1102 (setq headers (cdr headers))) | |
1103 ((cdar alist) | |
1104 (setq alist (cdr alist)) | |
1105 (setq headers (cdr headers)) | |
1106 nil ; ignore already downloaded | |
1107 ) | |
1108 (t | |
1109 (setq alist (cdr alist)) | |
1110 (setq headers (cdr headers)) | |
1111 | |
1112 ;; This article isn't in the agent. Check to see | |
1113 ;; if it is in the cache. If it is, it's been | |
1114 ;; downloaded. | |
1115 (while (and cached (< (car cached) a)) | |
1116 (setq cached (cdr cached))) | |
1117 (unless (equal a (car cached)) | |
1118 (gnus-agent-append-to-list tail-undownloaded a)))))) | |
1119 | |
1120 (while headers | |
1121 (let ((num (pop headers))) | |
1122 (gnus-agent-append-to-list tail-undownloaded num) | |
1123 (gnus-agent-append-to-list tail-unfetched num))) | |
1124 | |
1125 (setq gnus-newsgroup-undownloaded (cdr undownloaded) | |
1126 gnus-newsgroup-unfetched (cdr unfetched)))))) | |
643 | 1127 |
644 (defun gnus-agent-catchup () | 1128 (defun gnus-agent-catchup () |
645 "Mark all undownloaded articles as read." | 1129 "Mark as read all unhandled articles. |
1130 An article is unhandled if it is neither cached, nor downloaded, nor | |
1131 downloadable." | |
646 (interactive) | 1132 (interactive) |
647 (save-excursion | 1133 (save-excursion |
648 (while gnus-newsgroup-undownloaded | 1134 (let ((articles gnus-newsgroup-undownloaded)) |
649 (gnus-summary-mark-article | 1135 (when (or gnus-newsgroup-downloadable |
650 (pop gnus-newsgroup-undownloaded) gnus-catchup-mark))) | 1136 gnus-newsgroup-cached) |
651 (gnus-summary-position-point)) | 1137 (setq articles (gnus-sorted-ndifference |
1138 (gnus-sorted-ndifference | |
1139 (gnus-copy-sequence articles) | |
1140 gnus-newsgroup-downloadable) | |
1141 gnus-newsgroup-cached))) | |
1142 | |
1143 (while articles | |
1144 (gnus-summary-mark-article | |
1145 (pop articles) gnus-catchup-mark))) | |
1146 (gnus-summary-position-point))) | |
1147 | |
1148 (defun gnus-agent-summary-fetch-series () | |
1149 (interactive) | |
1150 (when gnus-newsgroup-processable | |
1151 (setq gnus-newsgroup-downloadable | |
1152 (let* ((dl gnus-newsgroup-downloadable) | |
1153 (processable (sort (gnus-copy-sequence gnus-newsgroup-processable) '<)) | |
1154 (gnus-newsgroup-downloadable processable)) | |
1155 (gnus-agent-summary-fetch-group) | |
1156 | |
1157 ;; For each article that I processed that is no longer | |
1158 ;; undownloaded, remove its processable mark. | |
1159 | |
1160 (mapc #'gnus-summary-remove-process-mark | |
1161 (gnus-sorted-ndifference gnus-newsgroup-processable gnus-newsgroup-undownloaded)) | |
1162 | |
1163 ;; The preceeding call to (gnus-agent-summary-fetch-group) | |
1164 ;; updated the temporary gnus-newsgroup-downloadable to | |
1165 ;; remove each article successfully fetched. Now, I | |
1166 ;; update the real gnus-newsgroup-downloadable to only | |
1167 ;; include undownloaded articles. | |
1168 (gnus-sorted-ndifference dl (gnus-sorted-ndifference processable gnus-newsgroup-undownloaded)))))) | |
1169 | |
1170 (defun gnus-agent-summary-fetch-group (&optional all) | |
1171 "Fetch the downloadable articles in the group. | |
1172 Optional arg ALL, if non-nil, means to fetch all articles." | |
1173 (interactive "P") | |
1174 (let ((articles | |
1175 (if all gnus-newsgroup-articles | |
1176 gnus-newsgroup-downloadable)) | |
1177 (gnus-command-method (gnus-find-method-for-group gnus-newsgroup-name)) | |
1178 fetched-articles) | |
1179 (gnus-agent-while-plugged | |
1180 (unless articles | |
1181 (error "No articles to download")) | |
1182 (gnus-agent-with-fetch | |
1183 (setq gnus-newsgroup-undownloaded | |
1184 (gnus-sorted-ndifference | |
1185 gnus-newsgroup-undownloaded | |
1186 (setq fetched-articles | |
1187 (gnus-agent-fetch-articles | |
1188 gnus-newsgroup-name articles))))) | |
1189 (save-excursion | |
1190 (dolist (article articles) | |
1191 (let ((was-marked-downloadable | |
1192 (memq article gnus-newsgroup-downloadable))) | |
1193 (cond (gnus-agent-mark-unread-after-downloaded | |
1194 (setq gnus-newsgroup-downloadable | |
1195 (delq article gnus-newsgroup-downloadable)) | |
1196 | |
1197 (gnus-summary-mark-article article gnus-unread-mark)) | |
1198 (was-marked-downloadable | |
1199 (gnus-summary-set-agent-mark article t))) | |
1200 (when (gnus-summary-goto-subject article nil t) | |
1201 (gnus-summary-update-download-mark article)))))) | |
1202 fetched-articles)) | |
1203 | |
1204 (defun gnus-agent-fetch-selected-article () | |
1205 "Fetch the current article as it is selected. | |
1206 This can be added to `gnus-select-article-hook' or | |
1207 `gnus-mark-article-hook'." | |
1208 (let ((gnus-command-method gnus-current-select-method)) | |
1209 (when (and gnus-plugged (gnus-agent-method-p gnus-command-method)) | |
1210 (when (gnus-agent-fetch-articles | |
1211 gnus-newsgroup-name | |
1212 (list gnus-current-article)) | |
1213 (setq gnus-newsgroup-undownloaded | |
1214 (delq gnus-current-article gnus-newsgroup-undownloaded)) | |
1215 (gnus-summary-update-download-mark gnus-current-article))))) | |
652 | 1216 |
653 ;;; | 1217 ;;; |
654 ;;; Internal functions | 1218 ;;; Internal functions |
655 ;;; | 1219 ;;; |
656 | 1220 |
1221 (defun gnus-agent-synchronize-group-flags (group actions server) | |
1222 "Update a plugged group by performing the indicated actions." | |
1223 (let* ((gnus-command-method (gnus-server-to-method server)) | |
1224 (info | |
1225 ;; This initializer is required as gnus-request-set-mark | |
1226 ;; calls gnus-group-real-name to strip off the host name | |
1227 ;; before calling the backend. Now that the backend is | |
1228 ;; trying to call gnus-request-set-mark, I have to | |
1229 ;; reconstruct the original group name. | |
1230 (or (gnus-get-info group) | |
1231 (gnus-get-info | |
1232 (setq group (gnus-group-full-name | |
1233 group gnus-command-method)))))) | |
1234 (gnus-request-set-mark group actions) | |
1235 | |
1236 (when info | |
1237 (dolist (action actions) | |
1238 (let ((range (nth 0 action)) | |
1239 (what (nth 1 action)) | |
1240 (marks (nth 2 action))) | |
1241 (dolist (mark marks) | |
1242 (cond ((eq mark 'read) | |
1243 (gnus-info-set-read | |
1244 info | |
1245 (funcall (if (eq what 'add) | |
1246 'gnus-range-add | |
1247 'gnus-remove-from-range) | |
1248 (gnus-info-read info) | |
1249 range)) | |
1250 (gnus-get-unread-articles-in-group | |
1251 info | |
1252 (gnus-active (gnus-info-group info)))) | |
1253 ((memq mark '(tick)) | |
1254 (let ((info-marks (assoc mark (gnus-info-marks info)))) | |
1255 (unless info-marks | |
1256 (gnus-info-set-marks info (cons (setq info-marks (list mark)) (gnus-info-marks info)))) | |
1257 (setcdr info-marks (funcall (if (eq what 'add) | |
1258 'gnus-range-add | |
1259 'gnus-remove-from-range) | |
1260 (cdr info-marks) | |
1261 range)))))))) | |
1262 | |
1263 ;;Marks can be synchronized at any time by simply toggling from | |
1264 ;;unplugged to plugged. If that is what is happening right now, make | |
1265 ;;sure that the group buffer is up to date. | |
1266 (when (gnus-buffer-live-p gnus-group-buffer) | |
1267 (gnus-group-update-group group t))) | |
1268 nil)) | |
1269 | |
657 (defun gnus-agent-save-active (method) | 1270 (defun gnus-agent-save-active (method) |
658 (gnus-agent-save-active-1 method 'gnus-active-to-gnus-format)) | |
659 | |
660 (defun gnus-agent-save-active-1 (method function) | |
661 (when (gnus-agent-method-p method) | 1271 (when (gnus-agent-method-p method) |
662 (let* ((gnus-command-method method) | 1272 (let* ((gnus-command-method method) |
663 (new (gnus-make-hashtable (count-lines (point-min) (point-max)))) | 1273 (new (gnus-make-hashtable (count-lines (point-min) (point-max)))) |
664 (file (gnus-agent-lib-file "active"))) | 1274 (file (gnus-agent-lib-file "active"))) |
665 (funcall function nil new) | 1275 (gnus-active-to-gnus-format nil new) |
666 (gnus-agent-write-active file new) | 1276 (gnus-agent-write-active file new) |
667 (erase-buffer) | 1277 (erase-buffer) |
668 (nnheader-insert-file-contents file)))) | 1278 (nnheader-insert-file-contents file)))) |
669 | 1279 |
670 (defun gnus-agent-write-active (file new) | 1280 (defun gnus-agent-write-active (file new) |
671 (let ((orig (gnus-make-hashtable (count-lines (point-min) (point-max)))) | |
672 (file (gnus-agent-lib-file "active")) | |
673 elem osym) | |
674 (when (file-exists-p file) | |
675 (with-temp-buffer | |
676 (nnheader-insert-file-contents file) | |
677 (gnus-active-to-gnus-format nil orig)) | |
678 (mapatoms | |
679 (lambda (sym) | |
680 (when (and sym (boundp sym)) | |
681 (if (and (boundp (setq osym (intern (symbol-name sym) orig))) | |
682 (setq elem (symbol-value osym))) | |
683 (setcdr elem (cdr (symbol-value sym))) | |
684 (set (intern (symbol-name sym) orig) (symbol-value sym))))) | |
685 new)) | |
686 (gnus-make-directory (file-name-directory file)) | 1281 (gnus-make-directory (file-name-directory file)) |
687 (let ((coding-system-for-write gnus-agent-file-coding-system)) | 1282 (let ((nnmail-active-file-coding-system gnus-agent-file-coding-system)) |
688 ;; The hashtable contains real names of groups, no more prefix | 1283 ;; The hashtable contains real names of groups. However, do NOT |
689 ;; removing, so set `full' to `t'. | 1284 ;; add the foreign server prefix as gnus-active-to-gnus-format |
690 (gnus-write-active-file file orig t)))) | 1285 ;; will add it while reading the file. |
691 | 1286 (gnus-write-active-file file new nil))) |
692 (defun gnus-agent-save-groups (method) | 1287 |
693 (gnus-agent-save-active-1 method 'gnus-groups-to-gnus-format)) | 1288 ;;;###autoload |
1289 (defun gnus-agent-possibly-alter-active (group active &optional info) | |
1290 "Possibly expand a group's active range to include articles | |
1291 downloaded into the agent." | |
1292 (let* ((gnus-command-method (or gnus-command-method | |
1293 (gnus-find-method-for-group group)))) | |
1294 (when (gnus-agent-method-p gnus-command-method) | |
1295 (let* ((local (gnus-agent-get-local group)) | |
1296 (active-min (or (car active) 0)) | |
1297 (active-max (or (cdr active) 0)) | |
1298 (agent-min (or (car local) active-min)) | |
1299 (agent-max (or (cdr local) active-max))) | |
1300 | |
1301 (when (< agent-min active-min) | |
1302 (setcar active agent-min)) | |
1303 | |
1304 (when (> agent-max active-max) | |
1305 (setcdr active agent-max)) | |
1306 | |
1307 (when (and info (< agent-max (- active-min 100))) | |
1308 ;; I'm expanding the active range by such a large amount | |
1309 ;; that there is a gap of more than 100 articles between the | |
1310 ;; last article known to the agent and the first article | |
1311 ;; currently available on the server. This gap contains | |
1312 ;; articles that have been lost, mark them as read so that | |
1313 ;; gnus doesn't waste resources trying to fetch them. | |
1314 | |
1315 ;; NOTE: I don't do this for smaller gaps (< 100) as I don't | |
1316 ;; want to modify the local file everytime someone restarts | |
1317 ;; gnus. The small gap will cause a tiny performance hit | |
1318 ;; when gnus tries, and fails, to retrieve the articles. | |
1319 ;; Still that should be smaller than opening a buffer, | |
1320 ;; printing this list to the buffer, and then writing it to a | |
1321 ;; file. | |
1322 | |
1323 (let ((read (gnus-info-read info))) | |
1324 (gnus-info-set-read | |
1325 info | |
1326 (gnus-range-add | |
1327 read | |
1328 (list (cons (1+ agent-max) | |
1329 (1- active-min)))))) | |
1330 | |
1331 ;; Lie about the agent's local range for this group to | |
1332 ;; disable the set read each time this server is opened. | |
1333 ;; NOTE: Opening this group will restore the valid local | |
1334 ;; range but it will also expand the local range to | |
1335 ;; incompass the new active range. | |
1336 (gnus-agent-set-local group agent-min (1- active-min))))))) | |
694 | 1337 |
695 (defun gnus-agent-save-group-info (method group active) | 1338 (defun gnus-agent-save-group-info (method group active) |
1339 "Update a single group's active range in the agent's copy of the server's active file." | |
696 (when (gnus-agent-method-p method) | 1340 (when (gnus-agent-method-p method) |
697 (let* ((gnus-command-method method) | 1341 (let* ((gnus-command-method (or method gnus-command-method)) |
698 (coding-system-for-write nnheader-file-coding-system) | 1342 (coding-system-for-write nnheader-file-coding-system) |
699 (file-name-coding-system nnmail-pathname-coding-system) | 1343 (file-name-coding-system nnmail-pathname-coding-system) |
700 (file (gnus-agent-lib-file "active")) | 1344 (file (gnus-agent-lib-file "active")) |
701 oactive) | 1345 oactive-min oactive-max) |
702 (gnus-make-directory (file-name-directory file)) | 1346 (gnus-make-directory (file-name-directory file)) |
703 (with-temp-file file | 1347 (with-temp-file file |
704 ;; Emacs got problem to match non-ASCII group in multibyte buffer. | 1348 ;; Emacs got problem to match non-ASCII group in multibyte buffer. |
705 (mm-disable-multibyte) | 1349 (mm-disable-multibyte) |
706 (when (file-exists-p file) | 1350 (when (file-exists-p file) |
707 (nnheader-insert-file-contents file)) | 1351 (nnheader-insert-file-contents file) |
708 (goto-char (point-min)) | 1352 |
709 (when (re-search-forward | 1353 (goto-char (point-min)) |
710 (concat "^" (regexp-quote group) " ") nil t) | 1354 (when (re-search-forward |
711 (save-excursion | 1355 (concat "^" (regexp-quote group) " ") nil t) |
712 (save-restriction | 1356 (save-excursion |
713 (narrow-to-region (match-beginning 0) | 1357 (setq oactive-max (read (current-buffer)) ;; max |
714 (progn | 1358 oactive-min (read (current-buffer)))) ;; min |
715 (forward-line 1) | 1359 (gnus-delete-line))) |
716 (point))) | 1360 (when active |
717 (setq oactive (car (nnmail-parse-active))))) | 1361 (insert (format "%S %d %d y\n" (intern group) |
718 (gnus-delete-line)) | 1362 (max (or oactive-max (cdr active)) (cdr active)) |
719 (insert (format "%S %d %d y\n" (intern group) | 1363 (min (or oactive-min (car active)) (car active)))) |
720 (cdr active) | 1364 (goto-char (point-max)) |
721 (or (car oactive) (car active)))) | 1365 (while (search-backward "\\." nil t) |
722 (goto-char (point-max)) | 1366 (delete-char 1))))))) |
723 (while (search-backward "\\." nil t) | 1367 |
724 (delete-char 1)))))) | 1368 (defun gnus-agent-get-group-info (method group) |
1369 "Get a single group's active range in the agent's copy of the server's active file." | |
1370 (when (gnus-agent-method-p method) | |
1371 (let* ((gnus-command-method (or method gnus-command-method)) | |
1372 (coding-system-for-write nnheader-file-coding-system) | |
1373 (file-name-coding-system nnmail-pathname-coding-system) | |
1374 (file (gnus-agent-lib-file "active")) | |
1375 oactive-min oactive-max) | |
1376 (gnus-make-directory (file-name-directory file)) | |
1377 (with-temp-buffer | |
1378 ;; Emacs got problem to match non-ASCII group in multibyte buffer. | |
1379 (mm-disable-multibyte) | |
1380 (when (file-exists-p file) | |
1381 (nnheader-insert-file-contents file) | |
1382 | |
1383 (goto-char (point-min)) | |
1384 (when (re-search-forward | |
1385 (concat "^" (regexp-quote group) " ") nil t) | |
1386 (save-excursion | |
1387 (setq oactive-max (read (current-buffer)) ;; max | |
1388 oactive-min (read (current-buffer))) ;; min | |
1389 (cons oactive-min oactive-max)))))))) | |
725 | 1390 |
726 (defun gnus-agent-group-path (group) | 1391 (defun gnus-agent-group-path (group) |
727 "Translate GROUP into a file name." | 1392 "Translate GROUP into a file name." |
728 (if nnmail-use-long-file-names | 1393 |
729 (gnus-group-real-name group) | 1394 ;; NOTE: This is what nnmail-group-pathname does as of Apr 2003. |
730 (nnheader-translate-file-chars | 1395 ;; The two methods must be kept synchronized, which is why |
731 (nnheader-replace-chars-in-string | 1396 ;; gnus-agent-group-pathname was added. |
732 (nnheader-replace-duplicate-chars-in-string | 1397 |
733 (nnheader-replace-chars-in-string | 1398 (setq group |
734 (gnus-group-real-name group) | 1399 (nnheader-translate-file-chars |
735 ?/ ?_) | 1400 (nnheader-replace-duplicate-chars-in-string |
736 ?. ?_) | 1401 (nnheader-replace-chars-in-string |
737 ?. ?/)))) | 1402 (gnus-group-real-name (gnus-group-decoded-name group)) |
738 | 1403 ?/ ?_) |
739 | 1404 ?. ?_))) |
740 | 1405 (if (or nnmail-use-long-file-names |
741 (defun gnus-agent-method-p (method) | 1406 (file-directory-p (expand-file-name group (gnus-agent-directory)))) |
742 "Say whether METHOD is covered by the agent." | 1407 group |
743 (member method gnus-agent-covered-methods)) | 1408 (mm-encode-coding-string |
1409 (nnheader-replace-chars-in-string group ?. ?/) | |
1410 nnmail-pathname-coding-system))) | |
1411 | |
1412 (defun gnus-agent-group-pathname (group) | |
1413 "Translate GROUP into a file name." | |
1414 ;; nnagent uses nnmail-group-pathname to read articles while | |
1415 ;; unplugged. The agent must, therefore, use the same directory | |
1416 ;; while plugged. | |
1417 (let ((gnus-command-method (or gnus-command-method | |
1418 (gnus-find-method-for-group group)))) | |
1419 (nnmail-group-pathname (gnus-group-real-name | |
1420 (gnus-group-decoded-name group)) | |
1421 (gnus-agent-directory)))) | |
744 | 1422 |
745 (defun gnus-agent-get-function (method) | 1423 (defun gnus-agent-get-function (method) |
746 (if (and (not gnus-plugged) | 1424 (if (gnus-online method) |
747 (gnus-agent-method-p method)) | 1425 (car method) |
748 (progn | 1426 (require 'nnagent) |
749 (require 'nnagent) | 1427 'nnagent)) |
750 'nnagent) | 1428 |
751 (car method))) | 1429 (defun gnus-agent-covered-methods () |
1430 "Return the subset of methods that are covered by the agent." | |
1431 (delq nil (mapcar #'gnus-server-to-method gnus-agent-covered-methods))) | |
752 | 1432 |
753 ;;; History functions | 1433 ;;; History functions |
754 | 1434 |
755 (defun gnus-agent-history-buffer () | 1435 (defun gnus-agent-history-buffer () |
756 (cdr (assoc (gnus-agent-method) gnus-agent-history-buffers))) | 1436 (cdr (assoc (gnus-agent-method) gnus-agent-history-buffers))) |
768 (let ((file (gnus-agent-lib-file "history"))) | 1448 (let ((file (gnus-agent-lib-file "history"))) |
769 (when (file-exists-p file) | 1449 (when (file-exists-p file) |
770 (nnheader-insert-file-contents file)) | 1450 (nnheader-insert-file-contents file)) |
771 (set (make-local-variable 'gnus-agent-file-name) file)))) | 1451 (set (make-local-variable 'gnus-agent-file-name) file)))) |
772 | 1452 |
773 (defun gnus-agent-save-history () | |
774 (save-excursion | |
775 (set-buffer gnus-agent-current-history) | |
776 (gnus-make-directory (file-name-directory gnus-agent-file-name)) | |
777 (let ((coding-system-for-write gnus-agent-file-coding-system)) | |
778 (write-region (1+ (point-min)) (point-max) | |
779 gnus-agent-file-name nil 'silent)))) | |
780 | |
781 (defun gnus-agent-close-history () | 1453 (defun gnus-agent-close-history () |
782 (when (gnus-buffer-live-p gnus-agent-current-history) | 1454 (when (gnus-buffer-live-p gnus-agent-current-history) |
783 (kill-buffer gnus-agent-current-history) | 1455 (kill-buffer gnus-agent-current-history) |
784 (setq gnus-agent-history-buffers | 1456 (setq gnus-agent-history-buffers |
785 (delq (assoc (gnus-agent-method) gnus-agent-history-buffers) | 1457 (delq (assoc (gnus-agent-method) gnus-agent-history-buffers) |
786 gnus-agent-history-buffers)))) | 1458 gnus-agent-history-buffers)))) |
787 | 1459 |
788 (defun gnus-agent-enter-history (id group-arts date) | |
789 (save-excursion | |
790 (set-buffer gnus-agent-current-history) | |
791 (goto-char (point-max)) | |
792 (let ((p (point))) | |
793 (insert id "\t" (number-to-string date) "\t") | |
794 (while group-arts | |
795 (insert (format "%S" (intern (caar group-arts))) | |
796 " " (number-to-string (cdr (pop group-arts))) | |
797 " ")) | |
798 (insert "\n") | |
799 (while (search-backward "\\." p t) | |
800 (delete-char 1))))) | |
801 | |
802 (defun gnus-agent-article-in-history-p (id) | |
803 (save-excursion | |
804 (set-buffer (gnus-agent-history-buffer)) | |
805 (goto-char (point-min)) | |
806 (search-forward (concat "\n" id "\t") nil t))) | |
807 | |
808 (defun gnus-agent-history-path (id) | |
809 (save-excursion | |
810 (set-buffer (gnus-agent-history-buffer)) | |
811 (goto-char (point-min)) | |
812 (when (search-forward (concat "\n" id "\t") nil t) | |
813 (let ((method (gnus-agent-method))) | |
814 (let (paths group) | |
815 (while (not (numberp (setq group (read (current-buffer))))) | |
816 (push (concat method "/" group) paths)) | |
817 (nreverse paths)))))) | |
818 | |
819 ;;; | 1460 ;;; |
820 ;;; Fetching | 1461 ;;; Fetching |
821 ;;; | 1462 ;;; |
822 | 1463 |
823 (defun gnus-agent-fetch-articles (group articles) | 1464 (defun gnus-agent-fetch-articles (group articles) |
824 "Fetch ARTICLES from GROUP and put them into the Agent." | 1465 "Fetch ARTICLES from GROUP and put them into the Agent." |
825 (when articles | 1466 (when articles |
826 ;; Prune off articles that we have already fetched. | 1467 (gnus-agent-load-alist group) |
827 (while (and articles | 1468 (let* ((alist gnus-agent-article-alist) |
828 (cdr (assq (car articles) gnus-agent-article-alist))) | 1469 (headers (if (< (length articles) 2) nil gnus-newsgroup-headers)) |
829 (pop articles)) | 1470 (selected-sets (list nil)) |
830 (let ((arts articles)) | 1471 (current-set-size 0) |
831 (while (cdr arts) | 1472 article |
832 (if (cdr (assq (cadr arts) gnus-agent-article-alist)) | 1473 header-number) |
833 (setcdr arts (cddr arts)) | 1474 ;; Check each article |
834 (setq arts (cdr arts))))) | 1475 (while (setq article (pop articles)) |
835 (when articles | 1476 ;; Skip alist entries preceeding this article |
836 (let ((dir (concat | 1477 (while (> article (or (caar alist) (1+ article))) |
837 (gnus-agent-directory) | 1478 (setq alist (cdr alist))) |
838 (gnus-agent-group-path group) "/")) | 1479 |
839 (date (time-to-days (current-time))) | 1480 ;; Prune off articles that we have already fetched. |
840 (case-fold-search t) | 1481 (unless (and (eq article (caar alist)) |
841 pos crosses id elem) | 1482 (cdar alist)) |
842 (gnus-make-directory dir) | 1483 ;; Skip headers preceeding this article |
843 (gnus-message 7 "Fetching articles for %s..." group) | 1484 (while (> article |
844 ;; Fetch the articles from the backend. | 1485 (setq header-number |
845 (if (gnus-check-backend-function 'retrieve-articles group) | 1486 (let* ((header (car headers))) |
846 (setq pos (gnus-retrieve-articles articles group)) | 1487 (if header |
847 (with-temp-buffer | 1488 (mail-header-number header) |
848 (let (article) | 1489 (1+ article))))) |
849 (while (setq article (pop articles)) | 1490 (setq headers (cdr headers))) |
850 (when (or | 1491 |
851 (gnus-backlog-request-article group article | 1492 ;; Add this article to the current set |
852 nntp-server-buffer) | 1493 (setcar selected-sets (cons article (car selected-sets))) |
853 (gnus-request-article article group)) | 1494 |
854 (goto-char (point-max)) | 1495 ;; Update the set size, when the set is too large start a |
855 (push (cons article (point)) pos) | 1496 ;; new one. I do this after adding the article as I want at |
856 (insert-buffer-substring nntp-server-buffer))) | 1497 ;; least one article in each set. |
857 (copy-to-buffer nntp-server-buffer (point-min) (point-max)) | 1498 (when (< gnus-agent-max-fetch-size |
858 (setq pos (nreverse pos))))) | 1499 (setq current-set-size |
859 ;; Then save these articles into the Agent. | 1500 (+ current-set-size |
860 (save-excursion | 1501 (if (= header-number article) |
861 (set-buffer nntp-server-buffer) | 1502 (let ((char-size (mail-header-chars |
862 (while pos | 1503 (car headers)))) |
863 (narrow-to-region (cdar pos) (or (cdadr pos) (point-max))) | 1504 (if (<= char-size 0) |
864 (goto-char (point-min)) | 1505 ;; The char size was missing/invalid, |
865 (when (search-forward "\n\n" nil t) | 1506 ;; assume a worst-case situation of |
866 (when (search-backward "\nXrefs: " nil t) | 1507 ;; 65 char/line. If the line count |
867 ;; Handle crossposting. | 1508 ;; is missing, arbitrarily assume a |
868 (skip-chars-forward "^ ") | 1509 ;; size of 1000 characters. |
869 (skip-chars-forward " ") | 1510 (max (* 65 (mail-header-lines |
870 (setq crosses nil) | 1511 (car headers))) |
871 (while (looking-at "\\([^: \n]+\\):\\([0-9]+\\) +") | 1512 1000) |
872 (push (cons (buffer-substring (match-beginning 1) | 1513 char-size)) |
873 (match-end 1)) | 1514 0)))) |
874 (buffer-substring (match-beginning 2) | 1515 (setcar selected-sets (nreverse (car selected-sets))) |
875 (match-end 2))) | 1516 (setq selected-sets (cons nil selected-sets) |
876 crosses) | 1517 current-set-size 0)))) |
877 (goto-char (match-end 0))) | 1518 |
878 (gnus-agent-crosspost crosses (caar pos)))) | 1519 (when (or (cdr selected-sets) (car selected-sets)) |
879 (goto-char (point-min)) | 1520 (let* ((fetched-articles (list nil)) |
880 (if (not (re-search-forward "^Message-ID: *<\\([^>\n]+\\)>" nil t)) | 1521 (tail-fetched-articles fetched-articles) |
881 (setq id "No-Message-ID-in-article") | 1522 (dir (gnus-agent-group-pathname group)) |
882 (setq id (buffer-substring (match-beginning 1) (match-end 1)))) | 1523 (date (time-to-days (current-time))) |
883 (let ((coding-system-for-write | 1524 (case-fold-search t) |
884 gnus-agent-file-coding-system)) | 1525 pos crosses id) |
885 (write-region (point-min) (point-max) | 1526 |
886 (concat dir (number-to-string (caar pos))) | 1527 (setcar selected-sets (nreverse (car selected-sets))) |
887 nil 'silent)) | 1528 (setq selected-sets (nreverse selected-sets)) |
888 (when (setq elem (assq (caar pos) gnus-agent-article-alist)) | 1529 |
889 (setcdr elem t)) | 1530 (gnus-make-directory dir) |
890 (gnus-agent-enter-history | 1531 (gnus-message 7 "Fetching articles for %s..." group) |
891 id (or crosses (list (cons group (caar pos)))) date) | 1532 |
892 (widen) | 1533 (unwind-protect |
893 (pop pos))) | 1534 (while (setq articles (pop selected-sets)) |
894 (gnus-agent-save-alist group))))) | 1535 ;; Fetch the articles from the backend. |
895 | 1536 (if (gnus-check-backend-function 'retrieve-articles group) |
896 (defun gnus-agent-crosspost (crosses article) | 1537 (setq pos (gnus-retrieve-articles articles group)) |
1538 (with-temp-buffer | |
1539 (let (article) | |
1540 (while (setq article (pop articles)) | |
1541 (gnus-message 10 "Fetching article %s for %s..." | |
1542 article group) | |
1543 (when (or | |
1544 (gnus-backlog-request-article group article | |
1545 nntp-server-buffer) | |
1546 (gnus-request-article article group)) | |
1547 (goto-char (point-max)) | |
1548 (push (cons article (point)) pos) | |
1549 (insert-buffer-substring nntp-server-buffer))) | |
1550 (copy-to-buffer | |
1551 nntp-server-buffer (point-min) (point-max)) | |
1552 (setq pos (nreverse pos))))) | |
1553 ;; Then save these articles into the Agent. | |
1554 (save-excursion | |
1555 (set-buffer nntp-server-buffer) | |
1556 (while pos | |
1557 (narrow-to-region (cdar pos) (or (cdadr pos) (point-max))) | |
1558 (goto-char (point-min)) | |
1559 (unless (eobp) ;; Don't save empty articles. | |
1560 (when (search-forward "\n\n" nil t) | |
1561 (when (search-backward "\nXrefs: " nil t) | |
1562 ;; Handle cross posting. | |
1563 (goto-char (match-end 0)) ; move to end of header name | |
1564 (skip-chars-forward "^ ") ; skip server name | |
1565 (skip-chars-forward " ") | |
1566 (setq crosses nil) | |
1567 (while (looking-at "\\([^: \n]+\\):\\([0-9]+\\) *") | |
1568 (push (cons (buffer-substring (match-beginning 1) | |
1569 (match-end 1)) | |
1570 (string-to-number | |
1571 (buffer-substring (match-beginning 2) | |
1572 (match-end 2)))) | |
1573 crosses) | |
1574 (goto-char (match-end 0))) | |
1575 (gnus-agent-crosspost crosses (caar pos) date))) | |
1576 (goto-char (point-min)) | |
1577 (if (not (re-search-forward | |
1578 "^Message-ID: *<\\([^>\n]+\\)>" nil t)) | |
1579 (setq id "No-Message-ID-in-article") | |
1580 (setq id (buffer-substring | |
1581 (match-beginning 1) (match-end 1)))) | |
1582 (let ((coding-system-for-write | |
1583 gnus-agent-file-coding-system)) | |
1584 (write-region (point-min) (point-max) | |
1585 (concat dir (number-to-string (caar pos))) | |
1586 nil 'silent)) | |
1587 | |
1588 (gnus-agent-append-to-list | |
1589 tail-fetched-articles (caar pos))) | |
1590 (widen) | |
1591 (setq pos (cdr pos))))) | |
1592 | |
1593 (gnus-agent-save-alist group (cdr fetched-articles) date) | |
1594 (gnus-message 7 "")) | |
1595 (cdr fetched-articles)))))) | |
1596 | |
1597 (defun gnus-agent-unfetch-articles (group articles) | |
1598 "Delete ARTICLES that were fetched from GROUP into the agent." | |
1599 (when articles | |
1600 (gnus-agent-load-alist group) | |
1601 (let* ((alist (cons nil gnus-agent-article-alist)) | |
1602 (articles (sort articles #'<)) | |
1603 (next-possibility alist) | |
1604 (delete-this (pop articles))) | |
1605 (while (and (cdr next-possibility) delete-this) | |
1606 (let ((have-this (caar (cdr next-possibility)))) | |
1607 (cond ((< delete-this have-this) | |
1608 (setq delete-this (pop articles))) | |
1609 ((= delete-this have-this) | |
1610 (let ((timestamp (cdar (cdr next-possibility)))) | |
1611 (when timestamp | |
1612 (let* ((file-name (concat (gnus-agent-group-pathname group) | |
1613 (number-to-string have-this)))) | |
1614 (delete-file file-name)))) | |
1615 | |
1616 (setcdr next-possibility (cddr next-possibility))) | |
1617 (t | |
1618 (setq next-possibility (cdr next-possibility)))))) | |
1619 (setq gnus-agent-article-alist (cdr alist)) | |
1620 (gnus-agent-save-alist group)))) | |
1621 | |
1622 (defun gnus-agent-crosspost (crosses article &optional date) | |
1623 (setq date (or date t)) | |
1624 | |
897 (let (gnus-agent-article-alist group alist beg end) | 1625 (let (gnus-agent-article-alist group alist beg end) |
898 (save-excursion | 1626 (save-excursion |
899 (set-buffer gnus-agent-overview-buffer) | 1627 (set-buffer gnus-agent-overview-buffer) |
900 (when (nnheader-find-nov-line article) | 1628 (when (nnheader-find-nov-line article) |
901 (forward-word 1) | 1629 (forward-word 1) |
904 (while crosses | 1632 (while crosses |
905 (setq group (caar crosses)) | 1633 (setq group (caar crosses)) |
906 (unless (setq alist (assoc group gnus-agent-group-alist)) | 1634 (unless (setq alist (assoc group gnus-agent-group-alist)) |
907 (push (setq alist (list group (gnus-agent-load-alist (caar crosses)))) | 1635 (push (setq alist (list group (gnus-agent-load-alist (caar crosses)))) |
908 gnus-agent-group-alist)) | 1636 gnus-agent-group-alist)) |
909 (setcdr alist (cons (cons (cdar crosses) t) (cdr alist))) | 1637 (setcdr alist (cons (cons (cdar crosses) date) (cdr alist))) |
910 (save-excursion | 1638 (save-excursion |
911 (set-buffer (gnus-get-buffer-create (format " *Gnus agent overview %s*" | 1639 (set-buffer (gnus-get-buffer-create (format " *Gnus agent overview %s*" |
912 group))) | 1640 group))) |
913 (when (= (point-max) (point-min)) | 1641 (when (= (point-max) (point-min)) |
914 (push (cons group (current-buffer)) gnus-agent-buffer-alist) | 1642 (push (cons group (current-buffer)) gnus-agent-buffer-alist) |
915 (ignore-errors | 1643 (ignore-errors |
916 (nnheader-insert-file-contents | 1644 (nnheader-insert-file-contents |
917 (gnus-agent-article-name ".overview" group)))) | 1645 (gnus-agent-article-name ".overview" group)))) |
918 (nnheader-find-nov-line (string-to-number (cdar crosses))) | 1646 (nnheader-find-nov-line (string-to-number (cdar crosses))) |
919 (insert (string-to-number (cdar crosses))) | 1647 (insert (string-to-number (cdar crosses))) |
920 (insert-buffer-substring gnus-agent-overview-buffer beg end)) | 1648 (insert-buffer-substring gnus-agent-overview-buffer beg end) |
921 (pop crosses)))) | 1649 (gnus-agent-check-overview-buffer)) |
1650 (setq crosses (cdr crosses))))) | |
1651 | |
1652 (defun gnus-agent-backup-overview-buffer () | |
1653 (when gnus-newsgroup-name | |
1654 (let ((root (gnus-agent-article-name ".overview" gnus-newsgroup-name)) | |
1655 (cnt 0) | |
1656 name) | |
1657 (while (file-exists-p | |
1658 (setq name (concat root "~" | |
1659 (int-to-string (setq cnt (1+ cnt))) "~")))) | |
1660 (write-region (point-min) (point-max) name nil 'no-msg) | |
1661 (gnus-message 1 "Created backup copy of overview in %s." name))) | |
1662 t) | |
1663 | |
1664 (defun gnus-agent-check-overview-buffer (&optional buffer) | |
1665 "Check the overview file given for sanity. | |
1666 In particular, checks that the file is sorted by article number | |
1667 and that there are no duplicates." | |
1668 (let ((prev-num -1) | |
1669 (backed-up nil)) | |
1670 (save-excursion | |
1671 (when buffer | |
1672 (set-buffer buffer)) | |
1673 (save-restriction | |
1674 (widen) | |
1675 (goto-char (point-min)) | |
1676 | |
1677 (while (< (point) (point-max)) | |
1678 (let ((p (point)) | |
1679 (cur (condition-case nil | |
1680 (read (current-buffer)) | |
1681 (error nil)))) | |
1682 (cond | |
1683 ((or (not (integerp cur)) | |
1684 (not (eq (char-after) ?\t))) | |
1685 (or backed-up | |
1686 (setq backed-up (gnus-agent-backup-overview-buffer))) | |
1687 (gnus-message 1 | |
1688 "Overview buffer contains garbage '%s'." | |
1689 (buffer-substring | |
1690 p (gnus-point-at-eol)))) | |
1691 ((= cur prev-num) | |
1692 (or backed-up | |
1693 (setq backed-up (gnus-agent-backup-overview-buffer))) | |
1694 (gnus-message 1 | |
1695 "Duplicate overview line for %d" cur) | |
1696 (delete-region p (progn (forward-line 1) (point)))) | |
1697 ((< cur prev-num) | |
1698 (or backed-up | |
1699 (setq backed-up (gnus-agent-backup-overview-buffer))) | |
1700 (gnus-message 1 "Overview buffer not sorted!") | |
1701 (sort-numeric-fields 1 (point-min) (point-max)) | |
1702 (goto-char (point-min)) | |
1703 (setq prev-num -1)) | |
1704 (t | |
1705 (setq prev-num cur))) | |
1706 (forward-line 1))))))) | |
922 | 1707 |
923 (defun gnus-agent-flush-cache () | 1708 (defun gnus-agent-flush-cache () |
924 (save-excursion | 1709 (save-excursion |
925 (while gnus-agent-buffer-alist | 1710 (while gnus-agent-buffer-alist |
926 (set-buffer (cdar gnus-agent-buffer-alist)) | 1711 (set-buffer (cdar gnus-agent-buffer-alist)) |
928 gnus-agent-file-coding-system)) | 1713 gnus-agent-file-coding-system)) |
929 (write-region (point-min) (point-max) | 1714 (write-region (point-min) (point-max) |
930 (gnus-agent-article-name ".overview" | 1715 (gnus-agent-article-name ".overview" |
931 (caar gnus-agent-buffer-alist)) | 1716 (caar gnus-agent-buffer-alist)) |
932 nil 'silent)) | 1717 nil 'silent)) |
933 (pop gnus-agent-buffer-alist)) | 1718 (setq gnus-agent-buffer-alist (cdr gnus-agent-buffer-alist))) |
934 (while gnus-agent-group-alist | 1719 (while gnus-agent-group-alist |
935 (with-temp-file (caar gnus-agent-group-alist) | 1720 (with-temp-file (gnus-agent-article-name |
1721 ".agentview" (caar gnus-agent-group-alist)) | |
936 (princ (cdar gnus-agent-group-alist)) | 1722 (princ (cdar gnus-agent-group-alist)) |
1723 (insert "\n") | |
1724 (princ 1 (current-buffer)) | |
937 (insert "\n")) | 1725 (insert "\n")) |
938 (pop gnus-agent-group-alist)))) | 1726 (setq gnus-agent-group-alist (cdr gnus-agent-group-alist))))) |
939 | 1727 |
940 (if (fboundp 'union) | 1728 ;;;###autoload |
941 (defalias 'gnus-agent-union 'union) | 1729 (defun gnus-agent-find-parameter (group symbol) |
942 (defun gnus-agent-union (l1 l2) | 1730 "Search for GROUPs SYMBOL in the group's parameters, the group's |
943 "Set union of lists L1 and L2." | 1731 topic parameters, the group's category, or the customizable |
944 (cond ((null l1) l2) | 1732 variables. Returns the first non-nil value found." |
945 ((null l2) l1) | 1733 (or (gnus-group-find-parameter group symbol t) |
946 ((equal l1 l2) l1) | 1734 (gnus-group-parameter-value (cdr (gnus-group-category group)) symbol t) |
947 (t | 1735 (symbol-value |
948 (or (>= (length l1) (length l2)) | 1736 (cdr |
949 (setq l1 (prog1 l2 (setq l2 l1)))) | 1737 (assq symbol |
950 (while l2 | 1738 '((agent-short-article . gnus-agent-short-article) |
951 (or (memq (car l2) l1) | 1739 (agent-long-article . gnus-agent-long-article) |
952 (push (car l2) l1)) | 1740 (agent-low-score . gnus-agent-low-score) |
953 (pop l2)) | 1741 (agent-high-score . gnus-agent-high-score) |
954 l1)))) | 1742 (agent-days-until-old . gnus-agent-expire-days) |
1743 (agent-enable-expiration | |
1744 . gnus-agent-enable-expiration) | |
1745 (agent-predicate . gnus-agent-predicate))))))) | |
955 | 1746 |
956 (defun gnus-agent-fetch-headers (group &optional force) | 1747 (defun gnus-agent-fetch-headers (group &optional force) |
957 (let ((articles (gnus-list-of-unread-articles group)) | 1748 "Fetch interesting headers into the agent. The group's overview |
958 (gnus-decode-encoded-word-function 'identity) | 1749 file will be updated to include the headers while a list of available |
959 (file (gnus-agent-article-name ".overview" group))) | 1750 article numbers will be returned." |
960 ;; Add article with marks to list of article headers we want to fetch. | 1751 (let* ((fetch-all (and gnus-agent-consider-all-articles |
961 (dolist (arts (gnus-info-marks (gnus-get-info group))) | 1752 ;; Do not fetch all headers if the predicate |
962 (setq articles (gnus-agent-union (gnus-uncompress-sequence (cdr arts)) | 1753 ;; implies that we only consider unread articles. |
963 articles))) | 1754 (not (gnus-predicate-implies-unread |
964 (setq articles (sort articles '<)) | 1755 (gnus-agent-find-parameter group |
965 ;; Remove known articles. | 1756 'agent-predicate))))) |
966 (when (gnus-agent-load-alist group) | 1757 (articles (if fetch-all |
967 (setq articles (gnus-sorted-intersection | 1758 (gnus-uncompress-range (gnus-active group)) |
968 articles | 1759 (gnus-list-of-unread-articles group))) |
969 (gnus-uncompress-range | 1760 (gnus-decode-encoded-word-function 'identity) |
970 (cons (1+ (caar (last gnus-agent-article-alist))) | 1761 (file (gnus-agent-article-name ".overview" group))) |
971 (cdr (gnus-active group))))))) | 1762 |
972 ;; Fetch them. | 1763 (unless fetch-all |
973 (gnus-make-directory (nnheader-translate-file-chars | 1764 ;; Add articles with marks to the list of article headers we want to |
974 (file-name-directory file) t)) | 1765 ;; fetch. Don't fetch articles solely on the basis of a recent or seen |
1766 ;; mark, but do fetch recent or seen articles if they have other, more | |
1767 ;; interesting marks. (We have to fetch articles with boring marks | |
1768 ;; because otherwise the agent will remove their marks.) | |
1769 (dolist (arts (gnus-info-marks (gnus-get-info group))) | |
1770 (unless (memq (car arts) '(seen recent killed cache)) | |
1771 (setq articles (gnus-range-add articles (cdr arts))))) | |
1772 (setq articles (sort (gnus-uncompress-sequence articles) '<))) | |
1773 | |
1774 ;; At this point, I have the list of articles to consider for | |
1775 ;; fetching. This is the list that I'll return to my caller. Some | |
1776 ;; of these articles may have already been fetched. That's OK as | |
1777 ;; the fetch article code will filter those out. Internally, I'll | |
1778 ;; filter this list to just those articles whose headers need to | |
1779 ;; be fetched. | |
1780 (let ((articles articles)) | |
1781 ;; Remove known articles. | |
1782 (when (and (or gnus-agent-cache | |
1783 (not gnus-plugged)) | |
1784 (gnus-agent-load-alist group)) | |
1785 ;; Remove articles marked as downloaded. | |
1786 (if fetch-all | |
1787 ;; I want to fetch all headers in the active range. | |
1788 ;; Therefore, exclude only those headers that are in the | |
1789 ;; article alist. | |
1790 ;; NOTE: This is probably NOT what I want to do after | |
1791 ;; agent expiration in this group. | |
1792 (setq articles (gnus-agent-uncached-articles articles group)) | |
1793 | |
1794 ;; I want to only fetch those headers that have never been | |
1795 ;; fetched. Therefore, exclude all headers that are, or | |
1796 ;; WERE, in the article alist. | |
1797 (let ((low (1+ (caar (last gnus-agent-article-alist)))) | |
1798 (high (cdr (gnus-active group)))) | |
1799 ;; Low can be greater than High when the same group is | |
1800 ;; fetched twice in the same session {The first fetch will | |
1801 ;; fill the article alist such that (last | |
1802 ;; gnus-agent-article-alist) equals (cdr (gnus-active | |
1803 ;; group))}. The addition of one(the 1+ above) then | |
1804 ;; forces Low to be greater than High. When this happens, | |
1805 ;; gnus-list-range-intersection returns nil which | |
1806 ;; indicates that no headers need to be fetched. -- Kevin | |
1807 (setq articles (gnus-list-range-intersection | |
1808 articles (list (cons low high))))))) | |
1809 | |
1810 (gnus-message | |
1811 10 "gnus-agent-fetch-headers: undownloaded articles are '%s'" | |
1812 (gnus-compress-sequence articles t)) | |
1813 | |
1814 (save-excursion | |
1815 (set-buffer nntp-server-buffer) | |
1816 | |
1817 (if articles | |
1818 (progn | |
1819 (gnus-message 7 "Fetching headers for %s..." group) | |
1820 | |
1821 ;; Fetch them. | |
1822 (gnus-make-directory (nnheader-translate-file-chars | |
1823 (file-name-directory file) t)) | |
1824 | |
1825 (unless (eq 'nov (gnus-retrieve-headers articles group)) | |
1826 (nnvirtual-convert-headers)) | |
1827 (gnus-agent-check-overview-buffer) | |
1828 ;; Move these headers to the overview buffer so that | |
1829 ;; gnus-agent-braid-nov can merge them with the contents | |
1830 ;; of FILE. | |
1831 (copy-to-buffer | |
1832 gnus-agent-overview-buffer (point-min) (point-max)) | |
1833 ;; NOTE: Call g-a-brand-nov even when the file does not | |
1834 ;; exist. As a minimum, it will validate the article | |
1835 ;; numbers already in the buffer. | |
1836 (gnus-agent-braid-nov group articles file) | |
1837 (let ((coding-system-for-write | |
1838 gnus-agent-file-coding-system)) | |
1839 (gnus-agent-check-overview-buffer) | |
1840 (write-region (point-min) (point-max) file nil 'silent)) | |
1841 (gnus-agent-save-alist group articles nil) | |
1842 articles) | |
1843 (ignore-errors | |
1844 (erase-buffer) | |
1845 (nnheader-insert-file-contents file))))) | |
1846 articles)) | |
1847 | |
1848 (defsubst gnus-agent-read-article-number () | |
1849 "Reads the article number at point. Returns nil when a valid article number can not be read." | |
1850 | |
1851 ;; It is unfortunate but the read function quietly overflows | |
1852 ;; integer. As a result, I have to use string operations to test | |
1853 ;; for overflow BEFORE calling read. | |
1854 (when (looking-at "[0-9]+\t") | |
1855 (let ((len (- (match-end 0) (match-beginning 0)))) | |
1856 (cond ((< len 9) | |
1857 (read (current-buffer))) | |
1858 ((= len 9) | |
1859 ;; Many 9 digit base-10 numbers can be represented in a 27-bit int | |
1860 ;; Back convert from int to string to ensure that this is one of them. | |
1861 (let* ((str1 (buffer-substring (match-beginning 0) (1- (match-end 0)))) | |
1862 (num (read (current-buffer))) | |
1863 (str2 (int-to-string num))) | |
1864 (when (equal str1 str2) | |
1865 num))))))) | |
1866 | |
1867 (defsubst gnus-agent-copy-nov-line (article) | |
1868 "Copy the indicated ARTICLE from the overview buffer to the nntp server buffer." | |
1869 (let (art b e) | |
1870 (set-buffer gnus-agent-overview-buffer) | |
1871 (while (and (not (eobp)) | |
1872 (or (not (setq art (gnus-agent-read-article-number))) | |
1873 (< art article))) | |
1874 (forward-line 1)) | |
1875 (beginning-of-line) | |
1876 (if (or (eobp) | |
1877 (not (eq article art))) | |
1878 (set-buffer nntp-server-buffer) | |
1879 (setq b (point)) | |
1880 (setq e (progn (forward-line 1) (point))) | |
1881 (set-buffer nntp-server-buffer) | |
1882 (insert-buffer-substring gnus-agent-overview-buffer b e)))) | |
1883 | |
1884 (defun gnus-agent-braid-nov (group articles file) | |
1885 "Merge agent overview data with given file. | |
1886 Takes unvalidated headers for ARTICLES from | |
1887 `gnus-agent-overview-buffer' and validated headers from the given | |
1888 FILE and places the combined valid headers into | |
1889 `nntp-server-buffer'. This function can be used, when file | |
1890 doesn't exist, to valid the overview buffer." | |
1891 (let (start last) | |
1892 (set-buffer gnus-agent-overview-buffer) | |
1893 (goto-char (point-min)) | |
1894 (set-buffer nntp-server-buffer) | |
1895 (erase-buffer) | |
1896 (when (file-exists-p file) | |
1897 (nnheader-insert-file-contents file)) | |
1898 (goto-char (point-max)) | |
1899 (forward-line -1) | |
1900 | |
1901 (unless (or (= (point-min) (point-max)) | |
1902 (< (setq last (read (current-buffer))) (car articles))) | |
1903 ;; Old and new overlap -- We do it the hard way. | |
1904 (when (nnheader-find-nov-line (car articles)) | |
1905 ;; Replacing existing NOV entry | |
1906 (delete-region (point) (progn (forward-line 1) (point)))) | |
1907 (gnus-agent-copy-nov-line (pop articles)) | |
1908 | |
1909 (ignore-errors | |
1910 (while articles | |
1911 (while (let ((art (read (current-buffer)))) | |
1912 (cond ((< art (car articles)) | |
1913 (forward-line 1) | |
1914 t) | |
1915 ((= art (car articles)) | |
1916 (beginning-of-line) | |
1917 (delete-region | |
1918 (point) (progn (forward-line 1) (point))) | |
1919 nil) | |
1920 (t | |
1921 (beginning-of-line) | |
1922 nil)))) | |
1923 | |
1924 (gnus-agent-copy-nov-line (pop articles))))) | |
1925 | |
1926 (goto-char (point-max)) | |
1927 | |
1928 ;; Append the remaining lines | |
975 (when articles | 1929 (when articles |
976 (gnus-message 7 "Fetching headers for %s..." group) | 1930 (when last |
977 (save-excursion | 1931 (set-buffer gnus-agent-overview-buffer) |
978 (set-buffer nntp-server-buffer) | 1932 (setq start (point)) |
979 (unless (eq 'nov (gnus-retrieve-headers articles group)) | 1933 (set-buffer nntp-server-buffer)) |
980 (nnvirtual-convert-headers)) | 1934 |
981 ;; Save these headers for later processing. | 1935 (let ((p (point))) |
982 (copy-to-buffer gnus-agent-overview-buffer (point-min) (point-max)) | 1936 (insert-buffer-substring gnus-agent-overview-buffer start) |
983 (when (file-exists-p file) | 1937 (goto-char p)) |
984 (gnus-agent-braid-nov group articles file)) | 1938 |
985 (let ((coding-system-for-write | 1939 (setq last (or last -134217728)) |
986 gnus-agent-file-coding-system)) | 1940 (while (catch 'problems |
987 (write-region (point-min) (point-max) file nil 'silent)) | 1941 (let (sort art) |
988 (gnus-agent-save-alist group articles nil) | 1942 (while (not (eobp)) |
989 (gnus-agent-enter-history | 1943 (setq art (gnus-agent-read-article-number)) |
990 "last-header-fetched-for-session" | 1944 (cond ((not art) |
991 (list (cons group (nth (- (length articles) 1) articles))) | 1945 ;; Bad art num - delete this line |
992 (time-to-days (current-time))) | 1946 (beginning-of-line) |
993 articles)))) | 1947 (delete-region (point) (progn (forward-line 1) (point)))) |
994 | 1948 ((< art last) |
995 (defsubst gnus-agent-copy-nov-line (article) | 1949 ;; Art num out of order - enable sort |
996 (let (b e) | 1950 (setq sort t) |
997 (set-buffer gnus-agent-overview-buffer) | 1951 (forward-line 1)) |
998 (setq b (point)) | 1952 ((= art last) |
999 (if (eq article (read (current-buffer))) | 1953 ;; Bad repeat of art number - delete this line |
1000 (setq e (progn (forward-line 1) (point))) | 1954 (beginning-of-line) |
1955 (delete-region (point) (progn (forward-line 1) (point)))) | |
1956 (t | |
1957 ;; Good art num | |
1958 (setq last art) | |
1959 (forward-line 1)))) | |
1960 (when sort | |
1961 ;; something is seriously wrong as we simply shouldn't see out-of-order data. | |
1962 ;; First, we'll fix the sort. | |
1963 (sort-numeric-fields 1 (point-min) (point-max)) | |
1964 | |
1965 ;; but now we have to consider that we may have duplicate rows... | |
1966 ;; so reset to beginning of file | |
1967 (goto-char (point-min)) | |
1968 (setq last -134217728) | |
1969 | |
1970 ;; and throw a code that restarts this scan | |
1971 (throw 'problems t)) | |
1972 nil)))))) | |
1973 | |
1974 ;; Keeps the compiler from warning about the free variable in | |
1975 ;; gnus-agent-read-agentview. | |
1976 (eval-when-compile | |
1977 (defvar gnus-agent-read-agentview)) | |
1978 | |
1979 (defun gnus-agent-load-alist (group) | |
1980 "Load the article-state alist for GROUP." | |
1981 ;; Bind free variable that's used in `gnus-agent-read-agentview'. | |
1982 (let ((gnus-agent-read-agentview group)) | |
1983 (setq gnus-agent-article-alist | |
1984 (gnus-cache-file-contents | |
1985 (gnus-agent-article-name ".agentview" group) | |
1986 'gnus-agent-file-loading-cache | |
1987 'gnus-agent-read-agentview)))) | |
1988 | |
1989 (defun gnus-agent-read-agentview (file) | |
1990 "Load FILE and do a `read' there." | |
1991 (with-temp-buffer | |
1992 (condition-case nil | |
1001 (progn | 1993 (progn |
1002 (beginning-of-line) | 1994 (nnheader-insert-file-contents file) |
1003 (setq e b))) | 1995 (goto-char (point-min)) |
1004 (set-buffer nntp-server-buffer) | 1996 (let ((alist (read (current-buffer))) |
1005 (insert-buffer-substring gnus-agent-overview-buffer b e))) | 1997 (version (condition-case nil (read (current-buffer)) |
1006 | 1998 (end-of-file 0))) |
1007 (defun gnus-agent-braid-nov (group articles file) | 1999 changed-version) |
1008 (set-buffer gnus-agent-overview-buffer) | 2000 |
1009 (goto-char (point-min)) | 2001 (cond |
1010 (set-buffer nntp-server-buffer) | 2002 ((= version 0) |
1011 (erase-buffer) | 2003 (let ((inhibit-quit t) |
1012 (nnheader-insert-file-contents file) | 2004 entry) |
1013 (goto-char (point-max)) | 2005 (gnus-agent-open-history) |
1014 (if (or (= (point-min) (point-max)) | 2006 (set-buffer (gnus-agent-history-buffer)) |
1015 (progn | 2007 (goto-char (point-min)) |
1016 (forward-line -1) | 2008 (while (not (eobp)) |
1017 (< (read (current-buffer)) (car articles)))) | 2009 (if (and (looking-at |
1018 ;; We have only headers that are after the older headers, | 2010 "[^\t\n]+\t\\([0-9]+\\)\t\\([^ \n]+\\) \\([0-9]+\\)") |
1019 ;; so we just append them. | 2011 (string= (match-string 2) |
1020 (progn | 2012 gnus-agent-read-agentview) |
1021 (goto-char (point-max)) | 2013 (setq entry (assoc (string-to-number (match-string 3)) alist))) |
1022 (insert-buffer-substring gnus-agent-overview-buffer)) | 2014 (setcdr entry (string-to-number (match-string 1)))) |
1023 ;; We do it the hard way. | 2015 (forward-line 1)) |
1024 (nnheader-find-nov-line (car articles)) | 2016 (gnus-agent-close-history) |
1025 (gnus-agent-copy-nov-line (car articles)) | 2017 (setq changed-version t))) |
1026 (pop articles) | 2018 ((= version 1) |
1027 (while (and articles | 2019 (setq changed-version (not (= 1 gnus-agent-article-alist-save-format)))) |
1028 (not (eobp))) | 2020 ((= version 2) |
1029 (while (and (not (eobp)) | 2021 (let (uncomp) |
1030 (< (read (current-buffer)) (car articles))) | 2022 (mapcar |
1031 (forward-line 1)) | 2023 (lambda (comp-list) |
1032 (beginning-of-line) | 2024 (let ((state (car comp-list)) |
1033 (unless (eobp) | 2025 (sequence (inline |
1034 (gnus-agent-copy-nov-line (car articles)) | 2026 (gnus-uncompress-range |
1035 (setq articles (cdr articles)))) | 2027 (cdr comp-list))))) |
1036 (when articles | 2028 (mapcar (lambda (article-id) |
1037 (let (b e) | 2029 (setq uncomp (cons (cons article-id state) uncomp))) |
1038 (set-buffer gnus-agent-overview-buffer) | 2030 sequence))) |
1039 (setq b (point) | 2031 alist) |
1040 e (point-max)) | 2032 (setq alist (sort uncomp 'car-less-than-car))) |
1041 (set-buffer nntp-server-buffer) | 2033 (setq changed-version (not (= 2 gnus-agent-article-alist-save-format))))) |
1042 (insert-buffer-substring gnus-agent-overview-buffer b e))))) | 2034 (when changed-version |
1043 | 2035 (let ((gnus-agent-article-alist alist)) |
1044 (defun gnus-agent-load-alist (group &optional dir) | 2036 (gnus-agent-save-alist gnus-agent-read-agentview))) |
1045 "Load the article-state alist for GROUP." | 2037 alist)) |
1046 (setq gnus-agent-article-alist | 2038 (file-error nil)))) |
1047 (gnus-agent-read-file | 2039 |
1048 (if dir | 2040 (defun gnus-agent-save-alist (group &optional articles state) |
1049 (expand-file-name ".agentview" dir) | |
1050 (gnus-agent-article-name ".agentview" group))))) | |
1051 | |
1052 (defun gnus-agent-save-alist (group &optional articles state dir) | |
1053 "Save the article-state alist for GROUP." | 2041 "Save the article-state alist for GROUP." |
1054 (let ((file-name-coding-system nnmail-pathname-coding-system)) | 2042 (let* ((file-name-coding-system nnmail-pathname-coding-system) |
1055 (with-temp-file (if dir | 2043 (prev (cons nil gnus-agent-article-alist)) |
1056 (expand-file-name ".agentview" dir) | 2044 (all prev) |
1057 (gnus-agent-article-name ".agentview" group)) | 2045 print-level print-length item article) |
1058 (princ (setq gnus-agent-article-alist | 2046 (while (setq article (pop articles)) |
1059 (nconc gnus-agent-article-alist | 2047 (while (and (cdr prev) |
1060 (mapcar (lambda (article) (cons article state)) | 2048 (< (caadr prev) article)) |
1061 articles))) | 2049 (setq prev (cdr prev))) |
1062 (current-buffer)) | 2050 (cond |
1063 (insert "\n")))) | 2051 ((not (cdr prev)) |
2052 (setcdr prev (list (cons article state)))) | |
2053 ((> (caadr prev) article) | |
2054 (setcdr prev (cons (cons article state) (cdr prev)))) | |
2055 ((= (caadr prev) article) | |
2056 (setcdr (cadr prev) state))) | |
2057 (setq prev (cdr prev))) | |
2058 (setq gnus-agent-article-alist (cdr all)) | |
2059 | |
2060 (gnus-agent-set-local group | |
2061 (caar gnus-agent-article-alist) | |
2062 (caar (last gnus-agent-article-alist))) | |
2063 | |
2064 (gnus-make-directory (gnus-agent-article-name "" group)) | |
2065 (with-temp-file (gnus-agent-article-name ".agentview" group) | |
2066 (cond ((eq gnus-agent-article-alist-save-format 1) | |
2067 (princ gnus-agent-article-alist (current-buffer))) | |
2068 ((eq gnus-agent-article-alist-save-format 2) | |
2069 (let ((compressed nil)) | |
2070 (mapcar (lambda (pair) | |
2071 (let* ((article-id (car pair)) | |
2072 (day-of-download (cdr pair)) | |
2073 (comp-list (assq day-of-download compressed))) | |
2074 (if comp-list | |
2075 (setcdr comp-list | |
2076 (cons article-id (cdr comp-list))) | |
2077 (setq compressed | |
2078 (cons (list day-of-download article-id) | |
2079 compressed))) | |
2080 nil)) gnus-agent-article-alist) | |
2081 (mapcar (lambda (comp-list) | |
2082 (setcdr comp-list | |
2083 (gnus-compress-sequence | |
2084 (nreverse (cdr comp-list))))) | |
2085 compressed) | |
2086 (princ compressed (current-buffer))))) | |
2087 (insert "\n") | |
2088 (princ gnus-agent-article-alist-save-format (current-buffer)) | |
2089 (insert "\n")))) | |
2090 | |
2091 (defvar gnus-agent-article-local nil) | |
2092 (defvar gnus-agent-file-loading-local nil) | |
2093 | |
2094 (defun gnus-agent-load-local (&optional method) | |
2095 "Load the METHOD'S local file. The local file contains min/max | |
2096 article counts for each of the method's subscribed groups." | |
2097 (let ((gnus-command-method (or method gnus-command-method))) | |
2098 (setq gnus-agent-article-local | |
2099 (gnus-cache-file-contents | |
2100 (gnus-agent-lib-file "local") | |
2101 'gnus-agent-file-loading-local | |
2102 'gnus-agent-read-and-cache-local)))) | |
2103 | |
2104 (defun gnus-agent-read-and-cache-local (file) | |
2105 "Load and read FILE then bind its contents to | |
2106 gnus-agent-article-local. If that variable had `dirty' (also known as | |
2107 modified) original contents, they are first saved to their own file." | |
2108 | |
2109 (if (and gnus-agent-article-local | |
2110 (symbol-value (intern "+dirty" gnus-agent-article-local))) | |
2111 (gnus-agent-save-local)) | |
2112 (gnus-agent-read-local file)) | |
2113 | |
2114 (defun gnus-agent-read-local (file) | |
2115 "Load FILE and do a `read' there." | |
2116 (let ((my-obarray (gnus-make-hashtable (count-lines (point-min) | |
2117 (point-max)))) | |
2118 (line 1)) | |
2119 (with-temp-buffer | |
2120 (condition-case nil | |
2121 (let ((nnheader-file-coding-system gnus-agent-file-coding-system)) | |
2122 (nnheader-insert-file-contents file)) | |
2123 (file-error)) | |
2124 | |
2125 (goto-char (point-min)) | |
2126 ;; Skip any comments at the beginning of the file (the only place where they may appear) | |
2127 (while (= (following-char) ?\;) | |
2128 (forward-line 1) | |
2129 (setq line (1+ line))) | |
2130 | |
2131 (while (not (eobp)) | |
2132 (condition-case err | |
2133 (let (group | |
2134 min | |
2135 max | |
2136 (cur (current-buffer))) | |
2137 (setq group (read cur) | |
2138 min (read cur) | |
2139 max (read cur)) | |
2140 | |
2141 (when (stringp group) | |
2142 (setq group (intern group my-obarray))) | |
2143 | |
2144 ;; NOTE: The '+ 0' ensure that min and max are both numerics. | |
2145 (set group (cons (+ 0 min) (+ 0 max)))) | |
2146 (error | |
2147 (gnus-message 3 "Warning - invalid agent local: %s on line %d: %s" | |
2148 file line (error-message-string err)))) | |
2149 (forward-line 1) | |
2150 (setq line (1+ line)))) | |
2151 | |
2152 (set (intern "+dirty" my-obarray) nil) | |
2153 (set (intern "+method" my-obarray) gnus-command-method) | |
2154 my-obarray)) | |
2155 | |
2156 (defun gnus-agent-save-local (&optional force) | |
2157 "Save gnus-agent-article-local under it method's agent.lib directory." | |
2158 (let ((my-obarray gnus-agent-article-local)) | |
2159 (when (and my-obarray | |
2160 (or force (symbol-value (intern "+dirty" my-obarray)))) | |
2161 (let* ((gnus-command-method (symbol-value (intern "+method" my-obarray))) | |
2162 ;; NOTE: gnus-command-method is used within gnus-agent-lib-file. | |
2163 (dest (gnus-agent-lib-file "local"))) | |
2164 (gnus-make-directory (gnus-agent-lib-file "")) | |
2165 | |
2166 (let ((buffer-file-coding-system gnus-agent-file-coding-system)) | |
2167 (with-temp-file dest | |
2168 (let ((gnus-command-method (symbol-value (intern "+method" my-obarray))) | |
2169 (file-name-coding-system nnmail-pathname-coding-system) | |
2170 print-level print-length item article | |
2171 (standard-output (current-buffer))) | |
2172 (mapatoms (lambda (symbol) | |
2173 (cond ((not (boundp symbol)) | |
2174 nil) | |
2175 ((member (symbol-name symbol) '("+dirty" "+method")) | |
2176 nil) | |
2177 (t | |
2178 (let ((range (symbol-value symbol))) | |
2179 (when range | |
2180 (prin1 symbol) | |
2181 (princ " ") | |
2182 (princ (car range)) | |
2183 (princ " ") | |
2184 (princ (cdr range)) | |
2185 (princ "\n")))))) | |
2186 my-obarray)))))))) | |
2187 | |
2188 (defun gnus-agent-get-local (group &optional gmane method) | |
2189 (let* ((gmane (or gmane (gnus-group-real-name group))) | |
2190 (gnus-command-method (or method (gnus-find-method-for-group group))) | |
2191 (local (gnus-agent-load-local)) | |
2192 (symb (intern gmane local)) | |
2193 (minmax (and (boundp symb) (symbol-value symb)))) | |
2194 (unless minmax | |
2195 ;; Bind these so that gnus-agent-load-alist doesn't change the | |
2196 ;; current alist (i.e. gnus-agent-article-alist) | |
2197 (let* ((gnus-agent-article-alist gnus-agent-article-alist) | |
2198 (gnus-agent-file-loading-cache gnus-agent-file-loading-cache) | |
2199 (alist (gnus-agent-load-alist group))) | |
2200 (when alist | |
2201 (setq minmax | |
2202 (cons (caar alist) | |
2203 (caar (last alist)))) | |
2204 (gnus-agent-set-local group (car minmax) (cdr minmax) | |
2205 gmane gnus-command-method local)))) | |
2206 minmax)) | |
2207 | |
2208 (defun gnus-agent-set-local (group min max &optional gmane method local) | |
2209 (let* ((gmane (or gmane (gnus-group-real-name group))) | |
2210 (gnus-command-method (or method (gnus-find-method-for-group group))) | |
2211 (local (or local (gnus-agent-load-local))) | |
2212 (symb (intern gmane local)) | |
2213 (minmax (and (boundp symb) (symbol-value symb)))) | |
2214 | |
2215 (if (cond ((and minmax | |
2216 (or (not (eq min (car minmax))) | |
2217 (not (eq max (cdr minmax))))) | |
2218 (setcar minmax min) | |
2219 (setcdr minmax max) | |
2220 t) | |
2221 (minmax | |
2222 nil) | |
2223 ((and min max) | |
2224 (set symb (cons min max)) | |
2225 t) | |
2226 (t | |
2227 (unintern symb local))) | |
2228 (set (intern "+dirty" local) t)))) | |
1064 | 2229 |
1065 (defun gnus-agent-article-name (article group) | 2230 (defun gnus-agent-article-name (article group) |
1066 (expand-file-name (if (stringp article) article (string-to-number article)) | 2231 (expand-file-name article |
1067 (file-name-as-directory | 2232 (file-name-as-directory |
1068 (expand-file-name (gnus-agent-group-path group) | 2233 (gnus-agent-group-pathname group)))) |
1069 (gnus-agent-directory))))) | |
1070 | 2234 |
1071 (defun gnus-agent-batch-confirmation (msg) | 2235 (defun gnus-agent-batch-confirmation (msg) |
1072 "Show error message and return t." | 2236 "Show error message and return t." |
1073 (gnus-message 1 msg) | 2237 (gnus-message 1 msg) |
1074 t) | 2238 t) |
1087 (interactive) | 2251 (interactive) |
1088 (unless gnus-agent-covered-methods | 2252 (unless gnus-agent-covered-methods |
1089 (error "No servers are covered by the Gnus agent")) | 2253 (error "No servers are covered by the Gnus agent")) |
1090 (unless gnus-plugged | 2254 (unless gnus-plugged |
1091 (error "Can't fetch articles while Gnus is unplugged")) | 2255 (error "Can't fetch articles while Gnus is unplugged")) |
1092 (let ((methods gnus-agent-covered-methods) | 2256 (let ((methods (gnus-agent-covered-methods)) |
1093 groups group gnus-command-method) | 2257 groups group gnus-command-method) |
1094 (save-excursion | 2258 (save-excursion |
1095 (while methods | 2259 (while methods |
1096 (condition-case err | 2260 (setq gnus-command-method (car methods)) |
1097 (progn | 2261 (when (and (or (gnus-server-opened gnus-command-method) |
1098 (setq gnus-command-method (car methods)) | 2262 (gnus-open-server gnus-command-method)) |
1099 (when (or (gnus-server-opened gnus-command-method) | 2263 (gnus-online gnus-command-method)) |
1100 (gnus-open-server gnus-command-method)) | 2264 (setq groups (gnus-groups-from-server (car methods))) |
1101 (setq groups (gnus-groups-from-server (car methods))) | 2265 (gnus-agent-with-fetch |
1102 (gnus-agent-with-fetch | 2266 (while (setq group (pop groups)) |
1103 (while (setq group (pop groups)) | 2267 (when (<= (gnus-group-level group) |
1104 (when (<= (gnus-group-level group) gnus-agent-handle-level) | 2268 gnus-agent-handle-level) |
1105 (gnus-agent-fetch-group-1 group gnus-command-method)))))) | 2269 (if (or debug-on-error debug-on-quit) |
1106 (error | 2270 (gnus-agent-fetch-group-1 |
1107 (unless (funcall gnus-agent-confirmation-function | 2271 group gnus-command-method) |
1108 (format "Error (%s). Continue? " err)) | 2272 (condition-case err |
1109 (error "Cannot fetch articles into the Gnus agent"))) | 2273 (gnus-agent-fetch-group-1 |
1110 (quit | 2274 group gnus-command-method) |
1111 (unless (funcall gnus-agent-confirmation-function | 2275 (error |
1112 (format "Quit (%s). Continue? " err)) | 2276 (unless (funcall gnus-agent-confirmation-function |
1113 (signal 'quit "Cannot fetch articles into the Gnus agent.")))) | 2277 (format "Error %s while fetching session. Should gnus continue? " |
1114 (pop methods)) | 2278 (error-message-string err))) |
2279 (error "Cannot fetch articles into the Gnus agent"))) | |
2280 (quit | |
2281 (gnus-agent-regenerate-group group) | |
2282 (unless (funcall gnus-agent-confirmation-function | |
2283 (format | |
2284 "%s while fetching session. Should gnus continue? " | |
2285 (error-message-string err))) | |
2286 (signal 'quit | |
2287 "Cannot fetch articles into the Gnus agent"))))))))) | |
2288 (setq methods (cdr methods))) | |
2289 (gnus-run-hooks 'gnus-agent-fetched-hook) | |
1115 (gnus-message 6 "Finished fetching articles into the Gnus agent")))) | 2290 (gnus-message 6 "Finished fetching articles into the Gnus agent")))) |
1116 | 2291 |
1117 (defun gnus-agent-fetch-group-1 (group method) | 2292 (defun gnus-agent-fetch-group-1 (group method) |
1118 "Fetch GROUP." | 2293 "Fetch GROUP." |
1119 (let ((gnus-command-method method) | 2294 (let ((gnus-command-method method) |
1120 (gnus-newsgroup-name group) | 2295 (gnus-newsgroup-name group) |
1121 gnus-newsgroup-dependencies gnus-newsgroup-headers | 2296 (gnus-newsgroup-dependencies gnus-newsgroup-dependencies) |
1122 gnus-newsgroup-scored gnus-headers gnus-score | 2297 (gnus-newsgroup-headers gnus-newsgroup-headers) |
1123 gnus-use-cache articles arts | 2298 (gnus-newsgroup-scored gnus-newsgroup-scored) |
1124 category predicate info marks score-param | 2299 (gnus-use-cache gnus-use-cache) |
1125 (gnus-summary-expunge-below gnus-summary-expunge-below) | 2300 (gnus-summary-expunge-below gnus-summary-expunge-below) |
1126 (gnus-summary-mark-below gnus-summary-mark-below) | 2301 (gnus-summary-mark-below gnus-summary-mark-below) |
1127 (gnus-orphan-score gnus-orphan-score) | 2302 (gnus-orphan-score gnus-orphan-score) |
1128 ;; Maybe some other gnus-summary local variables should also | 2303 ;; Maybe some other gnus-summary local variables should also |
1129 ;; be put here. | 2304 ;; be put here. |
2305 | |
2306 gnus-headers | |
2307 gnus-score | |
2308 articles arts | |
2309 category predicate info marks score-param | |
1130 ) | 2310 ) |
1131 (unless (gnus-check-group group) | 2311 (unless (gnus-check-group group) |
1132 (error "Can't open server for %s" group)) | 2312 (error "Can't open server for %s" group)) |
2313 | |
1133 ;; Fetch headers. | 2314 ;; Fetch headers. |
1134 (when (and (or (gnus-active group) (gnus-activate-group group)) | 2315 (when (or gnus-newsgroup-active |
1135 (setq articles (gnus-agent-fetch-headers group)) | 2316 (gnus-active group) |
1136 (let ((nntp-server-buffer gnus-agent-overview-buffer)) | 2317 (gnus-activate-group group)) |
1137 ;; Parse them and see which articles we want to fetch. | 2318 (let ((marked-articles gnus-newsgroup-downloadable)) |
1138 (setq gnus-newsgroup-dependencies | 2319 ;; Identify the articles marked for download |
1139 (make-vector (length articles) 0)) | 2320 (unless gnus-newsgroup-active |
1140 (setq gnus-newsgroup-headers | 2321 ;; The variable gnus-newsgroup-active was selected as I need |
1141 (gnus-get-newsgroup-headers-xover articles nil nil | 2322 ;; a gnus-summary local variable that is NOT bound to any |
1142 group)) | 2323 ;; value (its global value should default to nil). |
1143 ;; `gnus-agent-overview-buffer' may be killed for | 2324 (dolist (mark gnus-agent-download-marks) |
1144 ;; timeout reason. If so, recreate it. | 2325 (let ((arts (cdr (assq mark (gnus-info-marks |
1145 (gnus-agent-create-buffer))) | 2326 (setq info (gnus-get-info group))))))) |
1146 (setq category (gnus-group-category group)) | 2327 (when arts |
1147 (setq predicate | 2328 (setq marked-articles (nconc (gnus-uncompress-range arts) |
1148 (gnus-get-predicate | 2329 marked-articles)) |
1149 (or (gnus-group-find-parameter group 'agent-predicate t) | 2330 )))) |
1150 (cadr category)))) | 2331 (setq marked-articles (sort marked-articles '<)) |
1151 (if (memq (caaddr predicate) '(gnus-agent-true gnus-agent-false)) | 2332 |
1152 ;; Simple implementation | 2333 ;; Fetch any new articles from the server |
1153 (setq arts | 2334 (setq articles (gnus-agent-fetch-headers group)) |
1154 (and (eq (caaddr predicate) 'gnus-agent-true) articles)) | 2335 |
1155 (setq arts nil) | 2336 ;; Merge new articles with marked |
1156 (setq score-param | 2337 (setq articles (sort (append marked-articles articles) '<)) |
1157 (or (gnus-group-get-parameter group 'agent-score t) | 2338 |
1158 (caddr category))) | 2339 (when articles |
1159 ;; Translate score-param into real one | 2340 ;; Parse them and see which articles we want to fetch. |
1160 (cond | 2341 (setq gnus-newsgroup-dependencies |
1161 ((not score-param)) | 2342 (or gnus-newsgroup-dependencies |
1162 ((eq score-param 'file) | 2343 (make-vector (length articles) 0))) |
1163 (setq score-param (gnus-all-score-files group))) | 2344 (setq gnus-newsgroup-headers |
1164 ((stringp (car score-param))) | 2345 (or gnus-newsgroup-headers |
1165 (t | 2346 (gnus-get-newsgroup-headers-xover articles nil nil |
1166 (setq score-param (list (list score-param))))) | 2347 group))) |
1167 (when score-param | 2348 ;; `gnus-agent-overview-buffer' may be killed for |
1168 (gnus-score-headers score-param)) | 2349 ;; timeout reason. If so, recreate it. |
1169 (while (setq gnus-headers (pop gnus-newsgroup-headers)) | 2350 (gnus-agent-create-buffer) |
1170 (setq gnus-score | 2351 |
1171 (or (cdr (assq (mail-header-number gnus-headers) | 2352 ;; Figure out how to select articles in this group |
1172 gnus-newsgroup-scored)) | 2353 (setq category (gnus-group-category group)) |
1173 gnus-summary-default-score)) | 2354 |
1174 (when (funcall predicate) | 2355 (setq predicate |
1175 (push (mail-header-number gnus-headers) | 2356 (gnus-get-predicate |
1176 arts)))) | 2357 (gnus-agent-find-parameter group 'agent-predicate))) |
1177 ;; Fetch the articles. | 2358 |
1178 (when arts | 2359 ;; If the selection predicate requires scoring, score each header |
1179 (gnus-agent-fetch-articles group arts))) | 2360 (unless (memq predicate '(gnus-agent-true gnus-agent-false)) |
1180 ;; Perhaps we have some additional articles to fetch. | 2361 (let ((score-param |
1181 (setq arts (assq 'download (gnus-info-marks | 2362 (gnus-agent-find-parameter group 'agent-score-file))) |
1182 (setq info (gnus-get-info group))))) | 2363 ;; Translate score-param into real one |
1183 (when (cdr arts) | 2364 (cond |
1184 (gnus-agent-fetch-articles | 2365 ((not score-param)) |
1185 group (gnus-uncompress-range (cdr arts))) | 2366 ((eq score-param 'file) |
1186 (setq marks (delq arts (gnus-info-marks info))) | 2367 (setq score-param (gnus-all-score-files group))) |
1187 (gnus-info-set-marks info marks) | 2368 ((stringp (car score-param))) |
1188 (gnus-dribble-enter | 2369 (t |
1189 (concat "(gnus-group-set-info '" | 2370 (setq score-param (list (list score-param))))) |
1190 (gnus-prin1-to-string info) | 2371 (when score-param |
1191 ")"))))) | 2372 (gnus-score-headers score-param)))) |
2373 | |
2374 (unless (and (eq predicate 'gnus-agent-false) | |
2375 (not marked-articles)) | |
2376 (let ((arts (list nil))) | |
2377 (let ((arts-tail arts) | |
2378 (alist (gnus-agent-load-alist group)) | |
2379 (marked-articles marked-articles) | |
2380 (gnus-newsgroup-headers gnus-newsgroup-headers)) | |
2381 (while (setq gnus-headers (pop gnus-newsgroup-headers)) | |
2382 (let ((num (mail-header-number gnus-headers))) | |
2383 ;; Determine if this article is already in the cache | |
2384 (while (and alist | |
2385 (> num (caar alist))) | |
2386 (setq alist (cdr alist))) | |
2387 | |
2388 (unless (and (eq num (caar alist)) | |
2389 (cdar alist)) | |
2390 | |
2391 ;; Determine if this article was marked for download. | |
2392 (while (and marked-articles | |
2393 (> num (car marked-articles))) | |
2394 (setq marked-articles | |
2395 (cdr marked-articles))) | |
2396 | |
2397 ;; When this article is marked, or selected by the | |
2398 ;; predicate, add it to the download list | |
2399 (when (or (eq num (car marked-articles)) | |
2400 (let ((gnus-score | |
2401 (or (cdr | |
2402 (assq num gnus-newsgroup-scored)) | |
2403 gnus-summary-default-score)) | |
2404 (gnus-agent-long-article | |
2405 (gnus-agent-find-parameter | |
2406 group 'agent-long-article)) | |
2407 (gnus-agent-short-article | |
2408 (gnus-agent-find-parameter | |
2409 group 'agent-short-article)) | |
2410 (gnus-agent-low-score | |
2411 (gnus-agent-find-parameter | |
2412 group 'agent-low-score)) | |
2413 (gnus-agent-high-score | |
2414 (gnus-agent-find-parameter | |
2415 group 'agent-high-score)) | |
2416 (gnus-agent-expire-days | |
2417 (gnus-agent-find-parameter | |
2418 group 'agent-days-until-old))) | |
2419 (funcall predicate))) | |
2420 (gnus-agent-append-to-list arts-tail num)))))) | |
2421 | |
2422 (let (fetched-articles) | |
2423 ;; Fetch all selected articles | |
2424 (setq gnus-newsgroup-undownloaded | |
2425 (gnus-sorted-ndifference | |
2426 gnus-newsgroup-undownloaded | |
2427 (setq fetched-articles | |
2428 (if (cdr arts) | |
2429 (gnus-agent-fetch-articles group (cdr arts)) | |
2430 nil)))) | |
2431 | |
2432 (let ((unfetched-articles | |
2433 (gnus-sorted-ndifference (cdr arts) fetched-articles))) | |
2434 (if gnus-newsgroup-active | |
2435 ;; Update the summary buffer | |
2436 (progn | |
2437 (dolist (article marked-articles) | |
2438 (gnus-summary-set-agent-mark article t)) | |
2439 (dolist (article fetched-articles) | |
2440 (when gnus-agent-mark-unread-after-downloaded | |
2441 (setq gnus-newsgroup-downloadable | |
2442 (delq article gnus-newsgroup-downloadable)) | |
2443 (gnus-summary-mark-article | |
2444 article gnus-unread-mark)) | |
2445 (when (gnus-summary-goto-subject article nil t) | |
2446 (gnus-summary-update-download-mark article))) | |
2447 (dolist (article unfetched-articles) | |
2448 (gnus-summary-mark-article | |
2449 article gnus-canceled-mark))) | |
2450 | |
2451 ;; Update the group buffer. | |
2452 | |
2453 ;; When some, or all, of the marked articles came | |
2454 ;; from the download mark. Remove that mark. I | |
2455 ;; didn't do this earlier as I only want to remove | |
2456 ;; the marks after the fetch is completed. | |
2457 | |
2458 (dolist (mark gnus-agent-download-marks) | |
2459 (when (eq mark 'download) | |
2460 (let ((marked-arts | |
2461 (assq mark (gnus-info-marks | |
2462 (setq info (gnus-get-info group)))))) | |
2463 (when (cdr marked-arts) | |
2464 (setq marks | |
2465 (delq marked-arts (gnus-info-marks info))) | |
2466 (gnus-info-set-marks info marks))))) | |
2467 (let ((read (gnus-info-read | |
2468 (or info (setq info (gnus-get-info group)))))) | |
2469 (gnus-info-set-read | |
2470 info (gnus-add-to-range read unfetched-articles))) | |
2471 | |
2472 (gnus-group-update-group group t) | |
2473 (sit-for 0) | |
2474 | |
2475 (gnus-dribble-enter | |
2476 (concat "(gnus-group-set-info '" | |
2477 (gnus-prin1-to-string info) | |
2478 ")")))))))))))) | |
1192 | 2479 |
1193 ;;; | 2480 ;;; |
1194 ;;; Agent Category Mode | 2481 ;;; Agent Category Mode |
1195 ;;; | 2482 ;;; |
1196 | 2483 |
1197 (defvar gnus-category-mode-hook nil | 2484 (defvar gnus-category-mode-hook nil |
1198 "Hook run in `gnus-category-mode' buffers.") | 2485 "Hook run in `gnus-category-mode' buffers.") |
1199 | 2486 |
1200 (defvar gnus-category-line-format " %(%20c%): %g\n" | 2487 (defvar gnus-category-line-format " %(%20c%): %g\n" |
1201 "Format of category lines.") | 2488 "Format of category lines. |
2489 | |
2490 Valid specifiers include: | |
2491 %c Topic name (string) | |
2492 %g The number of groups in the topic (integer) | |
2493 | |
2494 General format specifiers can also be used. See Info node | |
2495 `(gnus)Formatting Variables'.") | |
1202 | 2496 |
1203 (defvar gnus-category-mode-line-format "Gnus: %%b" | 2497 (defvar gnus-category-mode-line-format "Gnus: %%b" |
1204 "The format specification for the category mode line.") | 2498 "The format specification for the category mode line.") |
2499 | |
2500 (defvar gnus-agent-predicate 'false | |
2501 "The selection predicate used when no other source is available.") | |
1205 | 2502 |
1206 (defvar gnus-agent-short-article 100 | 2503 (defvar gnus-agent-short-article 100 |
1207 "Articles that have fewer lines than this are short.") | 2504 "Articles that have fewer lines than this are short.") |
1208 | 2505 |
1209 (defvar gnus-agent-long-article 200 | 2506 (defvar gnus-agent-long-article 200 |
1240 (gnus-define-keys gnus-category-mode-map | 2537 (gnus-define-keys gnus-category-mode-map |
1241 "q" gnus-category-exit | 2538 "q" gnus-category-exit |
1242 "k" gnus-category-kill | 2539 "k" gnus-category-kill |
1243 "c" gnus-category-copy | 2540 "c" gnus-category-copy |
1244 "a" gnus-category-add | 2541 "a" gnus-category-add |
2542 "e" gnus-agent-customize-category | |
1245 "p" gnus-category-edit-predicate | 2543 "p" gnus-category-edit-predicate |
1246 "g" gnus-category-edit-groups | 2544 "g" gnus-category-edit-groups |
1247 "s" gnus-category-edit-score | 2545 "s" gnus-category-edit-score |
1248 "l" gnus-category-list | 2546 "l" gnus-category-list |
1249 | 2547 |
1260 gnus-category-menu gnus-category-mode-map "" | 2558 gnus-category-menu gnus-category-mode-map "" |
1261 '("Categories" | 2559 '("Categories" |
1262 ["Add" gnus-category-add t] | 2560 ["Add" gnus-category-add t] |
1263 ["Kill" gnus-category-kill t] | 2561 ["Kill" gnus-category-kill t] |
1264 ["Copy" gnus-category-copy t] | 2562 ["Copy" gnus-category-copy t] |
2563 ["Edit category" gnus-agent-customize-category t] | |
1265 ["Edit predicate" gnus-category-edit-predicate t] | 2564 ["Edit predicate" gnus-category-edit-predicate t] |
1266 ["Edit score" gnus-category-edit-score t] | 2565 ["Edit score" gnus-category-edit-score t] |
1267 ["Edit groups" gnus-category-edit-groups t] | 2566 ["Edit groups" gnus-category-edit-groups t] |
1268 ["Exit" gnus-category-exit t])) | 2567 ["Exit" gnus-category-exit t])) |
1269 | 2568 |
1273 "Major mode for listing and editing agent categories. | 2572 "Major mode for listing and editing agent categories. |
1274 | 2573 |
1275 All normal editing commands are switched off. | 2574 All normal editing commands are switched off. |
1276 \\<gnus-category-mode-map> | 2575 \\<gnus-category-mode-map> |
1277 For more in-depth information on this mode, read the manual | 2576 For more in-depth information on this mode, read the manual |
1278 (`\\[gnus-info-find-node]'). | 2577 \(`\\[gnus-info-find-node]'). |
1279 | 2578 |
1280 The following commands are available: | 2579 The following commands are available: |
1281 | 2580 |
1282 \\{gnus-category-mode-map}" | 2581 \\{gnus-category-mode-map}" |
1283 (interactive) | 2582 (interactive) |
1291 (setq mode-line-process nil) | 2590 (setq mode-line-process nil) |
1292 (use-local-map gnus-category-mode-map) | 2591 (use-local-map gnus-category-mode-map) |
1293 (buffer-disable-undo) | 2592 (buffer-disable-undo) |
1294 (setq truncate-lines t) | 2593 (setq truncate-lines t) |
1295 (setq buffer-read-only t) | 2594 (setq buffer-read-only t) |
1296 (gnus-run-hooks 'gnus-category-mode-hook)) | 2595 (gnus-run-mode-hooks 'gnus-category-mode-hook)) |
1297 | 2596 |
1298 (defalias 'gnus-category-position-point 'gnus-goto-colon) | 2597 (defalias 'gnus-category-position-point 'gnus-goto-colon) |
1299 | 2598 |
1300 (defun gnus-category-insert-line (category) | 2599 (defun gnus-category-insert-line (category) |
1301 (let* ((gnus-tmp-name (car category)) | 2600 (let* ((gnus-tmp-name (format "%s" (car category))) |
1302 (gnus-tmp-groups (length (cadddr category)))) | 2601 (gnus-tmp-groups (length (gnus-agent-cat-groups category)))) |
1303 (beginning-of-line) | 2602 (beginning-of-line) |
1304 (gnus-add-text-properties | 2603 (gnus-add-text-properties |
1305 (point) | 2604 (point) |
1306 (prog1 (1+ (point)) | 2605 (prog1 (1+ (point)) |
1307 ;; Insert the text. | 2606 ;; Insert the text. |
1331 (gnus-category-insert-line (pop alist))) | 2630 (gnus-category-insert-line (pop alist))) |
1332 (goto-char (point-min)) | 2631 (goto-char (point-min)) |
1333 (gnus-category-position-point))) | 2632 (gnus-category-position-point))) |
1334 | 2633 |
1335 (defun gnus-category-name () | 2634 (defun gnus-category-name () |
1336 (or (get-text-property (gnus-point-at-bol) 'gnus-category) | 2635 (or (intern (get-text-property (gnus-point-at-bol) 'gnus-category)) |
1337 (error "No category on the current line"))) | 2636 (error "No category on the current line"))) |
1338 | 2637 |
1339 (defun gnus-category-read () | 2638 (defun gnus-category-read () |
1340 "Read the category alist." | 2639 "Read the category alist." |
1341 (setq gnus-category-alist | 2640 (setq gnus-category-alist |
1342 (or (gnus-agent-read-file | 2641 (or |
1343 (nnheader-concat gnus-agent-directory "lib/categories")) | 2642 (with-temp-buffer |
1344 (list (list 'default 'short nil nil))))) | 2643 (ignore-errors |
2644 (nnheader-insert-file-contents (nnheader-concat gnus-agent-directory "lib/categories")) | |
2645 (goto-char (point-min)) | |
2646 ;; This code isn't temp, it will be needed so long as | |
2647 ;; anyone may be migrating from an older version. | |
2648 | |
2649 ;; Once we're certain that people will not revert to an | |
2650 ;; earlier version, we can take out the old-list code in | |
2651 ;; gnus-category-write. | |
2652 (let* ((old-list (read (current-buffer))) | |
2653 (new-list (ignore-errors (read (current-buffer))))) | |
2654 (if new-list | |
2655 new-list | |
2656 ;; Convert from a positional list to an alist. | |
2657 (mapcar | |
2658 (lambda (c) | |
2659 (setcdr c | |
2660 (delq nil | |
2661 (gnus-mapcar | |
2662 (lambda (valu symb) | |
2663 (if valu | |
2664 (cons symb valu))) | |
2665 (cdr c) | |
2666 '(agent-predicate agent-score-file agent-groups)))) | |
2667 c) | |
2668 old-list))))) | |
2669 (list (gnus-agent-cat-make 'default 'short))))) | |
1345 | 2670 |
1346 (defun gnus-category-write () | 2671 (defun gnus-category-write () |
1347 "Write the category alist." | 2672 "Write the category alist." |
1348 (setq gnus-category-predicate-cache nil | 2673 (setq gnus-category-predicate-cache nil |
1349 gnus-category-group-cache nil) | 2674 gnus-category-group-cache nil) |
1350 (gnus-make-directory (nnheader-concat gnus-agent-directory "lib")) | 2675 (gnus-make-directory (nnheader-concat gnus-agent-directory "lib")) |
1351 (with-temp-file (nnheader-concat gnus-agent-directory "lib/categories") | 2676 (with-temp-file (nnheader-concat gnus-agent-directory "lib/categories") |
2677 ;; This prin1 is temporary. It exists so that people can revert | |
2678 ;; to an earlier version of gnus-agent. | |
2679 (prin1 (mapcar (lambda (c) | |
2680 (list (car c) | |
2681 (cdr (assoc 'agent-predicate c)) | |
2682 (cdr (assoc 'agent-score-file c)) | |
2683 (cdr (assoc 'agent-groups c)))) | |
2684 gnus-category-alist) | |
2685 (current-buffer)) | |
2686 (newline) | |
1352 (prin1 gnus-category-alist (current-buffer)))) | 2687 (prin1 gnus-category-alist (current-buffer)))) |
1353 | 2688 |
1354 (defun gnus-category-edit-predicate (category) | 2689 (defun gnus-category-edit-predicate (category) |
1355 "Edit the predicate for CATEGORY." | 2690 "Edit the predicate for CATEGORY." |
1356 (interactive (list (gnus-category-name))) | 2691 (interactive (list (gnus-category-name))) |
1357 (let ((info (assq category gnus-category-alist))) | 2692 (let ((info (assq category gnus-category-alist))) |
1358 (gnus-edit-form | 2693 (gnus-edit-form |
1359 (cadr info) (format "Editing the predicate for category %s" category) | 2694 (gnus-agent-cat-predicate info) |
2695 (format "Editing the select predicate for category %s" category) | |
1360 `(lambda (predicate) | 2696 `(lambda (predicate) |
1361 (setcar (cdr (assq ',category gnus-category-alist)) predicate) | 2697 ;; Avoid run-time execution of setf form |
2698 ;; (setf (gnus-agent-cat-predicate (assq ',category gnus-category-alist)) | |
2699 ;; predicate) | |
2700 ;; use its expansion instead: | |
2701 (gnus-agent-cat-set-property (assq ',category gnus-category-alist) | |
2702 'agent-predicate predicate) | |
2703 | |
1362 (gnus-category-write) | 2704 (gnus-category-write) |
1363 (gnus-category-list))))) | 2705 (gnus-category-list))))) |
1364 | 2706 |
1365 (defun gnus-category-edit-score (category) | 2707 (defun gnus-category-edit-score (category) |
1366 "Edit the score expression for CATEGORY." | 2708 "Edit the score expression for CATEGORY." |
1367 (interactive (list (gnus-category-name))) | 2709 (interactive (list (gnus-category-name))) |
1368 (let ((info (assq category gnus-category-alist))) | 2710 (let ((info (assq category gnus-category-alist))) |
1369 (gnus-edit-form | 2711 (gnus-edit-form |
1370 (caddr info) | 2712 (gnus-agent-cat-score-file info) |
1371 (format "Editing the score expression for category %s" category) | 2713 (format "Editing the score expression for category %s" category) |
1372 `(lambda (groups) | 2714 `(lambda (score-file) |
1373 (setcar (cddr (assq ',category gnus-category-alist)) groups) | 2715 ;; Avoid run-time execution of setf form |
2716 ;; (setf (gnus-agent-cat-score-file (assq ',category gnus-category-alist)) | |
2717 ;; score-file) | |
2718 ;; use its expansion instead: | |
2719 (gnus-agent-cat-set-property (assq ',category gnus-category-alist) | |
2720 'agent-score-file score-file) | |
2721 | |
1374 (gnus-category-write) | 2722 (gnus-category-write) |
1375 (gnus-category-list))))) | 2723 (gnus-category-list))))) |
1376 | 2724 |
1377 (defun gnus-category-edit-groups (category) | 2725 (defun gnus-category-edit-groups (category) |
1378 "Edit the group list for CATEGORY." | 2726 "Edit the group list for CATEGORY." |
1379 (interactive (list (gnus-category-name))) | 2727 (interactive (list (gnus-category-name))) |
1380 (let ((info (assq category gnus-category-alist))) | 2728 (let ((info (assq category gnus-category-alist))) |
1381 (gnus-edit-form | 2729 (gnus-edit-form |
1382 (cadddr info) (format "Editing the group list for category %s" category) | 2730 (gnus-agent-cat-groups info) |
2731 (format "Editing the group list for category %s" category) | |
1383 `(lambda (groups) | 2732 `(lambda (groups) |
1384 (setcar (nthcdr 3 (assq ',category gnus-category-alist)) groups) | 2733 ;; Avoid run-time execution of setf form |
2734 ;; (setf (gnus-agent-cat-groups (assq ',category gnus-category-alist)) | |
2735 ;; groups) | |
2736 ;; use its expansion instead: | |
2737 (gnus-agent-set-cat-groups (assq ',category gnus-category-alist) | |
2738 groups) | |
2739 | |
1385 (gnus-category-write) | 2740 (gnus-category-write) |
1386 (gnus-category-list))))) | 2741 (gnus-category-list))))) |
1387 | 2742 |
1388 (defun gnus-category-kill (category) | 2743 (defun gnus-category-kill (category) |
1389 "Kill the current category." | 2744 "Kill the current category." |
1396 | 2751 |
1397 (defun gnus-category-copy (category to) | 2752 (defun gnus-category-copy (category to) |
1398 "Copy the current category." | 2753 "Copy the current category." |
1399 (interactive (list (gnus-category-name) (intern (read-string "New name: ")))) | 2754 (interactive (list (gnus-category-name) (intern (read-string "New name: ")))) |
1400 (let ((info (assq category gnus-category-alist))) | 2755 (let ((info (assq category gnus-category-alist))) |
1401 (push (list to (gnus-copy-sequence (cadr info)) | 2756 (push (let ((newcat (gnus-copy-sequence info))) |
1402 (gnus-copy-sequence (caddr info)) nil) | 2757 (setf (gnus-agent-cat-name newcat) to) |
2758 (setf (gnus-agent-cat-groups newcat) nil) | |
2759 newcat) | |
1403 gnus-category-alist) | 2760 gnus-category-alist) |
1404 (gnus-category-write) | 2761 (gnus-category-write) |
1405 (gnus-category-list))) | 2762 (gnus-category-list))) |
1406 | 2763 |
1407 (defun gnus-category-add (category) | 2764 (defun gnus-category-add (category) |
1408 "Create a new category." | 2765 "Create a new category." |
1409 (interactive "SCategory name: ") | 2766 (interactive "SCategory name: ") |
1410 (when (assq category gnus-category-alist) | 2767 (when (assq category gnus-category-alist) |
1411 (error "Category %s already exists" category)) | 2768 (error "Category %s already exists" category)) |
1412 (push (list category 'false nil nil) | 2769 (push (gnus-agent-cat-make category) |
1413 gnus-category-alist) | 2770 gnus-category-alist) |
1414 (gnus-category-write) | 2771 (gnus-category-write) |
1415 (gnus-category-list)) | 2772 (gnus-category-list)) |
1416 | 2773 |
1417 (defun gnus-category-list () | 2774 (defun gnus-category-list () |
1432 '((spam . gnus-agent-spam-p) | 2789 '((spam . gnus-agent-spam-p) |
1433 (short . gnus-agent-short-p) | 2790 (short . gnus-agent-short-p) |
1434 (long . gnus-agent-long-p) | 2791 (long . gnus-agent-long-p) |
1435 (low . gnus-agent-low-scored-p) | 2792 (low . gnus-agent-low-scored-p) |
1436 (high . gnus-agent-high-scored-p) | 2793 (high . gnus-agent-high-scored-p) |
2794 (read . gnus-agent-read-p) | |
1437 (true . gnus-agent-true) | 2795 (true . gnus-agent-true) |
1438 (false . gnus-agent-false)) | 2796 (false . gnus-agent-false)) |
1439 "Mapping from short score predicate symbols to predicate functions.") | 2797 "Mapping from short score predicate symbols to predicate functions.") |
1440 | 2798 |
1441 (defun gnus-agent-spam-p () | 2799 (defun gnus-agent-spam-p () |
1463 | 2821 |
1464 (defun gnus-agent-high-scored-p () | 2822 (defun gnus-agent-high-scored-p () |
1465 "Say whether an article has a high score or not." | 2823 "Say whether an article has a high score or not." |
1466 (> gnus-score gnus-agent-high-score)) | 2824 (> gnus-score gnus-agent-high-score)) |
1467 | 2825 |
1468 (defun gnus-category-make-function (cat) | 2826 (defun gnus-agent-read-p () |
1469 "Make a function from category CAT." | 2827 "Say whether an article is read or not." |
1470 `(lambda () ,(gnus-category-make-function-1 cat))) | 2828 (gnus-member-of-range (mail-header-number gnus-headers) |
2829 (gnus-info-read (gnus-get-info gnus-newsgroup-name)))) | |
2830 | |
2831 (defun gnus-category-make-function (predicate) | |
2832 "Make a function from PREDICATE." | |
2833 (let ((func (gnus-category-make-function-1 predicate))) | |
2834 (if (and (= (length func) 1) | |
2835 (symbolp (car func))) | |
2836 (car func) | |
2837 (gnus-byte-compile `(lambda () ,func))))) | |
1471 | 2838 |
1472 (defun gnus-agent-true () | 2839 (defun gnus-agent-true () |
1473 "Return t." | 2840 "Return t." |
1474 t) | 2841 t) |
1475 | 2842 |
1476 (defun gnus-agent-false () | 2843 (defun gnus-agent-false () |
1477 "Return nil." | 2844 "Return nil." |
1478 nil) | 2845 nil) |
1479 | 2846 |
1480 (defun gnus-category-make-function-1 (cat) | 2847 (defun gnus-category-make-function-1 (predicate) |
1481 "Make a function from category CAT." | 2848 "Make a function from PREDICATE." |
1482 (cond | 2849 (cond |
1483 ;; Functions are just returned as is. | 2850 ;; Functions are just returned as is. |
1484 ((or (symbolp cat) | 2851 ((or (symbolp predicate) |
1485 (gnus-functionp cat)) | 2852 (functionp predicate)) |
1486 `(,(or (cdr (assq cat gnus-category-predicate-alist)) | 2853 `(,(or (cdr (assq predicate gnus-category-predicate-alist)) |
1487 cat))) | 2854 predicate))) |
1488 ;; More complex category. | 2855 ;; More complex predicate. |
1489 ((consp cat) | 2856 ((consp predicate) |
1490 `(,(cond | 2857 `(,(cond |
1491 ((memq (car cat) '(& and)) | 2858 ((memq (car predicate) '(& and)) |
1492 'and) | 2859 'and) |
1493 ((memq (car cat) '(| or)) | 2860 ((memq (car predicate) '(| or)) |
1494 'or) | 2861 'or) |
1495 ((memq (car cat) gnus-category-not) | 2862 ((memq (car predicate) gnus-category-not) |
1496 'not)) | 2863 'not)) |
1497 ,@(mapcar 'gnus-category-make-function-1 (cdr cat)))) | 2864 ,@(mapcar 'gnus-category-make-function-1 (cdr predicate)))) |
1498 (t | 2865 (t |
1499 (error "Unknown category type: %s" cat)))) | 2866 (error "Unknown predicate type: %s" predicate)))) |
1500 | 2867 |
1501 (defun gnus-get-predicate (predicate) | 2868 (defun gnus-get-predicate (predicate) |
1502 "Return the predicate for CATEGORY." | 2869 "Return the function implementing PREDICATE." |
1503 (or (cdr (assoc predicate gnus-category-predicate-cache)) | 2870 (or (cdr (assoc predicate gnus-category-predicate-cache)) |
1504 (cdar (push (cons predicate | 2871 (let ((func (gnus-category-make-function predicate))) |
1505 (gnus-category-make-function predicate)) | 2872 (setq gnus-category-predicate-cache |
1506 gnus-category-predicate-cache)))) | 2873 (nconc gnus-category-predicate-cache |
2874 (list (cons predicate func)))) | |
2875 func))) | |
2876 | |
2877 (defun gnus-predicate-implies-unread (predicate) | |
2878 "Say whether PREDICATE implies unread articles only. | |
2879 It is okay to miss some cases, but there must be no false positives. | |
2880 That is, if this predicate returns true, then indeed the predicate must | |
2881 return only unread articles." | |
2882 (eq t (gnus-function-implies-unread-1 | |
2883 (gnus-category-make-function-1 predicate)))) | |
2884 | |
2885 (defun gnus-function-implies-unread-1 (function) | |
2886 "Recursively evaluate a predicate function to determine whether it can select | |
2887 any read articles. Returns t if the function is known to never | |
2888 return read articles, nil when it is known to always return read | |
2889 articles, and t_nil when the function may return both read and unread | |
2890 articles." | |
2891 (let ((func (car function)) | |
2892 (args (mapcar 'gnus-function-implies-unread-1 (cdr function)))) | |
2893 (cond ((eq func 'and) | |
2894 (cond ((memq t args) ; if any argument returns only unread articles | |
2895 ;; then that argument constrains the result to only unread articles. | |
2896 t) | |
2897 ((memq 't_nil args) ; if any argument is indeterminate | |
2898 ;; then the result is indeterminate | |
2899 't_nil))) | |
2900 ((eq func 'or) | |
2901 (cond ((memq nil args) ; if any argument returns read articles | |
2902 ;; then that argument ensures that the results includes read articles. | |
2903 nil) | |
2904 ((memq 't_nil args) ; if any argument is indeterminate | |
2905 ;; then that argument ensures that the results are indeterminate | |
2906 't_nil) | |
2907 (t ; if all arguments return only unread articles | |
2908 ;; then the result returns only unread articles | |
2909 t))) | |
2910 ((eq func 'not) | |
2911 (cond ((eq (car args) 't_nil) ; if the argument is indeterminate | |
2912 ; then the result is indeterminate | |
2913 (car args)) | |
2914 (t ; otherwise | |
2915 ; toggle the result to be the opposite of the argument | |
2916 (not (car args))))) | |
2917 ((eq func 'gnus-agent-read-p) | |
2918 nil) ; The read predicate NEVER returns unread articles | |
2919 ((eq func 'gnus-agent-false) | |
2920 t) ; The false predicate returns t as the empty set excludes all read articles | |
2921 ((eq func 'gnus-agent-true) | |
2922 nil) ; The true predicate ALWAYS returns read articles | |
2923 ((catch 'found-match | |
2924 (let ((alist gnus-category-predicate-alist)) | |
2925 (while alist | |
2926 (if (eq func (cdar alist)) | |
2927 (throw 'found-match t) | |
2928 (setq alist (cdr alist)))))) | |
2929 't_nil) ; All other predicates return read and unread articles | |
2930 (t | |
2931 (error "Unknown predicate function: %s" function))))) | |
1507 | 2932 |
1508 (defun gnus-group-category (group) | 2933 (defun gnus-group-category (group) |
1509 "Return the category GROUP belongs to." | 2934 "Return the category GROUP belongs to." |
1510 (unless gnus-category-group-cache | 2935 (unless gnus-category-group-cache |
1511 (setq gnus-category-group-cache (gnus-make-hashtable 1000)) | 2936 (setq gnus-category-group-cache (gnus-make-hashtable 1000)) |
1512 (let ((cs gnus-category-alist) | 2937 (let ((cs gnus-category-alist) |
1513 groups cat) | 2938 groups cat) |
1514 (while (setq cat (pop cs)) | 2939 (while (setq cat (pop cs)) |
1515 (setq groups (cadddr cat)) | 2940 (setq groups (gnus-agent-cat-groups cat)) |
1516 (while groups | 2941 (while groups |
1517 (gnus-sethash (pop groups) cat gnus-category-group-cache))))) | 2942 (gnus-sethash (pop groups) cat gnus-category-group-cache))))) |
1518 (or (gnus-gethash group gnus-category-group-cache) | 2943 (or (gnus-gethash group gnus-category-group-cache) |
1519 (assq 'default gnus-category-alist))) | 2944 (assq 'default gnus-category-alist))) |
1520 | 2945 |
1521 (defun gnus-agent-expire () | 2946 (defun gnus-agent-expire-group (group &optional articles force) |
1522 "Expire all old articles." | 2947 "Expire all old articles in GROUP. |
2948 If you want to force expiring of certain articles, this function can | |
2949 take ARTICLES, and FORCE parameters as well. | |
2950 | |
2951 The articles on which the expiration process runs are selected as follows: | |
2952 if ARTICLES is null, all read and unmarked articles. | |
2953 if ARTICLES is t, all articles. | |
2954 if ARTICLES is a list, just those articles. | |
2955 FORCE is equivalent to setting the expiration predicates to true." | |
2956 (interactive | |
2957 (list (let ((def (or (gnus-group-group-name) | |
2958 gnus-newsgroup-name))) | |
2959 (let ((select (read-string (if def | |
2960 (concat "Group Name (" | |
2961 def "): ") | |
2962 "Group Name: ")))) | |
2963 (if (and (equal "" select) | |
2964 def) | |
2965 def | |
2966 select))))) | |
2967 | |
2968 (if (not group) | |
2969 (gnus-agent-expire articles group force) | |
2970 (let ( ;; Bind gnus-agent-expire-stats to enable tracking of | |
2971 ;; expiration statistics of this single group | |
2972 (gnus-agent-expire-stats (list 0 0 0.0))) | |
2973 (if (or (not (eq articles t)) | |
2974 (yes-or-no-p | |
2975 (concat "Are you sure that you want to " | |
2976 "expire all articles in " group "? "))) | |
2977 (let ((gnus-command-method (gnus-find-method-for-group group)) | |
2978 (overview (gnus-get-buffer-create " *expire overview*")) | |
2979 orig) | |
2980 (unwind-protect | |
2981 (let ((active-file (gnus-agent-lib-file "active"))) | |
2982 (when (file-exists-p active-file) | |
2983 (with-temp-buffer | |
2984 (nnheader-insert-file-contents active-file) | |
2985 (gnus-active-to-gnus-format | |
2986 gnus-command-method | |
2987 (setq orig (gnus-make-hashtable | |
2988 (count-lines (point-min) (point-max)))))) | |
2989 (save-excursion | |
2990 (gnus-agent-expire-group-1 | |
2991 group overview (gnus-gethash-safe group orig) | |
2992 articles force)))) | |
2993 (kill-buffer overview)))) | |
2994 (gnus-message 4 (gnus-agent-expire-done-message))))) | |
2995 | |
2996 (defun gnus-agent-expire-group-1 (group overview active articles force) | |
2997 ;; Internal function - requires caller to have set | |
2998 ;; gnus-command-method, initialized overview buffer, and to have | |
2999 ;; provided a non-nil active | |
3000 | |
3001 (let ((dir (gnus-agent-group-pathname group))) | |
3002 (when (boundp 'gnus-agent-expire-current-dirs) | |
3003 (set 'gnus-agent-expire-current-dirs | |
3004 (cons dir | |
3005 (symbol-value 'gnus-agent-expire-current-dirs)))) | |
3006 | |
3007 (if (and (not force) | |
3008 (eq 'DISABLE (gnus-agent-find-parameter group | |
3009 'agent-enable-expiration))) | |
3010 (gnus-message 5 "Expiry skipping over %s" group) | |
3011 (gnus-message 5 "Expiring articles in %s" group) | |
3012 (gnus-agent-load-alist group) | |
3013 (let* ((bytes-freed 0) | |
3014 (files-deleted 0) | |
3015 (nov-entries-deleted 0) | |
3016 (info (gnus-get-info group)) | |
3017 (alist gnus-agent-article-alist) | |
3018 (day (- (time-to-days (current-time)) | |
3019 (gnus-agent-find-parameter group 'agent-days-until-old))) | |
3020 (specials (if (and alist | |
3021 (not force)) | |
3022 ;; This could be a bit of a problem. I need to | |
3023 ;; keep the last article to avoid refetching | |
3024 ;; headers when using nntp in the backend. At | |
3025 ;; the same time, if someone uses a backend | |
3026 ;; that supports article moving then I may have | |
3027 ;; to remove the last article to complete the | |
3028 ;; move. Right now, I'm going to assume that | |
3029 ;; FORCE overrides specials. | |
3030 (list (caar (last alist))))) | |
3031 (unreads ;; Articles that are excluded from the | |
3032 ;; expiration process | |
3033 (cond (gnus-agent-expire-all | |
3034 ;; All articles are marked read by global decree | |
3035 nil) | |
3036 ((eq articles t) | |
3037 ;; All articles are marked read by function | |
3038 ;; parameter | |
3039 nil) | |
3040 ((not articles) | |
3041 ;; Unread articles are marked protected from | |
3042 ;; expiration Don't call | |
3043 ;; gnus-list-of-unread-articles as it returns | |
3044 ;; articles that have not been fetched into the | |
3045 ;; agent. | |
3046 (ignore-errors | |
3047 (gnus-agent-unread-articles group))) | |
3048 (t | |
3049 ;; All articles EXCEPT those named by the caller | |
3050 ;; are protected from expiration | |
3051 (gnus-sorted-difference | |
3052 (gnus-uncompress-range | |
3053 (cons (caar alist) | |
3054 (caar (last alist)))) | |
3055 (sort articles '<))))) | |
3056 (marked ;; More articles that are excluded from the | |
3057 ;; expiration process | |
3058 (cond (gnus-agent-expire-all | |
3059 ;; All articles are unmarked by global decree | |
3060 nil) | |
3061 ((eq articles t) | |
3062 ;; All articles are unmarked by function | |
3063 ;; parameter | |
3064 nil) | |
3065 (articles | |
3066 ;; All articles may as well be unmarked as the | |
3067 ;; unreads list already names the articles we are | |
3068 ;; going to keep | |
3069 nil) | |
3070 (t | |
3071 ;; Ticked and/or dormant articles are excluded | |
3072 ;; from expiration | |
3073 (nconc | |
3074 (gnus-uncompress-range | |
3075 (cdr (assq 'tick (gnus-info-marks info)))) | |
3076 (gnus-uncompress-range | |
3077 (cdr (assq 'dormant | |
3078 (gnus-info-marks info)))))))) | |
3079 (nov-file (concat dir ".overview")) | |
3080 (cnt 0) | |
3081 (completed -1) | |
3082 dlist | |
3083 type) | |
3084 | |
3085 ;; The normal article alist contains elements that look like | |
3086 ;; (article# . fetch_date) I need to combine other | |
3087 ;; information with this list. For example, a flag indicating | |
3088 ;; that a particular article MUST BE KEPT. To do this, I'm | |
3089 ;; going to transform the elements to look like (article# | |
3090 ;; fetch_date keep_flag NOV_entry_marker) Later, I'll reverse | |
3091 ;; the process to generate the expired article alist. | |
3092 | |
3093 ;; Convert the alist elements to (article# fetch_date nil | |
3094 ;; nil). | |
3095 (setq dlist (mapcar (lambda (e) | |
3096 (list (car e) (cdr e) nil nil)) alist)) | |
3097 | |
3098 ;; Convert the keep lists to elements that look like (article# | |
3099 ;; nil keep_flag nil) then append it to the expanded dlist | |
3100 ;; These statements are sorted by ascending precidence of the | |
3101 ;; keep_flag. | |
3102 (setq dlist (nconc dlist | |
3103 (mapcar (lambda (e) | |
3104 (list e nil 'unread nil)) | |
3105 unreads))) | |
3106 (setq dlist (nconc dlist | |
3107 (mapcar (lambda (e) | |
3108 (list e nil 'marked nil)) | |
3109 marked))) | |
3110 (setq dlist (nconc dlist | |
3111 (mapcar (lambda (e) | |
3112 (list e nil 'special nil)) | |
3113 specials))) | |
3114 | |
3115 (set-buffer overview) | |
3116 (erase-buffer) | |
3117 (buffer-disable-undo) | |
3118 (when (file-exists-p nov-file) | |
3119 (gnus-message 7 "gnus-agent-expire: Loading overview...") | |
3120 (nnheader-insert-file-contents nov-file) | |
3121 (goto-char (point-min)) | |
3122 | |
3123 (let (p) | |
3124 (while (< (setq p (point)) (point-max)) | |
3125 (condition-case nil | |
3126 ;; If I successfully read an integer (the plus zero | |
3127 ;; ensures a numeric type), prepend a marker entry | |
3128 ;; to the list | |
3129 (push (list (+ 0 (read (current-buffer))) nil nil | |
3130 (set-marker (make-marker) p)) | |
3131 dlist) | |
3132 (error | |
3133 (gnus-message 1 "gnus-agent-expire: read error \ | |
3134 occurred when reading expression at %s in %s. Skipping to next \ | |
3135 line." (point) nov-file))) | |
3136 ;; Whether I succeeded, or failed, it doesn't matter. | |
3137 ;; Move to the next line then try again. | |
3138 (forward-line 1))) | |
3139 | |
3140 (gnus-message | |
3141 7 "gnus-agent-expire: Loading overview... Done")) | |
3142 (set-buffer-modified-p nil) | |
3143 | |
3144 ;; At this point, all of the information is in dlist. The | |
3145 ;; only problem is that much of it is spread across multiple | |
3146 ;; entries. Sort then MERGE!! | |
3147 (gnus-message 7 "gnus-agent-expire: Sorting entries... ") | |
3148 ;; If two entries have the same article-number then sort by | |
3149 ;; ascending keep_flag. | |
3150 (let ((special 0) | |
3151 (marked 1) | |
3152 (unread 2)) | |
3153 (setq dlist | |
3154 (sort dlist | |
3155 (lambda (a b) | |
3156 (cond ((< (nth 0 a) (nth 0 b)) | |
3157 t) | |
3158 ((> (nth 0 a) (nth 0 b)) | |
3159 nil) | |
3160 (t | |
3161 (let ((a (or (symbol-value (nth 2 a)) | |
3162 3)) | |
3163 (b (or (symbol-value (nth 2 b)) | |
3164 3))) | |
3165 (<= a b)))))))) | |
3166 (gnus-message 7 "gnus-agent-expire: Sorting entries... Done") | |
3167 (gnus-message 7 "gnus-agent-expire: Merging entries... ") | |
3168 (let ((dlist dlist)) | |
3169 (while (cdr dlist) ; I'm not at the end-of-list | |
3170 (if (eq (caar dlist) (caadr dlist)) | |
3171 (let ((first (cdr (car dlist))) | |
3172 (secnd (cdr (cadr dlist)))) | |
3173 (setcar first (or (car first) | |
3174 (car secnd))) ; fetch_date | |
3175 (setq first (cdr first) | |
3176 secnd (cdr secnd)) | |
3177 (setcar first (or (car first) | |
3178 (car secnd))) ; Keep_flag | |
3179 (setq first (cdr first) | |
3180 secnd (cdr secnd)) | |
3181 (setcar first (or (car first) | |
3182 (car secnd))) ; NOV_entry_marker | |
3183 | |
3184 (setcdr dlist (cddr dlist))) | |
3185 (setq dlist (cdr dlist))))) | |
3186 (gnus-message 7 "gnus-agent-expire: Merging entries... Done") | |
3187 | |
3188 (let* ((len (float (length dlist))) | |
3189 (alist (list nil)) | |
3190 (tail-alist alist)) | |
3191 (while dlist | |
3192 (let ((new-completed (truncate (* 100.0 | |
3193 (/ (setq cnt (1+ cnt)) | |
3194 len)))) | |
3195 message-log-max) | |
3196 (when (> new-completed completed) | |
3197 (setq completed new-completed) | |
3198 (gnus-message 7 "%3d%% completed..." completed))) | |
3199 (let* ((entry (car dlist)) | |
3200 (article-number (nth 0 entry)) | |
3201 (fetch-date (nth 1 entry)) | |
3202 (keep (nth 2 entry)) | |
3203 (marker (nth 3 entry))) | |
3204 | |
3205 (cond | |
3206 ;; Kept articles are unread, marked, or special. | |
3207 (keep | |
3208 (gnus-agent-message 10 | |
3209 "gnus-agent-expire: %s:%d: Kept %s article%s." | |
3210 group article-number keep (if fetch-date " and file" "")) | |
3211 (when fetch-date | |
3212 (unless (file-exists-p | |
3213 (concat dir (number-to-string | |
3214 article-number))) | |
3215 (setf (nth 1 entry) nil) | |
3216 (gnus-agent-message 3 "gnus-agent-expire cleared \ | |
3217 download flag on %s:%d as the cached article file is missing." | |
3218 group (caar dlist))) | |
3219 (unless marker | |
3220 (gnus-message 1 "gnus-agent-expire detected a \ | |
3221 missing NOV entry. Run gnus-agent-regenerate-group to restore it."))) | |
3222 (gnus-agent-append-to-list | |
3223 tail-alist | |
3224 (cons article-number fetch-date))) | |
3225 | |
3226 ;; The following articles are READ, UNMARKED, and | |
3227 ;; ORDINARY. See if they can be EXPIRED!!! | |
3228 ((setq type | |
3229 (cond | |
3230 ((not (integerp fetch-date)) | |
3231 'read) ;; never fetched article (may expire | |
3232 ;; right now) | |
3233 ((not (file-exists-p | |
3234 (concat dir (number-to-string | |
3235 article-number)))) | |
3236 (setf (nth 1 entry) nil) | |
3237 'externally-expired) ;; Can't find the cached | |
3238 ;; article. Handle case | |
3239 ;; as though this article | |
3240 ;; was never fetched. | |
3241 | |
3242 ;; We now have the arrival day, so we see | |
3243 ;; whether it's old enough to be expired. | |
3244 ((< fetch-date day) | |
3245 'expired) | |
3246 (force | |
3247 'forced))) | |
3248 | |
3249 ;; I found some reason to expire this entry. | |
3250 | |
3251 (let ((actions nil)) | |
3252 (when (memq type '(forced expired)) | |
3253 (ignore-errors ; Just being paranoid. | |
3254 (let* ((file-name (nnheader-concat dir (number-to-string | |
3255 article-number))) | |
3256 (size (float (nth 7 (file-attributes file-name))))) | |
3257 (incf bytes-freed size) | |
3258 (incf files-deleted) | |
3259 (delete-file file-name)) | |
3260 (push "expired cached article" actions)) | |
3261 (setf (nth 1 entry) nil) | |
3262 ) | |
3263 | |
3264 (when marker | |
3265 (push "NOV entry removed" actions) | |
3266 (goto-char marker) | |
3267 | |
3268 (incf nov-entries-deleted) | |
3269 | |
3270 (let ((from (gnus-point-at-bol)) | |
3271 (to (progn (forward-line 1) (point)))) | |
3272 (incf bytes-freed (- to from)) | |
3273 (delete-region from to))) | |
3274 | |
3275 ;; If considering all articles is set, I can only | |
3276 ;; expire article IDs that are no longer in the | |
3277 ;; active range (That is, articles that preceed the | |
3278 ;; first article in the new alist). | |
3279 (if (and gnus-agent-consider-all-articles | |
3280 (>= article-number (car active))) | |
3281 ;; I have to keep this ID in the alist | |
3282 (gnus-agent-append-to-list | |
3283 tail-alist (cons article-number fetch-date)) | |
3284 (push (format "Removed %s article number from \ | |
3285 article alist" type) actions)) | |
3286 | |
3287 (when actions | |
3288 (gnus-agent-message 8 "gnus-agent-expire: %s:%d: %s" | |
3289 group article-number | |
3290 (mapconcat 'identity actions ", "))))) | |
3291 (t | |
3292 (gnus-agent-message | |
3293 10 "gnus-agent-expire: %s:%d: Article kept as \ | |
3294 expiration tests failed." group article-number) | |
3295 (gnus-agent-append-to-list | |
3296 tail-alist (cons article-number fetch-date))) | |
3297 ) | |
3298 | |
3299 ;; Clean up markers as I want to recycle this buffer | |
3300 ;; over several groups. | |
3301 (when marker | |
3302 (set-marker marker nil)) | |
3303 | |
3304 (setq dlist (cdr dlist)))) | |
3305 | |
3306 (setq alist (cdr alist)) | |
3307 | |
3308 (let ((inhibit-quit t)) | |
3309 (unless (equal alist gnus-agent-article-alist) | |
3310 (setq gnus-agent-article-alist alist) | |
3311 (gnus-agent-save-alist group)) | |
3312 | |
3313 (when (buffer-modified-p) | |
3314 (let ((coding-system-for-write | |
3315 gnus-agent-file-coding-system)) | |
3316 (gnus-make-directory dir) | |
3317 (write-region (point-min) (point-max) nov-file nil | |
3318 'silent) | |
3319 ;; clear the modified flag as that I'm not confused by | |
3320 ;; its status on the next pass through this routine. | |
3321 (set-buffer-modified-p nil))) | |
3322 | |
3323 (when (eq articles t) | |
3324 (gnus-summary-update-info)))) | |
3325 | |
3326 (when (boundp 'gnus-agent-expire-stats) | |
3327 (let ((stats (symbol-value 'gnus-agent-expire-stats))) | |
3328 (incf (nth 2 stats) bytes-freed) | |
3329 (incf (nth 1 stats) files-deleted) | |
3330 (incf (nth 0 stats) nov-entries-deleted))) | |
3331 )))) | |
3332 | |
3333 (defun gnus-agent-expire (&optional articles group force) | |
3334 "Expire all old articles. | |
3335 If you want to force expiring of certain articles, this function can | |
3336 take ARTICLES, GROUP and FORCE parameters as well. | |
3337 | |
3338 The articles on which the expiration process runs are selected as follows: | |
3339 if ARTICLES is null, all read and unmarked articles. | |
3340 if ARTICLES is t, all articles. | |
3341 if ARTICLES is a list, just those articles. | |
3342 Setting GROUP will limit expiration to that group. | |
3343 FORCE is equivalent to setting the expiration predicates to true." | |
1523 (interactive) | 3344 (interactive) |
1524 (let ((methods gnus-agent-covered-methods) | 3345 |
1525 (day (- (time-to-days (current-time)) gnus-agent-expire-days)) | 3346 (if group |
1526 gnus-command-method sym group articles | 3347 (gnus-agent-expire-group group articles force) |
1527 history overview file histories elem art nov-file low info | 3348 (if (or (not (eq articles t)) |
1528 unreads marked article orig lowest highest) | 3349 (yes-or-no-p "Are you sure that you want to expire all \ |
1529 (save-excursion | 3350 articles in every agentized group? ")) |
1530 (setq overview (gnus-get-buffer-create " *expire overview*")) | 3351 (let ((methods (gnus-agent-covered-methods)) |
1531 (while (setq gnus-command-method (pop methods)) | 3352 ;; Bind gnus-agent-expire-current-dirs to enable tracking |
1532 (when (file-exists-p (gnus-agent-lib-file "active")) | 3353 ;; of agent directories. |
1533 (with-temp-buffer | 3354 (gnus-agent-expire-current-dirs nil) |
1534 (nnheader-insert-file-contents (gnus-agent-lib-file "active")) | 3355 ;; Bind gnus-agent-expire-stats to enable tracking of |
1535 (gnus-active-to-gnus-format | 3356 ;; expiration statistics across all groups |
1536 gnus-command-method | 3357 (gnus-agent-expire-stats (list 0 0 0.0)) |
1537 (setq orig (gnus-make-hashtable | 3358 gnus-command-method overview orig) |
1538 (count-lines (point-min) (point-max)))))) | 3359 (setq overview (gnus-get-buffer-create " *expire overview*")) |
1539 (let ((expiry-hashtb (gnus-make-hashtable 1023))) | 3360 (unwind-protect |
1540 (gnus-agent-open-history) | 3361 (while (setq gnus-command-method (pop methods)) |
1541 (set-buffer | 3362 (let ((active-file (gnus-agent-lib-file "active"))) |
1542 (setq gnus-agent-current-history | 3363 (when (file-exists-p active-file) |
1543 (setq history (gnus-agent-history-buffer)))) | 3364 (with-temp-buffer |
1544 (goto-char (point-min)) | 3365 (nnheader-insert-file-contents active-file) |
1545 (when (> (buffer-size) 1) | 3366 (gnus-active-to-gnus-format |
1546 (goto-char (point-min)) | 3367 gnus-command-method |
1547 (while (not (eobp)) | 3368 (setq orig (gnus-make-hashtable |
1548 (skip-chars-forward "^\t") | 3369 (count-lines (point-min) (point-max)))))) |
1549 (if (> (read (current-buffer)) day) | 3370 (dolist (expiring-group (gnus-groups-from-server |
1550 ;; New article; we don't expire it. | 3371 gnus-command-method)) |
1551 (forward-line 1) | 3372 (let* ((active |
1552 ;; Old article. Schedule it for possible nuking. | 3373 (gnus-gethash-safe expiring-group orig))) |
1553 (while (not (eolp)) | 3374 |
1554 (setq sym (let ((obarray expiry-hashtb) s) | 3375 (when active |
1555 (setq s (read (current-buffer))) | 3376 (save-excursion |
1556 (if (stringp s) (intern s) s))) | 3377 (gnus-agent-expire-group-1 |
1557 (if (boundp sym) | 3378 expiring-group overview active articles force)))))))) |
1558 (set sym (cons (cons (read (current-buffer)) (point)) | 3379 (kill-buffer overview)) |
1559 (symbol-value sym))) | 3380 (gnus-agent-expire-unagentized-dirs) |
1560 (set sym (list (cons (read (current-buffer)) (point))))) | 3381 (gnus-message 4 (gnus-agent-expire-done-message)))))) |
1561 (skip-chars-forward " ")) | 3382 |
1562 (forward-line 1))) | 3383 (defun gnus-agent-expire-done-message () |
1563 ;; We now have all articles that can possibly be expired. | 3384 (if (and (> gnus-verbose 4) |
1564 (mapatoms | 3385 (boundp 'gnus-agent-expire-stats)) |
1565 (lambda (sym) | 3386 (let* ((stats (symbol-value 'gnus-agent-expire-stats)) |
1566 (setq group (symbol-name sym) | 3387 (size (nth 2 stats)) |
1567 articles (sort (symbol-value sym) 'car-less-than-car) | 3388 (units '(B KB MB GB))) |
1568 low (car (gnus-active group)) | 3389 (while (and (> size 1024.0) |
1569 info (gnus-get-info group) | 3390 (cdr units)) |
1570 unreads (ignore-errors | 3391 (setq size (/ size 1024.0) |
1571 (gnus-list-of-unread-articles group)) | 3392 units (cdr units))) |
1572 marked (nconc | 3393 |
1573 (gnus-uncompress-range | 3394 (format "Expiry recovered %d NOV entries, deleted %d files,\ |
1574 (cdr (assq 'tick (gnus-info-marks info)))) | 3395 and freed %f %s." |
1575 (gnus-uncompress-range | 3396 (nth 0 stats) |
1576 (cdr (assq 'dormant | 3397 (nth 1 stats) |
1577 (gnus-info-marks info))))) | 3398 size (car units))) |
1578 nov-file (gnus-agent-article-name ".overview" group) | 3399 "Expiry...done")) |
1579 lowest nil | 3400 |
1580 highest nil) | 3401 (defun gnus-agent-expire-unagentized-dirs () |
1581 (gnus-agent-load-alist group) | 3402 (when (and gnus-agent-expire-unagentized-dirs |
1582 (gnus-message 5 "Expiring articles in %s" group) | 3403 (boundp 'gnus-agent-expire-current-dirs)) |
1583 (set-buffer overview) | 3404 (let* ((keep (gnus-make-hashtable)) |
1584 (erase-buffer) | 3405 ;; Formally bind gnus-agent-expire-current-dirs so that the |
1585 (when (file-exists-p nov-file) | 3406 ;; compiler will not complain about free references. |
1586 (nnheader-insert-file-contents nov-file)) | 3407 (gnus-agent-expire-current-dirs |
1587 (goto-char (point-min)) | 3408 (symbol-value 'gnus-agent-expire-current-dirs)) |
1588 (setq article 0) | 3409 dir) |
1589 (while (setq elem (pop articles)) | 3410 |
1590 (setq article (car elem)) | 3411 (gnus-sethash gnus-agent-directory t keep) |
1591 (when (or (null low) | 3412 (while gnus-agent-expire-current-dirs |
1592 (< article low) | 3413 (setq dir (pop gnus-agent-expire-current-dirs)) |
1593 gnus-agent-expire-all | 3414 (when (and (stringp dir) |
1594 (and (not (memq article unreads)) | 3415 (file-directory-p dir)) |
1595 (not (memq article marked)))) | 3416 (while (not (gnus-gethash dir keep)) |
1596 ;; Find and nuke the NOV line. | 3417 (gnus-sethash dir t keep) |
1597 (while (and (not (eobp)) | 3418 (setq dir (file-name-directory (directory-file-name dir)))))) |
1598 (or (not (numberp | 3419 |
1599 (setq art (read (current-buffer))))) | 3420 (let* (to-remove |
1600 (< art article))) | 3421 checker |
1601 (if (and (numberp art) | 3422 (checker |
1602 (file-exists-p | 3423 (function |
1603 (gnus-agent-article-name | 3424 (lambda (d) |
1604 (number-to-string art) group))) | 3425 "Given a directory, check it and its subdirectories for |
1605 (progn | 3426 membership in the keep hash. If it isn't found, add |
1606 (unless lowest | 3427 it to to-remove." |
1607 (setq lowest art)) | 3428 (let ((files (directory-files d)) |
1608 (setq highest art) | 3429 file) |
1609 (forward-line 1)) | 3430 (while (setq file (pop files)) |
1610 ;; Remove old NOV lines that have no articles. | 3431 (cond ((equal file ".") ; Ignore self |
1611 (gnus-delete-line))) | 3432 nil) |
1612 (if (or (eobp) | 3433 ((equal file "..") ; Ignore parent |
1613 (/= art article)) | 3434 nil) |
1614 (beginning-of-line) | 3435 ((equal file ".overview") |
1615 (gnus-delete-line)) | 3436 ;; Directory must contain .overview to be |
1616 ;; Nuke the article. | 3437 ;; agent's cache of a group. |
1617 (when (file-exists-p | 3438 (let ((d (file-name-as-directory d)) |
1618 (setq file (gnus-agent-article-name | 3439 r) |
1619 (number-to-string article) | 3440 ;; Search ancestor's for last directory NOT |
1620 group))) | 3441 ;; found in keep hash. |
1621 (delete-file file)) | 3442 (while (not (gnus-gethash |
1622 ;; Schedule the history line for nuking. | 3443 (setq d (file-name-directory d)) keep)) |
1623 (push (cdr elem) histories))) | 3444 (setq r d |
1624 (gnus-make-directory (file-name-directory nov-file)) | 3445 d (directory-file-name d))) |
1625 (let ((coding-system-for-write | 3446 ;; if ANY ancestor was NOT in keep hash and |
1626 gnus-agent-file-coding-system)) | 3447 ;; it it's already in to-remove, add it to |
1627 (write-region (point-min) (point-max) nov-file nil 'silent)) | 3448 ;; to-remove. |
1628 ;; Delete the unwanted entries in the alist. | 3449 (if (and r |
1629 (setq gnus-agent-article-alist | 3450 (not (member r to-remove))) |
1630 (sort gnus-agent-article-alist 'car-less-than-car)) | 3451 (push r to-remove)))) |
1631 (let* ((alist gnus-agent-article-alist) | 3452 ((file-directory-p (setq file (nnheader-concat d file))) |
1632 (prev (cons nil alist)) | 3453 (funcall checker file))))))))) |
1633 (first prev) | 3454 (funcall checker (expand-file-name gnus-agent-directory)) |
1634 expired) | 3455 |
1635 (while (and alist | 3456 (when (and to-remove |
1636 (<= (caar alist) article)) | 3457 (or gnus-expert-user |
1637 (if (or (not (cdar alist)) | 3458 (gnus-y-or-n-p |
1638 (not (file-exists-p | 3459 "gnus-agent-expire has identified local directories that are\ |
1639 (gnus-agent-article-name | 3460 not currently required by any agentized group. Do you wish to consider\ |
1640 (number-to-string | 3461 deleting them?"))) |
1641 (caar alist)) | 3462 (while to-remove |
1642 group)))) | 3463 (let ((dir (pop to-remove))) |
1643 (progn | 3464 (if (gnus-y-or-n-p (format "Delete %s? " dir)) |
1644 (push (caar alist) expired) | 3465 (let* (delete-recursive |
1645 (setcdr prev (setq alist (cdr alist)))) | 3466 (delete-recursive |
1646 (setq prev alist | 3467 (function |
1647 alist (cdr alist)))) | 3468 (lambda (f-or-d) |
1648 (setq gnus-agent-article-alist (cdr first)) | 3469 (ignore-errors |
1649 (gnus-agent-save-alist group) | 3470 (if (file-directory-p f-or-d) |
1650 ;; Mark all articles up to the first article | 3471 (condition-case nil |
1651 ;; in `gnus-article-alist' as read. | 3472 (delete-directory f-or-d) |
1652 (when (and info (caar gnus-agent-article-alist)) | 3473 (file-error |
1653 (setcar (nthcdr 2 info) | 3474 (mapcar (lambda (f) |
1654 (gnus-range-add | 3475 (or (member f '("." "..")) |
1655 (nth 2 info) | 3476 (funcall delete-recursive |
1656 (cons 1 (- (caar gnus-agent-article-alist) 1))))) | 3477 (nnheader-concat |
1657 ;; Maybe everything has been expired from `gnus-article-alist' | 3478 f-or-d f)))) |
1658 ;; and so the above marking as read could not be conducted, | 3479 (directory-files f-or-d)) |
1659 ;; or there are expired article within the range of the alist. | 3480 (delete-directory f-or-d))) |
1660 (when (and info | 3481 (delete-file f-or-d))))))) |
1661 expired | 3482 (funcall delete-recursive dir)))))))))) |
1662 (or (not (caar gnus-agent-article-alist)) | |
1663 (> (car expired) | |
1664 (caar gnus-agent-article-alist)))) | |
1665 (setcar (nthcdr 2 info) | |
1666 (gnus-add-to-range | |
1667 (nth 2 info) | |
1668 (nreverse expired)))) | |
1669 (gnus-dribble-enter | |
1670 (concat "(gnus-group-set-info '" | |
1671 (gnus-prin1-to-string info) | |
1672 ")"))) | |
1673 (when lowest | |
1674 (if (gnus-gethash group orig) | |
1675 (setcar (gnus-gethash group orig) lowest) | |
1676 (gnus-sethash group (cons lowest highest) orig)))) | |
1677 expiry-hashtb) | |
1678 (set-buffer history) | |
1679 (setq histories (nreverse (sort histories '<))) | |
1680 (while histories | |
1681 (goto-char (pop histories)) | |
1682 (gnus-delete-line)) | |
1683 (gnus-agent-save-history) | |
1684 (gnus-agent-close-history) | |
1685 (gnus-write-active-file | |
1686 (gnus-agent-lib-file "active") orig)) | |
1687 (gnus-message 4 "Expiry...done"))))))) | |
1688 | 3483 |
1689 ;;;###autoload | 3484 ;;;###autoload |
1690 (defun gnus-agent-batch () | 3485 (defun gnus-agent-batch () |
3486 "Start Gnus, send queue and fetch session." | |
1691 (interactive) | 3487 (interactive) |
1692 (let ((init-file-user "") | 3488 (let ((init-file-user "") |
1693 (gnus-always-read-dribble-file t)) | 3489 (gnus-always-read-dribble-file t)) |
1694 (gnus)) | 3490 (gnus)) |
1695 (gnus-group-send-drafts) | 3491 (let ((gnus-agent-confirmation-function 'gnus-agent-batch-confirmation)) |
1696 (gnus-agent-fetch-session)) | 3492 (gnus-group-send-queue) |
3493 (gnus-agent-fetch-session))) | |
3494 | |
3495 (defun gnus-agent-unread-articles (group) | |
3496 (let* ((read (gnus-info-read (gnus-get-info group))) | |
3497 (known (gnus-agent-load-alist group)) | |
3498 (unread (list nil)) | |
3499 (tail-unread unread)) | |
3500 (while (and known read) | |
3501 (let ((candidate (car (pop known)))) | |
3502 (while (let* ((range (car read)) | |
3503 (min (if (numberp range) range (car range))) | |
3504 (max (if (numberp range) range (cdr range)))) | |
3505 (cond ((or (not min) | |
3506 (< candidate min)) | |
3507 (gnus-agent-append-to-list tail-unread candidate) | |
3508 nil) | |
3509 ((> candidate max) | |
3510 (setq read (cdr read)) | |
3511 ;; return t so that I always loop one more | |
3512 ;; time. If I just iterated off the end of | |
3513 ;; read, min will become nil and the current | |
3514 ;; candidate will be added to the unread list. | |
3515 t)))))) | |
3516 (while known | |
3517 (gnus-agent-append-to-list tail-unread (car (pop known)))) | |
3518 (cdr unread))) | |
3519 | |
3520 (defun gnus-agent-uncached-articles (articles group &optional cached-header) | |
3521 "Restrict ARTICLES to numbers already fetched. | |
3522 Returns a sublist of ARTICLES that excludes those article ids in GROUP | |
3523 that have already been fetched. | |
3524 If CACHED-HEADER is nil, articles are only excluded if the article itself | |
3525 has been fetched." | |
3526 | |
3527 ;; Logically equivalent to: (gnus-sorted-difference articles (mapcar | |
3528 ;; 'car gnus-agent-article-alist)) | |
3529 | |
3530 ;; Functionally, I don't need to construct a temp list using mapcar. | |
3531 | |
3532 (if (and (or gnus-agent-cache (not gnus-plugged)) | |
3533 (gnus-agent-load-alist group)) | |
3534 (let* ((ref gnus-agent-article-alist) | |
3535 (arts articles) | |
3536 (uncached (list nil)) | |
3537 (tail-uncached uncached)) | |
3538 (while (and ref arts) | |
3539 (let ((v1 (car arts)) | |
3540 (v2 (caar ref))) | |
3541 (cond ((< v1 v2) ; v1 does not appear in the reference list | |
3542 (gnus-agent-append-to-list tail-uncached v1) | |
3543 (setq arts (cdr arts))) | |
3544 ((= v1 v2) | |
3545 (unless (or cached-header (cdar ref)) ; v1 is already cached | |
3546 (gnus-agent-append-to-list tail-uncached v1)) | |
3547 (setq arts (cdr arts)) | |
3548 (setq ref (cdr ref))) | |
3549 (t ; reference article (v2) preceeds the list being filtered | |
3550 (setq ref (cdr ref)))))) | |
3551 (while arts | |
3552 (gnus-agent-append-to-list tail-uncached (pop arts))) | |
3553 (cdr uncached)) | |
3554 ;; if gnus-agent-load-alist fails, no articles are cached. | |
3555 articles)) | |
3556 | |
3557 (defun gnus-agent-retrieve-headers (articles group &optional fetch-old) | |
3558 (save-excursion | |
3559 (gnus-agent-create-buffer) | |
3560 (let ((gnus-decode-encoded-word-function 'identity) | |
3561 (file (gnus-agent-article-name ".overview" group)) | |
3562 cached-articles uncached-articles) | |
3563 (gnus-make-directory (nnheader-translate-file-chars | |
3564 (file-name-directory file) t)) | |
3565 | |
3566 ;; Populate temp buffer with known headers | |
3567 (when (file-exists-p file) | |
3568 (with-current-buffer gnus-agent-overview-buffer | |
3569 (erase-buffer) | |
3570 (let ((nnheader-file-coding-system | |
3571 gnus-agent-file-coding-system)) | |
3572 (nnheader-insert-nov-file file (car articles))))) | |
3573 | |
3574 (if (setq uncached-articles (gnus-agent-uncached-articles articles group | |
3575 t)) | |
3576 (progn | |
3577 ;; Populate nntp-server-buffer with uncached headers | |
3578 (set-buffer nntp-server-buffer) | |
3579 (erase-buffer) | |
3580 (cond ((not (eq 'nov (let (gnus-agent) ; Turn off agent | |
3581 (gnus-retrieve-headers | |
3582 uncached-articles group fetch-old)))) | |
3583 (nnvirtual-convert-headers)) | |
3584 ((eq 'nntp (car gnus-current-select-method)) | |
3585 ;; The author of gnus-get-newsgroup-headers-xover | |
3586 ;; reports that the XOVER command is commonly | |
3587 ;; unreliable. The problem is that recently | |
3588 ;; posted articles may not be entered into the | |
3589 ;; NOV database in time to respond to my XOVER | |
3590 ;; query. | |
3591 ;; | |
3592 ;; I'm going to use his assumption that the NOV | |
3593 ;; database is updated in order of ascending | |
3594 ;; article ID. Therefore, a response containing | |
3595 ;; article ID N implies that all articles from 1 | |
3596 ;; to N-1 are up-to-date. Therefore, missing | |
3597 ;; articles in that range have expired. | |
3598 | |
3599 (set-buffer nntp-server-buffer) | |
3600 (let* ((fetched-articles (list nil)) | |
3601 (tail-fetched-articles fetched-articles) | |
3602 (min (cond ((numberp fetch-old) | |
3603 (max 1 (- (car articles) fetch-old))) | |
3604 (fetch-old | |
3605 1) | |
3606 (t | |
3607 (car articles)))) | |
3608 (max (car (last articles)))) | |
3609 | |
3610 ;; Get the list of articles that were fetched | |
3611 (goto-char (point-min)) | |
3612 (let ((pm (point-max)) | |
3613 art) | |
3614 (while (< (point) pm) | |
3615 (when (setq art (gnus-agent-read-article-number)) | |
3616 (gnus-agent-append-to-list tail-fetched-articles art)) | |
3617 (forward-line 1))) | |
3618 | |
3619 ;; Clip this list to the headers that will | |
3620 ;; actually be returned | |
3621 (setq fetched-articles (gnus-list-range-intersection | |
3622 (cdr fetched-articles) | |
3623 (cons min max))) | |
3624 | |
3625 ;; Clip the uncached articles list to exclude | |
3626 ;; IDs after the last FETCHED header. The | |
3627 ;; excluded IDs may be fetchable using HEAD. | |
3628 (if (car tail-fetched-articles) | |
3629 (setq uncached-articles | |
3630 (gnus-list-range-intersection | |
3631 uncached-articles | |
3632 (cons (car uncached-articles) | |
3633 (car tail-fetched-articles))))) | |
3634 | |
3635 ;; Create the list of articles that were | |
3636 ;; "successfully" fetched. Success, in this | |
3637 ;; case, means that the ID should not be | |
3638 ;; fetched again. In the case of an expired | |
3639 ;; article, the header will not be fetched. | |
3640 (setq uncached-articles | |
3641 (gnus-sorted-nunion fetched-articles | |
3642 uncached-articles)) | |
3643 ))) | |
3644 | |
3645 ;; Erase the temp buffer | |
3646 (set-buffer gnus-agent-overview-buffer) | |
3647 (erase-buffer) | |
3648 | |
3649 ;; Copy the nntp-server-buffer to the temp buffer | |
3650 (set-buffer nntp-server-buffer) | |
3651 (copy-to-buffer gnus-agent-overview-buffer (point-min) (point-max)) | |
3652 | |
3653 ;; Merge the temp buffer with the known headers (found on | |
3654 ;; disk in FILE) into the nntp-server-buffer | |
3655 (when uncached-articles | |
3656 (gnus-agent-braid-nov group uncached-articles file)) | |
3657 | |
3658 ;; Save the new set of known headers to FILE | |
3659 (set-buffer nntp-server-buffer) | |
3660 (let ((coding-system-for-write | |
3661 gnus-agent-file-coding-system)) | |
3662 (gnus-agent-check-overview-buffer) | |
3663 (write-region (point-min) (point-max) file nil 'silent)) | |
3664 | |
3665 ;; Update the group's article alist to include the newly | |
3666 ;; fetched articles. | |
3667 (gnus-agent-load-alist group) | |
3668 (gnus-agent-save-alist group uncached-articles nil) | |
3669 ) | |
3670 | |
3671 ;; Copy the temp buffer to the nntp-server-buffer | |
3672 (set-buffer nntp-server-buffer) | |
3673 (erase-buffer) | |
3674 (insert-buffer-substring gnus-agent-overview-buffer))) | |
3675 | |
3676 (if (and fetch-old | |
3677 (not (numberp fetch-old))) | |
3678 t ; Don't remove anything. | |
3679 (nnheader-nov-delete-outside-range | |
3680 (if fetch-old (max 1 (- (car articles) fetch-old)) | |
3681 (car articles)) | |
3682 (car (last articles))) | |
3683 t) | |
3684 | |
3685 'nov)) | |
3686 | |
3687 (defun gnus-agent-request-article (article group) | |
3688 "Retrieve ARTICLE in GROUP from the agent cache." | |
3689 (when (and gnus-agent | |
3690 (or gnus-agent-cache | |
3691 (not gnus-plugged)) | |
3692 (numberp article)) | |
3693 (let* ((gnus-command-method (gnus-find-method-for-group group)) | |
3694 (file (gnus-agent-article-name (number-to-string article) group)) | |
3695 (buffer-read-only nil)) | |
3696 (when (and (file-exists-p file) | |
3697 (> (nth 7 (file-attributes file)) 0)) | |
3698 (erase-buffer) | |
3699 (gnus-kill-all-overlays) | |
3700 (let ((coding-system-for-read gnus-cache-coding-system)) | |
3701 (insert-file-contents file)) | |
3702 t)))) | |
3703 | |
3704 (defun gnus-agent-regenerate-group (group &optional reread) | |
3705 "Regenerate GROUP. | |
3706 If REREAD is t, all articles in the .overview are marked as unread. | |
3707 If REREAD is a list, the specified articles will be marked as unread. | |
3708 In addition, their NOV entries in .overview will be refreshed using | |
3709 the articles' current headers. | |
3710 If REREAD is not nil, downloaded articles are marked as unread." | |
3711 (interactive | |
3712 (list (let ((def (or (gnus-group-group-name) | |
3713 gnus-newsgroup-name))) | |
3714 (let ((select (read-string (if def | |
3715 (concat "Group Name (" | |
3716 def "): ") | |
3717 "Group Name: ")))) | |
3718 (if (and (equal "" select) | |
3719 def) | |
3720 def | |
3721 select))) | |
3722 (catch 'mark | |
3723 (while (let (c | |
3724 (cursor-in-echo-area t) | |
3725 (echo-keystrokes 0)) | |
3726 (message "Mark as unread: (n)one / (a)ll / all (d)ownloaded articles? (n) ") | |
3727 (setq c (read-char-exclusive)) | |
3728 | |
3729 (cond ((or (eq c ?\r) (eq c ?n) (eq c ?N)) | |
3730 (throw 'mark nil)) | |
3731 ((or (eq c ?a) (eq c ?A)) | |
3732 (throw 'mark t)) | |
3733 ((or (eq c ?d) (eq c ?D)) | |
3734 (throw 'mark 'some))) | |
3735 (gnus-message 3 "Ignoring unexpected input") | |
3736 (sit-for 1) | |
3737 t))))) | |
3738 (when group | |
3739 (gnus-message 5 "Regenerating in %s" group) | |
3740 (let* ((gnus-command-method (or gnus-command-method | |
3741 (gnus-find-method-for-group group))) | |
3742 (file (gnus-agent-article-name ".overview" group)) | |
3743 (dir (file-name-directory file)) | |
3744 point | |
3745 (downloaded (if (file-exists-p dir) | |
3746 (sort (mapcar (lambda (name) (string-to-number name)) | |
3747 (directory-files dir nil "^[0-9]+$" t)) | |
3748 '>) | |
3749 (progn (gnus-make-directory dir) nil))) | |
3750 dl nov-arts | |
3751 alist header | |
3752 regenerated) | |
3753 | |
3754 (mm-with-unibyte-buffer | |
3755 (if (file-exists-p file) | |
3756 (let ((nnheader-file-coding-system | |
3757 gnus-agent-file-coding-system)) | |
3758 (nnheader-insert-file-contents file))) | |
3759 (set-buffer-modified-p nil) | |
3760 | |
3761 ;; Load the article IDs found in the overview file. As a | |
3762 ;; side-effect, validate the file contents. | |
3763 (let ((load t)) | |
3764 (while load | |
3765 (setq load nil) | |
3766 (goto-char (point-min)) | |
3767 (while (< (point) (point-max)) | |
3768 (cond ((and (looking-at "[0-9]+\t") | |
3769 (<= (- (match-end 0) (match-beginning 0)) 9)) | |
3770 (push (read (current-buffer)) nov-arts) | |
3771 (forward-line 1) | |
3772 (let ((l1 (car nov-arts)) | |
3773 (l2 (cadr nov-arts))) | |
3774 (cond ((and (listp reread) (memq l1 reread)) | |
3775 (gnus-delete-line) | |
3776 (setq nov-arts (cdr nov-arts)) | |
3777 (gnus-message 4 "gnus-agent-regenerate-group: NOV\ | |
3778 entry of article %s deleted." l1)) | |
3779 ((not l2) | |
3780 nil) | |
3781 ((< l1 l2) | |
3782 (gnus-message 3 "gnus-agent-regenerate-group: NOV\ | |
3783 entries are NOT in ascending order.") | |
3784 ;; Don't sort now as I haven't verified | |
3785 ;; that every line begins with a number | |
3786 (setq load t)) | |
3787 ((= l1 l2) | |
3788 (forward-line -1) | |
3789 (gnus-message 4 "gnus-agent-regenerate-group: NOV\ | |
3790 entries contained duplicate of article %s. Duplicate deleted." l1) | |
3791 (gnus-delete-line) | |
3792 (setq nov-arts (cdr nov-arts)))))) | |
3793 (t | |
3794 (gnus-message 1 "gnus-agent-regenerate-group: NOV\ | |
3795 entries contained line that did not begin with an article number. Deleted\ | |
3796 line.") | |
3797 (gnus-delete-line)))) | |
3798 (when load | |
3799 (gnus-message 5 "gnus-agent-regenerate-group: Sorting NOV\ | |
3800 entries into ascending order.") | |
3801 (sort-numeric-fields 1 (point-min) (point-max)) | |
3802 (setq nov-arts nil)))) | |
3803 (gnus-agent-check-overview-buffer) | |
3804 | |
3805 ;; Construct a new article alist whose nodes match every header | |
3806 ;; in the .overview file. As a side-effect, missing headers are | |
3807 ;; reconstructed from the downloaded article file. | |
3808 (while (or downloaded nov-arts) | |
3809 (cond ((and downloaded | |
3810 (or (not nov-arts) | |
3811 (> (car downloaded) (car nov-arts)))) | |
3812 ;; This entry is missing from the overview file | |
3813 (gnus-message 3 "Regenerating NOV %s %d..." group | |
3814 (car downloaded)) | |
3815 (let ((file (concat dir (number-to-string (car downloaded))))) | |
3816 (mm-with-unibyte-buffer | |
3817 (nnheader-insert-file-contents file) | |
3818 (nnheader-remove-body) | |
3819 (setq header (nnheader-parse-naked-head))) | |
3820 (mail-header-set-number header (car downloaded)) | |
3821 (if nov-arts | |
3822 (let ((key (concat "^" (int-to-string (car nov-arts)) | |
3823 "\t"))) | |
3824 (or (re-search-backward key nil t) | |
3825 (re-search-forward key)) | |
3826 (forward-line 1)) | |
3827 (goto-char (point-min))) | |
3828 (nnheader-insert-nov header)) | |
3829 (setq nov-arts (cons (car downloaded) nov-arts))) | |
3830 ((eq (car downloaded) (car nov-arts)) | |
3831 ;; This entry in the overview has been downloaded | |
3832 (push (cons (car downloaded) | |
3833 (time-to-days | |
3834 (nth 5 (file-attributes | |
3835 (concat dir (number-to-string | |
3836 (car downloaded))))))) alist) | |
3837 (setq downloaded (cdr downloaded)) | |
3838 (setq nov-arts (cdr nov-arts))) | |
3839 (t | |
3840 ;; This entry in the overview has not been downloaded | |
3841 (push (cons (car nov-arts) nil) alist) | |
3842 (setq nov-arts (cdr nov-arts))))) | |
3843 | |
3844 ;; When gnus-agent-consider-all-articles is set, | |
3845 ;; gnus-agent-regenerate-group should NOT remove article IDs from | |
3846 ;; the alist. Those IDs serve as markers to indicate that an | |
3847 ;; attempt has been made to fetch that article's header. | |
3848 | |
3849 ;; When gnus-agent-consider-all-articles is NOT set, | |
3850 ;; gnus-agent-regenerate-group can remove the article ID of every | |
3851 ;; article (with the exception of the last ID in the list - it's | |
3852 ;; special) that no longer appears in the overview. In this | |
3853 ;; situtation, the last article ID in the list implies that it, | |
3854 ;; and every article ID preceeding it, have been fetched from the | |
3855 ;; server. | |
3856 | |
3857 (if gnus-agent-consider-all-articles | |
3858 ;; Restore all article IDs that were not found in the overview file. | |
3859 (let* ((n (cons nil alist)) | |
3860 (merged n) | |
3861 (o (gnus-agent-load-alist group))) | |
3862 (while o | |
3863 (let ((nID (caadr n)) | |
3864 (oID (caar o))) | |
3865 (cond ((not nID) | |
3866 (setq n (setcdr n (list (list oID)))) | |
3867 (setq o (cdr o))) | |
3868 ((< oID nID) | |
3869 (setcdr n (cons (list oID) (cdr n))) | |
3870 (setq o (cdr o))) | |
3871 ((= oID nID) | |
3872 (setq o (cdr o)) | |
3873 (setq n (cdr n))) | |
3874 (t | |
3875 (setq n (cdr n)))))) | |
3876 (setq alist (cdr merged))) | |
3877 ;; Restore the last article ID if it is not already in the new alist | |
3878 (let ((n (last alist)) | |
3879 (o (last (gnus-agent-load-alist group)))) | |
3880 (cond ((not o) | |
3881 nil) | |
3882 ((not n) | |
3883 (push (cons (caar o) nil) alist)) | |
3884 ((< (caar n) (caar o)) | |
3885 (setcdr n (list (car o))))))) | |
3886 | |
3887 (let ((inhibit-quit t)) | |
3888 (if (setq regenerated (buffer-modified-p)) | |
3889 (let ((coding-system-for-write gnus-agent-file-coding-system)) | |
3890 (write-region (point-min) (point-max) file nil 'silent))) | |
3891 | |
3892 (setq regenerated (or regenerated | |
3893 (and reread gnus-agent-article-alist) | |
3894 (not (equal alist gnus-agent-article-alist)))) | |
3895 | |
3896 (setq gnus-agent-article-alist alist) | |
3897 | |
3898 (when regenerated | |
3899 (gnus-agent-save-alist group) | |
3900 | |
3901 ;; I have to alter the group's active range NOW as | |
3902 ;; gnus-make-ascending-articles-unread will use it to | |
3903 ;; recalculate the number of unread articles in the group | |
3904 | |
3905 (let ((group (gnus-group-real-name group)) | |
3906 (group-active (or (gnus-active group) | |
3907 (gnus-activate-group group)))) | |
3908 (gnus-agent-possibly-alter-active group group-active))))) | |
3909 | |
3910 (when (and reread gnus-agent-article-alist) | |
3911 (gnus-agent-synchronize-group-flags | |
3912 group | |
3913 (list (list | |
3914 (if (listp reread) | |
3915 reread | |
3916 (delq nil (mapcar (function (lambda (c) | |
3917 (cond ((eq reread t) | |
3918 (car c)) | |
3919 ((cdr c) | |
3920 (car c))))) | |
3921 gnus-agent-article-alist))) | |
3922 'del '(read))) | |
3923 gnus-command-method) | |
3924 | |
3925 (when (gnus-buffer-live-p gnus-group-buffer) | |
3926 (gnus-group-update-group group t))) | |
3927 | |
3928 (gnus-message 5 "") | |
3929 regenerated))) | |
3930 | |
3931 ;;;###autoload | |
3932 (defun gnus-agent-regenerate (&optional clean reread) | |
3933 "Regenerate all agent covered files. | |
3934 If CLEAN, obsolete (ignore)." | |
3935 (interactive "P") | |
3936 (let (regenerated) | |
3937 (gnus-message 4 "Regenerating Gnus agent files...") | |
3938 (dolist (gnus-command-method (gnus-agent-covered-methods)) | |
3939 (dolist (group (gnus-groups-from-server gnus-command-method)) | |
3940 (setq regenerated (or (gnus-agent-regenerate-group group reread) | |
3941 regenerated)))) | |
3942 (gnus-message 4 "Regenerating Gnus agent files...done") | |
3943 | |
3944 regenerated)) | |
3945 | |
3946 (defun gnus-agent-go-online (&optional force) | |
3947 "Switch servers into online status." | |
3948 (interactive (list t)) | |
3949 (dolist (server gnus-opened-servers) | |
3950 (when (eq (nth 1 server) 'offline) | |
3951 (if (if (eq force 'ask) | |
3952 (gnus-y-or-n-p | |
3953 (format "Switch %s:%s into online status? " | |
3954 (caar server) (cadar server))) | |
3955 force) | |
3956 (setcar (nthcdr 1 server) 'close))))) | |
3957 | |
3958 (defun gnus-agent-toggle-group-plugged (group) | |
3959 "Toggle the status of the server of the current group." | |
3960 (interactive (list (gnus-group-group-name))) | |
3961 (let* ((method (gnus-find-method-for-group group)) | |
3962 (status (cadr (assoc method gnus-opened-servers)))) | |
3963 (if (eq status 'offline) | |
3964 (gnus-server-set-status method 'closed) | |
3965 (gnus-close-server method) | |
3966 (gnus-server-set-status method 'offline)) | |
3967 (message "Turn %s:%s from %s to %s." (car method) (cadr method) | |
3968 (if (eq status 'offline) 'offline 'online) | |
3969 (if (eq status 'offline) 'online 'offline)))) | |
3970 | |
3971 (defun gnus-agent-group-covered-p (group) | |
3972 (gnus-agent-method-p (gnus-group-method group))) | |
1697 | 3973 |
1698 (provide 'gnus-agent) | 3974 (provide 'gnus-agent) |
1699 | 3975 |
3976 ;;; arch-tag: b0ba4afc-5229-4cee-ad25-9956daa4e91e | |
1700 ;;; gnus-agent.el ends here | 3977 ;;; gnus-agent.el ends here |