More thorough job of identifying mailboxes we shouldn't be caching.
authorMichael M Slusarz <slusarz@curecanti.org>
Wed, 25 Mar 2009 18:45:16 +0000 (12:45 -0600)
committerMichael M Slusarz <slusarz@curecanti.org>
Wed, 25 Mar 2009 19:06:57 +0000 (13:06 -0600)
Based primarily on NOMODSEQ (RFC 4551) and UIDNOTSTICKY (RFC 4315)
responses.

framework/Imap_Client/lib/Horde/Imap/Client/Base.php
framework/Imap_Client/lib/Horde/Imap/Client/Cclient.php
framework/Imap_Client/lib/Horde/Imap/Client/Socket.php

index a843865..06ab2fc 100644 (file)
@@ -196,10 +196,15 @@ abstract class Horde_Imap_Client_Base
     /**
      * Initialize the Horde_Imap_Client_Cache object, if necessary.
      *
+     * @param boolean $current  If true, we are going to update the currently
+     *                          selected mailbox. Add an additional check to
+     *                          see if caching is available in current
+     *                          mailbox.
+     *
      * @return boolean  Returns true if caching is enabled.
      * @throws Horde_Imap_Client_Exception
      */
-    protected function _initCache()
+    protected function _initCache($current = false)
     {
         if (empty($this->_params['cache']['fields'])) {
             return false;
@@ -214,7 +219,14 @@ abstract class Horde_Imap_Client_Base
             )));
         }
 
-        return true;
+        if (!$current) {
+            return true;
+        }
+
+        /* If UIDs are labeled as not sticky, don't cache since UIDs will
+         * change on every access. */
+        $status = $this->status($this->_selected, Horde_Imap_Client::STATUS_UIDNOTSTICKY);
+        return !$status['uidnotsticky'];
     }
 
     /**
@@ -260,7 +272,9 @@ abstract class Horde_Imap_Client_Base
             }
         }
         $capability = strtoupper($capability);
-        return isset($this->_init['capability'][$capability]) ? $this->_init['capability'][$capability] : false;
+        return isset($this->_init['capability'][$capability])
+            ? $this->_init['capability'][$capability]
+            : false;
     }
 
     /**
@@ -909,9 +923,9 @@ abstract class Horde_Imap_Client_Base
      *   Return key: 'highestmodseq'
      *   Return format: (integer) If the server supports the CONDSTORE IMAP
      *                  extension, this will be the highest mod-sequence value
-     *                  of all messages in the mailbox or 0 if the mailbox
-     *                  does not support mod-sequences. Else, this value will
-     *                  be undefined.
+     *                  of all messages in the mailbox. Else 0 if CONDSTORE
+     *                  not available or the mailbox does not support
+     *                  mod-sequences.
      *
      * Flag: Horde_Imap_Client::STATUS_UIDNOTSTICKY
      *   Return key: 'uidnotsticky'
@@ -962,6 +976,19 @@ abstract class Horde_Imap_Client_Base
             }
         }
 
+        /* Catch flags that are not supported. */
+        if (($flags & Horde_Imap_Client::STATUS_HIGHESTMODSEQ) &&
+            isset($this->_init['enabled']['CONDSTORE'])) {
+            $ret['highestmodseq'] = 0;
+            $flags &= ~$val;
+        }
+
+        if (($flags & Horde_Imap_Client::STATUS_UIDNOTSTICKY) &&
+            !$this->queryCapability('UIDPLUS')) {
+            $ret['uidnotsticky'] = false;
+            $flags &= ~$val;
+        }
+
         if (!$flags) {
             return $ret;
         }
@@ -1123,7 +1150,8 @@ abstract class Horde_Imap_Client_Base
         }
 
         /* If we are caching, search for deleted messages. */
-        if (!empty($options['expunge']) && $this->_initCache()) {
+        if (!empty($options['expunge']) &&
+            $this->_initCache(true)) {
             $search_query = new Horde_Imap_Client_Search_Query();
             $search_query->flag('\\deleted', true);
             $search_res = $this->search($this->_selected, $search_query);
@@ -1301,6 +1329,8 @@ abstract class Horde_Imap_Client_Base
 
         $type = empty($options['sort']) ? 'match' : 'sort';
 
+        $this->openMailbox($mailbox, Horde_Imap_Client::OPEN_AUTO);
+
         /* Take advantage of search result caching.  If CONDSTORE available,
          * we can cache all queries and invalidate the cache when the MODSEQ
          * changes. If CONDSTORE not available, we can only store queries
@@ -1308,7 +1338,7 @@ abstract class Horde_Imap_Client_Base
          * array - the generated query is already added to '_query' key
          * above. */
         $cache = null;
-        if ($this->_initCache() &&
+        if ($this->_initCache(true) &&
             (isset($this->_init['enabled']['CONDSTORE']) ||
              !$query->flagSearch())) {
             $cache = $this->_getSearchCache('search', $mailbox, $options);
@@ -1320,8 +1350,6 @@ abstract class Horde_Imap_Client_Base
             }
         }
 
-        $this->openMailbox($mailbox, Horde_Imap_Client::OPEN_AUTO);
-
         $ret = $this->_search($query, $options);
 
         if (!empty($options['reverse'])) {
@@ -1441,12 +1469,14 @@ abstract class Horde_Imap_Client_Base
      */
     public function thread($mailbox, $options = array())
     {
+        $this->openMailbox($mailbox, Horde_Imap_Client::OPEN_AUTO);
+
         /* Take advantage of search result caching.  If CONDSTORE available,
          * we can cache all queries and invalidate the cache when the MODSEQ
          * changes. If CONDSTORE not available, we can only store queries
          * that don't involve flags. See search() for similar caching. */
         $cache = null;
-        if ($this->_initCache() &&
+        if ($this->_initCache(true) &&
             (isset($this->_init['enabled']['CONDSTORE']) ||
              empty($options['search']) ||
              !$options['search']->flagSearch())) {
@@ -1456,8 +1486,6 @@ abstract class Horde_Imap_Client_Base
             }
         }
 
-        $this->openMailbox($mailbox, Horde_Imap_Client::OPEN_AUTO);
-
         $ob = new Horde_Imap_Client_Thread($this->_thread($options), empty($options['sequence']) ? 'uid' : 'sequence');
 
         if ($cache) {
@@ -1835,7 +1863,6 @@ abstract class Horde_Imap_Client_Base
     public function fetch($mailbox, $criteria, $options = array())
     {
         $cache_array = $get_fields = $new_criteria = $ret = array();
-        $cf = $this->_initCache() ? $this->_params['cache']['fields'] : array();
         $qresync = isset($this->_init['enabled']['QRESYNC']);
         $seq = !empty($options['sequence']);
 
@@ -1853,13 +1880,16 @@ abstract class Horde_Imap_Client_Base
             throw new Horde_Imap_Client_Exception('The vanished FETCH modifier is missing a pre-requisite.');
         }
 
+        $this->openMailbox($mailbox, Horde_Imap_Client::OPEN_AUTO);
+
+        $cf = $this->_initCache(true)
+            ? $this->_params['cache']['fields']
+            : array();
+
         /* The 'changedsince' modifier implicitly adds the MODSEQ FETCH item.
          * (RFC 4551 [3.3.1]). A UID SEARCH will always return UID
          * information (RFC 3501 [6.4.8]). Don't add to criteria because it
          * simply creates a longer FETCH command. */
-
-        $this->openMailbox($mailbox, Horde_Imap_Client::OPEN_AUTO);
-
         if (!empty($cf)) {
             /* We need the UIDVALIDITY for the current mailbox. */
             $status_res = $this->status($this->_selected, Horde_Imap_Client::STATUS_HIGHESTMODSEQ | Horde_Imap_Client::STATUS_UIDVALIDITY);
@@ -2443,7 +2473,11 @@ abstract class Horde_Imap_Client_Base
      */
     protected function _updateCache($data, $options = array())
     {
-        if (!$this->_initCache()) {
+        $mailbox = empty($options['mailbox'])
+            ? $this->_selected
+            : $options['mailbox'];
+
+        if (!$this->_initCache(empty($options['mailbox']))) {
             return;
         }
 
@@ -2453,7 +2487,6 @@ abstract class Horde_Imap_Client_Base
 
         $cf = $this->_params['cache']['fields'];
         $tocache = array();
-        $mailbox = empty($options['mailbox']) ? $this->_selected : $options['mailbox'];
 
         $status_flags = 0;
         if (isset($this->_init['enabled']['CONDSTORE'])) {
index eb1bdd7..dac5099 100644 (file)
@@ -732,7 +732,7 @@ class Horde_Imap_Client_Cclient extends Horde_Imap_Client_Base
 
             /* If we are using a cache, we need to get the list of
              * messages that will be expunged. */
-            if ($this->_initCache()) {
+            if ($this->_initCache($this->_selected)) {
                 if ($use_seq) {
                     $res = $this->search($this->_selected, $search_query);
                     $expunged = $res['match'];
index c8e6ce5..15055e9 100644 (file)
@@ -822,18 +822,11 @@ class Horde_Imap_Client_Socket extends Horde_Imap_Client_Base
             $this->_init['enabled']['CONDSTORE'] = true;
         }
 
-        if (isset($this->_init['enabled']['CONDSTORE'])) {
-            /* MODSEQ should be set if CONDSTORE is active. Some servers won't
-             * advertise in SELECT/EXAMINE info though. */
-            if (!isset($this->_temp['mailbox']['highestmodseq'])) {
-                $this->_temp['mailbox']['highestmodseq'] = 1;
-            } elseif ($qresync &&
-                      empty($this->_temp['mailbox']['highestmodseq'])) {
-                /* Check that MODSEQ is enabled for the mailbox and, if not,
-                 * delete the cache. Note: Invalidating the cache based on
-                 * UIDVALIDITY is done within Horde_Imap_Client_Cache::. */
-                $this->_cache->deleteMailbox($mailbox);
-            }
+        /* MODSEQ should be set if CONDSTORE is active. Some servers won't
+         * advertise in SELECT/EXAMINE info though. */
+        if (isset($this->_init['enabled']['CONDSTORE']) &&
+            !isset($this->_temp['mailbox']['highestmodseq'])) {
+            $this->_temp['mailbox']['highestmodseq'] = 1;
         }
     }
 
@@ -1274,7 +1267,7 @@ class Horde_Imap_Client_Socket extends Horde_Imap_Client_Base
         } else {
             // If caching, we need to know the UIDs being deleted, so call
             // expunge() before calling close().
-            if ($this->_initCache()) {
+            if ($this->_initCache(true)) {
                 $this->expunge($this->_selected);
             }
 
@@ -1305,7 +1298,7 @@ class Horde_Imap_Client_Socket extends Horde_Imap_Client_Base
         $seq = !empty($options['sequence']);
         $s_res = null;
         $uidplus = $this->queryCapability('UIDPLUS');
-        $use_cache = $this->_initCache();
+        $use_cache = $this->_initCache(true);
 
         if (empty($options['ids'])) {
             $uid_string = '1:*';
@@ -1398,15 +1391,20 @@ class Horde_Imap_Client_Socket extends Horde_Imap_Client_Base
             if (!empty($expunged)) {
                 $this->_cache->deleteMsgs($mailbox, $expunged);
                 $tmp['mailbox']['messages'] -= $i;
-                if (isset($this->_init['enabled']['QRESYNC'])) {
-                    $this->_updateMetaData($mailbox, array('HICmodseq' => $this->_temp['mailbox']['highestmodseq']));
-                } elseif (isset($this->_init['enabled']['CONDSTORE'])) {
-                    /* Unfortunately, RFC 4551 does not provide any method to
-                     * obtain the HIGHESTMODSEQ after an EXPUNGE is completed.
-                     * Instead, unselect the mailbox - if we need to reselect
-                     * the mailbox, the HIGHESTMODSEQ info will appear in the
-                     * EXAMINE/SELECT response. */
-                    $this->close();
+
+                /* Update MODSEQ if active for mailbox. */
+                if (!empty($this->_temp['mailbox']['highestmodseq'])) {
+                    if (isset($this->_init['enabled']['QRESYNC'])) {
+                        $this->_updateMetaData($mailbox, array('HICmodseq' => $this->_temp['mailbox']['highestmodseq']));
+                    } else {
+                        /* Unfortunately, RFC 4551 does not provide any method
+                         * to obtain the HIGHESTMODSEQ after an EXPUNGE is
+                         * completed. Instead, unselect the mailbox - if we
+                         * need to reselect the mailbox, the HIGHESTMODSEQ
+                         * info will appear in the EXAMINE/SELECT
+                         * HIGHESTMODSEQ response. */
+                        $this->close();
+                    }
                 }
             }
 
@@ -2637,7 +2635,8 @@ class Horde_Imap_Client_Socket extends Horde_Imap_Client_Base
         $cmd_prefix = (empty($options['sequence']) ? 'UID ' : '') .
                       'STORE ' . $seq . ' ';
 
-        $ucsince = null;
+        $condstore = $ucsince = null;
+
         if (empty($this->_temp['mailbox']['highestmodseq'])) {
             if (!empty($options['unchangedsince'])) {
                 /* RFC 4551 [3.1] - trying to do a UNCHANGEDSINCE STORE on a
@@ -2645,12 +2644,21 @@ class Horde_Imap_Client_Socket extends Horde_Imap_Client_Base
                  * here and throw an exception. */
                 throw new Horde_Imap_Client_Exception('Mailbox does not support mod-sequences.', Horde_Imap_Client_Exception::MBOXNOMODSEQ);
             }
-        } elseif (!empty($options['unchangedsince'])) {
-            $ucsince = intval($options['unchangedsince']);
         } else {
-            /* If CONDSTORE is enabled, we need to add UNCHANGEDSINCE output
-             * to ensure we get MODSEQ updated information. */
-            $ucsince = $this->_temp['mailbox']['highestmodseq'];
+            if (!empty($options['unchangedsince'])) {
+                $ucsince = intval($options['unchangedsince']);
+            }
+
+            if (isset($this->_init['enabled']['CONDSTORE'])) {
+                /* If we reach here, MODSEQ is active for mailbox. */
+                $condstore = true;
+
+                /* If CONDSTORE is enabled, we need to verify UNCHANGEDSINCE
+                 * added to ensure we get MODSEQ updated information. */
+                if (is_null($ucsince)) {
+                    $ucsince = $this->_temp['mailbox']['highestmodseq'];
+                }
+            }
         }
 
         if ($ucsince) {
@@ -2671,8 +2679,7 @@ class Horde_Imap_Client_Socket extends Horde_Imap_Client_Base
 
         /* Update the flags in the cache. Only update if store was successful
          * and flag information was not returned. */
-        if (!empty($this->_temp['fetchresp']) &&
-            isset($this->_init['enabled']['CONDSTORE'])) {
+        if ($condstore && !empty($this->_temp['fetchresp'])) {
             $fr = $this->_temp['fetchresp'];
             $out = $uids = array();
 
@@ -3576,9 +3583,18 @@ class Horde_Imap_Client_Socket extends Horde_Imap_Client_Base
             break;
 
         case 'HIGHESTMODSEQ':
+            // Defined by RFC 4551 [3.1.1]
+            $this->_temp['mailbox']['highestmodseq'] = $data;
+            break;
+
         case 'NOMODSEQ':
-            // Defined by RFC 4551 [3.1.1 & 3.1.2]
-            $this->_temp['mailbox']['highestmodseq'] = ($code == 'HIGHESTMODSEQ') ? $data : 0;
+            // Defined by RFC 4551 [3.1.2]
+            $this->_temp['mailbox']['highestmodseq'] = 0;
+
+            // Delete cache for mailbox, if it exists.
+            if ($this->_initCache()) {
+                $this->_cache->deleteMailbox($this->_selected);
+            }
             break;
 
         case 'MODIFIED':