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