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