More intelligent handling of untagged responses, based on closer
authorChris Hanson <org/chris-hanson/cph>
Mon, 8 May 2000 04:31:01 +0000 (04:31 +0000)
committerChris Hanson <org/chris-hanson/cph>
Mon, 8 May 2000 04:31:01 +0000 (04:31 +0000)
reading of specification.  Add support for BADCHARSET and UIDNEXT
response codes.

v7/src/imail/imail-imap.scm

index 89877fabfc6dda4963cc91be9a14b8e6867f3c14..770d002c5a9a986d44762310c13851c889fed78e 100644 (file)
@@ -1,6 +1,6 @@
 ;;; -*-Scheme-*-
 ;;;
-;;; $Id: imail-imap.scm,v 1.18 2000/05/05 17:18:14 cph Exp $
+;;; $Id: imail-imap.scm,v 1.19 2000/05/08 04:31:01 cph Exp $
 ;;;
 ;;; Copyright (c) 1999-2000 Massachusetts Institute of Technology
 ;;;
            (loop (car q.r) (fix:- i 1)))))
     s))
 
+(define (base26-string->nonnegative-integer s)
+  (let ((end (string-length s)))
+    (let loop ((start 0) (n 0))
+      (if (fix:< start end)
+         (let ((digit (- (vector-8b-ref s start) (char->integer #\A))))
+           (if (not (<= 0 digit 25))
+               (error:bad-range-argument s
+                                         'BASE26-STRING->NONNEGATIVE-INTEGER))
+           (loop (fix:+ start 1) (+ (* n 26) digit)))
+         n))))
+
 (define (enqueue-imap-response connection response)
   (let ((queue (imap-connection-response-queue connection)))
     (let ((next (cons response '())))
 
 (define-class (<imap-folder> (constructor (url connection))) (<folder>)
   (connection define accessor)
+  (read-only? define standard)
   (allowed-flags define standard)
   (permanent-flags define standard)
   (permanent-keywords? define standard)
+  (uidnext define standard)
   (uidvalidity define standard)
-  (first-unseen define standard)
+  (unseen define standard)
   (messages define standard initial-value '#()))
 
 (define-class <imap-message> (<message>)
   (uid define accessor)
-  (length define accessor)
-  (envelope define accessor))
+  (length define accessor))
 
 (define make-imap-message
   (let ((constructor
         (instance-constructor <imap-message>
                               '(HEADER-FIELDS BODY FLAGS PROPERTIES
-                                              UID LENGTH ENVELOPE))))
-    (lambda (uid flags length envelope)
-      (constructor 'UNCACHED 'UNCACHED (map imap-flag->imail-flag flags)
-                  '() uid length envelope))))
-
-(let ((demand-loader
-       (lambda (generic slot-name item-name noun transform)
-        (let ((modifier (slot-modifier <imap-message> slot-name)))
-          (define-method generic ((message <imap-message>))
-            (if (eq? 'UNCACHED (call-next-method message))
-                (modifier
-                 message
-                 (transform
-                  (translate-string-line-endings
-                   (car
-                    (let ((index (message-index message)))
-                      ((imail-message-wrapper "Reading " noun
-                                              " for message "
-                                              (number->string (+ index 1)))
-                       (lambda ()
-                         (imap:command:fetch (imap-folder-connection
-                                              (message-folder message))
-                                             index
-                                             (list item-name))))))))))
-            (call-next-method message))))))
-  (demand-loader message-header-fields 'HEADER-FIELDS 'RFC822.HEADER "headers"
-                (lambda (string)
-                  (lines->header-fields
-                   (except-last-pair! (string->lines string)))))
-  (demand-loader message-body 'BODY 'RFC822.TEXT "body" identity-procedure))
-\f
+                                              UID LENGTH))))
+    (lambda (uid flags rfc822.size rfc822.header)
+      (constructor (lines->header-fields
+                   (except-last-pair!
+                    (string->lines
+                     (translate-string-line-endings rfc822.header))))
+                  'UNCACHED
+                  (map imap-flag->imail-flag flags)
+                  '()
+                  uid
+                  rfc822.size))))
+
+(let ((modifier (slot-modifier <imap-message> 'BODY)))
+  (define-method message-body ((message <imap-message>))
+    (let ((body (call-next-method message)))
+      (if (eq? 'UNCACHED body)
+         (let ((body
+                (translate-string-line-endings
+                 (car
+                  (let ((index (message-index message)))
+                    ((imail-message-wrapper "Reading body for message "
+                                            (number->string (+ index 1)))
+                     (lambda ()
+                       (imap:command:fetch (imap-folder-connection
+                                            (message-folder message))
+                                           index
+                                           '(RFC822.TEXT)))))))))
+           (modifier message body)
+           body)
+         body))))
+
 (define-method set-message-flags! ((message <imap-message>) flags)
-  (call-next-method message flags)
-  (let ((old-flags (message-flags message))
-       (folder (message-folder message))
-       (index (message-index message)))
-    (let ((connection (imap-folder-connection folder))
-         (diff
-          (lambda (f1 f2)
-            (map imail-flag->imap-flag
-                 (list-transform-positive (flags-difference f1 f2)
-                   (let ((flags (imap-folder-permanent-flags folder))
-                         (keywords? (imap-folder-permanent-keywords? folder)))
-                     (lambda (flag)
-                       (if (string-prefix? "\\" flag)
-                           (flags-member? flag flags)
-                           keywords?))))))))
-      (imap:command:store-flags+ connection index (diff flags old-flags))
-      (imap:command:store-flags- connection index (diff old-flags flags)))))
-
-(define (flags-difference f1 f2)
-  (if (pair? f1)
-      (if (flags-member? (car f1) f2)
-         (flags-difference (cdr f1) f2)
-         (cons (car f1) (flags-difference (cdr f1) f2)))
-      '()))
+  (call-next-method
+   message
+   (map imap-flag->imail-flag
+       (imap:response:fetch-attribute
+        (imap:command:store-flags
+         (imap-folder-connection (message-folder message))
+         (message-index message)
+         (map imail-flag->imap-flag (flags-delete "\\recent" flags)))
+        'FLAGS))))
 
 (define (imap-flag->imail-flag flag)
   (case flag
   (without-interrupts
    (lambda ()
      (for-each-vector-element (imap-folder-messages folder) detach-message)
+     (set-imap-folder-read-only?! folder #f)
      (set-imap-folder-allowed-flags! folder '())
      (set-imap-folder-permanent-flags! folder '())
      (set-imap-folder-permanent-keywords?! folder #f)
+     (set-imap-folder-uidnext! folder #f)
      (set-imap-folder-uidvalidity! folder #f)
-     (set-imap-folder-first-unseen! folder #f)
+     (set-imap-folder-unseen! folder #f)
      (set-imap-folder-messages! folder '#()))))
 
 (define (set-imap-folder-length! folder count)
          ((imail-message-wrapper "Reading message outlines")
           (lambda ()
             (imap:command:fetch-range connection 0 end
-                                      '(UID FLAGS RFC822.SIZE ENVELOPE))))
+                                      '(UID FLAGS RFC822.SIZE
+                                            RFC822.HEADER))))
          (cdr responses))
         (index start (fix:+ index 1)))
        ((fix:= index end))
   (guarantee-imap-folder-open folder)
   (vector-ref (imap-folder-messages folder) index))
 
-(define-method first-unseen-message ((folder <imap-folder>))
+(define-method unseen-message ((folder <imap-folder>))
   (guarantee-imap-folder-open folder)
-  (let ((unseen (imap-folder-first-unseen folder)))
+  (let ((unseen (imap-folder-unseen folder)))
     (and unseen
         (get-message folder unseen))))
 
                                           items))
       '()))
 
-(define (imap:command:uid-fetch connection uid items)
-  (let ((response
-        (imap:command:single-response imap:response:fetch?
-                                      connection 'UID 'FETCH uid items)))
-    (map (lambda (item)
-          (imap:response:fetch-attribute response item))
-        items)))
-
-(define (imap:command:store-flags+ connection index flags)
-  (if (pair? flags)
-      (imap:command:no-response connection 'STORE index '+FLAGS.SILENT flags)))
-
-(define (imap:command:store-flags- connection index flags)
-  (if (pair? flags)
-      (imap:command:no-response connection 'STORE index '-FLAGS.SILENT flags)))
+(define (imap:command:store-flags connection index flags)
+  (imap:command:single-response imap:response:fetch?
+                               connection 'STORE index 'FLAGS flags))
 
 (define (imap:command:expunge connection)
   ((imail-message-wrapper "Expunging messages")
   (let ((port (imap-connection-port connection)))
     (let loop ()
       (let ((response (imap:read-server-response port)))
-       (if (imap:response:tag response)
-           (let ((responses
-                  (process-responses
-                   connection command
-                   (dequeue-imap-responses connection))))
-             (cond ((not (string-ci=? tag (imap:response:tag response)))
-                    (error "Out-of-sequence tag:"
-                           (imap:response:tag response) tag))
-                   ((or (imap:response:ok? response)
-                        (imap:response:no? response))
-                    (cons response responses))
-                   (else
-                    (error "IMAP protocol error:" response))))
-           (begin
-             (enqueue-imap-response connection response)
-             (loop)))))))
+       (let ((tag* (imap:response:tag response)))
+         (if tag*
+             (let ((responses
+                    (process-responses
+                     connection command
+                     (dequeue-imap-responses connection))))
+               (if (string-ci=? tag tag*)
+                   (if (or (imap:response:ok? response)
+                           (imap:response:no? response))
+                       (cons response responses)
+                       (error "IMAP protocol error:" response))
+                   (if (< (base26-string->nonnegative-integer tag*)
+                          (base26-string->nonnegative-integer tag))
+                       ;; If this is an old tag, ignore it and move on.
+                       (loop)
+                       (error "Out-of-sequence tag:" tag* tag))))
+             (begin
+               (enqueue-imap-response connection response)
+               (loop))))))))
 \f
 (define (process-responses connection command responses)
   (if (pair? responses)
         (let ((code (imap:response:response-text-code response))
               (string (imap:response:response-text-string response)))
           (if code
-              (process-response-text connection code string))
+              (process-response-text connection command code string))
           (if (and (imap:response:bye? response)
                    (not (eq? command 'LOGOUT)))
               (begin
                 (close-imap-connection connection)
                 (error "Server shut down connection:" string))))
+        (if (or (imap:response:no? response)
+                (imap:response:bad? response))
+            (imail-present-user-alert
+             (lambda (port)
+               (write-string "Notice from IMAP server:" port)
+               (newline port)
+               (display text port)
+               (newline port))))
         (imap:response:preauth? response))
        ((imap:response:exists? response)
         (let ((count (imap:response:exists-count response))
         #f)
        ((imap:response:recent? response)
         #f)
-       ((or (imap:response:capability? response)
-            (imap:response:fetch? response)
-            (imap:response:list? response)
-            (imap:response:lsub? response)
-            (imap:response:search? response)
-            (imap:response:status? response))
-        #t)
+       ((imap:response:capability? response)
+        (eq? command 'CAPABILITY))
+       ((imap:response:list? response)
+        (eq? command 'LIST))
+       ((imap:response:lsub? response)
+        (eq? command 'LSUB))
+       ((imap:response:search? response)
+        (eq? command 'SEARCH))
+       ((imap:response:status? response)
+        (eq? command 'STATUS))
+       ((imap:response:fetch? response)
+        (memq command '(FETCH STORE)))
        (else
         (error "Illegal server response:" response))))
 \f
-(define (process-response-text connection code text)
-  (cond ((imap:response-code:uidvalidity? code)
+(define (process-response-text connection command code text)
+  (cond ((imap:response-code:alert? code)
+        (imail-present-user-alert
+         (lambda (port)
+           (write-string "Alert from IMAP server:" port)
+           (newline port)
+           (display text port)
+           (newline port))))
+       ((imap:response-code:permanentflags? code)
+        (let ((pflags (imap:response-code:permanentflags code))
+              (folder (selected-imap-folder connection)))
+          (set-imap-folder-permanent-keywords?!
+           folder
+           (if (memq '\* pflags) #t #f))
+          (set-imap-folder-permanent-flags!
+           folder
+           (map imap-flag->imail-flag (delq '\* pflags)))
+          (folder-modified! folder)))
+       ((imap:response-code:read-only? code)
+        (let ((folder (selected-imap-folder connection)))
+          (set-imap-folder-read-only?! folder #t)
+          (folder-modified! folder)))
+       ((imap:response-code:read-write? code)
+        (let ((folder (selected-imap-folder connection)))
+          (set-imap-folder-read-only?! folder #f)
+          (folder-modified! folder)))
+       ((imap:response-code:uidnext? code)
+        (let ((folder (selected-imap-folder connection)))
+          (set-imap-folder-uidnext! folder (imap:response-code:uidnext code))
+          (folder-modified! folder)))
+       ((imap:response-code:uidvalidity? code)
         (let ((folder (selected-imap-folder connection))
               (uidvalidity (imap:response-code:uidvalidity code)))
           (if (let ((uidvalidity* (imap-folder-uidvalidity folder)))
           (folder-modified! folder)))
        ((imap:response-code:unseen? code)
         (let ((folder (selected-imap-folder connection)))
-          (set-imap-folder-first-unseen!
+          (set-imap-folder-unseen!
            folder
            (- (imap:response-code:unseen code) 1))
           (folder-modified! folder)))
-       ((imap:response-code:permanentflags? code)
-        (let ((pflags (imap:response-code:permanentflags code))
-              (folder (selected-imap-folder connection)))
-          (set-imap-folder-permanent-keywords?!
-           folder
-           (if (memq '\* pflags) #t #f))
-          (set-imap-folder-permanent-flags!
-           folder
-           (map imap-flag->imail-flag (delq '\* pflags)))
-          (folder-modified! folder)))
-       ((imap:response-code:alert? code)
-        (imail-present-user-alert
-         (lambda (port)
-           (write-string "Alert from IMAP server:" port)
-           (newline port)
-           (display text port)
-           (newline port))))
        #|
-       ((or (imap:response-code:newname? code)
+       ((or (imap:response-code:badcharset? code)
+            (imap:response-code:newname? code)
             (imap:response-code:parse? code)
-            (imap:response-code:read-only? code)
-            (imap:response-code:read-write? code)
             (imap:response-code:trycreate? code))
         unspecific)
        |#