Commit | Line | Data |
---|---|---|
eec82323 | 1 | ;;; nnml.el --- mail spool access for Gnus |
e84b4b86 | 2 | |
ab422c4d | 3 | ;; Copyright (C) 1995-2013 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 |
62 | Using nov databases will speed up header fetching considerably. |
63 | This variable shouldn't be flipped much. If you have, for some reason, | |
64 | set this to t, and want to set it to nil again, you should always run | |
65 | the `nnml-generate-nov-databases' command. The function will go | |
66 | through all nnml directories and generate nov databases for them | |
67 | all. 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 | ||
78 | If it is a string, use it as the file extension which specifies | |
79 | the compression program. You can set it to \".bz2\" if your Emacs | |
80 | supports auto-compression using the bzip2 program. A value of t | |
81 | is equivalent to \".gz\".") | |
82 | ||
83 | (defvoo nnml-compressed-files-size-threshold 1000 | |
84 | "Default size threshold for compressed message files. | |
85 | Message files with bodies larger than that many characters will | |
86 | be automatically compressed if `nnml-use-compressed-files' is | |
87 | non-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. |
674 | GROUP-ART is a list that each element is a cons of a group name and an | |
675 | article 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 | ||
871 | Unless 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. | |
976 | Use 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. | |
996 | Use 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)) | |
1097 | (funcall nnmail-delete-file-function oldfile)) | |
1098 | ;; 2/ Update all marks for this article: | |
1099 | ;; #### NOTE: it is possible that the new article number | |
1100 | ;; #### already belongs to a range, whereas the corresponding | |
1101 | ;; #### article doesn't exist (for example, if you delete an | |
1102 | ;; #### article). For that reason, it is important to update | |
e4769531 | 1103 | ;; #### the ranges (meaning remove nonexistent articles) before |
01c52d31 MB |
1104 | ;; #### doing anything on them. |
1105 | ;; 2 a/ read articles: | |
1106 | (let ((read (gnus-info-read info))) | |
1107 | (setq read (gnus-remove-from-range read (list new-number))) | |
1108 | (when (gnus-member-of-range old-number read) | |
1109 | (setq read (gnus-remove-from-range read (list old-number))) | |
1110 | (setq read (gnus-add-to-range read (list new-number)))) | |
1111 | (gnus-info-set-read info read)) | |
1112 | ;; 2 b/ marked articles: | |
1113 | (let ((oldmarks (gnus-info-marks info)) | |
1114 | mark newmarks) | |
1115 | (while (setq mark (pop oldmarks)) | |
1116 | (setcdr mark (gnus-remove-from-range (cdr mark) | |
1117 | (list new-number))) | |
1118 | (when (gnus-member-of-range old-number (cdr mark)) | |
1119 | (setcdr mark (gnus-remove-from-range (cdr mark) | |
1120 | (list old-number))) | |
1121 | (setcdr mark (gnus-add-to-range (cdr mark) | |
1122 | (list new-number)))) | |
1123 | (push mark newmarks)) | |
1124 | (gnus-info-set-marks info newmarks)) | |
1125 | ;; 3/ Update the NOV entry for this article: | |
1126 | (unless nnml-nov-is-evil | |
20a673b2 | 1127 | (with-current-buffer (nnml-open-nov group) |
01c52d31 MB |
1128 | (when (nnheader-find-nov-line old-number) |
1129 | ;; Replace the article number: | |
1130 | (looking-at old-number-string) | |
1131 | (replace-match new-number-string nil t) | |
1132 | ;; Update the Xref header: | |
1133 | (when (re-search-forward | |
1134 | (concat "\\(Xref:[^\t\n]* \\)\\<" | |
1135 | (regexp-quote | |
1136 | (concat group ":" old-number-string)) | |
1137 | "\\>") | |
1138 | (point-at-eol) t) | |
1139 | (replace-match | |
1140 | (concat "\\1" group ":" new-number-string)))))) | |
1141 | ;; 4/ Possibly remove the article from the backlog: | |
1142 | (when gnus-keep-backlog | |
1143 | ;; #### NOTE: instead of removing the article, we could | |
1144 | ;; #### modify the backlog to reflect the numbering change, | |
1145 | ;; #### but I don't think it's worth it. | |
1146 | (gnus-backlog-remove-article group-full-name old-number) | |
1147 | (gnus-backlog-remove-article group-full-name new-number)))) | |
1148 | (setq new-number (1+ new-number))))) | |
1149 | (if (not compacted) | |
1150 | ;; No compaction had to be done: | |
1151 | t | |
1152 | ;; Some articles have actually been renamed: | |
1153 | ;; 1/ Rebuild active information: | |
1154 | (let ((entry (assoc group nnml-group-alist)) | |
1155 | (active (cons 1 (1- new-number)))) | |
1156 | (setq nnml-group-alist (delq entry nnml-group-alist)) | |
1157 | (push (list group active) nnml-group-alist) | |
1158 | ;; Update the active hashtable to let the *Group* buffer display | |
1159 | ;; up-to-date lines. I don't think that either gnus-newsrc-hashtb or | |
1160 | ;; gnus-newwrc-alist are out of date, since all we did is to modify | |
1161 | ;; the info of the group internally. | |
1162 | (gnus-set-active group-full-name active)) | |
1163 | ;; 1 bis/ | |
1164 | ;; #### NOTE: normally, we should save the overview (NOV) file | |
89b163db G |
1165 | ;; #### here. However, there is no such function as |
1166 | ;; #### nnml-save-nov for a single group. Only for all | |
1167 | ;; #### groups. Gnus inconsistency is getting worse every | |
1168 | ;; #### day... ;; 3/ Save everything if this was not part of | |
1169 | ;; #### a bigger operation: | |
01c52d31 MB |
1170 | (if (not save) |
1171 | ;; Nothing to save (yet): | |
1172 | t | |
1173 | ;; Something to save: | |
1174 | ;; a/ Save the NOV databases: | |
1175 | ;; #### NOTE: this should be done directory per directory in 1bis | |
1176 | ;; #### above. See comment there. | |
1177 | (nnml-save-nov) | |
1178 | ;; b/ Save the active file: | |
1179 | (nnmail-save-active nnml-group-alist nnml-active-file) | |
1180 | t))))) | |
1181 | ||
1182 | (defun nnml-request-compact (&optional server) | |
1183 | "Request compaction of all SERVER nnml groups." | |
1184 | (interactive (list (or (nnoo-current-server 'nnml) ""))) | |
1185 | (nnmail-activate 'nnml) | |
1186 | (unless (nnml-server-opened server) | |
1187 | (nnml-open-server server)) | |
1188 | (setq nnml-directory (expand-file-name nnml-directory)) | |
1189 | (let* ((groups (gnus-groups-from-server | |
1190 | (gnus-server-to-method (format "nnml:%s" server)))) | |
1191 | (first (pop groups)) | |
1192 | group) | |
1193 | (when first | |
1194 | (while (setq group (pop groups)) | |
1195 | (nnml-request-compact-group (gnus-group-real-name group) server)) | |
1196 | (nnml-request-compact-group (gnus-group-real-name first) server t)))) | |
1197 | ||
eec82323 LMI |
1198 | |
1199 | (provide 'nnml) | |
1200 | ||
1201 | ;;; nnml.el ends here |