1.1.1
by Chris Hanson
Import upstream version 7.7.90 |
1 |
#| -*-Scheme-*-
|
2 |
||
3 |
$Id: imail-rmail.scm,v 1.73 2003/02/14 18:28:14 cph Exp $
|
|
4 |
||
5 |
Copyright 1999-2002 Massachusetts Institute of Technology
|
|
6 |
||
7 |
This file is part of MIT/GNU Scheme.
|
|
8 |
||
9 |
MIT/GNU Scheme is free software; you can redistribute it and/or modify
|
|
10 |
it under the terms of the GNU General Public License as published by
|
|
11 |
the Free Software Foundation; either version 2 of the License, or (at
|
|
12 |
your option) any later version.
|
|
13 |
||
14 |
MIT/GNU Scheme is distributed in the hope that it will be useful, but
|
|
15 |
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
16 |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
17 |
General Public License for more details.
|
|
18 |
||
19 |
You should have received a copy of the GNU General Public License
|
|
20 |
along with MIT/GNU Scheme; if not, write to the Free Software
|
|
21 |
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
|
|
22 |
USA.
|
|
23 |
||
24 |
|#
|
|
1
by Chris Hanson
Import upstream version 7.7.0 |
25 |
|
26 |
;;;; IMAIL mail reader: RMAIL back end
|
|
27 |
||
28 |
(declare (usual-integrations)) |
|
29 |
||
30 |
(define-class <rmail-folder-type> (<file-folder-type>)) |
|
31 |
||
32 |
(define-file-folder-type <rmail-folder-type> "Rmail" |
|
1.1.1
by Chris Hanson
Import upstream version 7.7.90 |
33 |
(lambda (pathname) |
34 |
(check-file-prefix pathname "BABYL OPTIONS:"))) |
|
1
by Chris Hanson
Import upstream version 7.7.0 |
35 |
|
36 |
;;;; Server
|
|
37 |
||
38 |
(define-method create-file-folder-file (url (type <rmail-folder-type>)) |
|
39 |
type
|
|
40 |
(call-with-binary-output-file (pathname-url-pathname url) |
|
41 |
(lambda (port) |
|
42 |
(write-rmail-file-header (make-rmail-folder-header-fields '()) port)))) |
|
43 |
||
44 |
;;;; Folder
|
|
45 |
||
46 |
(define-class (<rmail-folder> (constructor (locator))) (<file-folder>) |
|
47 |
(header-fields define standard)) |
|
48 |
||
49 |
(define-method rmail-folder-header-fields ((folder <folder>)) |
|
50 |
(compute-rmail-folder-header-fields folder)) |
|
51 |
||
52 |
(define (compute-rmail-folder-header-fields folder) |
|
53 |
(make-rmail-folder-header-fields (folder-flags folder))) |
|
54 |
||
55 |
(define (make-rmail-folder-header-fields flags) |
|
56 |
(list (make-header-field "Version" "5") |
|
57 |
(make-header-field "Labels" |
|
58 |
(decorated-string-append |
|
59 |
"" "," "" |
|
60 |
(flags->rmail-labels flags))) |
|
61 |
(make-header-field "Note" "This is the header of an rmail file.") |
|
62 |
(make-header-field "Note" "If you are seeing it in rmail,") |
|
63 |
(make-header-field "Note" "it means the file has no messages in it."))) |
|
64 |
||
65 |
(define-method %open-file-resource (url (type <rmail-folder-type>)) |
|
66 |
type
|
|
67 |
(maybe-make-resource url make-rmail-folder)) |
|
68 |
||
69 |
;;;; Message
|
|
70 |
||
71 |
(define-class (<rmail-message> |
|
72 |
(constructor (header-fields body flags |
|
73 |
displayed-header-fields
|
|
74 |
internal-time))) |
|
75 |
(<file-message>) |
|
76 |
displayed-header-fields
|
|
77 |
internal-time) |
|
78 |
||
79 |
(define-generic rmail-message-displayed-header-fields (message)) |
|
80 |
||
81 |
(define-file-external-message-method rmail-message-displayed-header-fields |
|
82 |
<rmail-message>
|
|
83 |
'DISPLAYED-HEADER-FIELDS
|
|
84 |
string->header-fields) |
|
85 |
||
86 |
(define-method rmail-message-displayed-header-fields ((message <message>)) |
|
87 |
message
|
|
88 |
'UNDEFINED) |
|
89 |
||
90 |
(let ((accessor (slot-accessor <rmail-message> 'INTERNAL-TIME))) |
|
91 |
(define-method message-internal-time ((message <rmail-message>)) |
|
92 |
(or (accessor message) |
|
93 |
(call-next-method message)))) |
|
94 |
||
95 |
(define-method make-message-copy ((message <message>) (folder <rmail-folder>)) |
|
96 |
folder
|
|
97 |
(make-rmail-message (message-header-fields message) |
|
98 |
(file-message-body message) |
|
99 |
(list-copy (message-flags message)) |
|
100 |
(rmail-message-displayed-header-fields message) |
|
101 |
(message-internal-time message))) |
|
102 |
||
103 |
;;;; Read RMAIL file
|
|
104 |
||
105 |
(define-method revert-file-folder ((folder <rmail-folder>)) |
|
106 |
(read-file-folder-contents folder |
|
107 |
(lambda (port) |
|
108 |
(set-rmail-folder-header-fields! folder (read-rmail-prolog port)) |
|
109 |
(let loop ((line #f) (index 0) (messages '())) |
|
110 |
(if (= 0 (remainder index 100)) |
|
111 |
(imail-ui:progress-meter index #f)) |
|
112 |
(call-with-values (lambda () (read-rmail-message folder port line)) |
|
113 |
(lambda (message line) |
|
114 |
(if message |
|
115 |
(begin |
|
116 |
(attach-message! message folder index) |
|
117 |
(loop line (+ index 1) (cons message messages))) |
|
118 |
(list->vector (reverse! messages))))))))) |
|
119 |
||
120 |
(define (read-rmail-prolog port) |
|
121 |
(if (not (rmail-prolog-start-line? (read-required-line port))) |
|
122 |
(error "Not an RMAIL file:" port)) |
|
123 |
(lines->header-fields (read-lines-to-eom port))) |
|
124 |
||
125 |
(define (read-rmail-message folder port read-ahead-line) |
|
126 |
(let ((line (or read-ahead-line (read-line port)))) |
|
127 |
(cond ((eof-object? line) |
|
128 |
(values #f #f)) |
|
129 |
((rmail-prolog-start-line? line) |
|
130 |
(discard-to-eom port) |
|
131 |
(read-rmail-message folder port #f)) |
|
132 |
((rmail-message-start-line? line) |
|
133 |
(values (read-rmail-message-1 folder port) #f)) |
|
134 |
((umail-delimiter? line) |
|
135 |
(read-umail-message folder line port |
|
136 |
(lambda (line) |
|
137 |
(or (rmail-prolog-start-line? line) |
|
138 |
(rmail-message-start-line? line) |
|
139 |
(umail-delimiter? line))))) |
|
140 |
(else |
|
141 |
(error "Malformed RMAIL file:" port))))) |
|
142 |
||
143 |
(define (read-rmail-message-1 folder port) |
|
144 |
(call-with-values (lambda () (read-rmail-attributes-line port)) |
|
145 |
(lambda (formatted? flags) |
|
146 |
(let* ((headers (read-rmail-alternate-headers port)) |
|
147 |
(displayed-headers (read-rmail-displayed-headers port)) |
|
148 |
(body (read-rmail-body port)) |
|
149 |
(finish |
|
150 |
(lambda (headers displayed-headers) |
|
151 |
(call-with-values |
|
152 |
(lambda () |
|
153 |
(parse-rmail-internal-time-header folder headers)) |
|
154 |
(lambda (headers time) |
|
155 |
(make-rmail-message headers |
|
156 |
body
|
|
157 |
flags
|
|
158 |
displayed-headers
|
|
159 |
time)))))) |
|
160 |
(if formatted? |
|
161 |
(finish headers displayed-headers) |
|
162 |
(finish displayed-headers 'UNDEFINED)))))) |
|
163 |
||
164 |
(define (read-rmail-attributes-line port) |
|
165 |
(let ((line (read-required-line port))) |
|
166 |
(let ((n (string-length line)) |
|
167 |
(lose |
|
168 |
(lambda () |
|
169 |
(error "Malformed RMAIL message-attributes line:" line)))) |
|
170 |
(if (not (and (fix:>= n 3) |
|
171 |
(char=? (string-ref line 1) #\,))) |
|
172 |
(lose)) |
|
173 |
(values (cond ((char=? (string-ref line 0) #\0) #f) |
|
174 |
((char=? (string-ref line 0) #\1) #t) |
|
175 |
(else (lose))) |
|
176 |
(let loop ((i 2) (flags '()) (unseen? #f)) |
|
177 |
(if (fix:< i n) |
|
178 |
(if (or (char=? (string-ref line i) #\space) |
|
179 |
(char=? (string-ref line i) #\,)) |
|
180 |
(loop (fix:+ i 1) flags unseen?) |
|
181 |
(let scan-token ((i* (fix:+ i 1))) |
|
182 |
(if (or (fix:= i* n) |
|
183 |
(char=? (string-ref line i*) #\space) |
|
184 |
(char=? (string-ref line i*) #\,)) |
|
185 |
(let ((flag (substring line i i*))) |
|
186 |
(if (string-ci=? flag "unseen") |
|
187 |
(loop i* flags #t) |
|
188 |
(loop i* (cons flag flags) unseen?))) |
|
189 |
(scan-token (fix:+ i* 1))))) |
|
190 |
(if unseen? |
|
191 |
(reverse! flags) |
|
192 |
(cons "seen" (reverse! flags))))))))) |
|
193 |
||
194 |
(define (read-rmail-alternate-headers port) |
|
195 |
(let ((start (xstring-port/position port))) |
|
196 |
(make-file-external-ref |
|
197 |
start
|
|
198 |
(let* ((separator rmail-message:headers-separator) |
|
199 |
(s0 (string-ref separator 0)) |
|
200 |
(sl (string-length separator))) |
|
201 |
(let loop () |
|
202 |
(let ((char (read-required-char port))) |
|
203 |
(cond ((char=? char #\newline) |
|
204 |
(let ((end (- (xstring-port/position port) 1))) |
|
205 |
(if (not (string=? separator (read-required-line port))) |
|
206 |
(error "Missing RMAIL headers-separator string:" port)) |
|
207 |
end)) |
|
208 |
((char=? char s0) |
|
209 |
(let ((line (read-required-line port))) |
|
210 |
(if (substring=? line 0 (string-length line) |
|
211 |
separator 1 sl) |
|
212 |
(- (xstring-port/position port) |
|
213 |
(+ (string-length line) 1)) |
|
214 |
(loop)))) |
|
215 |
(else |
|
216 |
(skip-to-line-start port) |
|
217 |
(loop))))))))) |
|
218 |
||
219 |
(define (read-rmail-displayed-headers port) |
|
220 |
(let ((start (xstring-port/position port))) |
|
221 |
(skip-past-blank-line port) |
|
222 |
(make-file-external-ref start (- (xstring-port/position port) 1)))) |
|
223 |
||
224 |
(define (read-rmail-body port) |
|
225 |
(let ((start (xstring-port/position port))) |
|
226 |
(input-port/discard-chars port rmail-message:end-char-set) |
|
227 |
(input-port/discard-char port) |
|
228 |
(make-file-external-ref start (- (xstring-port/position port) 1)))) |
|
229 |
||
230 |
(define (parse-rmail-internal-time-header folder headers) |
|
231 |
(call-with-values |
|
232 |
(lambda () (file-folder-strip-internal-headers folder headers)) |
|
233 |
(lambda (headers internal-headers) |
|
234 |
(values headers |
|
235 |
(let ((v |
|
236 |
(get-first-header-field internal-headers |
|
237 |
"X-IMAIL-INTERNAL-TIME"
|
|
238 |
#f))) |
|
239 |
(and v |
|
240 |
(parse-header-field-date v))))))) |
|
241 |
||
242 |
;;;; Write RMAIL file
|
|
243 |
||
244 |
(define-method write-file-folder ((folder <rmail-folder>) pathname) |
|
245 |
(call-with-binary-output-file pathname |
|
246 |
(lambda (port) |
|
247 |
(write-rmail-file-header (rmail-folder-header-fields folder) port) |
|
248 |
(for-each-vector-element (file-folder-messages folder) |
|
249 |
(lambda (message) |
|
250 |
(write-rmail-message message port)))))) |
|
251 |
||
252 |
(define-method append-message-to-file (message url (type <rmail-folder-type>)) |
|
253 |
type
|
|
254 |
(call-with-binary-append-file (pathname-url-pathname url) |
|
255 |
(lambda (port) |
|
256 |
(write-rmail-message message port)))) |
|
257 |
||
258 |
(define (write-rmail-file-header header-fields port) |
|
259 |
(write-string "BABYL OPTIONS: -*- rmail -*-" port) |
|
260 |
(newline port) |
|
261 |
(write-header-fields header-fields port) |
|
262 |
(write-char rmail-message:end-char port)) |
|
263 |
||
264 |
(define (write-rmail-message message port) |
|
265 |
(write-char rmail-message:start-char port) |
|
266 |
(newline port) |
|
267 |
(let ((headers |
|
268 |
(let ((headers (message-header-fields message)) |
|
269 |
(time (message-internal-time message))) |
|
270 |
(if time |
|
271 |
(cons (make-header-field "X-IMAIL-INTERNAL-TIME" |
|
272 |
(universal-time->string time)) |
|
273 |
headers) |
|
274 |
headers))) |
|
275 |
(displayed-headers (rmail-message-displayed-header-fields message))) |
|
276 |
(let ((formatted? (not (eq? 'UNDEFINED displayed-headers)))) |
|
277 |
(write-rmail-attributes-line message formatted? port) |
|
278 |
(if formatted? (write-header-fields headers port)) |
|
279 |
(write-string rmail-message:headers-separator port) |
|
280 |
(newline port) |
|
281 |
(write-header-fields (if formatted? displayed-headers headers) port) |
|
282 |
(write-message-body message port) |
|
283 |
(fresh-line port) |
|
284 |
(write-char rmail-message:end-char port)))) |
|
285 |
||
286 |
(define (write-rmail-attributes-line message formatted? port) |
|
287 |
(write-char (if formatted? #\1 #\0) port) |
|
288 |
(write-char #\, port) |
|
289 |
(call-with-values (lambda () (flags->rmail-markers (message-flags message))) |
|
290 |
(lambda (attributes labels) |
|
291 |
(let ((write-markers |
|
292 |
(lambda (markers) |
|
293 |
(for-each (lambda (marker) |
|
294 |
(write-char #\space port) |
|
295 |
(write-string marker port) |
|
296 |
(write-char #\, port)) |
|
297 |
markers)))) |
|
298 |
(write-markers attributes) |
|
299 |
(write-char #\, port) |
|
300 |
(write-markers labels)))) |
|
301 |
(newline port)) |
|
302 |
||
303 |
;;;; Attributes and labels
|
|
304 |
||
305 |
(define (rmail-markers->flags attributes labels) |
|
306 |
(let loop ((strings (append attributes labels)) (flags '())) |
|
307 |
(if (pair? strings) |
|
308 |
(loop (cdr strings) (cons (car strings) flags)) |
|
309 |
(reverse! |
|
310 |
(if (flags-member? "unseen" flags) |
|
311 |
(flags-delete! "unseen" flags) |
|
312 |
(cons "seen" flags)))))) |
|
313 |
||
314 |
(define (flags->rmail-markers flags) |
|
315 |
(let loop |
|
316 |
((flags |
|
317 |
(if (flags-member? "seen" flags) |
|
318 |
(flags-delete "seen" flags) |
|
319 |
(cons "unseen" flags))) |
|
320 |
(attributes '()) |
|
321 |
(labels '())) |
|
322 |
(if (pair? flags) |
|
323 |
(if (member (car flags) rmail-attributes) |
|
324 |
(loop (cdr flags) (cons (car flags) attributes) labels) |
|
325 |
(loop (cdr flags) attributes (cons (car flags) labels))) |
|
326 |
(values (reverse! attributes) (reverse! labels))))) |
|
327 |
||
328 |
(define (flags->rmail-labels flags) |
|
329 |
(call-with-values (lambda () (flags->rmail-markers flags)) |
|
330 |
(lambda (attributes labels) |
|
331 |
attributes
|
|
332 |
labels))) |
|
333 |
||
334 |
;;;; Syntactic Markers
|
|
335 |
||
336 |
(define (rmail-prolog-start-line? line) |
|
337 |
(string-prefix? "BABYL OPTIONS:" line)) |
|
338 |
||
339 |
(define (rmail-prolog-end-line? line) |
|
340 |
(string-prefix? "\037" line)) |
|
341 |
||
342 |
(define (rmail-message-start-line? line) |
|
343 |
(string=? "\f" line)) |
|
344 |
||
345 |
(define rmail-message:headers-separator |
|
346 |
"*** EOOH ***") |
|
347 |
||
348 |
(define rmail-message:start-char |
|
349 |
#\page) |
|
350 |
||
351 |
(define rmail-message:end-char |
|
352 |
(integer->char #x1f)) |
|
353 |
||
354 |
(define rmail-message:end-char-set |
|
355 |
(char-set rmail-message:end-char)) |
|
356 |
||
357 |
(define rmail-attributes |
|
358 |
'("deleted" "answered" "unseen" "filed" "forwarded" "edited" "resent")) |
|
359 |
||
360 |
(define (read-lines-to-eom port) |
|
361 |
(source->list |
|
362 |
(lambda () |
|
363 |
(if (eqv? rmail-message:end-char (peek-char port)) |
|
364 |
(begin |
|
365 |
(read-char port) ;discard |
|
366 |
(make-eof-object port)) |
|
367 |
(read-required-line port))))) |
|
368 |
||
369 |
(define (read-to-eom port) |
|
370 |
(let ((string (read-string rmail-message:end-char-set port))) |
|
371 |
(if (or (eof-object? string) |
|
372 |
(eof-object? (read-char port))) |
|
373 |
(error "EOF while reading RMAIL message body:" port)) |
|
374 |
string)) |
|
375 |
||
376 |
(define (discard-to-eom port) |
|
377 |
(input-port/discard-chars port rmail-message:end-char-set) |
|
378 |
(input-port/discard-char port)) |