Fix up comment convention on the arch-tag lines.
[bpt/emacs.git] / lisp / gnus / nnsoup.el
CommitLineData
eec82323 1;;; nnsoup.el --- SOUP access for Gnus
16409b0b 2
e84b4b86 3;; Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003,
e3fe4da0 4;; 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
eec82323 5
6748645f 6;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
23f87bed 7;; Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
eec82323
LMI
8;; Keywords: news, mail
9
10;; This file is part of GNU Emacs.
11
12;; GNU Emacs is free software; you can redistribute it and/or modify
13;; it under the terms of the GNU General Public License as published by
5a9dffec 14;; the Free Software Foundation; either version 3, or (at your option)
eec82323
LMI
15;; any later version.
16
17;; GNU Emacs is distributed in the hope that it will be useful,
18;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20;; GNU General Public License for more details.
21
22;; You should have received a copy of the GNU General Public License
23;; along with GNU Emacs; see the file COPYING. If not, write to the
3a35cf56
LK
24;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
25;; Boston, MA 02110-1301, USA.
eec82323
LMI
26
27;;; Commentary:
28
29;;; Code:
30
31(require 'nnheader)
32(require 'nnmail)
33(require 'gnus-soup)
34(require 'gnus-msg)
35(require 'nnoo)
36(eval-when-compile (require 'cl))
37
38(nnoo-declare nnsoup)
39
b66f54c1 40(defvoo nnsoup-directory (nnheader-concat gnus-home-directory "SOUP/")
eec82323
LMI
41 "*SOUP packet directory.")
42
16409b0b
GM
43(defvoo nnsoup-tmp-directory
44 (cond ((fboundp 'temp-directory) (temp-directory))
45 ((boundp 'temporary-file-directory) temporary-file-directory)
46 ("/tmp/"))
eec82323
LMI
47 "*Where nnsoup will store temporary files.")
48
16409b0b 49(defvoo nnsoup-replies-directory (expand-file-name "replies/" nnsoup-directory)
eec82323
LMI
50 "*Directory where outgoing packets will be composed.")
51
16409b0b 52(defvoo nnsoup-replies-format-type ?u ;; u is USENET news format.
eec82323
LMI
53 "*Format of the replies packages.")
54
55(defvoo nnsoup-replies-index-type ?n
56 "*Index type of the replies packages.")
57
16409b0b 58(defvoo nnsoup-active-file (expand-file-name "active" nnsoup-directory)
eec82323
LMI
59 "Active file.")
60
b66f54c1
MB
61(defvoo nnsoup-packer (concat "tar cf - %s | gzip > "
62 (expand-file-name gnus-home-directory)
63 "Soupin%d.tgz")
eec82323
LMI
64 "Format string command for packing a SOUP packet.
65The SOUP files will be inserted where the %s is in the string.
66This string MUST contain both %s and %d. The file number will be
67inserted where %d appears.")
68
69(defvoo nnsoup-unpacker "gunzip -c %s | tar xvf -"
70 "*Format string command for unpacking a SOUP packet.
71The SOUP packet file name will be inserted at the %s.")
72
b66f54c1 73(defvoo nnsoup-packet-directory gnus-home-directory
eec82323
LMI
74 "*Where nnsoup will look for incoming packets.")
75
76(defvoo nnsoup-packet-regexp "Soupout"
77 "*Regular expression matching SOUP packets in `nnsoup-packet-directory'.")
78
6748645f 79(defvoo nnsoup-always-save t
06856b12 80 "If non-nil commit the reply buffer on each message send.
16409b0b 81This is necessary if using message mode outside Gnus with nnsoup as a
6748645f
LMI
82backend for the messages.")
83
eec82323
LMI
84\f
85
86(defconst nnsoup-version "nnsoup 0.0"
87 "nnsoup version.")
88
89(defvoo nnsoup-status-string "")
90(defvoo nnsoup-group-alist nil)
91(defvoo nnsoup-current-prefix 0)
92(defvoo nnsoup-replies-list nil)
93(defvoo nnsoup-buffers nil)
94(defvoo nnsoup-current-group nil)
95(defvoo nnsoup-group-alist-touched nil)
96(defvoo nnsoup-article-alist nil)
eec82323
LMI
97\f
98
99;;; Interface functions.
100
101(nnoo-define-basics nnsoup)
102
103(deffoo nnsoup-retrieve-headers (sequence &optional group server fetch-old)
104 (nnsoup-possibly-change-group group)
105 (save-excursion
106 (set-buffer nntp-server-buffer)
107 (erase-buffer)
108 (let ((areas (cddr (assoc nnsoup-current-group nnsoup-group-alist)))
109 (articles sequence)
110 (use-nov t)
111 useful-areas this-area-seq msg-buf)
112 (if (stringp (car sequence))
113 ;; We don't support fetching by Message-ID.
114 'headers
115 ;; We go through all the areas and find which files the
116 ;; articles in SEQUENCE come from.
117 (while (and areas sequence)
118 ;; Peel off areas that are below sequence.
d942a83d 119 (while (and areas (< (cdar (car areas)) (car sequence)))
eec82323
LMI
120 (setq areas (cdr areas)))
121 (when areas
122 ;; This is a useful area.
123 (push (car areas) useful-areas)
124 (setq this-area-seq nil)
125 ;; We take note whether this MSG has a corresponding IDX
126 ;; for later use.
127 (when (or (= (gnus-soup-encoding-index
128 (gnus-soup-area-encoding (nth 1 (car areas)))) ?n)
129 (not (file-exists-p
130 (nnsoup-file
131 (gnus-soup-area-prefix (nth 1 (car areas)))))))
132 (setq use-nov nil))
133 ;; We assign the portion of `sequence' that is relevant to
134 ;; this MSG packet to this packet.
d942a83d 135 (while (and sequence (<= (car sequence) (cdar (car areas))))
eec82323
LMI
136 (push (car sequence) this-area-seq)
137 (setq sequence (cdr sequence)))
138 (setcar useful-areas (cons (nreverse this-area-seq)
139 (car useful-areas)))))
140
141 ;; We now have a list of article numbers and corresponding
142 ;; areas.
143 (setq useful-areas (nreverse useful-areas))
144
145 ;; Two different approaches depending on whether all the MSG
146 ;; files have corresponding IDX files. If they all do, we
147 ;; simply return the relevant IDX files and let Gnus sort out
148 ;; what lines are relevant. If some of the IDX files are
149 ;; missing, we must return HEADs for all the articles.
150 (if use-nov
151 ;; We have IDX files for all areas.
152 (progn
153 (while useful-areas
154 (goto-char (point-max))
155 (let ((b (point))
156 (number (car (nth 1 (car useful-areas))))
157 (index-buffer (nnsoup-index-buffer
158 (gnus-soup-area-prefix
159 (nth 2 (car useful-areas))))))
160 (when index-buffer
161 (insert-buffer-substring index-buffer)
162 (goto-char b)
8f688cb0 163 ;; We have to remove the index number entries and
eec82323
LMI
164 ;; insert article numbers instead.
165 (while (looking-at "[0-9]+")
166 (replace-match (int-to-string number) t t)
167 (incf number)
168 (forward-line 1))))
169 (setq useful-areas (cdr useful-areas)))
170 'nov)
171 ;; We insert HEADs.
172 (while useful-areas
173 (setq articles (caar useful-areas)
174 useful-areas (cdr useful-areas))
175 (while articles
176 (when (setq msg-buf
177 (nnsoup-narrow-to-article
178 (car articles) (cdar useful-areas) 'head))
179 (goto-char (point-max))
180 (insert (format "221 %d Article retrieved.\n" (car articles)))
181 (insert-buffer-substring msg-buf)
182 (goto-char (point-max))
183 (insert ".\n"))
184 (setq articles (cdr articles))))
185
186 (nnheader-fold-continuation-lines)
187 'headers)))))
188
189(deffoo nnsoup-open-server (server &optional defs)
190 (nnoo-change-server 'nnsoup server defs)
191 (when (not (file-exists-p nnsoup-directory))
192 (condition-case ()
193 (make-directory nnsoup-directory t)
194 (error t)))
195 (cond
196 ((not (file-exists-p nnsoup-directory))
197 (nnsoup-close-server)
198 (nnheader-report 'nnsoup "Couldn't create directory: %s" nnsoup-directory))
199 ((not (file-directory-p (file-truename nnsoup-directory)))
200 (nnsoup-close-server)
201 (nnheader-report 'nnsoup "Not a directory: %s" nnsoup-directory))
202 (t
203 (nnsoup-read-active-file)
204 (nnheader-report 'nnsoup "Opened server %s using directory %s"
205 server nnsoup-directory)
206 t)))
207
208(deffoo nnsoup-request-close ()
209 (nnsoup-write-active-file)
210 (nnsoup-write-replies)
211 (gnus-soup-save-areas)
212 ;; Kill all nnsoup buffers.
213 (let (buffer)
214 (while nnsoup-buffers
215 (setq buffer (cdr (pop nnsoup-buffers)))
216 (and buffer
217 (buffer-name buffer)
218 (kill-buffer buffer))))
219 (setq nnsoup-group-alist nil
220 nnsoup-group-alist-touched nil
221 nnsoup-current-group nil
222 nnsoup-replies-list nil)
223 (nnoo-close-server 'nnoo)
224 t)
225
226(deffoo nnsoup-request-article (id &optional newsgroup server buffer)
227 (nnsoup-possibly-change-group newsgroup)
228 (let (buf)
229 (save-excursion
230 (set-buffer (or buffer nntp-server-buffer))
231 (erase-buffer)
232 (when (and (not (stringp id))
233 (setq buf (nnsoup-narrow-to-article id)))
234 (insert-buffer-substring buf)
235 t))))
236
237(deffoo nnsoup-request-group (group &optional server dont-check)
238 (nnsoup-possibly-change-group group)
239 (if dont-check
240 t
241 (let ((active (cadr (assoc group nnsoup-group-alist))))
242 (if (not active)
243 (nnheader-report 'nnsoup "No such group: %s" group)
244 (nnheader-insert
245 "211 %d %d %d %s\n"
246 (max (1+ (- (cdr active) (car active))) 0)
247 (car active) (cdr active) group)))))
248
249(deffoo nnsoup-request-type (group &optional article)
250 (nnsoup-possibly-change-group group)
a8151ef7 251 ;; Try to guess the type based on the first article in the group.
eec82323
LMI
252 (when (not article)
253 (setq article
d942a83d 254 (cdar (car (cddr (assoc group nnsoup-group-alist))))))
eec82323
LMI
255 (if (not article)
256 'unknown
257 (let ((kind (gnus-soup-encoding-kind
258 (gnus-soup-area-encoding
259 (nth 1 (nnsoup-article-to-area
260 article nnsoup-current-group))))))
261 (cond ((= kind ?m) 'mail)
a1506d29 262 ((= kind ?n) 'news)
eec82323
LMI
263 (t 'unknown)))))
264
265(deffoo nnsoup-close-group (group &optional server)
266 ;; Kill all nnsoup buffers.
267 (let ((buffers nnsoup-buffers)
268 elem)
269 (while buffers
270 (when (equal (car (setq elem (pop buffers))) group)
271 (setq nnsoup-buffers (delq elem nnsoup-buffers))
272 (and (cdr elem) (buffer-name (cdr elem))
273 (kill-buffer (cdr elem))))))
274 t)
275
276(deffoo nnsoup-request-list (&optional server)
277 (save-excursion
278 (set-buffer nntp-server-buffer)
279 (erase-buffer)
280 (unless nnsoup-group-alist
281 (nnsoup-read-active-file))
282 (let ((alist nnsoup-group-alist)
283 (standard-output (current-buffer))
284 entry)
285 (while (setq entry (pop alist))
286 (insert (car entry) " ")
287 (princ (cdadr entry))
288 (insert " ")
289 (princ (caadr entry))
290 (insert " y\n"))
291 t)))
292
293(deffoo nnsoup-request-scan (group &optional server)
294 (nnsoup-unpack-packets))
295
296(deffoo nnsoup-request-newgroups (date &optional server)
297 (nnsoup-request-list))
298
299(deffoo nnsoup-request-list-newsgroups (&optional server)
300 nil)
301
302(deffoo nnsoup-request-post (&optional server)
303 (nnsoup-store-reply "news")
304 t)
305
306(deffoo nnsoup-request-mail (&optional server)
307 (nnsoup-store-reply "mail")
308 t)
309
310(deffoo nnsoup-request-expire-articles (articles group &optional server force)
311 (nnsoup-possibly-change-group group)
312 (let* ((total-infolist (assoc group nnsoup-group-alist))
313 (active (cadr total-infolist))
314 (infolist (cddr total-infolist))
315 info range-list mod-time prefix)
316 (while infolist
317 (setq info (pop infolist)
318 range-list (gnus-uncompress-range (car info))
319 prefix (gnus-soup-area-prefix (nth 1 info)))
16409b0b 320 (when;; All the articles in this file are marked for expiry.
eec82323
LMI
321 (and (or (setq mod-time (nth 5 (file-attributes
322 (nnsoup-file prefix))))
323 (setq mod-time (nth 5 (file-attributes
324 (nnsoup-file prefix t)))))
325 (gnus-sublist-p articles range-list)
326 ;; This file is old enough.
327 (nnmail-expired-article-p group mod-time force))
328 ;; Ok, we delete this file.
329 (when (ignore-errors
330 (nnheader-message
331 5 "Deleting %s in group %s..." (nnsoup-file prefix)
332 group)
333 (when (file-exists-p (nnsoup-file prefix))
334 (delete-file (nnsoup-file prefix)))
335 (nnheader-message
336 5 "Deleting %s in group %s..." (nnsoup-file prefix t)
337 group)
338 (when (file-exists-p (nnsoup-file prefix t))
339 (delete-file (nnsoup-file prefix t)))
340 t)
341 (setcdr (cdr total-infolist) (delq info (cddr total-infolist)))
23f87bed 342 (setq articles (gnus-sorted-difference articles range-list))))
eec82323
LMI
343 (when (not mod-time)
344 (setcdr (cdr total-infolist) (delq info (cddr total-infolist)))))
345 (if (cddr total-infolist)
346 (setcar active (caaadr (cdr total-infolist)))
347 (setcar active (1+ (cdr active))))
348 (nnsoup-write-active-file t)
349 ;; Return the articles that weren't expired.
350 articles))
351
352\f
353;;; Internal functions
354
355(defun nnsoup-possibly-change-group (group &optional force)
356 (when (and group
357 (not (equal nnsoup-current-group group)))
358 (setq nnsoup-article-alist nil)
359 (setq nnsoup-current-group group))
360 t)
361
362(defun nnsoup-read-active-file ()
363 (setq nnsoup-group-alist nil)
364 (when (file-exists-p nnsoup-active-file)
365 (ignore-errors
366 (load nnsoup-active-file t t t))
367 ;; Be backwards compatible.
368 (when (and nnsoup-group-alist
369 (not (atom (caadar nnsoup-group-alist))))
370 (let ((alist nnsoup-group-alist)
371 entry e min max)
372 (while (setq e (cdr (setq entry (pop alist))))
373 (setq min (caaar e))
01c52d31 374 (setq max (cdar (car (last e))))
eec82323
LMI
375 (setcdr entry (cons (cons min max) (cdr entry)))))
376 (setq nnsoup-group-alist-touched t))
377 nnsoup-group-alist))
378
379(defun nnsoup-write-active-file (&optional force)
380 (when (and nnsoup-group-alist
381 (or force
382 nnsoup-group-alist-touched))
383 (setq nnsoup-group-alist-touched nil)
16409b0b 384 (with-temp-file nnsoup-active-file
eec82323
LMI
385 (gnus-prin1 `(setq nnsoup-group-alist ',nnsoup-group-alist))
386 (insert "\n")
387 (gnus-prin1 `(setq nnsoup-current-prefix ,nnsoup-current-prefix))
388 (insert "\n"))))
389
390(defun nnsoup-next-prefix ()
391 "Return the next free prefix."
392 (let (prefix)
393 (while (or (file-exists-p
394 (nnsoup-file (setq prefix (int-to-string
395 nnsoup-current-prefix))))
396 (file-exists-p (nnsoup-file prefix t)))
397 (incf nnsoup-current-prefix))
398 (incf nnsoup-current-prefix)
399 prefix))
400
401(defun nnsoup-file-name (dir file)
35ef97a5 402 "Return the full name of FILE (in any case) in DIR."
eec82323
LMI
403 (let* ((case-fold-search t)
404 (files (directory-files dir t))
405 (regexp (concat (regexp-quote file) "$")))
406 (car (delq nil
407 (mapcar
408 (lambda (file)
409 (if (string-match regexp file)
410 file
411 nil))
412 files)))))
413
414(defun nnsoup-read-areas ()
415 (let ((areas-file (nnsoup-file-name nnsoup-tmp-directory "areas")))
416 (when areas-file
417 (save-excursion
418 (set-buffer nntp-server-buffer)
419 (let ((areas (gnus-soup-parse-areas areas-file))
420 entry number area lnum cur-prefix file)
421 ;; Go through all areas in the new AREAS file.
422 (while (setq area (pop areas))
423 ;; Change the name to the permanent name and move the files.
424 (setq cur-prefix (nnsoup-next-prefix))
6748645f 425 (nnheader-message 5 "Incorporating file %s..." cur-prefix)
eec82323 426 (when (file-exists-p
16409b0b
GM
427 (setq file
428 (expand-file-name
429 (concat (gnus-soup-area-prefix area) ".IDX")
430 nnsoup-tmp-directory)))
eec82323
LMI
431 (rename-file file (nnsoup-file cur-prefix)))
432 (when (file-exists-p
16409b0b
GM
433 (setq file (expand-file-name
434 (concat (gnus-soup-area-prefix area) ".MSG")
435 nnsoup-tmp-directory)))
eec82323
LMI
436 (rename-file file (nnsoup-file cur-prefix t))
437 (gnus-soup-set-area-prefix area cur-prefix)
438 ;; Find the number of new articles in this area.
439 (setq number (nnsoup-number-of-articles area))
440 (if (not (setq entry (assoc (gnus-soup-area-name area)
441 nnsoup-group-alist)))
442 ;; If this is a new area (group), we just add this info to
443 ;; the group alist.
444 (push (list (gnus-soup-area-name area)
445 (cons 1 number)
446 (list (cons 1 number) area))
447 nnsoup-group-alist)
448 ;; There are already articles in this group, so we add this
449 ;; info to the end of the entry.
450 (nconc entry (list (list (cons (1+ (setq lnum (cdadr entry)))
451 (+ lnum number))
452 area)))
453 (setcdr (cadr entry) (+ lnum number))))))
454 (nnsoup-write-active-file t)
455 (delete-file areas-file)))))
456
457(defun nnsoup-number-of-articles (area)
458 (save-excursion
459 (cond
460 ;; If the number is in the area info, we just return it.
461 ((gnus-soup-area-number area)
462 (gnus-soup-area-number area))
463 ;; If there is an index file, we just count the lines.
464 ((/= (gnus-soup-encoding-index (gnus-soup-area-encoding area)) ?n)
465 (set-buffer (nnsoup-index-buffer (gnus-soup-area-prefix area)))
466 (count-lines (point-min) (point-max)))
467 ;; We do it the hard way - re-searching through the message
468 ;; buffer.
469 (t
470 (set-buffer (nnsoup-message-buffer (gnus-soup-area-prefix area)))
471 (unless (assoc (gnus-soup-area-prefix area) nnsoup-article-alist)
472 (nnsoup-dissect-buffer area))
473 (length (cdr (assoc (gnus-soup-area-prefix area)
474 nnsoup-article-alist)))))))
475
476(defun nnsoup-dissect-buffer (area)
477 (let ((mbox-delim (concat "^" message-unix-mail-delimiter))
478 (format (gnus-soup-encoding-format (gnus-soup-area-encoding area)))
479 (i 0)
480 alist len)
481 (goto-char (point-min))
482 (cond
483 ;; rnews batch format
16409b0b
GM
484 ((or (= format ?u)
485 (= format ?n)) ;; Gnus back compatibility.
eec82323
LMI
486 (while (looking-at "^#! *rnews \\(+[0-9]+\\) *$")
487 (forward-line 1)
488 (push (list
489 (incf i) (point)
490 (progn
491 (forward-char (string-to-number (match-string 1)))
492 (point)))
493 alist)))
494 ;; Unix mbox format
495 ((= format ?m)
496 (while (looking-at mbox-delim)
497 (forward-line 1)
498 (push (list
499 (incf i) (point)
500 (progn
501 (if (re-search-forward mbox-delim nil t)
502 (beginning-of-line)
503 (goto-char (point-max)))
504 (point)))
505 alist)))
506 ;; MMDF format
507 ((= format ?M)
508 (while (looking-at "\^A\^A\^A\^A\n")
509 (forward-line 1)
510 (push (list
511 (incf i) (point)
512 (progn
513 (if (search-forward "\n\^A\^A\^A\^A\n" nil t)
514 (beginning-of-line)
515 (goto-char (point-max)))
516 (point)))
517 alist)))
518 ;; Binary format
519 ((or (= format ?B) (= format ?b))
520 (while (not (eobp))
521 (setq len (+ (* (char-after (point)) (expt 2.0 24))
522 (* (char-after (+ (point) 1)) (expt 2 16))
523 (* (char-after (+ (point) 2)) (expt 2 8))
524 (char-after (+ (point) 3))))
525 (push (list
526 (incf i) (+ (point) 4)
527 (progn
528 (forward-char (floor (+ len 4)))
529 (point)))
530 alist)))
531 (t
532 (error "Unknown format: %c" format)))
533 (push (cons (gnus-soup-area-prefix area) alist) nnsoup-article-alist)))
534
535(defun nnsoup-index-buffer (prefix &optional message)
536 (let* ((file (concat prefix (if message ".MSG" ".IDX")))
537 (buffer-name (concat " *nnsoup " file "*")))
538 (or (get-buffer buffer-name) ; File already loaded.
16409b0b 539 (when (file-exists-p (expand-file-name file nnsoup-directory))
eec82323
LMI
540 (save-excursion ; Load the file.
541 (set-buffer (get-buffer-create buffer-name))
16409b0b 542 (buffer-disable-undo)
eec82323 543 (push (cons nnsoup-current-group (current-buffer)) nnsoup-buffers)
16409b0b
GM
544 (nnheader-insert-file-contents
545 (expand-file-name file nnsoup-directory))
eec82323
LMI
546 (current-buffer))))))
547
548(defun nnsoup-file (prefix &optional message)
549 (expand-file-name
16409b0b
GM
550 (concat prefix (if message ".MSG" ".IDX"))
551 nnsoup-directory))
eec82323
LMI
552
553(defun nnsoup-message-buffer (prefix)
554 (nnsoup-index-buffer prefix 'msg))
555
556(defun nnsoup-unpack-packets ()
557 "Unpack all packets in `nnsoup-packet-directory'."
558 (let ((packets (directory-files
01c52d31
MB
559 nnsoup-packet-directory t nnsoup-packet-regexp)))
560 (dolist (packet packets)
6748645f 561 (nnheader-message 5 "nnsoup: unpacking %s..." packet)
eec82323
LMI
562 (if (not (gnus-soup-unpack-packet
563 nnsoup-tmp-directory nnsoup-unpacker packet))
6748645f 564 (nnheader-message 5 "Couldn't unpack %s" packet)
eec82323
LMI
565 (delete-file packet)
566 (nnsoup-read-areas)
6748645f 567 (nnheader-message 5 "Unpacking...done")))))
eec82323
LMI
568
569(defun nnsoup-narrow-to-article (article &optional area head)
570 (let* ((area (or area (nnsoup-article-to-area article nnsoup-current-group)))
571 (prefix (and area (gnus-soup-area-prefix (nth 1 area))))
572 (msg-buf (and prefix (nnsoup-index-buffer prefix 'msg)))
573 beg end)
574 (when area
575 (save-excursion
576 (cond
577 ;; There is no MSG file.
578 ((null msg-buf)
579 nil)
580 ;; We use the index file to find out where the article
581 ;; begins and ends.
582 ((and (= (gnus-soup-encoding-index
583 (gnus-soup-area-encoding (nth 1 area)))
584 ?c)
585 (file-exists-p (nnsoup-file prefix)))
586 (set-buffer (nnsoup-index-buffer prefix))
587 (widen)
588 (goto-char (point-min))
589 (forward-line (- article (caar area)))
590 (setq beg (read (current-buffer)))
591 (forward-line 1)
592 (if (looking-at "[0-9]+")
593 (progn
594 (setq end (read (current-buffer)))
595 (set-buffer msg-buf)
596 (widen)
597 (let ((format (gnus-soup-encoding-format
598 (gnus-soup-area-encoding (nth 1 area)))))
599 (goto-char end)
16409b0b 600 (when (or (= format ?u) (= format ?n) (= format ?m))
eec82323
LMI
601 (setq end (progn (forward-line -1) (point))))))
602 (set-buffer msg-buf))
603 (widen)
604 (narrow-to-region beg (or end (point-max))))
605 (t
606 (set-buffer msg-buf)
607 (widen)
608 (unless (assoc (gnus-soup-area-prefix (nth 1 area))
609 nnsoup-article-alist)
610 (nnsoup-dissect-buffer (nth 1 area)))
611 (let ((entry (assq article (cdr (assoc (gnus-soup-area-prefix
612 (nth 1 area))
613 nnsoup-article-alist)))))
614 (when entry
615 (narrow-to-region (cadr entry) (caddr entry))))))
616 (goto-char (point-min))
617 (if (not head)
618 ()
619 (narrow-to-region
620 (point-min)
621 (if (search-forward "\n\n" nil t)
622 (1- (point))
623 (point-max))))
624 msg-buf))))
625
626;;;###autoload
627(defun nnsoup-pack-replies ()
628 "Make an outbound package of SOUP replies."
629 (interactive)
630 (unless (file-exists-p nnsoup-replies-directory)
6748645f 631 (nnheader-message 5 "No such directory: %s" nnsoup-replies-directory))
eec82323
LMI
632 ;; Write all data buffers.
633 (gnus-soup-save-areas)
634 ;; Write the active file.
635 (nnsoup-write-active-file)
636 ;; Write the REPLIES file.
637 (nnsoup-write-replies)
638 ;; Check whether there is anything here.
639 (when (null (directory-files nnsoup-replies-directory nil "\\.MSG$"))
a8151ef7 640 (error "No files to pack"))
eec82323
LMI
641 ;; Pack all these files into a SOUP packet.
642 (gnus-soup-pack nnsoup-replies-directory nnsoup-packer))
643
644(defun nnsoup-write-replies ()
645 "Write the REPLIES file."
646 (when nnsoup-replies-list
647 (gnus-soup-write-replies nnsoup-replies-directory nnsoup-replies-list)
648 (setq nnsoup-replies-list nil)))
649
650(defun nnsoup-article-to-area (article group)
651 "Return the area that ARTICLE in GROUP is located in."
652 (let ((areas (cddr (assoc group nnsoup-group-alist))))
d942a83d 653 (while (and areas (< (cdar (car areas)) article))
eec82323
LMI
654 (setq areas (cdr areas)))
655 (and areas (car areas))))
656
657(defvar nnsoup-old-functions
23f87bed 658 (list message-send-mail-real-function message-send-news-function))
eec82323
LMI
659
660;;;###autoload
661(defun nnsoup-set-variables ()
662 "Use the SOUP methods for posting news and mailing mail."
663 (interactive)
664 (setq message-send-news-function 'nnsoup-request-post)
23f87bed 665 (setq message-send-mail-real-function 'nnsoup-request-mail))
eec82323
LMI
666
667;;;###autoload
668(defun nnsoup-revert-variables ()
669 "Revert posting and mailing methods to the standard Emacs methods."
670 (interactive)
23f87bed 671 (setq message-send-mail-real-function (car nnsoup-old-functions))
eec82323
LMI
672 (setq message-send-news-function (cadr nnsoup-old-functions)))
673
674(defun nnsoup-store-reply (kind)
675 ;; Mostly stolen from `message.el'.
676 (require 'mail-utils)
677 (let ((tembuf (generate-new-buffer " message temp"))
678 (case-fold-search nil)
679 delimline
680 (mailbuf (current-buffer)))
681 (unwind-protect
682 (save-excursion
683 (save-restriction
684 (message-narrow-to-headers)
685 (if (equal kind "mail")
686 (message-generate-headers message-required-mail-headers)
687 (message-generate-headers message-required-news-headers)))
688 (set-buffer tembuf)
689 (erase-buffer)
690 (insert-buffer-substring mailbuf)
691 ;; Remove some headers.
692 (save-restriction
693 (message-narrow-to-headers)
694 ;; Remove some headers.
695 (message-remove-header message-ignored-mail-headers t))
696 (goto-char (point-max))
697 ;; require one newline at the end.
698 (or (= (preceding-char) ?\n)
699 (insert ?\n))
700 (let ((case-fold-search t))
701 ;; Change header-delimiter to be what sendmail expects.
702 (goto-char (point-min))
703 (re-search-forward
16409b0b 704 (concat "^" (regexp-quote mail-header-separator) "\n"))
eec82323
LMI
705 (replace-match "\n")
706 (backward-char 1)
707 (setq delimline (point-marker))
eec82323 708 (goto-char (1+ delimline))
eec82323
LMI
709 (let ((msg-buf
710 (gnus-soup-store
711 nnsoup-replies-directory
712 (nnsoup-kind-to-prefix kind) nil nnsoup-replies-format-type
713 nnsoup-replies-index-type))
714 (num 0))
715 (when (and msg-buf (bufferp msg-buf))
716 (save-excursion
717 (set-buffer msg-buf)
718 (goto-char (point-min))
719 (while (re-search-forward "^#! *rnews" nil t)
6748645f
LMI
720 (incf num))
721 (when nnsoup-always-save
722 (save-buffer)))
723 (nnheader-message 5 "Stored %d messages" num)))
eec82323
LMI
724 (nnsoup-write-replies)
725 (kill-buffer tembuf))))))
726
727(defun nnsoup-kind-to-prefix (kind)
728 (unless nnsoup-replies-list
729 (setq nnsoup-replies-list
730 (gnus-soup-parse-replies
16409b0b 731 (expand-file-name "REPLIES" nnsoup-replies-directory))))
eec82323
LMI
732 (let ((replies nnsoup-replies-list))
733 (while (and replies
734 (not (string= kind (gnus-soup-reply-kind (car replies)))))
735 (setq replies (cdr replies)))
736 (if replies
737 (gnus-soup-reply-prefix (car replies))
738 (push (vector (gnus-soup-unique-prefix nnsoup-replies-directory)
739 kind
740 (format "%c%c%c"
741 nnsoup-replies-format-type
742 nnsoup-replies-index-type
743 (if (string= kind "news")
744 ?n ?m)))
745 nnsoup-replies-list)
746 (gnus-soup-reply-prefix (car nnsoup-replies-list)))))
747
748(defun nnsoup-make-active ()
749 "(Re-)create the SOUP active file."
750 (interactive)
751 (let ((files (sort (directory-files nnsoup-directory t "IDX$")
752 (lambda (f1 f2)
753 (< (progn (string-match "/\\([0-9]+\\)\\." f1)
e9bd5782 754 (string-to-number (match-string 1 f1)))
eec82323 755 (progn (string-match "/\\([0-9]+\\)\\." f2)
e9bd5782 756 (string-to-number (match-string 1 f2)))))))
eec82323
LMI
757 active group lines ident elem min)
758 (set-buffer (get-buffer-create " *nnsoup work*"))
01c52d31
MB
759 (dolist (file files)
760 (nnheader-message 5 "Doing %s..." file)
eec82323 761 (erase-buffer)
01c52d31 762 (nnheader-insert-file-contents file)
eec82323
LMI
763 (goto-char (point-min))
764 (if (not (re-search-forward "^[^\t]*\t[^\t]*\t[^\t]*\t[^\t]*\t[^\t]*\t[^\t]*\t[^\t]*\t[^\t]*\t *\\(Xref: \\)? *[^ ]* \\([^ ]+\\):[0-9]" nil t))
765 (setq group "unknown")
766 (setq group (match-string 2)))
767 (setq lines (count-lines (point-min) (point-max)))
768 (setq ident (progn (string-match
01c52d31
MB
769 "/\\([0-9]+\\)\\." file)
770 (match-string 1 file)))
eec82323
LMI
771 (if (not (setq elem (assoc group active)))
772 (push (list group (cons 1 lines)
773 (list (cons 1 lines)
16409b0b 774 (vector ident group "ucm" "" lines)))
eec82323
LMI
775 active)
776 (nconc elem
777 (list
778 (list (cons (1+ (setq min (cdadr elem)))
779 (+ min lines))
16409b0b 780 (vector ident group "ucm" "" lines))))
01c52d31 781 (setcdr (cadr elem) (+ min lines))))
6748645f 782 (nnheader-message 5 "")
eec82323
LMI
783 (setq nnsoup-group-alist active)
784 (nnsoup-write-active-file t)))
785
786(defun nnsoup-delete-unreferenced-message-files ()
787 "Delete any *.MSG and *.IDX files that aren't known by nnsoup."
788 (interactive)
789 (let* ((known (apply 'nconc (mapcar
790 (lambda (ga)
791 (mapcar
792 (lambda (area)
793 (gnus-soup-area-prefix (cadr area)))
794 (cddr ga)))
795 nnsoup-group-alist)))
796 (regexp "\\.MSG$\\|\\.IDX$")
797 (files (directory-files nnsoup-directory nil regexp))
01c52d31 798 non-files)
eec82323 799 ;; Find all files that aren't known by nnsoup.
01c52d31 800 (dolist (file files)
eec82323
LMI
801 (string-match regexp file)
802 (unless (member (substring file 0 (match-beginning 0)) known)
803 (push file non-files)))
804 ;; Sort and delete the files.
805 (setq non-files (sort non-files 'string<))
806 (map-y-or-n-p "Delete file %s? "
16409b0b
GM
807 (lambda (file) (delete-file
808 (expand-file-name file nnsoup-directory)))
eec82323
LMI
809 non-files)))
810
811(provide 'nnsoup)
812
cbee283d 813;; arch-tag: b0451389-5703-4450-9425-f66f6b38c828
eec82323 814;;; nnsoup.el ends here