Fix up comment convention on the arch-tag lines.
[bpt/emacs.git] / lisp / gnus / gnus-cache.el
CommitLineData
eec82323 1;;; gnus-cache.el --- cache interface for Gnus
e84b4b86 2
91472578 3;; Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003,
e3fe4da0 4;; 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
eec82323 5
6748645f 6;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
eec82323
LMI
7;; Keywords: news
8
9;; This file is part of GNU Emacs.
10
11;; GNU Emacs is free software; you can redistribute it and/or modify
12;; it under the terms of the GNU General Public License as published by
5a9dffec 13;; the Free Software Foundation; either version 3, or (at your option)
eec82323
LMI
14;; any later version.
15
16;; GNU Emacs is distributed in the hope that it will be useful,
17;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19;; GNU General Public License for more details.
20
21;; You should have received a copy of the GNU General Public License
22;; along with GNU Emacs; see the file COPYING. If not, write to the
3a35cf56
LK
23;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
24;; Boston, MA 02110-1301, USA.
eec82323
LMI
25
26;;; Commentary:
27
28;;; Code:
29
cef98b13
GM
30;; For Emacs < 22.2.
31(eval-and-compile
32 (unless (fboundp 'declare-function) (defmacro declare-function (&rest r))))
33
7df7482d
RS
34(eval-when-compile (require 'cl))
35
eec82323 36(require 'gnus)
cef98b13
GM
37(require 'gnus-sum)
38
eec82323 39(eval-when-compile
01c52d31 40 (unless (fboundp 'gnus-agent-load-alist)
cef98b13 41 (defun gnus-agent-load-alist (group))))
eec82323 42
eec82323 43(defcustom gnus-cache-active-file
6ab8077a 44 (expand-file-name "active" gnus-cache-directory)
eec82323
LMI
45 "*The cache active file."
46 :group 'gnus-cache
47 :type 'file)
48
49(defcustom gnus-cache-enter-articles '(ticked dormant)
50 "Classes of articles to enter into the cache."
51 :group 'gnus-cache
52 :type '(set (const ticked) (const dormant) (const unread) (const read)))
53
54(defcustom gnus-cache-remove-articles '(read)
55 "Classes of articles to remove from the cache."
56 :group 'gnus-cache
57 :type '(set (const ticked) (const dormant) (const unread) (const read)))
58
6748645f
LMI
59(defcustom gnus-cacheable-groups nil
60 "*Groups that match this regexp will be cached.
61
62If you only want to cache your nntp groups, you could set this
63variable to \"^nntp\".
64
65If a group matches both gnus-cacheable-groups and gnus-uncacheable-groups
66it's not cached."
67 :group 'gnus-cache
68 :type '(choice (const :tag "off" nil)
16409b0b 69 regexp))
6748645f 70
eec82323
LMI
71(defcustom gnus-uncacheable-groups nil
72 "*Groups that match this regexp will not be cached.
73
74If you want to avoid caching your nnml groups, you could set this
6748645f
LMI
75variable to \"^nnml\".
76
77If a group matches both gnus-cacheable-groups and gnus-uncacheable-groups
78it's not cached."
eec82323
LMI
79 :group 'gnus-cache
80 :type '(choice (const :tag "off" nil)
81 regexp))
82
6748645f
LMI
83(defvar gnus-cache-overview-coding-system 'raw-text
84 "Coding system used on Gnus cache files.")
85
16409b0b
GM
86(defvar gnus-cache-coding-system 'raw-text
87 "Coding system used on Gnus cache files.")
88
eec82323
LMI
89\f
90
91;;; Internal variables.
92
93(defvar gnus-cache-removable-articles nil)
94(defvar gnus-cache-buffer nil)
95(defvar gnus-cache-active-hashtb nil)
96(defvar gnus-cache-active-altered nil)
01c52d31 97(defvar gnus-cache-total-fetched-hashtb nil)
eec82323 98
cef98b13
GM
99(declare-function nnvirtual-find-group-art "nnvirtual" (group article))
100
eec82323 101(eval-and-compile
b890d447 102 (autoload 'nnml-generate-nov-databases-directory "nnml")
eec82323
LMI
103 (autoload 'nnvirtual-find-group-art "nnvirtual"))
104
105\f
106
107;;; Functions called from Gnus.
108
109(defun gnus-cache-open ()
110 "Initialize the cache."
111 (when (or (file-exists-p gnus-cache-directory)
112 (and gnus-use-cache
113 (not (eq gnus-use-cache 'passive))))
114 (gnus-cache-read-active)))
115
116;; Complexities of byte-compiling make this kludge necessary. Eeek.
117(ignore-errors
118 (gnus-add-shutdown 'gnus-cache-close 'gnus))
119
120(defun gnus-cache-close ()
121 "Shut down the cache."
122 (gnus-cache-write-active)
123 (gnus-cache-save-buffers)
124 (setq gnus-cache-active-hashtb nil))
125
126(defun gnus-cache-save-buffers ()
127 ;; save the overview buffer if it exists and has been modified
128 ;; delete empty cache subdirectories
129 (when gnus-cache-buffer
130 (let ((buffer (cdr gnus-cache-buffer))
131 (overview-file (gnus-cache-file-name
132 (car gnus-cache-buffer) ".overview")))
133 ;; write the overview only if it was modified
f4dd4ae8
MB
134 (when (and (buffer-live-p buffer) (buffer-modified-p buffer))
135 (with-current-buffer buffer
eec82323
LMI
136 (if (> (buffer-size) 0)
137 ;; Non-empty overview, write it to a file.
6748645f
LMI
138 (let ((coding-system-for-write
139 gnus-cache-overview-coding-system))
140 (gnus-write-buffer overview-file))
01c52d31
MB
141 (let ((file-name-coding-system nnmail-pathname-coding-system))
142 ;; Empty overview file, remove it
143 (when (file-exists-p overview-file)
144 (delete-file overview-file))
145 ;; If possible, remove group's cache subdirectory.
146 (condition-case nil
147 ;; FIXME: we can detect the error type and warn the user
148 ;; of any inconsistencies (articles w/o nov entries?).
149 ;; for now, just be conservative...delete only if safe -- sj
150 (delete-directory (file-name-directory overview-file))
151 (error))))
152
153 (gnus-cache-update-overview-total-fetched-for
154 (car gnus-cache-buffer) overview-file)))
eec82323
LMI
155 ;; Kill the buffer -- it's either unmodified or saved.
156 (gnus-kill-buffer buffer)
157 (setq gnus-cache-buffer nil))))
158
159(defun gnus-cache-possibly-enter-article
16409b0b 160 (group article ticked dormant unread &optional force)
eec82323
LMI
161 (when (and (or force (not (eq gnus-use-cache 'passive)))
162 (numberp article)
16409b0b 163 (> article 0)) ; This might be a dummy article.
01c52d31
MB
164 (let ((number article)
165 file headers lines-chars
166 (file-name-coding-system nnmail-pathname-coding-system))
16409b0b
GM
167 ;; If this is a virtual group, we find the real group.
168 (when (gnus-virtual-group-p group)
169 (let ((result (nnvirtual-find-group-art
170 (gnus-group-real-name group) article)))
171 (setq group (car result)
172 number (cdr result))))
a8151ef7
LMI
173 (when (and number
174 (> number 0) ; Reffed article.
eec82323 175 (or force
23f87bed 176 (and (gnus-cache-fully-p group)
eec82323
LMI
177 (gnus-cache-member-of-class
178 gnus-cache-enter-articles ticked dormant unread)))
179 (not (file-exists-p (setq file (gnus-cache-file-name
180 group number)))))
181 ;; Possibly create the cache directory.
6748645f 182 (gnus-make-directory (file-name-directory file))
eec82323
LMI
183 ;; Save the article in the cache.
184 (if (file-exists-p file)
185 t ; The article already is saved.
186 (save-excursion
187 (set-buffer nntp-server-buffer)
16409b0b
GM
188 (require 'gnus-art)
189 (let ((gnus-use-cache nil)
190 (gnus-article-decode-hook nil))
eec82323
LMI
191 (gnus-request-article-this-buffer number group))
192 (when (> (buffer-size) 0)
16409b0b 193 (let ((coding-system-for-write gnus-cache-coding-system))
01c52d31
MB
194 (gnus-write-buffer file)
195 (gnus-cache-update-file-total-fetched-for group file))
196 (setq lines-chars (nnheader-get-lines-and-char))
23f87bed
MB
197 (nnheader-remove-body)
198 (setq headers (nnheader-parse-naked-head))
16409b0b 199 (mail-header-set-number headers number)
01c52d31
MB
200 (mail-header-set-lines headers (car lines-chars))
201 (mail-header-set-chars headers (cadr lines-chars))
eec82323
LMI
202 (gnus-cache-change-buffer group)
203 (set-buffer (cdr gnus-cache-buffer))
204 (goto-char (point-max))
205 (forward-line -1)
206 (while (condition-case ()
207 (when (not (bobp))
208 (> (read (current-buffer)) number))
209 (error
210 ;; The line was malformed, so we just remove it!!
211 (gnus-delete-line)
212 t))
213 (forward-line -1))
214 (if (bobp)
215 (if (not (eobp))
216 (progn
217 (beginning-of-line)
218 (when (< (read (current-buffer)) number)
219 (forward-line 1)))
220 (beginning-of-line))
221 (forward-line 1))
222 (beginning-of-line)
16409b0b 223 (nnheader-insert-nov headers)
eec82323
LMI
224 ;; Update the active info.
225 (set-buffer gnus-summary-buffer)
23f87bed
MB
226 (gnus-cache-possibly-update-active group (cons number number))
227 (setq gnus-newsgroup-cached
228 (gnus-add-to-sorted-list gnus-newsgroup-cached article))
eec82323
LMI
229 (gnus-summary-update-secondary-mark article))
230 t))))))
231
232(defun gnus-cache-enter-remove-article (article)
233 "Mark ARTICLE for later possible removal."
234 (when article
235 (push article gnus-cache-removable-articles)))
236
237(defun gnus-cache-possibly-remove-articles ()
238 "Possibly remove some of the removable articles."
239 (if (not (gnus-virtual-group-p gnus-newsgroup-name))
240 (gnus-cache-possibly-remove-articles-1)
241 (let ((arts gnus-cache-removable-articles)
242 ga)
243 (while arts
244 (when (setq ga (nnvirtual-find-group-art
245 (gnus-group-real-name gnus-newsgroup-name) (pop arts)))
246 (let ((gnus-cache-removable-articles (list (cdr ga)))
247 (gnus-newsgroup-name (car ga)))
248 (gnus-cache-possibly-remove-articles-1)))))
249 (setq gnus-cache-removable-articles nil)))
250
251(defun gnus-cache-possibly-remove-articles-1 ()
252 "Possibly remove some of the removable articles."
23f87bed 253 (when (gnus-cache-fully-p gnus-newsgroup-name)
01c52d31 254 (let ((cache-articles gnus-newsgroup-cached))
eec82323 255 (gnus-cache-change-buffer gnus-newsgroup-name)
01c52d31
MB
256 (dolist (article gnus-cache-removable-articles)
257 (when (memq article cache-articles)
eec82323
LMI
258 ;; The article was in the cache, so we see whether we are
259 ;; supposed to remove it from the cache.
260 (gnus-cache-possibly-remove-article
261 article (memq article gnus-newsgroup-marked)
262 (memq article gnus-newsgroup-dormant)
263 (or (memq article gnus-newsgroup-unreads)
264 (memq article gnus-newsgroup-unselected))))))
265 ;; The overview file might have been modified, save it
266 ;; safe because we're only called at group exit anyway.
267 (gnus-cache-save-buffers)))
268
269(defun gnus-cache-request-article (article group)
270 "Retrieve ARTICLE in GROUP from the cache."
271 (let ((file (gnus-cache-file-name group article))
01c52d31
MB
272 (buffer-read-only nil)
273 (file-name-coding-system nnmail-pathname-coding-system))
eec82323
LMI
274 (when (file-exists-p file)
275 (erase-buffer)
276 (gnus-kill-all-overlays)
16409b0b
GM
277 (let ((coding-system-for-read gnus-cache-coding-system))
278 (insert-file-contents file))
eec82323
LMI
279 t)))
280
281(defun gnus-cache-possibly-alter-active (group active)
282 "Alter the ACTIVE info for GROUP to reflect the articles in the cache."
eec82323
LMI
283 (when gnus-cache-active-hashtb
284 (let ((cache-active (gnus-gethash group gnus-cache-active-hashtb)))
a8151ef7
LMI
285 (when cache-active
286 (when (< (car cache-active) (car active))
287 (setcar active (car cache-active)))
288 (when (> (cdr cache-active) (cdr active))
289 (setcdr active (cdr cache-active)))))))
eec82323
LMI
290
291(defun gnus-cache-retrieve-headers (articles group &optional fetch-old)
292 "Retrieve the headers for ARTICLES in GROUP."
293 (let ((cached
294 (setq gnus-newsgroup-cached (gnus-cache-articles-in-group group))))
295 (if (not cached)
296 ;; No cached articles here, so we just retrieve them
297 ;; the normal way.
298 (let ((gnus-use-cache nil))
299 (gnus-retrieve-headers articles group fetch-old))
23f87bed 300 (let ((uncached-articles (gnus-sorted-difference articles cached))
eec82323 301 (cache-file (gnus-cache-file-name group ".overview"))
01c52d31
MB
302 type
303 (file-name-coding-system nnmail-pathname-coding-system))
eec82323
LMI
304 ;; We first retrieve all the headers that we don't have in
305 ;; the cache.
306 (let ((gnus-use-cache nil))
307 (when uncached-articles
308 (setq type (and articles
309 (gnus-retrieve-headers
310 uncached-articles group fetch-old)))))
311 (gnus-cache-save-buffers)
312 ;; Then we insert the cached headers.
313 (save-excursion
314 (cond
315 ((not (file-exists-p cache-file))
316 ;; There are no cached headers.
317 type)
318 ((null type)
319 ;; There were no uncached headers (or retrieval was
320 ;; unsuccessful), so we use the cached headers exclusively.
321 (set-buffer nntp-server-buffer)
322 (erase-buffer)
a1506d29 323 (let ((coding-system-for-read
16409b0b
GM
324 gnus-cache-overview-coding-system))
325 (insert-file-contents cache-file))
eec82323
LMI
326 'nov)
327 ((eq type 'nov)
328 ;; We have both cached and uncached NOV headers, so we
329 ;; braid them.
330 (gnus-cache-braid-nov group cached)
331 type)
332 (t
333 ;; We braid HEADs.
334 (gnus-cache-braid-heads group (gnus-sorted-intersection
335 cached articles))
336 type)))))))
337
338(defun gnus-cache-enter-article (&optional n)
339 "Enter the next N articles into the cache.
340If not given a prefix, use the process marked articles instead.
341Returns the list of articles entered."
342 (interactive "P")
01c52d31
MB
343 (let (out)
344 (dolist (article (gnus-summary-work-articles n))
6748645f 345 (gnus-summary-remove-process-mark article)
eec82323
LMI
346 (if (natnump article)
347 (when (gnus-cache-possibly-enter-article
348 gnus-newsgroup-name article
eec82323 349 nil nil nil t)
23f87bed 350 (setq gnus-newsgroup-undownloaded (delq article gnus-newsgroup-undownloaded))
eec82323
LMI
351 (push article out))
352 (gnus-message 2 "Can't cache article %d" article))
23f87bed 353 (gnus-summary-update-download-mark article)
eec82323
LMI
354 (gnus-summary-update-secondary-mark article))
355 (gnus-summary-next-subject 1)
356 (gnus-summary-position-point)
357 (nreverse out)))
358
23f87bed 359(defun gnus-cache-remove-article (&optional n)
eec82323
LMI
360 "Remove the next N articles from the cache.
361If not given a prefix, use the process marked articles instead.
362Returns the list of articles removed."
363 (interactive "P")
eec82323 364 (gnus-cache-change-buffer gnus-newsgroup-name)
01c52d31
MB
365 (let (out)
366 (dolist (article (gnus-summary-work-articles n))
6748645f 367 (gnus-summary-remove-process-mark article)
eec82323 368 (when (gnus-cache-possibly-remove-article article nil nil nil t)
23f87bed
MB
369 (when gnus-newsgroup-agentized
370 (let ((alist (gnus-agent-load-alist gnus-newsgroup-name)))
371 (unless (cdr (assoc article alist))
372 (setq gnus-newsgroup-undownloaded
e84b4b86 373 (gnus-add-to-sorted-list
23f87bed 374 gnus-newsgroup-undownloaded article)))))
eec82323 375 (push article out))
23f87bed 376 (gnus-summary-update-download-mark article)
eec82323
LMI
377 (gnus-summary-update-secondary-mark article))
378 (gnus-summary-next-subject 1)
379 (gnus-summary-position-point)
380 (nreverse out)))
381
382(defun gnus-cached-article-p (article)
383 "Say whether ARTICLE is cached in the current group."
384 (memq article gnus-newsgroup-cached))
385
386(defun gnus-summary-insert-cached-articles ()
387 "Insert all the articles cached for this group into the current buffer."
388 (interactive)
23f87bed
MB
389 (let ((gnus-verbose (max 6 gnus-verbose)))
390 (if (not gnus-newsgroup-cached)
391 (gnus-message 3 "No cached articles for this group")
392 (gnus-summary-goto-subjects gnus-newsgroup-cached))))
eec82323 393
23f87bed
MB
394(defun gnus-summary-limit-include-cached ()
395 "Limit the summary buffer to articles that are cached."
396 (interactive)
397 (let ((gnus-verbose (max 6 gnus-verbose)))
398 (if gnus-newsgroup-cached
399 (progn
400 (gnus-summary-limit gnus-newsgroup-cached)
401 (gnus-summary-position-point))
402 (gnus-message 3 "No cached articles for this group"))))
6748645f 403
eec82323
LMI
404;;; Internal functions.
405
406(defun gnus-cache-change-buffer (group)
407 (and gnus-cache-buffer
408 ;; See if the current group's overview cache has been loaded.
409 (or (string= group (car gnus-cache-buffer))
410 ;; Another overview cache is current, save it.
411 (gnus-cache-save-buffers)))
412 ;; if gnus-cache buffer is nil, create it
413 (unless gnus-cache-buffer
414 ;; Create cache buffer
415 (save-excursion
416 (setq gnus-cache-buffer
417 (cons group
6748645f
LMI
418 (set-buffer (gnus-get-buffer-create
419 " *gnus-cache-overview*"))))
eec82323
LMI
420 ;; Insert the contents of this group's cache overview.
421 (erase-buffer)
01c52d31
MB
422 (let ((file (gnus-cache-file-name group ".overview"))
423 (file-name-coding-system nnmail-pathname-coding-system))
eec82323
LMI
424 (when (file-exists-p file)
425 (nnheader-insert-file-contents file)))
426 ;; We have a fresh (empty/just loaded) buffer,
427 ;; mark it as unmodified to save a redundant write later.
428 (set-buffer-modified-p nil))))
429
430;; Return whether an article is a member of a class.
431(defun gnus-cache-member-of-class (class ticked dormant unread)
432 (or (and ticked (memq 'ticked class))
433 (and dormant (memq 'dormant class))
434 (and unread (memq 'unread class))
435 (and (not unread) (not ticked) (not dormant) (memq 'read class))))
436
01c52d31
MB
437(defvar gnus-cache-decoded-group-names nil
438 "Alist of original group names and decoded group names.
439Decoding is done according to `gnus-group-name-charset-method-alist'
440or `gnus-group-name-charset-group-alist'.")
441
442(defvar gnus-cache-unified-group-names nil
443 "Alist of unified decoded group names and original group names.
444A group name is decoded according to
445`gnus-group-name-charset-method-alist' or
446`gnus-group-name-charset-group-alist' first, and is encoded and
447decoded again according to `nnmail-pathname-coding-system',
448`file-name-coding-system', or `default-file-name-coding-system'.
449
450It is used when asking for a original group name from a cache
451directory name, in which non-ASCII characters might have been unified
452into the ones of a certain charset particularly if the `utf-8' coding
453system for example was used.")
454
455(defun gnus-cache-decoded-group-name (group)
456 "Return a decoded group name of GROUP."
457 (or (cdr (assoc group gnus-cache-decoded-group-names))
458 (let ((decoded (gnus-group-decoded-name group))
459 (coding (or nnmail-pathname-coding-system
460 (and (boundp 'file-name-coding-system)
461 file-name-coding-system)
462 (and (boundp 'default-file-name-coding-system)
463 default-file-name-coding-system))))
464 (push (cons group decoded) gnus-cache-decoded-group-names)
465 (push (cons (mm-decode-coding-string
466 (mm-encode-coding-string decoded coding)
467 coding)
468 group)
469 gnus-cache-unified-group-names)
470 decoded)))
471
eec82323 472(defun gnus-cache-file-name (group article)
01c52d31 473 (setq group (gnus-cache-decoded-group-name group))
6ab8077a
DL
474 (expand-file-name
475 (if (stringp article) article (int-to-string article))
476 (file-name-as-directory
477 (expand-file-name
478 (nnheader-translate-file-chars
479 (if (gnus-use-long-file-name 'not-cache)
480 group
481 (let ((group (nnheader-replace-duplicate-chars-in-string
482 (nnheader-replace-chars-in-string group ?/ ?_)
483 ?. ?_)))
484 ;; Translate the first colon into a slash.
485 (when (string-match ":" group)
23f87bed
MB
486 (setq group (concat (substring group 0 (match-beginning 0))
487 "/" (substring group (match-end 0)))))
6ab8077a
DL
488 (nnheader-replace-chars-in-string group ?. ?/)))
489 t)
490 gnus-cache-directory))))
eec82323
LMI
491
492(defun gnus-cache-update-article (group article)
493 "If ARTICLE is in the cache, remove it and re-enter it."
6748645f 494 (gnus-cache-change-buffer group)
16409b0b 495 (when (gnus-cache-possibly-remove-article article nil nil nil t)
eec82323
LMI
496 (let ((gnus-use-cache nil))
497 (gnus-cache-possibly-enter-article
16409b0b 498 gnus-newsgroup-name article
eec82323
LMI
499 nil nil nil t))))
500
501(defun gnus-cache-possibly-remove-article (article ticked dormant unread
502 &optional force)
503 "Possibly remove ARTICLE from the cache."
504 (let ((group gnus-newsgroup-name)
505 (number article)
01c52d31
MB
506 file
507 (file-name-coding-system nnmail-pathname-coding-system))
eec82323
LMI
508 ;; If this is a virtual group, we find the real group.
509 (when (gnus-virtual-group-p group)
510 (let ((result (nnvirtual-find-group-art
511 (gnus-group-real-name group) article)))
512 (setq group (car result)
513 number (cdr result))))
514 (setq file (gnus-cache-file-name group number))
515 (when (and (file-exists-p file)
516 (or force
517 (gnus-cache-member-of-class
518 gnus-cache-remove-articles ticked dormant unread)))
519 (save-excursion
01c52d31 520 (gnus-cache-update-file-total-fetched-for group file t)
eec82323 521 (delete-file file)
01c52d31 522
eec82323
LMI
523 (set-buffer (cdr gnus-cache-buffer))
524 (goto-char (point-min))
525 (when (or (looking-at (concat (int-to-string number) "\t"))
526 (search-forward (concat "\n" (int-to-string number) "\t")
527 (point-max) t))
01c52d31 528 (gnus-delete-line)))
23f87bed
MB
529 (unless (setq gnus-newsgroup-cached
530 (delq article gnus-newsgroup-cached))
531 (gnus-sethash gnus-newsgroup-name nil gnus-cache-active-hashtb)
532 (setq gnus-cache-active-altered t))
eec82323
LMI
533 (gnus-summary-update-secondary-mark article)
534 t)))
535
536(defun gnus-cache-articles-in-group (group)
537 "Return a sorted list of cached articles in GROUP."
a8151ef7 538 (let ((dir (file-name-directory (gnus-cache-file-name group 1)))
01c52d31
MB
539 articles
540 (file-name-coding-system nnmail-pathname-coding-system))
eec82323 541 (when (file-exists-p dir)
a8151ef7 542 (setq articles
e9bd5782 543 (sort (mapcar (lambda (name) (string-to-number name))
a8151ef7
LMI
544 (directory-files dir nil "^[0-9]+$" t))
545 '<))
546 ;; Update the cache active file, just to synch more.
23f87bed
MB
547 (if articles
548 (progn
549 (gnus-cache-update-active group (car articles) t)
550 (gnus-cache-update-active group (car (last articles))))
551 (when (gnus-gethash group gnus-cache-active-hashtb)
552 (gnus-sethash group nil gnus-cache-active-hashtb)
553 (setq gnus-cache-active-altered t)))
a8151ef7
LMI
554 articles)))
555
556(defun gnus-cache-braid-nov (group cached &optional file)
6748645f 557 (let ((cache-buf (gnus-get-buffer-create " *gnus-cache*"))
eec82323
LMI
558 beg end)
559 (gnus-cache-save-buffers)
560 (save-excursion
561 (set-buffer cache-buf)
eec82323 562 (erase-buffer)
01c52d31
MB
563 (let ((coding-system-for-read gnus-cache-overview-coding-system)
564 (file-name-coding-system nnmail-pathname-coding-system))
a1506d29 565 (insert-file-contents
16409b0b 566 (or file (gnus-cache-file-name group ".overview"))))
eec82323
LMI
567 (goto-char (point-min))
568 (insert "\n")
569 (goto-char (point-min)))
570 (set-buffer nntp-server-buffer)
571 (goto-char (point-min))
572 (while cached
573 (while (and (not (eobp))
574 (< (read (current-buffer)) (car cached)))
575 (forward-line 1))
576 (beginning-of-line)
23f87bed
MB
577 (set-buffer cache-buf)
578 (if (search-forward (concat "\n" (int-to-string (car cached)) "\t")
579 nil t)
01c52d31 580 (setq beg (point-at-bol)
23f87bed
MB
581 end (progn (end-of-line) (point)))
582 (setq beg nil))
583 (set-buffer nntp-server-buffer)
eec82323
LMI
584 (when beg
585 (insert-buffer-substring cache-buf beg end)
586 (insert "\n"))
587 (setq cached (cdr cached)))
588 (kill-buffer cache-buf)))
589
590(defun gnus-cache-braid-heads (group cached)
6748645f 591 (let ((cache-buf (gnus-get-buffer-create " *gnus-cache*")))
01c52d31 592 (with-current-buffer cache-buf
eec82323
LMI
593 (erase-buffer))
594 (set-buffer nntp-server-buffer)
595 (goto-char (point-min))
01c52d31 596 (dolist (entry cached)
eec82323
LMI
597 (while (and (not (eobp))
598 (looking-at "2.. +\\([0-9]+\\) ")
599 (< (progn (goto-char (match-beginning 1))
600 (read (current-buffer)))
01c52d31 601 entry))
eec82323
LMI
602 (search-forward "\n.\n" nil 'move))
603 (beginning-of-line)
23f87bed
MB
604 (set-buffer cache-buf)
605 (erase-buffer)
01c52d31
MB
606 (let ((coding-system-for-read gnus-cache-coding-system)
607 (file-name-coding-system nnmail-pathname-coding-system))
608 (insert-file-contents (gnus-cache-file-name group entry)))
23f87bed
MB
609 (goto-char (point-min))
610 (insert "220 ")
611 (princ (car cached) (current-buffer))
612 (insert " Article retrieved.\n")
613 (search-forward "\n\n" nil 'move)
614 (delete-region (point) (point-max))
615 (forward-char -1)
616 (insert ".")
617 (set-buffer nntp-server-buffer)
01c52d31 618 (insert-buffer-substring cache-buf))
eec82323
LMI
619 (kill-buffer cache-buf)))
620
621;;;###autoload
622(defun gnus-jog-cache ()
623 "Go through all groups and put the articles into the cache.
624
625Usage:
626$ emacs -batch -l ~/.emacs -l gnus -f gnus-jog-cache"
627 (interactive)
628 (let ((gnus-mark-article-hook nil)
629 (gnus-expert-user t)
16409b0b 630 (mail-sources nil)
eec82323
LMI
631 (gnus-use-dribble-file nil)
632 (gnus-novice-user nil)
633 (gnus-large-newsgroup nil))
634 ;; Start Gnus.
635 (gnus)
636 ;; Go through all groups...
637 (gnus-group-mark-buffer)
a8151ef7
LMI
638 (gnus-group-iterate nil
639 (lambda (group)
640 (let (gnus-auto-select-next)
641 (gnus-summary-read-group group nil t)
642 ;; ... and enter the articles into the cache.
643 (when (eq major-mode 'gnus-summary-mode)
644 (gnus-uu-mark-buffer)
645 (gnus-cache-enter-article)
646 (kill-buffer (current-buffer))))))))
eec82323
LMI
647
648(defun gnus-cache-read-active (&optional force)
649 "Read the cache active file."
650 (gnus-make-directory gnus-cache-directory)
a8151ef7 651 (if (or (not (file-exists-p gnus-cache-active-file))
6748645f 652 (zerop (nth 7 (file-attributes gnus-cache-active-file)))
a8151ef7 653 force)
eec82323
LMI
654 ;; There is no active file, so we generate one.
655 (gnus-cache-generate-active)
656 ;; We simply read the active file.
657 (save-excursion
658 (gnus-set-work-buffer)
16409b0b 659 (nnheader-insert-file-contents gnus-cache-active-file)
eec82323
LMI
660 (gnus-active-to-gnus-format
661 nil (setq gnus-cache-active-hashtb
662 (gnus-make-hashtable
663 (count-lines (point-min) (point-max)))))
664 (setq gnus-cache-active-altered nil))))
665
666(defun gnus-cache-write-active (&optional force)
667 "Write the active hashtb to the active file."
668 (when (or force
669 (and gnus-cache-active-hashtb
670 gnus-cache-active-altered))
16409b0b 671 (gnus-write-active-file gnus-cache-active-file gnus-cache-active-hashtb t)
eec82323
LMI
672 ;; Mark the active hashtb as unaltered.
673 (setq gnus-cache-active-altered nil)))
674
23f87bed
MB
675(defun gnus-cache-possibly-update-active (group active)
676 "Update active info bounds of GROUP with ACTIVE if necessary.
677The update is performed if ACTIVE contains a higher or lower bound
678than the current."
679 (let ((lower t) (higher t))
680 (if gnus-cache-active-hashtb
681 (let ((cache-active (gnus-gethash group gnus-cache-active-hashtb)))
682 (when cache-active
683 (unless (< (car active) (car cache-active))
684 (setq lower nil))
685 (unless (> (cdr active) (cdr cache-active))
686 (setq higher nil))))
687 (gnus-cache-read-active))
688 (when lower
689 (gnus-cache-update-active group (car active) t))
690 (when higher
691 (gnus-cache-update-active group (cdr active)))))
692
eec82323
LMI
693(defun gnus-cache-update-active (group number &optional low)
694 "Update the upper bound of the active info of GROUP to NUMBER.
695If LOW, update the lower bound instead."
696 (let ((active (gnus-gethash group gnus-cache-active-hashtb)))
697 (if (null active)
698 ;; We just create a new active entry for this group.
699 (gnus-sethash group (cons number number) gnus-cache-active-hashtb)
700 ;; Update the lower or upper bound.
701 (if low
702 (setcar active number)
703 (setcdr active number)))
704 ;; Mark the active hashtb as altered.
705 (setq gnus-cache-active-altered t)))
706
707;;;###autoload
708(defun gnus-cache-generate-active (&optional directory)
709 "Generate the cache active file."
710 (interactive)
711 (let* ((top (null directory))
712 (directory (expand-file-name (or directory gnus-cache-directory)))
01c52d31 713 (file-name-coding-system nnmail-pathname-coding-system)
eec82323
LMI
714 (files (directory-files directory 'full))
715 (group
716 (if top
717 ""
718 (string-match
6748645f
LMI
719 (concat "^" (regexp-quote
720 (file-name-as-directory
721 (expand-file-name gnus-cache-directory))))
eec82323
LMI
722 (directory-file-name directory))
723 (nnheader-replace-chars-in-string
724 (substring (directory-file-name directory) (match-end 0))
725 ?/ ?.)))
726 nums alphs)
727 (when top
728 (gnus-message 5 "Generating the cache active file...")
729 (setq gnus-cache-active-hashtb (gnus-make-hashtable 123)))
6748645f 730 (when (string-match "^\\(nn[^_]+\\)_" group)
23f87bed 731 (setq group (replace-match "\\1:" t nil group)))
eec82323
LMI
732 ;; Separate articles from all other files and directories.
733 (while files
734 (if (string-match "^[0-9]+$" (file-name-nondirectory (car files)))
e9bd5782 735 (push (string-to-number (file-name-nondirectory (pop files))) nums)
eec82323
LMI
736 (push (pop files) alphs)))
737 ;; If we have nums, then this is probably a valid group.
738 (when (setq nums (sort nums '<))
01c52d31
MB
739 ;; Use non-decoded group name.
740 ;; FIXME: this is kind of a workaround. The active file should
741 ;; be updated at the time articles are cached. It will make
742 ;; `gnus-cache-unified-group-names' needless.
743 (gnus-sethash (or (cdr (assoc group gnus-cache-unified-group-names))
744 group)
745 (cons (car nums) (gnus-last-element nums))
eec82323
LMI
746 gnus-cache-active-hashtb))
747 ;; Go through all the other files.
01c52d31
MB
748 (dolist (file alphs)
749 (when (and (file-directory-p file)
6748645f 750 (not (string-match "^\\."
01c52d31 751 (file-name-nondirectory file))))
eec82323 752 ;; We descend directories.
01c52d31 753 (gnus-cache-generate-active file)))
eec82323
LMI
754 ;; Write the new active file.
755 (when top
756 (gnus-cache-write-active t)
757 (gnus-message 5 "Generating the cache active file...done"))))
758
759;;;###autoload
760(defun gnus-cache-generate-nov-databases (dir)
761 "Generate NOV files recursively starting in DIR."
762 (interactive (list gnus-cache-directory))
763 (gnus-cache-close)
764 (let ((nnml-generate-active-function 'identity))
b890d447 765 (nnml-generate-nov-databases-directory dir))
01c52d31
MB
766
767 (setq gnus-cache-total-fetched-hashtb nil)
768
23f87bed 769 (gnus-cache-open))
eec82323
LMI
770
771(defun gnus-cache-move-cache (dir)
772 "Move the cache tree to somewhere else."
a8151ef7 773 (interactive "FMove the cache tree to: ")
eec82323
LMI
774 (rename-file gnus-cache-directory dir))
775
23f87bed
MB
776(defun gnus-cache-fully-p (&optional group)
777 "Returns non-nil if the cache should be fully used.
778If GROUP is non-nil, also cater to `gnus-cacheable-groups' and
779`gnus-uncacheable-groups'."
780 (and gnus-use-cache
781 (not (eq gnus-use-cache 'passive))
782 (if (null group)
783 t
784 (and (or (not gnus-cacheable-groups)
785 (string-match gnus-cacheable-groups group))
786 (or (not gnus-uncacheable-groups)
787 (not (string-match gnus-uncacheable-groups group)))))))
788
54506618
MB
789;;;###autoload
790(defun gnus-cache-rename-group (old-group new-group)
58090a8d
MB
791 "Rename OLD-GROUP as NEW-GROUP.
792Always updates the cache, even when disabled, as the old cache
793files would corrupt Gnus when the cache was next enabled. It
794depends on the caller to determine whether group renaming is
795supported."
54506618 796 (let ((old-dir (gnus-cache-file-name old-group ""))
01c52d31
MB
797 (new-dir (gnus-cache-file-name new-group ""))
798 (file-name-coding-system nnmail-pathname-coding-system))
54506618
MB
799 (gnus-rename-file old-dir new-dir t))
800
01c52d31
MB
801 (gnus-cache-rename-group-total-fetched-for old-group new-group)
802
54506618
MB
803 (let ((no-save gnus-cache-active-hashtb))
804 (unless gnus-cache-active-hashtb
805 (gnus-cache-read-active))
58090a8d
MB
806 (let* ((old-group-hash-value
807 (gnus-gethash old-group gnus-cache-active-hashtb))
808 (new-group-hash-value
809 (gnus-gethash new-group gnus-cache-active-hashtb))
810 (delta
811 (or old-group-hash-value new-group-hash-value)))
54506618
MB
812 (gnus-sethash new-group old-group-hash-value gnus-cache-active-hashtb)
813 (gnus-sethash old-group nil gnus-cache-active-hashtb)
814
815 (if no-save
816 (setq gnus-cache-active-altered delta)
817 (gnus-cache-write-active delta)))))
818
819;;;###autoload
820(defun gnus-cache-delete-group (group)
58090a8d
MB
821 "Delete GROUP from the cache.
822Always updates the cache, even when disabled, as the old cache
823files would corrupt gnus when the cache was next enabled.
824Depends upon the caller to determine whether group deletion is
825supported."
01c52d31
MB
826 (let ((dir (gnus-cache-file-name group ""))
827 (file-name-coding-system nnmail-pathname-coding-system))
aa0a8561 828 (gnus-delete-directory dir))
54506618 829
01c52d31
MB
830 (gnus-cache-delete-group-total-fetched-for group)
831
54506618
MB
832 (let ((no-save gnus-cache-active-hashtb))
833 (unless gnus-cache-active-hashtb
834 (gnus-cache-read-active))
835 (let* ((group-hash-value (gnus-gethash group gnus-cache-active-hashtb)))
836 (gnus-sethash group nil gnus-cache-active-hashtb)
837
838 (if no-save
839 (setq gnus-cache-active-altered group-hash-value)
840 (gnus-cache-write-active group-hash-value)))))
841
01c52d31
MB
842(defvar gnus-cache-inhibit-update-total-fetched-for nil)
843(defvar gnus-cache-need-update-total-fetched-for nil)
844
845(defmacro gnus-cache-with-refreshed-group (group &rest body)
846 `(prog1 (let ((gnus-cache-inhibit-update-total-fetched-for t))
847 ,@body)
848 (when (and gnus-cache-need-update-total-fetched-for
849 (not gnus-cache-inhibit-update-total-fetched-for))
850 (save-excursion
851 (set-buffer gnus-group-buffer)
852 (setq gnus-cache-need-update-total-fetched-for nil)
853 (gnus-group-update-group ,group t)))))
854
855(defun gnus-cache-update-file-total-fetched-for (group file &optional subtract)
856 (when gnus-cache-total-fetched-hashtb
857 (gnus-cache-with-refreshed-group
858 group
859 (let* ((entry (or (gnus-gethash group gnus-cache-total-fetched-hashtb)
860 (gnus-sethash group (make-vector 2 0)
861 gnus-cache-total-fetched-hashtb)))
862 size)
863
864 (if file
865 (setq size (or (nth 7 (file-attributes file)) 0))
866 (let* ((file-name-coding-system nnmail-pathname-coding-system)
867 (files (directory-files (gnus-cache-file-name group "")
868 t nil t))
869 file attrs)
870 (setq size 0.0)
871 (while (setq file (pop files))
872 (setq attrs (file-attributes file))
873 (unless (nth 0 attrs)
874 (incf size (float (nth 7 attrs)))))))
875
876 (setq gnus-cache-need-update-total-fetched-for t)
877
878 (incf (nth 1 entry) (if subtract (- size) size))))))
879
880(defun gnus-cache-update-overview-total-fetched-for (group file)
881 (when gnus-cache-total-fetched-hashtb
882 (gnus-cache-with-refreshed-group
883 group
884 (let* ((entry (or (gnus-gethash group gnus-cache-total-fetched-hashtb)
885 (gnus-sethash group (make-list 2 0)
886 gnus-cache-total-fetched-hashtb)))
887 (file-name-coding-system nnmail-pathname-coding-system)
888 (size (or (nth 7 (file-attributes
889 (or file
890 (gnus-cache-file-name group ".overview"))))
891 0)))
892 (setq gnus-cache-need-update-total-fetched-for t)
893 (setf (nth 0 entry) size)))))
894
895(defun gnus-cache-rename-group-total-fetched-for (old-group new-group)
896 "Record of disk space used by OLD-GROUP now associated with NEW-GROUP."
897 (when gnus-cache-total-fetched-hashtb
898 (let ((entry (gnus-gethash old-group gnus-cache-total-fetched-hashtb)))
899 (gnus-sethash new-group entry gnus-cache-total-fetched-hashtb)
900 (gnus-sethash old-group nil gnus-cache-total-fetched-hashtb))))
901
902(defun gnus-cache-delete-group-total-fetched-for (group)
903 "Delete record of disk space used by GROUP being deleted."
904 (when gnus-cache-total-fetched-hashtb
905 (gnus-sethash group nil gnus-cache-total-fetched-hashtb)))
906
907(defun gnus-cache-total-fetched-for (group &optional no-inhibit)
908 "Get total disk space used by the cache for the specified GROUP."
909 (unless (equal group "dummy.group")
910 (unless gnus-cache-total-fetched-hashtb
911 (setq gnus-cache-total-fetched-hashtb (gnus-make-hashtable 1024)))
912
913 (let* ((entry (gnus-gethash group gnus-cache-total-fetched-hashtb)))
914 (if entry
915 (apply '+ entry)
916 (let ((gnus-cache-inhibit-update-total-fetched-for (not no-inhibit)))
917 (+
918 (gnus-cache-update-overview-total-fetched-for group nil)
919 (gnus-cache-update-file-total-fetched-for group nil)))))))
920
eec82323
LMI
921(provide 'gnus-cache)
922
cbee283d 923;; arch-tag: 05a79442-8c58-4e65-bd0a-3cbb1b89a33a
eec82323 924;;; gnus-cache.el ends here