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