Regenerate ldefs-boot.el
[bpt/emacs.git] / lisp / gnus / nnml.el
CommitLineData
eec82323 1;;; nnml.el --- mail spool access for Gnus
e84b4b86 2
ba318903 3;; Copyright (C) 1995-2014 Free Software Foundation, Inc.
eec82323 4
01c52d31 5;; Authors: Didier Verna <didier@xemacs.org> (adding compaction)
89b163db 6;; Simon Josefsson <simon@josefsson.org>
01c52d31 7;; Lars Magne Ingebrigtsen <larsi@gnus.org>
23f87bed 8;; Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
eec82323
LMI
9;; Keywords: news, mail
10
11;; This file is part of GNU Emacs.
12
5e809f55 13;; GNU Emacs is free software: you can redistribute it and/or modify
eec82323 14;; it under the terms of the GNU General Public License as published by
5e809f55
GM
15;; the Free Software Foundation, either version 3 of the License, or
16;; (at your option) any later version.
eec82323
LMI
17
18;; GNU Emacs is distributed in the hope that it will be useful,
19;; but WITHOUT ANY WARRANTY; without even the implied warranty of
20;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21;; GNU General Public License for more details.
22
23;; You should have received a copy of the GNU General Public License
5e809f55 24;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
eec82323
LMI
25
26;;; Commentary:
27
28;; Based on nnspool.el by Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>.
29;; For an overview of what the interface functions do, please see the
30;; Gnus sources.
31
32;;; Code:
33
23f87bed 34(require 'gnus)
eec82323
LMI
35(require 'nnheader)
36(require 'nnmail)
37(require 'nnoo)
349f4e97 38(eval-when-compile (require 'cl))
23f87bed 39
8abf1b22
GM
40;; FIXME first is unused in this file.
41(autoload 'gnus-article-unpropagatable-p "gnus-sum")
42(autoload 'gnus-backlog-remove-article "gnus-bcklg")
eec82323
LMI
43
44(nnoo-declare nnml)
45
46(defvoo nnml-directory message-directory
a8151ef7 47 "Spool directory for the nnml mail backend.")
eec82323
LMI
48
49(defvoo nnml-active-file
16409b0b 50 (expand-file-name "active" nnml-directory)
eec82323
LMI
51 "Mail active file.")
52
53(defvoo nnml-newsgroups-file
16409b0b 54 (expand-file-name "newsgroups" nnml-directory)
eec82323
LMI
55 "Mail newsgroups description file.")
56
57(defvoo nnml-get-new-mail t
58 "If non-nil, nnml will check the incoming mail file and split the mail.")
59
60(defvoo nnml-nov-is-evil nil
23f87bed 61 "If non-nil, Gnus will never generate and use nov databases for mail spools.
eec82323
LMI
62Using nov databases will speed up header fetching considerably.
63This variable shouldn't be flipped much. If you have, for some reason,
64set this to t, and want to set it to nil again, you should always run
65the `nnml-generate-nov-databases' command. The function will go
66through all nnml directories and generate nov databases for them
67all. This may very well take some time.")
68
69(defvoo nnml-prepare-save-mail-hook nil
70 "Hook run narrowed to an article before saving.")
71
72(defvoo nnml-inhibit-expiry nil
73 "If non-nil, inhibit expiry.")
74
23f87bed 75(defvoo nnml-use-compressed-files nil
01c52d31
MB
76 "If non-nil, allow using compressed message files.
77
78If it is a string, use it as the file extension which specifies
79the compression program. You can set it to \".bz2\" if your Emacs
80supports auto-compression using the bzip2 program. A value of t
81is equivalent to \".gz\".")
82
83(defvoo nnml-compressed-files-size-threshold 1000
84 "Default size threshold for compressed message files.
85Message files with bodies larger than that many characters will
86be automatically compressed if `nnml-use-compressed-files' is
87non-nil.")
eec82323
LMI
88
89\f
90
91(defconst nnml-version "nnml 1.0"
92 "nnml version.")
93
94(defvoo nnml-nov-file-name ".overview")
95
96(defvoo nnml-current-directory nil)
97(defvoo nnml-current-group nil)
98(defvoo nnml-status-string "")
99(defvoo nnml-nov-buffer-alist nil)
100(defvoo nnml-group-alist nil)
101(defvoo nnml-active-timestamp nil)
102(defvoo nnml-article-file-alist nil)
103
104(defvoo nnml-generate-active-function 'nnml-generate-active-info)
105
6748645f
LMI
106(defvar nnml-nov-buffer-file-name nil)
107
16409b0b
GM
108(defvoo nnml-file-coding-system nnmail-file-coding-system)
109
23f87bed 110\f
eec82323
LMI
111;;; Interface functions.
112
113(nnoo-define-basics nnml)
114
01c52d31
MB
115(eval-when-compile
116 (defsubst nnml-group-name-charset (group server-or-method)
117 (gnus-group-name-charset
118 (if (stringp server-or-method)
119 (gnus-server-to-method
120 (if (string-match "\\+" server-or-method)
121 (concat (substring server-or-method 0 (match-beginning 0))
122 ":" (substring server-or-method (match-end 0)))
123 (concat "nnml:" server-or-method)))
124 (or server-or-method gnus-command-method '(nnml "")))
125 group)))
126
127(defun nnml-decoded-group-name (group &optional server-or-method)
128 "Return a decoded group name of GROUP on SERVER-OR-METHOD."
129 (if nnmail-group-names-not-encoded-p
130 group
131 (mm-decode-coding-string
132 group
133 (nnml-group-name-charset group server-or-method))))
134
135(defun nnml-encoded-group-name (group &optional server-or-method)
136 "Return an encoded group name of GROUP on SERVER-OR-METHOD."
137 (mm-encode-coding-string
138 group
139 (nnml-group-name-charset group server-or-method)))
140
141(defun nnml-group-pathname (group &optional file server)
142 "Return an absolute file name of FILE for GROUP on SERVER."
143 (nnmail-group-pathname (inline (nnml-decoded-group-name group server))
144 nnml-directory file))
145
eec82323
LMI
146(deffoo nnml-retrieve-headers (sequence &optional group server fetch-old)
147 (when (nnml-possibly-change-directory group server)
20a673b2 148 (with-current-buffer nntp-server-buffer
eec82323 149 (erase-buffer)
23f87bed
MB
150 (let* ((file nil)
151 (number (length sequence))
152 (count 0)
153 (file-name-coding-system nnmail-pathname-coding-system)
154 beg article)
eec82323
LMI
155 (if (stringp (car sequence))
156 'headers
157 (if (nnml-retrieve-headers-with-nov sequence fetch-old)
158 'nov
159 (while sequence
160 (setq article (car sequence))
161 (setq file (nnml-article-to-file article))
162 (when (and file
163 (file-exists-p file)
164 (not (file-directory-p file)))
165 (insert (format "221 %d Article retrieved.\n" article))
166 (setq beg (point))
167 (nnheader-insert-head file)
168 (goto-char beg)
23f87bed 169 (if (re-search-forward "\n\r?\n" nil t)
eec82323
LMI
170 (forward-char -1)
171 (goto-char (point-max))
172 (insert "\n\n"))
173 (insert ".\n")
174 (delete-region (point) (point-max)))
175 (setq sequence (cdr sequence))
176 (setq count (1+ count))
177 (and (numberp nnmail-large-newsgroup)
178 (> number nnmail-large-newsgroup)
179 (zerop (% count 20))
180 (nnheader-message 6 "nnml: Receiving headers... %d%%"
181 (/ (* count 100) number))))
182
183 (and (numberp nnmail-large-newsgroup)
184 (> number nnmail-large-newsgroup)
185 (nnheader-message 6 "nnml: Receiving headers...done"))
186
187 (nnheader-fold-continuation-lines)
188 'headers))))))
189
190(deffoo nnml-open-server (server &optional defs)
191 (nnoo-change-server 'nnml server defs)
192 (when (not (file-exists-p nnml-directory))
16409b0b 193 (ignore-errors (make-directory nnml-directory t)))
eec82323
LMI
194 (cond
195 ((not (file-exists-p nnml-directory))
196 (nnml-close-server)
197 (nnheader-report 'nnml "Couldn't create directory: %s" nnml-directory))
198 ((not (file-directory-p (file-truename nnml-directory)))
199 (nnml-close-server)
200 (nnheader-report 'nnml "Not a directory: %s" nnml-directory))
201 (t
202 (nnheader-report 'nnml "Opened server %s using directory %s"
203 server nnml-directory)
204 t)))
205
23f87bed 206(deffoo nnml-request-regenerate (server)
eec82323 207 (nnml-possibly-change-directory nil server)
23f87bed 208 (nnml-generate-nov-databases server)
eec82323
LMI
209 t)
210
211(deffoo nnml-request-article (id &optional group server buffer)
212 (nnml-possibly-change-directory group server)
213 (let* ((nntp-server-buffer (or buffer nntp-server-buffer))
16409b0b 214 (file-name-coding-system nnmail-pathname-coding-system)
eec82323
LMI
215 path gpath group-num)
216 (if (stringp id)
01c52d31 217 (when (and (setq group-num (nnml-find-group-number id server))
eec82323
LMI
218 (cdr
219 (assq (cdr group-num)
220 (nnheader-article-to-file-alist
01c52d31
MB
221 (setq gpath (nnml-group-pathname (car group-num)
222 nil server))))))
bb7f5cbc
G
223 (nnml-update-file-alist)
224 (setq path (concat gpath (if nnml-use-compressed-files
225 (cdr (assq (cdr group-num)
226 nnml-article-file-alist))
227 (number-to-string (cdr group-num))))))
eec82323
LMI
228 (setq path (nnml-article-to-file id)))
229 (cond
230 ((not path)
231 (nnheader-report 'nnml "No such article: %s" id))
232 ((not (file-exists-p path))
233 (nnheader-report 'nnml "No such file: %s" path))
234 ((file-directory-p path)
235 (nnheader-report 'nnml "File is a directory: %s" path))
16409b0b
GM
236 ((not (save-excursion (let ((nnmail-file-coding-system
237 nnml-file-coding-system))
238 (nnmail-find-file path))))
eec82323
LMI
239 (nnheader-report 'nnml "Couldn't read file: %s" path))
240 (t
241 (nnheader-report 'nnml "Article %s retrieved" id)
242 ;; We return the article number.
243 (cons (if group-num (car group-num) group)
e9bd5782 244 (string-to-number (file-name-nondirectory path)))))))
eec82323 245
286c4fc2 246(deffoo nnml-request-group (group &optional server dont-check info)
1428d46b
MB
247 (let ((file-name-coding-system nnmail-pathname-coding-system)
248 (decoded (nnml-decoded-group-name group server)))
d49d7823
KH
249 (cond
250 ((not (nnml-possibly-change-directory group server))
251 (nnheader-report 'nnml "Invalid group (no such directory)"))
252 ((not (file-exists-p nnml-current-directory))
253 (nnheader-report 'nnml "Directory %s does not exist"
254 nnml-current-directory))
255 ((not (file-directory-p nnml-current-directory))
256 (nnheader-report 'nnml "%s is not a directory" nnml-current-directory))
257 (dont-check
1428d46b 258 (nnheader-report 'nnml "Group %s selected" decoded)
d49d7823
KH
259 t)
260 (t
261 (nnheader-re-read-dir nnml-current-directory)
262 (nnmail-activate 'nnml)
263 (let ((active (nth 1 (assoc group nnml-group-alist))))
264 (if (not active)
1428d46b
MB
265 (nnheader-report 'nnml "No such group: %s" decoded)
266 (nnheader-report 'nnml "Selected group %s" decoded)
d49d7823
KH
267 (nnheader-insert "211 %d %d %d %s\n"
268 (max (1+ (- (cdr active) (car active))) 0)
269 (car active) (cdr active) group)))))))
eec82323
LMI
270
271(deffoo nnml-request-scan (&optional group server)
272 (setq nnml-article-file-alist nil)
273 (nnml-possibly-change-directory group server)
85816ac1 274 (nnmail-get-new-mail 'nnml 'nnml-save-incremental-nov nnml-directory group))
eec82323
LMI
275
276(deffoo nnml-close-group (group &optional server)
277 (setq nnml-article-file-alist nil)
278 t)
279
280(deffoo nnml-request-create-group (group &optional server args)
16409b0b 281 (nnml-possibly-change-directory nil server)
eec82323 282 (nnmail-activate 'nnml)
6748645f 283 (cond
01c52d31
MB
284 ((let ((file (directory-file-name (nnml-group-pathname group nil server)))
285 (file-name-coding-system nnmail-pathname-coding-system))
286 (and (file-exists-p file)
287 (not (file-directory-p file))))
288 (nnheader-report 'nnml "%s is a file"
289 (directory-file-name (nnml-group-pathname group
290 nil server))))
6748645f
LMI
291 ((assoc group nnml-group-alist)
292 t)
6748645f 293 (t
eec82323
LMI
294 (let (active)
295 (push (list group (setq active (cons 1 0)))
296 nnml-group-alist)
01c52d31 297 (nnml-possibly-create-directory group server)
eec82323 298 (nnml-possibly-change-directory group server)
01c52d31
MB
299 (let* ((file-name-coding-system nnmail-pathname-coding-system)
300 (articles (nnml-directory-articles nnml-current-directory)))
eec82323
LMI
301 (when articles
302 (setcar active (apply 'min articles))
303 (setcdr active (apply 'max articles))))
6748645f
LMI
304 (nnmail-save-active nnml-group-alist nnml-active-file)
305 t))))
eec82323
LMI
306
307(deffoo nnml-request-list (&optional server)
308 (save-excursion
d49d7823 309 (let ((nnmail-file-coding-system nnmail-active-file-coding-system)
16409b0b
GM
310 (file-name-coding-system nnmail-pathname-coding-system))
311 (nnmail-find-file nnml-active-file))
eec82323
LMI
312 (setq nnml-group-alist (nnmail-get-active))
313 t))
314
315(deffoo nnml-request-newgroups (date &optional server)
316 (nnml-request-list server))
317
318(deffoo nnml-request-list-newsgroups (&optional server)
319 (save-excursion
320 (nnmail-find-file nnml-newsgroups-file)))
321
16409b0b 322(deffoo nnml-request-expire-articles (articles group &optional server force)
eec82323 323 (nnml-possibly-change-directory group server)
01c52d31
MB
324 (let* ((file-name-coding-system nnmail-pathname-coding-system)
325 (active-articles
326 (nnml-directory-articles nnml-current-directory))
327 (is-old t)
328 (decoded (nnml-decoded-group-name group server))
329 article rest mod-time number target)
eec82323
LMI
330 (nnmail-activate 'nnml)
331
6748645f
LMI
332 (setq active-articles (sort active-articles '<))
333 ;; Articles not listed in active-articles are already gone,
334 ;; so don't try to expire them.
335 (setq articles (gnus-sorted-intersection articles active-articles))
336
eec82323 337 (while (and articles is-old)
23f87bed
MB
338 (if (and (setq article (nnml-article-to-file
339 (setq number (pop articles))))
340 (setq mod-time (nth 5 (file-attributes article)))
341 (nnml-deletable-article-p group number)
342 (setq is-old (nnmail-expired-article-p group mod-time force
343 nnml-inhibit-expiry)))
344 (progn
345 ;; Allow a special target group.
01c52d31
MB
346 (setq target nnmail-expiry-target)
347 (unless (eq target 'delete)
23f87bed
MB
348 (with-temp-buffer
349 (nnml-request-article number group server (current-buffer))
350 (let (nnml-current-directory
351 nnml-current-group
352 nnml-article-file-alist)
01c52d31
MB
353 (when (functionp target)
354 (setq target (funcall target group)))
9091f2d3
MB
355 (when (and target (not (eq target 'delete)))
356 (if (or (gnus-request-group target)
357 (gnus-request-create-group target))
358 (nnmail-expiry-target-group target group)
359 (setq target nil)))))
23f87bed
MB
360 ;; Maybe directory is changed during nnmail-expiry-target-group.
361 (nnml-possibly-change-directory group server))
01c52d31
MB
362 (if target
363 (progn
364 (nnheader-message 5 "Deleting article %s in %s"
365 number decoded)
366 (condition-case ()
367 (funcall nnmail-delete-file-function article)
368 (file-error
369 (push number rest)))
370 (setq active-articles (delq number active-articles))
371 (nnml-nov-delete-article group number))
372 (push number rest)))
23f87bed 373 (push number rest)))
eec82323
LMI
374 (let ((active (nth 1 (assoc group nnml-group-alist))))
375 (when active
376 (setcar active (or (and active-articles
377 (apply 'min active-articles))
378 (1+ (cdr active)))))
379 (nnmail-save-active nnml-group-alist nnml-active-file))
380 (nnml-save-nov)
381 (nconc rest articles)))
382
383(deffoo nnml-request-move-article
01c52d31 384 (article group server accept-form &optional last move-is-internal)
eec82323 385 (let ((buf (get-buffer-create " *nnml move*"))
01c52d31 386 (file-name-coding-system nnmail-pathname-coding-system)
eec82323
LMI
387 result)
388 (nnml-possibly-change-directory group server)
389 (nnml-update-file-alist)
390 (and
391 (nnml-deletable-article-p group article)
392 (nnml-request-article article group server)
a1506d29
JB
393 (let (nnml-current-directory
394 nnml-current-group
16409b0b 395 nnml-article-file-alist)
20a673b2 396 (with-current-buffer buf
16409b0b
GM
397 (insert-buffer-substring nntp-server-buffer)
398 (setq result (eval accept-form))
399 (kill-buffer (current-buffer))
400 result))
eec82323
LMI
401 (progn
402 (nnml-possibly-change-directory group server)
403 (condition-case ()
404 (funcall nnmail-delete-file-function
405 (nnml-article-to-file article))
406 (file-error nil))
407 (nnml-nov-delete-article group article)
408 (when last
409 (nnml-save-nov)
410 (nnmail-save-active nnml-group-alist nnml-active-file))))
411 result))
412
413(deffoo nnml-request-accept-article (group &optional server last)
414 (nnml-possibly-change-directory group server)
415 (nnmail-check-syntax)
416 (let (result)
417 (when nnmail-cache-accepted-message-ids
01c52d31 418 (nnmail-cache-insert (nnmail-fetch-field "message-id")
23f87bed
MB
419 group
420 (nnmail-fetch-field "subject")
421 (nnmail-fetch-field "from")))
eec82323
LMI
422 (if (stringp group)
423 (and
424 (nnmail-activate 'nnml)
425 (setq result (car (nnml-save-mail
01c52d31
MB
426 (list (cons group (nnml-active-number group
427 server)))
85816ac1 428 server t)))
eec82323
LMI
429 (progn
430 (nnmail-save-active nnml-group-alist nnml-active-file)
431 (and last (nnml-save-nov))))
432 (and
433 (nnmail-activate 'nnml)
01c52d31
MB
434 (if (and (not (setq result (nnmail-article-group
435 `(lambda (group)
436 (nnml-active-number group ,server)))))
eec82323
LMI
437 (yes-or-no-p "Moved to `junk' group; delete article? "))
438 (setq result 'junk)
85816ac1 439 (setq result (car (nnml-save-mail result server t))))
eec82323
LMI
440 (when last
441 (nnmail-save-active nnml-group-alist nnml-active-file)
442 (when nnmail-cache-accepted-message-ids
443 (nnmail-cache-close))
444 (nnml-save-nov))))
445 result))
446
23f87bed
MB
447(deffoo nnml-request-post (&optional server)
448 (nnmail-do-request-post 'nnml-request-accept-article server))
449
eec82323
LMI
450(deffoo nnml-request-replace-article (article group buffer)
451 (nnml-possibly-change-directory group)
20a673b2 452 (with-current-buffer buffer
eec82323
LMI
453 (nnml-possibly-create-directory group)
454 (let ((chars (nnmail-insert-lines))
455 (art (concat (int-to-string article) "\t"))
456 headers)
16409b0b
GM
457 (when (ignore-errors
458 (nnmail-write-region
459 (point-min) (point-max)
460 (or (nnml-article-to-file article)
461 (expand-file-name (int-to-string article)
462 nnml-current-directory))
463 nil (if (nnheader-be-verbose 5) nil 'nomesg))
464 t)
eec82323
LMI
465 (setq headers (nnml-parse-head chars article))
466 ;; Replace the NOV line in the NOV file.
20a673b2 467 (with-current-buffer (nnml-open-nov group)
eec82323
LMI
468 (goto-char (point-min))
469 (if (or (looking-at art)
470 (search-forward (concat "\n" art) nil t))
471 ;; Delete the old NOV line.
23f87bed 472 (gnus-delete-line)
eec82323
LMI
473 ;; The line isn't here, so we have to find out where
474 ;; we should insert it. (This situation should never
475 ;; occur, but one likes to make sure...)
476 (while (and (looking-at "[0-9]+\t")
e9bd5782 477 (< (string-to-number
eec82323
LMI
478 (buffer-substring
479 (match-beginning 0) (match-end 0)))
480 article)
481 (zerop (forward-line 1)))))
482 (beginning-of-line)
483 (nnheader-insert-nov headers)
484 (nnml-save-nov)
485 t)))))
486
487(deffoo nnml-request-delete-group (group &optional force server)
488 (nnml-possibly-change-directory group server)
01c52d31
MB
489 (let ((file (directory-file-name nnml-current-directory))
490 (file-name-coding-system nnmail-pathname-coding-system))
491 (if (file-exists-p file)
492 (if (file-directory-p file)
493 (progn
494 (when force
495 ;; Delete all articles in GROUP.
496 (let ((articles
497 (directory-files
498 nnml-current-directory t
499 (concat
500 nnheader-numerical-short-files
89b163db 501 "\\|" (regexp-quote nnml-nov-file-name) "$")))
01c52d31
MB
502 (decoded (nnml-decoded-group-name group server)))
503 (dolist (article articles)
504 (when (file-writable-p article)
505 (nnheader-message 5 "Deleting article %s in %s..."
506 (file-name-nondirectory article)
507 decoded)
508 (funcall nnmail-delete-file-function article))))
509 ;; Try to delete the directory itself.
510 (ignore-errors (delete-directory nnml-current-directory))))
511 (nnheader-report 'nnml "%s is not a directory" file))
512 (nnheader-report 'nnml "No such directory: %s/" file))
513 ;; Remove the group from all structures.
514 (setq nnml-group-alist
515 (delq (assoc group nnml-group-alist) nnml-group-alist)
516 nnml-current-group nil
517 nnml-current-directory nil)
518 ;; Save the active file.
519 (nnmail-save-active nnml-group-alist nnml-active-file))
eec82323
LMI
520 t)
521
522(deffoo nnml-request-rename-group (group new-name &optional server)
523 (nnml-possibly-change-directory group server)
01c52d31 524 (let ((new-dir (nnml-group-pathname new-name nil server))
4d8a28ec
MB
525 (old-dir (nnml-group-pathname group nil server))
526 (file-name-coding-system nnmail-pathname-coding-system))
16409b0b
GM
527 (when (ignore-errors
528 (make-directory new-dir t)
529 t)
eec82323
LMI
530 ;; We move the articles file by file instead of renaming
531 ;; the directory -- there may be subgroups in this group.
532 ;; One might be more clever, I guess.
01c52d31
MB
533 (dolist (file (nnheader-article-to-file-alist old-dir))
534 (rename-file
535 (concat old-dir (cdr file))
536 (concat new-dir (cdr file))))
eec82323
LMI
537 ;; Move .overview file.
538 (let ((overview (concat old-dir nnml-nov-file-name)))
539 (when (file-exists-p overview)
540 (rename-file overview (concat new-dir nnml-nov-file-name))))
541 (when (<= (length (directory-files old-dir)) 2)
16409b0b 542 (ignore-errors (delete-directory old-dir)))
eec82323
LMI
543 ;; That went ok, so we change the internal structures.
544 (let ((entry (assoc group nnml-group-alist)))
545 (when entry
546 (setcar entry new-name))
547 (setq nnml-current-directory nil
548 nnml-current-group nil)
549 ;; Save the new group alist.
550 (nnmail-save-active nnml-group-alist nnml-active-file)
551 t))))
552
553(deffoo nnml-set-status (article name value &optional group server)
554 (nnml-possibly-change-directory group server)
555 (let ((file (nnml-article-to-file article)))
556 (cond
557 ((not (file-exists-p file))
558 (nnheader-report 'nnml "File %s does not exist" file))
559 (t
16409b0b 560 (with-temp-file file
eec82323
LMI
561 (nnheader-insert-file-contents file)
562 (nnmail-replace-status name value))
563 t))))
564
565\f
566;;; Internal functions.
567
568(defun nnml-article-to-file (article)
569 (nnml-update-file-alist)
570 (let (file)
23f87bed
MB
571 (if (setq file
572 (if nnml-use-compressed-files
573 (cdr (assq article nnml-article-file-alist))
574 (number-to-string article)))
16409b0b 575 (expand-file-name file nnml-current-directory)
23f87bed
MB
576 (when (not nnheader-directory-files-is-safe)
577 ;; Just to make sure nothing went wrong when reading over NFS --
578 ;; check once more.
579 (when (file-exists-p
580 (setq file (expand-file-name (number-to-string article)
581 nnml-current-directory)))
582 (nnml-update-file-alist t)
583 file)))))
eec82323
LMI
584
585(defun nnml-deletable-article-p (group article)
586 "Say whether ARTICLE in GROUP can be deleted."
01c52d31
MB
587 (let ((file-name-coding-system nnmail-pathname-coding-system)
588 path)
eec82323
LMI
589 (when (setq path (nnml-article-to-file article))
590 (when (file-writable-p path)
591 (or (not nnmail-keep-last-article)
592 (not (eq (cdr (nth 1 (assoc group nnml-group-alist)))
593 article)))))))
594
595;; Find an article number in the current group given the Message-ID.
01c52d31 596(defun nnml-find-group-number (id server)
20a673b2 597 (with-current-buffer (get-buffer-create " *nnml id*")
eec82323
LMI
598 (let ((alist nnml-group-alist)
599 number)
600 ;; We want to look through all .overview files, but we want to
601 ;; start with the one in the current directory. It seems most
602 ;; likely that the article we are looking for is in that group.
01c52d31 603 (if (setq number (nnml-find-id nnml-current-group id server))
eec82323 604 (cons nnml-current-group number)
23f87bed 605 ;; It wasn't there, so we look through the other groups as well.
eec82323
LMI
606 (while (and (not number)
607 alist)
608 (or (string= (caar alist) nnml-current-group)
01c52d31 609 (setq number (nnml-find-id (caar alist) id server)))
eec82323
LMI
610 (or number
611 (setq alist (cdr alist))))
612 (and number
613 (cons (caar alist) number))))))
614
01c52d31 615(defun nnml-find-id (group id server)
eec82323 616 (erase-buffer)
01c52d31 617 (let ((nov (nnml-group-pathname group nnml-nov-file-name server))
eec82323
LMI
618 number found)
619 (when (file-exists-p nov)
620 (nnheader-insert-file-contents nov)
621 (while (and (not found)
622 (search-forward id nil t)) ; We find the ID.
623 ;; And the id is in the fourth field.
624 (if (not (and (search-backward "\t" nil t 4)
01c52d31 625 (not (search-backward "\t" (point-at-bol) t))))
eec82323
LMI
626 (forward-line 1)
627 (beginning-of-line)
628 (setq found t)
629 ;; We return the article number.
630 (setq number
16409b0b 631 (ignore-errors (read (current-buffer))))))
eec82323
LMI
632 number)))
633
634(defun nnml-retrieve-headers-with-nov (articles &optional fetch-old)
635 (if (or gnus-nov-is-evil nnml-nov-is-evil)
636 nil
16409b0b 637 (let ((nov (expand-file-name nnml-nov-file-name nnml-current-directory)))
eec82323 638 (when (file-exists-p nov)
20a673b2 639 (with-current-buffer nntp-server-buffer
eec82323
LMI
640 (erase-buffer)
641 (nnheader-insert-file-contents nov)
642 (if (and fetch-old
643 (not (numberp fetch-old)))
644 t ; Don't remove anything.
645 (nnheader-nov-delete-outside-range
646 (if fetch-old (max 1 (- (car articles) fetch-old))
647 (car articles))
648 (car (last articles)))
649 t))))))
650
651(defun nnml-possibly-change-directory (group &optional server)
652 (when (and server
653 (not (nnml-server-opened server)))
654 (nnml-open-server server))
655 (if (not group)
656 t
01c52d31 657 (let ((pathname (nnml-group-pathname group nil server))
16409b0b 658 (file-name-coding-system nnmail-pathname-coding-system))
eec82323
LMI
659 (when (not (equal pathname nnml-current-directory))
660 (setq nnml-current-directory pathname
661 nnml-current-group group
662 nnml-article-file-alist nil))
663 (file-exists-p nnml-current-directory))))
664
01c52d31
MB
665(defun nnml-possibly-create-directory (group &optional server)
666 (let ((dir (nnml-group-pathname group nil server))
667 (file-name-coding-system nnmail-pathname-coding-system))
16409b0b
GM
668 (unless (file-exists-p dir)
669 (make-directory (directory-file-name dir) t)
670 (nnheader-message 5 "Creating mail directory %s" dir))))
eec82323 671
85816ac1 672(defun nnml-save-mail (group-art &optional server full-nov)
01c52d31
MB
673 "Save a mail into the groups GROUP-ART in the nnml server SERVER.
674GROUP-ART is a list that each element is a cons of a group name and an
675article number. This function is called narrowed to an article."
676 (let* ((chars (nnmail-insert-lines))
677 (extension (and nnml-use-compressed-files
678 (> chars nnml-compressed-files-size-threshold)
679 (if (stringp nnml-use-compressed-files)
680 nnml-use-compressed-files
681 ".gz")))
682 decoded dec file first headers)
683 (when nnmail-group-names-not-encoded-p
684 (dolist (ga (prog1 group-art (setq group-art nil)))
685 (setq group-art (nconc group-art
686 (list (cons (nnml-encoded-group-name (car ga)
687 server)
688 (cdr ga))))
689 decoded (nconc decoded (list (car ga)))))
690 (setq dec decoded))
eec82323
LMI
691 (nnmail-insert-xref group-art)
692 (run-hooks 'nnmail-prepare-save-mail-hook)
693 (run-hooks 'nnml-prepare-save-mail-hook)
694 (goto-char (point-min))
695 (while (looking-at "From ")
696 (replace-match "X-From-Line: ")
697 (forward-line 1))
698 ;; We save the article in all the groups it belongs in.
01c52d31
MB
699 (dolist (ga group-art)
700 (if nnmail-group-names-not-encoded-p
701 (progn
702 (nnml-possibly-create-directory (car decoded) server)
703 (setq file (nnmail-group-pathname
704 (pop decoded) nnml-directory
705 (concat (number-to-string (cdr ga)) extension))))
706 (nnml-possibly-create-directory (car ga) server)
707 (setq file (nnml-group-pathname
708 (car ga) (concat (number-to-string (cdr ga)) extension)
709 server)))
710 (if first
711 ;; It was already saved, so we just make a hard link.
712 (let ((file-name-coding-system nnmail-pathname-coding-system))
713 (funcall nnmail-crosspost-link-function first file t))
714 ;; Save the article.
715 (nnmail-write-region (point-min) (point-max) file nil
716 (if (nnheader-be-verbose 5) nil 'nomesg))
717 (setq first file)))
eec82323
LMI
718 ;; Generate a nov line for this article. We generate the nov
719 ;; line after saving, because nov generation destroys the
720 ;; header.
721 (setq headers (nnml-parse-head chars))
722 ;; Output the nov line to all nov databases that should have it.
85816ac1
LMI
723 (let ((func (if full-nov
724 'nnml-add-nov
725 'nnml-add-incremental-nov)))
726 (if nnmail-group-names-not-encoded-p
727 (dolist (ga group-art)
728 (funcall func (pop dec) (cdr ga) headers))
01c52d31 729 (dolist (ga group-art)
85816ac1 730 (funcall func (car ga) (cdr ga) headers)))))
01c52d31
MB
731 group-art)
732
733(defun nnml-active-number (group &optional server)
734 "Compute the next article number in GROUP on SERVER."
e9d1aaff
KY
735 (let* ((encoded (if nnmail-group-names-not-encoded-p
736 (nnml-encoded-group-name group server)))
737 (active (cadr (assoc (or encoded group) nnml-group-alist))))
eec82323
LMI
738 ;; The group wasn't known to nnml, so we just create an active
739 ;; entry for it.
740 (unless active
741 ;; Perhaps the active file was corrupt? See whether
742 ;; there are any articles in this group.
01c52d31
MB
743 (nnml-possibly-create-directory group server)
744 (nnml-possibly-change-directory group server)
eec82323
LMI
745 (unless nnml-article-file-alist
746 (setq nnml-article-file-alist
747 (sort
23f87bed 748 (nnml-current-group-article-to-file-alist)
6748645f 749 'car-less-than-car)))
eec82323
LMI
750 (setq active
751 (if nnml-article-file-alist
752 (cons (caar nnml-article-file-alist)
753 (caar (last nnml-article-file-alist)))
754 (cons 1 0)))
e9d1aaff 755 (push (list (or encoded group) active) nnml-group-alist))
eec82323
LMI
756 (setcdr active (1+ (cdr active)))
757 (while (file-exists-p
01c52d31 758 (nnml-group-pathname group (int-to-string (cdr active)) server))
eec82323
LMI
759 (setcdr active (1+ (cdr active))))
760 (cdr active)))
761
85816ac1
LMI
762(defvar nnml-incremental-nov-buffer-alist nil)
763
764(defun nnml-save-incremental-nov ()
85816ac1
LMI
765 (save-excursion
766 (while nnml-incremental-nov-buffer-alist
767 (when (buffer-name (cdar nnml-incremental-nov-buffer-alist))
768 (set-buffer (cdar nnml-incremental-nov-buffer-alist))
769 (when (buffer-modified-p)
770 (nnmail-write-region (point-min) (point-max)
771 nnml-nov-buffer-file-name t 'nomesg))
772 (set-buffer-modified-p nil)
773 (kill-buffer (current-buffer)))
774 (setq nnml-incremental-nov-buffer-alist
5edc8186 775 (cdr nnml-incremental-nov-buffer-alist)))))
85816ac1
LMI
776
777(defun nnml-open-incremental-nov (group)
778 (or (cdr (assoc group nnml-incremental-nov-buffer-alist))
779 (let ((buffer (nnml-get-nov-buffer group t)))
780 (push (cons group buffer) nnml-incremental-nov-buffer-alist)
781 buffer)))
782
783(defun nnml-add-incremental-nov (group article headers)
784 "Add a nov line for the GROUP nov headers, incrementally."
20a673b2 785 (with-current-buffer (nnml-open-incremental-nov group)
85816ac1
LMI
786 (goto-char (point-max))
787 (mail-header-set-number headers article)
788 (nnheader-insert-nov headers)))
789
eec82323
LMI
790(defun nnml-add-nov (group article headers)
791 "Add a nov line for the GROUP base."
20a673b2 792 (with-current-buffer (nnml-open-nov group)
eec82323
LMI
793 (goto-char (point-max))
794 (mail-header-set-number headers article)
795 (nnheader-insert-nov headers)))
796
797(defsubst nnml-header-value ()
01c52d31 798 (buffer-substring (match-end 0) (point-at-eol)))
eec82323
LMI
799
800(defun nnml-parse-head (chars &optional number)
801 "Parse the head of the current buffer."
802 (save-excursion
803 (save-restriction
6748645f
LMI
804 (unless (zerop (buffer-size))
805 (narrow-to-region
806 (goto-char (point-min))
23f87bed
MB
807 (if (re-search-forward "\n\r?\n" nil t)
808 (1- (point))
809 (point-max))))
810 (let ((headers (nnheader-parse-naked-head)))
eec82323
LMI
811 (mail-header-set-chars headers chars)
812 (mail-header-set-number headers number)
813 headers))))
814
85816ac1 815(defun nnml-get-nov-buffer (group &optional incrementalp)
01c52d31 816 (let* ((decoded (nnml-decoded-group-name group))
85816ac1
LMI
817 (buffer (get-buffer-create (format " *nnml %soverview %s*"
818 (if incrementalp
819 "incremental "
820 "")
821 decoded)))
01c52d31 822 (file-name-coding-system nnmail-pathname-coding-system))
20a673b2 823 (with-current-buffer buffer
23f87bed 824 (set (make-local-variable 'nnml-nov-buffer-file-name)
01c52d31 825 (nnmail-group-pathname decoded nnml-directory nnml-nov-file-name))
23f87bed 826 (erase-buffer)
85816ac1
LMI
827 (when (and (not incrementalp)
828 (file-exists-p nnml-nov-buffer-file-name))
23f87bed
MB
829 (nnheader-insert-file-contents nnml-nov-buffer-file-name)))
830 buffer))
831
eec82323 832(defun nnml-open-nov (group)
1821a7b4
LMI
833 (or (let ((buffer (cdr (assoc group nnml-nov-buffer-alist))))
834 (and (buffer-name buffer)
835 buffer))
23f87bed 836 (let ((buffer (nnml-get-nov-buffer group)))
eec82323
LMI
837 (push (cons group buffer) nnml-nov-buffer-alist)
838 buffer)))
839
840(defun nnml-save-nov ()
841 (save-excursion
842 (while nnml-nov-buffer-alist
843 (when (buffer-name (cdar nnml-nov-buffer-alist))
844 (set-buffer (cdar nnml-nov-buffer-alist))
845 (when (buffer-modified-p)
9930767b
SM
846 (nnmail-write-region (point-min) (point-max)
847 nnml-nov-buffer-file-name nil 'nomesg))
eec82323
LMI
848 (set-buffer-modified-p nil)
849 (kill-buffer (current-buffer)))
5edc8186 850 (setq nnml-nov-buffer-alist (cdr nnml-nov-buffer-alist)))))
eec82323
LMI
851
852;;;###autoload
23f87bed 853(defun nnml-generate-nov-databases (&optional server)
eec82323 854 "Generate NOV databases in all nnml directories."
23f87bed 855 (interactive (list (or (nnoo-current-server 'nnml) "")))
eec82323
LMI
856 ;; Read the active file to make sure we don't re-use articles
857 ;; numbers in empty groups.
858 (nnmail-activate 'nnml)
23f87bed
MB
859 (unless (nnml-server-opened server)
860 (nnml-open-server server))
eec82323
LMI
861 (setq nnml-directory (expand-file-name nnml-directory))
862 ;; Recurse down the directories.
01c52d31 863 (nnml-generate-nov-databases-directory nnml-directory nil t)
eec82323
LMI
864 ;; Save the active file.
865 (nnmail-save-active nnml-group-alist nnml-active-file))
866
149caf54 867(defvar nnml-files)
01c52d31
MB
868(defun nnml-generate-nov-databases-directory (dir &optional seen no-active)
869 "Regenerate the NOV database in DIR.
870
871Unless no-active is non-nil, update the active file too."
872 (interactive (list (let ((file-name-coding-system
873 nnmail-pathname-coding-system))
874 (read-directory-name "Regenerate NOV in: "
875 nnml-directory nil t))))
eec82323 876 (setq dir (file-name-as-directory dir))
01c52d31
MB
877 (let ((file-name-coding-system nnmail-pathname-coding-system))
878 ;; Only scan this sub-tree if we haven't been here yet.
879 (unless (member (file-truename dir) seen)
880 (push (file-truename dir) seen)
881 ;; We descend recursively
882 (dolist (dir (directory-files dir t nil t))
16409b0b 883 (when (and (not (string-match "^\\." (file-name-nondirectory dir)))
eec82323 884 (file-directory-p dir))
01c52d31
MB
885 (nnml-generate-nov-databases-directory dir seen)))
886 ;; Do this directory.
149caf54 887 (let ((nnml-files (sort (nnheader-article-to-file-alist dir)
01c52d31 888 'car-less-than-car)))
149caf54 889 (if (not nnml-files)
01c52d31
MB
890 (let* ((group (nnheader-file-to-group
891 (directory-file-name dir) nnml-directory))
892 (info (cadr (assoc group nnml-group-alist))))
893 (when info
894 (setcar info (1+ (cdr info)))))
895 (funcall nnml-generate-active-function dir)
896 ;; Generate the nov file.
149caf54 897 (nnml-generate-nov-file dir nnml-files)
01c52d31
MB
898 (unless no-active
899 (nnmail-save-active nnml-group-alist nnml-active-file)))))))
eec82323 900
eec82323
LMI
901(defun nnml-generate-active-info (dir)
902 ;; Update the active info for this group.
01c52d31
MB
903 (let ((group (directory-file-name dir))
904 entry last)
905 (setq group (nnheader-file-to-group (nnml-encoded-group-name group)
906 nnml-directory)
907 entry (assoc group nnml-group-alist)
908 last (or (caadr entry) 0)
909 nnml-group-alist (delq entry nnml-group-alist))
eec82323 910 (push (list group
149caf54 911 (cons (or (caar nnml-files) (1+ last))
23f87bed 912 (max last
149caf54 913 (or (caar (last nnml-files))
23f87bed 914 0))))
eec82323
LMI
915 nnml-group-alist)))
916
917(defun nnml-generate-nov-file (dir files)
918 (let* ((dir (file-name-as-directory dir))
919 (nov (concat dir nnml-nov-file-name))
920 (nov-buffer (get-buffer-create " *nov*"))
921 chars file headers)
20a673b2 922 (with-current-buffer nov-buffer
eec82323 923 ;; Init the nov buffer.
16409b0b 924 (buffer-disable-undo)
eec82323
LMI
925 (erase-buffer)
926 (set-buffer nntp-server-buffer)
927 ;; Delete the old NOV file.
928 (when (file-exists-p nov)
929 (funcall nnmail-delete-file-function nov))
149caf54 930 (dolist (file files)
0617bb00
LMI
931 (let ((path (concat dir (cdr file))))
932 (unless (file-directory-p path)
933 (erase-buffer)
934 (nnheader-insert-file-contents path)
935 (narrow-to-region
936 (goto-char (point-min))
937 (progn
938 (re-search-forward "\n\r?\n" nil t)
939 (setq chars (- (point-max) (point)))
940 (max (point-min) (1- (point)))))
941 (unless (zerop (buffer-size))
942 (goto-char (point-min))
943 (setq headers (nnml-parse-head chars (car file)))
944 (with-current-buffer nov-buffer
945 (goto-char (point-max))
946 (nnheader-insert-nov headers)))
947 (widen))))
20a673b2 948 (with-current-buffer nov-buffer
9930767b 949 (nnmail-write-region (point-min) (point-max) nov nil 'nomesg)
eec82323
LMI
950 (kill-buffer (current-buffer))))))
951
952(defun nnml-nov-delete-article (group article)
20a673b2 953 (with-current-buffer (nnml-open-nov group)
eec82323
LMI
954 (when (nnheader-find-nov-line article)
955 (delete-region (point) (progn (forward-line 1) (point)))
956 (when (bobp)
957 (let ((active (cadr (assoc group nnml-group-alist)))
958 num)
959 (when active
960 (if (eobp)
961 (setf (car active) (1+ (cdr active)))
962 (when (and (setq num (ignore-errors (read (current-buffer))))
963 (numberp num))
964 (setf (car active) num)))))))
965 t))
966
a8151ef7 967(defun nnml-update-file-alist (&optional force)
23f87bed
MB
968 (when nnml-use-compressed-files
969 (when (or (not nnml-article-file-alist)
970 force)
971 (setq nnml-article-file-alist
972 (nnml-current-group-article-to-file-alist)))))
973
974(defun nnml-directory-articles (dir)
975 "Return a list of all article files in a directory.
976Use the nov database for that directory if available."
977 (if (or gnus-nov-is-evil nnml-nov-is-evil
978 (not (file-exists-p
979 (expand-file-name nnml-nov-file-name dir))))
980 (nnheader-directory-articles dir)
981 ;; build list from .overview if available
982 ;; We would use nnml-open-nov, except that nnml-nov-buffer-alist is
983 ;; defvoo'd, and we might get called when it hasn't been swapped in.
149caf54 984 (with-current-buffer (nnml-get-nov-buffer nnml-current-group)
23f87bed 985 (let ((list nil)
149caf54 986 art)
23f87bed
MB
987 (goto-char (point-min))
988 (while (not (eobp))
989 (setq art (read (current-buffer)))
990 (push art list)
991 (forward-line 1))
992 list))))
993
994(defun nnml-current-group-article-to-file-alist ()
995 "Return an alist of article/file pairs in the current group.
996Use the nov database for the current group if available."
997 (if (or nnml-use-compressed-files
998 gnus-nov-is-evil
999 nnml-nov-is-evil
1000 (not (file-exists-p
1001 (expand-file-name nnml-nov-file-name
1002 nnml-current-directory))))
1003 (nnheader-article-to-file-alist nnml-current-directory)
1004 ;; build list from .overview if available
149caf54 1005 (with-current-buffer (nnml-get-nov-buffer nnml-current-group)
23f87bed 1006 (let ((alist nil)
23f87bed 1007 art)
23f87bed
MB
1008 (goto-char (point-min))
1009 (while (not (eobp))
1010 (setq art (read (current-buffer)))
1011 ;; assume file name is unadorned (ie. not compressed etc)
1012 (push (cons art (int-to-string art)) alist)
1013 (forward-line 1))
1014 alist))))
1015
01c52d31
MB
1016;;;
1017;;; Group and server compaction. -- dvl
1018;;;
1019
1020;; #### FIXME: this function handles self Xref: entry correctly, but I don't
1021;; #### know how to handle external cross-references. I actually don't know if
1022;; #### this is handled correctly elsewhere. For instance, what happens if you
1023;; #### move all articles to a new group (that's what people do for manual
1024;; #### compaction) ?
1025
1026;; #### NOTE: the function below handles the article backlog. This is
1027;; #### conceptually the wrong place to do it because the backend is at a
1028;; #### lower level. However, this is the only place where we have the needed
1029;; #### information to do the job. Ideally, this function should not handle
1030;; #### the backlog by itself, but return a list of moved groups / articles to
1031;; #### the caller. This will become important to avoid code duplication when
1032;; #### other backends get a compaction feature. Also, note that invalidating
1033;; #### the "original article buffer" is already done at an upper level.
1034
1035;; Shouldn't `nnml-request-compact-group' be interactive? --rsteib
1036
1037(defun nnml-request-compact-group (group &optional server save)
1038 (nnml-possibly-change-directory group server)
1039 (unless nnml-article-file-alist
1040 (setq nnml-article-file-alist
1041 (sort (nnml-current-group-article-to-file-alist)
1042 'car-less-than-car)))
1043 (if (not nnml-article-file-alist)
1044 ;; The group is empty: do nothing but return t
1045 t
1046 ;; The group is not empty:
1047 (let* ((group-full-name
1048 (gnus-group-prefixed-name
1049 group
1050 (gnus-server-to-method (format "nnml:%s" server))))
1051 (info (gnus-get-info group-full-name))
1052 (new-number 1)
1053 compacted)
1054 (let ((articles nnml-article-file-alist)
1055 article)
1056 (while (setq article (pop articles))
1057 (let ((old-number (car article)))
1058 (when (> old-number new-number)
1059 ;; There is a gap here:
1060 (let ((old-number-string (int-to-string old-number))
1061 (new-number-string (int-to-string new-number)))
1062 (setq compacted t)
1063 ;; #### NOTE: `nnml-article-to-file' calls
1064 ;; #### `nnml-update-file-alist' (which in turn calls
1065 ;; #### `nnml-current-group-article-to-file-alist', which
1066 ;; #### might use the NOV database). This might turn out to be
1067 ;; #### inefficient. In that case, we will do the work
1068 ;; #### manually.
1069 ;; 1/ Move the article to a new file:
1070 (let* ((oldfile (nnml-article-to-file old-number))
1071 (newfile
1072 (gnus-replace-in-string
1073 oldfile
1074 ;; nnml-use-compressed-files might be any string, but
1075 ;; probably it's sufficient to take into account only
1076 ;; "\\.[a-z0-9]+". Note that we can't only use the
1077 ;; value of nnml-use-compressed-files because old
1078 ;; articles might have been saved with a different
1079 ;; value.
1080 (concat
1081 "\\(" old-number-string "\\)\\(\\(\\.[a-z0-9]+\\)?\\)$")
1082 (concat new-number-string "\\2"))))
1083 (with-current-buffer nntp-server-buffer
1084 (nnmail-find-file oldfile)
1085 ;; Update the Xref header in the article itself:
1086 (when (and (re-search-forward "^Xref: [^ ]+ " nil t)
1087 (re-search-forward
1088 (concat "\\<"
1089 (regexp-quote
1090 (concat group ":" old-number-string))
1091 "\\>")
1092 (point-at-eol) t))
1093 (replace-match
1094 (concat group ":" new-number-string)))
1095 ;; Save to the new file:
1096 (nnmail-write-region (point-min) (point-max) newfile))
c17b81a7
G
1097 (condition-case ()
1098 (funcall nnmail-delete-file-function oldfile)
1099 (file-error
1100 (message "Couldn't delete %s" oldfile))))
01c52d31
MB
1101 ;; 2/ Update all marks for this article:
1102 ;; #### NOTE: it is possible that the new article number
1103 ;; #### already belongs to a range, whereas the corresponding
1104 ;; #### article doesn't exist (for example, if you delete an
1105 ;; #### article). For that reason, it is important to update
e4769531 1106 ;; #### the ranges (meaning remove nonexistent articles) before
01c52d31
MB
1107 ;; #### doing anything on them.
1108 ;; 2 a/ read articles:
1109 (let ((read (gnus-info-read info)))
1110 (setq read (gnus-remove-from-range read (list new-number)))
1111 (when (gnus-member-of-range old-number read)
1112 (setq read (gnus-remove-from-range read (list old-number)))
1113 (setq read (gnus-add-to-range read (list new-number))))
1114 (gnus-info-set-read info read))
1115 ;; 2 b/ marked articles:
1116 (let ((oldmarks (gnus-info-marks info))
1117 mark newmarks)
1118 (while (setq mark (pop oldmarks))
1119 (setcdr mark (gnus-remove-from-range (cdr mark)
1120 (list new-number)))
1121 (when (gnus-member-of-range old-number (cdr mark))
1122 (setcdr mark (gnus-remove-from-range (cdr mark)
1123 (list old-number)))
1124 (setcdr mark (gnus-add-to-range (cdr mark)
1125 (list new-number))))
1126 (push mark newmarks))
1127 (gnus-info-set-marks info newmarks))
1128 ;; 3/ Update the NOV entry for this article:
1129 (unless nnml-nov-is-evil
20a673b2 1130 (with-current-buffer (nnml-open-nov group)
01c52d31
MB
1131 (when (nnheader-find-nov-line old-number)
1132 ;; Replace the article number:
1133 (looking-at old-number-string)
1134 (replace-match new-number-string nil t)
1135 ;; Update the Xref header:
1136 (when (re-search-forward
1137 (concat "\\(Xref:[^\t\n]* \\)\\<"
1138 (regexp-quote
1139 (concat group ":" old-number-string))
1140 "\\>")
1141 (point-at-eol) t)
1142 (replace-match
1143 (concat "\\1" group ":" new-number-string))))))
1144 ;; 4/ Possibly remove the article from the backlog:
1145 (when gnus-keep-backlog
1146 ;; #### NOTE: instead of removing the article, we could
1147 ;; #### modify the backlog to reflect the numbering change,
1148 ;; #### but I don't think it's worth it.
1149 (gnus-backlog-remove-article group-full-name old-number)
1150 (gnus-backlog-remove-article group-full-name new-number))))
1151 (setq new-number (1+ new-number)))))
1152 (if (not compacted)
1153 ;; No compaction had to be done:
1154 t
1155 ;; Some articles have actually been renamed:
1156 ;; 1/ Rebuild active information:
1157 (let ((entry (assoc group nnml-group-alist))
1158 (active (cons 1 (1- new-number))))
1159 (setq nnml-group-alist (delq entry nnml-group-alist))
1160 (push (list group active) nnml-group-alist)
1161 ;; Update the active hashtable to let the *Group* buffer display
1162 ;; up-to-date lines. I don't think that either gnus-newsrc-hashtb or
1163 ;; gnus-newwrc-alist are out of date, since all we did is to modify
1164 ;; the info of the group internally.
1165 (gnus-set-active group-full-name active))
1166 ;; 1 bis/
1167 ;; #### NOTE: normally, we should save the overview (NOV) file
89b163db
G
1168 ;; #### here. However, there is no such function as
1169 ;; #### nnml-save-nov for a single group. Only for all
1170 ;; #### groups. Gnus inconsistency is getting worse every
1171 ;; #### day... ;; 3/ Save everything if this was not part of
1172 ;; #### a bigger operation:
01c52d31
MB
1173 (if (not save)
1174 ;; Nothing to save (yet):
1175 t
1176 ;; Something to save:
1177 ;; a/ Save the NOV databases:
1178 ;; #### NOTE: this should be done directory per directory in 1bis
1179 ;; #### above. See comment there.
1180 (nnml-save-nov)
1181 ;; b/ Save the active file:
1182 (nnmail-save-active nnml-group-alist nnml-active-file)
1183 t)))))
1184
1185(defun nnml-request-compact (&optional server)
1186 "Request compaction of all SERVER nnml groups."
1187 (interactive (list (or (nnoo-current-server 'nnml) "")))
1188 (nnmail-activate 'nnml)
1189 (unless (nnml-server-opened server)
1190 (nnml-open-server server))
1191 (setq nnml-directory (expand-file-name nnml-directory))
1192 (let* ((groups (gnus-groups-from-server
1193 (gnus-server-to-method (format "nnml:%s" server))))
1194 (first (pop groups))
1195 group)
1196 (when first
1197 (while (setq group (pop groups))
1198 (nnml-request-compact-group (gnus-group-real-name group) server))
1199 (nnml-request-compact-group (gnus-group-real-name first) server t))))
1200
eec82323
LMI
1201
1202(provide 'nnml)
1203
1204;;; nnml.el ends here