Cache information returned by the IMAP LIST command. This should help
authorChris Hanson <org/chris-hanson/cph>
Tue, 29 May 2001 20:26:32 +0000 (20:26 +0000)
committerChris Hanson <org/chris-hanson/cph>
Tue, 29 May 2001 20:26:32 +0000 (20:26 +0000)
to reduce traffic on the wire.

v7/src/imail/imail-imap.scm

index 6dca6d4950c4ff5484c1b762421c792d987686bd..1ccd55862502d8cd8f4b1a4ceac64f50a939b194 100644 (file)
@@ -1,6 +1,6 @@
 ;;; -*-Scheme-*-
 ;;;
-;;; $Id: imail-imap.scm,v 1.168 2001/05/29 17:45:37 cph Exp $
+;;; $Id: imail-imap.scm,v 1.169 2001/05/29 20:26:32 cph Exp $
 ;;;
 ;;; Copyright (c) 1999-2001 Massachusetts Institute of Technology
 ;;;
   (host accessor imap-url-host)
   (port accessor imap-url-port)
   (mailbox accessor imap-url-mailbox)
-  (is-container? define standard
-                initial-value 'UNKNOWN))
+  (list-time define standard initial-value #f)
+  (exists? define standard)
+  (selectable? define standard)
+  (corresponding-container define standard))
 
 (define-class <imap-container-url> (<imap-url> <container-url>)
   (corresponding-folder define accessor))
@@ -57,7 +59,8 @@
           (generic (imap-container-url-corresponding-folder url))))))
   (reflect-1 imap-url-user-id)
   (reflect-1 imap-url-host)
-  (reflect-1 imap-url-port))
+  (reflect-1 imap-url-port)
+  (reflect-1 url-exists?))
 
 (define-method imap-url-mailbox ((url <container-url>))
   (let ((mailbox
        (string=? (imap-url-host url1) (imap-url-host url2))
        (= (imap-url-port url1) (imap-url-port url2))))
 \f
-(define-method url-exists? ((url <imap-url>))
-  (and (imap-url-info url) #t))
+(define-method url-exists? ((url <imap-folder-url>))
+  (guarantee-imap-url-list-info url)
+  (imap-folder-url-exists? url))
 
 (define-method folder-url-is-selectable? ((url <imap-folder-url>))
-  (let ((response (imap-url-info url)))
-    (and response
-        (not (memq '\NOSELECT (imap:response:list-flags response))))))
+  (guarantee-imap-url-list-info url)
+  (imap-folder-url-selectable? url))
 
 (define-method url-is-container? ((url <imap-folder-url>))
-  (let ((container (imap-folder-url-is-container? url)))
-    (if (eq? container 'UNKNOWN)
-       (let ((response (imap-url-info url)))
-         (and response
-              (let ((container
-                     (and (not (memq '\NOINFERIORS
-                                     (imap:response:list-flags response)))
-                          (imap-url-new-mailbox
-                           url
-                           (string-append (imap-url-mailbox url) "/")))))
-                (set-imap-folder-url-is-container?! url container)
-                container)))
-       container)))
-
-(define (imap-url-info url)
-  (let ((responses
-        (with-open-imap-connection url
-          (lambda (connection)
-            (imap:command:list connection
-                               ""
-                               (imap-url-server-mailbox url))))))
-    (and (pair? responses)
-        (null? (cdr responses))
-        (car responses))))
+  (guarantee-imap-url-list-info url)
+  (imap-folder-url-corresponding-container url))
+
+(define (guarantee-imap-url-list-info url)
+  (let ((t (get-universal-time))
+       (list-time (imap-folder-url-list-time url)))
+    (if (or (not list-time)
+           (> t (+ list-time imap-list-info-duration)))
+       (if (null? (run-list-command url (imap-url-server-mailbox url)))
+           (begin
+             (set-imap-folder-url-list-time! url t)
+             (set-imap-folder-url-exists?! url #f)
+             (set-imap-folder-url-selectable?! url #f)
+             (set-imap-folder-url-corresponding-container! url #f))))))
+
+(define (flush-imap-url-list-info url)
+  (set-imap-folder-url-list-time!
+   (if (imap-container-url? url)
+       (imap-container-url-corresponding-folder url)
+       url)
+   #f))
 
+;; Number of seconds for which LIST command info is assumed valid.
+;; Info is automatically invalidated at times that IMAIL knows to do
+;; so.  But other IMAP clients can invalidate this information without
+;; notifying IMAIL, so we must periodically refresh the info from the
+;; server.  (The protocol really ought to be fixed to provide
+;; asynchronous updates to this information.)
+(define imap-list-info-duration 60)
+\f
 (define-method url-base-name ((url <imap-folder-url>))
   (let ((mailbox (imap-url-mailbox url)))
     (let ((index (imap-mailbox-container-slash mailbox)))
                      prefix)))))))
 
 (define-method container-url-contents ((url <imap-container-url>))
-  (%imap-mailbox-completions (imap-url-mailbox url) url
-    (lambda (mailbox selectable? inferiors? tail)
-      (let ((container
-            (and inferiors?
-                 (imap-url-new-mailbox url (string-append mailbox "/")))))
-       (cond (selectable?
-              (let ((url (imap-url-new-mailbox url mailbox)))
-                (if (eq? (imap-folder-url-is-container? url) 'UNKNOWN)
-                    (set-imap-folder-url-is-container?! url container))
-                (cons url tail)))
-             (container (cons container tail))
-             (else tail))))))
+  (%imap-mailbox-completions (imap-url-mailbox url) url))
 \f
 ;;;; Completion
 
              (else (if-unique (car responses)))))))
 \f
 (define (imap-mailbox-completions prefix url)
-  (%imap-mailbox-completions prefix url
-    (lambda (mailbox selectable? inferiors? tail)
-      (cond (selectable? (cons mailbox tail))
-           (inferiors? (cons (string-append mailbox "/") tail))
-           (else tail)))))
-
-(define (%imap-mailbox-completions prefix url accumulator)
-  (with-open-imap-connection url
-    (lambda (connection)
-      (let loop
-         ((responses
-           (imap:command:list
-            connection
-            ""
-            (string-append (imap-mailbox/url->server url prefix) "%")))
-          (results '()))
-       (if (pair? responses)
-           (loop (cdr responses)
-                 (let ((flags (imap:response:list-flags (car responses)))
-                       (delimiter
-                        (imap:response:list-delimiter (car responses)))
+  (map imap-url-mailbox (%imap-mailbox-completions prefix url)))
+
+(define (%imap-mailbox-completions prefix url)
+  (let loop
+      ((urls
+       (run-list-command
+        url
+        (string-append (imap-mailbox/url->server url prefix) "%")))
+       (results '()))
+    (if (pair? urls)
+       (loop (cdr urls)
+             (cond ((imap-folder-url-selectable? (car urls))
+                    (cons (car urls) results))
+                   ((imap-folder-url-corresponding-container (car urls))
+                    => (lambda (url) (cons url results)))
+                   (else results)))
+       (reverse! results))))
+
+(define (run-list-command url mailbox)
+  (let ((t (get-universal-time)))
+    (map (lambda (response)
+          (let ((mailbox
+                 (let ((delimiter (imap:response:list-delimiter response))
                        (mailbox
                         (imap:decode-mailbox-name
-                         (imap:response:list-mailbox (car responses)))))
+                         (imap:response:list-mailbox response))))
                    (if delimiter
-                       (accumulator (string-replace mailbox
-                                                    (string-ref delimiter 0)
-                                                    #\/)
-                                    (not (memq '\NOSELECT flags))
-                                    (not (memq '\NOINFERIORS flags))
-                                    results)
-                       (accumulator mailbox
-                                    (not (memq '\NOSELECT flags))
-                                    #f
-                                    results))))
-           (reverse! results))))))
+                       (string-replace mailbox (string-ref delimiter 0) #\/)
+                       mailbox)))
+                (flags (imap:response:list-flags response)))
+            (let ((url (imap-url-new-mailbox url mailbox)))
+              (set-imap-folder-url-list-time! url t)
+              (set-imap-folder-url-exists?! url #t)
+              (set-imap-folder-url-selectable?! url
+                                                (not (memq '\NOSELECT flags)))
+              (set-imap-folder-url-corresponding-container!
+               url
+               (and (not (memq '\NOINFERIORS flags))
+                    (imap-url-new-mailbox url (string-append mailbox "/"))))
+              url)))
+        (with-open-imap-connection url
+          (lambda (connection)
+            (imap:command:list connection "" mailbox))))))
 \f
 ;;;; URL->server delimiter conversion
 
 ;;;; Server operations
 
 (define-method %create-resource ((url <imap-url>))
-  (with-open-imap-connection url
-    (lambda (connection)
-      (imap:command:create connection (imap-url-server-mailbox url)))))
+  (let ((resource
+        (with-open-imap-connection url
+          (lambda (connection)
+            (imap:command:create connection (imap-url-server-mailbox url))))))
+    (flush-imap-url-list-info url)
+    resource))
 
 (define-method %delete-resource ((url <imap-url>))
   (with-open-imap-connection url
     (lambda (connection)
-      (imap:command:delete connection (imap-url-server-mailbox url)))))
+      (imap:command:delete connection (imap-url-server-mailbox url))))
+  (flush-imap-url-list-info url))
 
 (define-method %rename-resource ((url <imap-url>) (new-url <imap-url>))
   (if (compatible-imap-urls? url new-url)
                               (imap-url-server-mailbox url)
                               (imap-url-server-mailbox new-url))))
       (error "Unable to perform rename between different IMAP accounts:"
-            url new-url)))
+            url new-url))
+  (flush-imap-url-list-info url)
+  (flush-imap-url-list-info new-url))
 
 (define-method %append-message ((message <message>) (url <imap-folder-url>))
   (let ((folder (message-folder message))