Continue trying to sort out the CONDSTORE/QRESYNC mess.
authorMichael M Slusarz <slusarz@curecanti.org>
Wed, 25 Mar 2009 06:38:32 +0000 (00:38 -0600)
committerMichael M Slusarz <slusarz@curecanti.org>
Wed, 25 Mar 2009 06:38:32 +0000 (00:38 -0600)
framework/Imap_Client/lib/Horde/Imap/Client/Base.php
framework/Imap_Client/lib/Horde/Imap/Client/Cache.php
framework/Imap_Client/lib/Horde/Imap/Client/Exception.php
framework/Imap_Client/lib/Horde/Imap/Client/Socket.php

index e5907df..aa025be 100644 (file)
@@ -907,9 +907,9 @@ abstract class Horde_Imap_Client_Base
      *
      * Flag: Horde_Imap_Client::STATUS_HIGHESTMODSEQ
      *   Return key: 'highestmodseq'
-     *   Return format: (mixed) If the server supports the CONDSTORE IMAP
+     *   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 null if the mailbox
+     *                  of all messages in the mailbox or 0 if the mailbox
      *                  does not support mod-sequences. Else, this value will
      *                  be undefined.
      *
@@ -1313,7 +1313,9 @@ abstract class Horde_Imap_Client_Base
              !$query->flagSearch())) {
             $cache = $this->_getSearchCache('search', $mailbox, $options);
             if (isset($cache['data'])) {
-                $cache['data'][$type] = $this->_utils->fromSequenceString($cache['data'][$type]);
+                if (isset($cache['data'][$type])) {
+                    $cache['data'][$type] = $this->_utils->fromSequenceString($cache['data'][$type]);
+                }
                 return $cache['data'];
             }
         }
@@ -1328,7 +1330,9 @@ abstract class Horde_Imap_Client_Base
 
         if ($cache) {
             $save = $ret;
-            $save[$type] = $this->_utils->toSequenceString($ret[$type], array('nosort' => true));
+            if (isset($save[$type])) {
+                $save[$type] = $this->_utils->toSequenceString($ret[$type], array('nosort' => true));
+            }
             $this->_setSearchCache($save, $cache);
         }
 
@@ -1903,15 +1907,14 @@ abstract class Horde_Imap_Client_Base
                     if (!$qresync) {
                         /* Grab all flags updated since the cached modseq
                          * val. */
-                        $metadata = $this->_cache->getMetaData($this->_selected, array('HICmodseq'));
+                        $metadata = $this->_cache->getMetaData($this->_selected, $status_res['uidvalidity'], array('HICmodseq'));
                         if (isset($metadata['HICmodseq']) &&
                             ($metadata['HICmodseq'] != $status_res['highestmodseq'])) {
                             $uids = $this->_cache->get($this->_selected, array(), array(), $status_res['uidvalidity']);
                             if (!empty($uids)) {
                                 $this->_fetch(array(Horde_Imap_Client::FETCH_FLAGS => true), array('changedsince' => $metadata['HICmodseq'], 'ids' => $uids));
                             }
-
-                            $this->_updateMetaData($mailbox, array('HICmodseq' => $status_res['highestmodseq']));
+                            $this->_updateMetaData($this->_selected, $status_res['highestmodseq']);
                         }
                     }
 
@@ -1966,14 +1969,7 @@ abstract class Horde_Imap_Client_Base
         }
 
         /* Get the cached values. */
-        try {
-            $data = $this->_cache->get($this->_selected, $uids, $get_fields, $status_res['uidvalidity']);
-        } catch (Horde_Imap_Client_Exception $e) {
-            if ($e->getCode() != Horde_Imap_Client_Exception::CACHEUIDINVALID) {
-                throw $e;
-            }
-            $data = array();
-        }
+        $data = $this->_cache->get($this->_selected, $uids, $get_fields, $status_res['uidvalidity']);
 
         // Build a list of what we still need.
         foreach ($uids as $val) {
@@ -2456,20 +2452,26 @@ abstract class Horde_Imap_Client_Base
         }
 
         $cf = $this->_params['cache']['fields'];
-        $is_flags = false;
-        $highestmodseq = $tocache = array();
+        $tocache = array();
         $mailbox = empty($options['mailbox']) ? $this->_selected : $options['mailbox'];
 
+        $status_flags = 0;
+        if (isset($this->_init['enabled']['CONDSTORE'])) {
+            $status_flags |= Horde_Imap_Client::STATUS_HIGHESTMODSEQ;
+        }
         if (empty($options['uidvalid'])) {
-            $status_res = $this->status($mailbox, Horde_Imap_Client::STATUS_HIGHESTMODSEQ | Horde_Imap_Client::STATUS_UIDVALIDITY);
-            $uidvalid = $status_res['uidvalidity'];
-            if (isset($status_res['highestmodseq'])) {
-                $highestmodseq[] = $status_res['highestmodseq'];
-            }
-        } else {
-            $uidvalid = $options['uidvalid'];
+            $status_flags |= Horde_Imap_Client::STATUS_UIDVALIDITY;
         }
 
+        $status_res = $this->status($mailbox, $status_flags);
+
+        $highestmodseq = isset($status_res['highestmodseq'])
+            ? array($status_res['highestmodseq'])
+            : array();
+        $uidvalid = isset($status_res['uidvalidity'])
+            ? $status_res['uidvalidity']
+            : $options['uidvalid'];
+
         reset($data);
         while (list($k, $v) = each($data)) {
             $tmp = array();
@@ -2505,7 +2507,6 @@ abstract class Horde_Imap_Client_Base
                             $highestmodseq[] = $v['modseq'];
                         }
                         $tmp['HICflags'] = $val;
-                        $is_flags = true;
                     }
                     break;
 
@@ -2528,14 +2529,14 @@ abstract class Horde_Imap_Client_Base
             }
         }
 
-        try {
-            $this->_cache->set($mailbox, $tocache, $uidvalid);
-            if ($is_flags) {
-                $this->_updateMetaData($mailbox, array('HICmodseq' => max($highestmodseq)));
-            }
-        } catch (Horde_Imap_Client_Exception $e) {
-            if ($e->getCode() != Horde_Imap_Client_Exception::CACHEUIDINVALID) {
-                throw $e;
+        $this->_cache->set($mailbox, $tocache, $uidvalid);
+
+        if (!empty($highestmodseq)) {
+            $modseq = max($highestmodseq);
+            $metadata = $this->_cache->getMetaData($this->_selected, $uidvalid, array('HICmodseq'));
+            if (isset($metadata['HICmodseq']) &&
+                ($metadata['HICmodseq'] != $modseq)) {
+                $this->_updateMetaData($mailbox, array('HICmodseq' => $modseq));
             }
         }
     }
@@ -2554,19 +2555,19 @@ abstract class Horde_Imap_Client_Base
     {
         ksort($options);
         $cache = hash('md5', $type . serialize($options));
-        $metadata = $this->_cache->getMetaData($mailbox, array('HICsearch'));
-
-        /* Do check for cache expiration for non-CONDSTORE hosts here. */
-        if (!isset($this->_init['enabled']['CONDSTORE'])) {
-            $mboxid = $this->getCacheId($mailbox);
-            if (isset($metadata['HICsearch']['cacheid']) &&
-                ($metadata['HICsearch']['cacheid'] != $mboxid)) {
-                $metadata['HICsearch'] = array();
-                if ($this->_debug) {
-                    fwrite($this->_debug, sprintf("Horde_Imap_Client: Expired %s results from cache (mailbox: %s; id: %s)\n", $type, $mailbox, $cache));
-                }
+
+        $status = $this->status($mailbox, Horde_Imap_Client::STATUS_UIDVALIDITY);
+        $metadata = $this->_cache->getMetaData($mailbox, $status['uidvalidity'], array('HICsearch'));
+        print_r($metadata);
+
+        $cacheid = $this->getCacheId($mailbox);
+        print "CACHEID: $cacheid\n";
+        if (isset($metadata['HICsearch']['cacheid']) &&
+            ($metadata['HICsearch']['cacheid'] != $cacheid)) {
+            $metadata['HICsearch'] = array();
+            if ($this->_debug) {
+                fwrite($this->_debug, sprintf("Horde_Imap_Client: Expired search results from cache (mailbox: %s)\n", $mailbox));
             }
-            $metadata['HICsearch']['cacheid'] = $mboxid;
         }
 
         if (isset($metadata['HICsearch'][$cache])) {
@@ -2576,6 +2577,8 @@ abstract class Horde_Imap_Client_Base
             return array('data' => unserialize($metadata['HICsearch'][$cache]));
         }
 
+        $metadata['HICsearch']['cacheid'] = $cacheid;
+
         return array(
             'id' => $cache,
             'mailbox' => $mailbox,
@@ -2594,6 +2597,7 @@ abstract class Horde_Imap_Client_Base
     protected function _setSearchCache($data, $cache)
     {
         $cache['metadata']['HICsearch'][$cache['id']] = serialize($data);
+
         $this->_updateMetaData($cache['mailbox'], $cache['metadata']);
 
         if ($this->_debug) {
@@ -2602,20 +2606,15 @@ abstract class Horde_Imap_Client_Base
     }
 
     /**
-     * Update metadata entries.
+     * Updates metadata for a mailbox.
      *
      * @param string $mailbox  Mailbox to update.
-     * @param array $data      The data to update with.
+     * @param string $data     The data to update.
      */
     protected function _updateMetaData($mailbox, $data)
     {
-        /* If we see that HICmodseq is being updated, we know that we have
-         * to invalidate the search cache. */
-        if (isset($data['HICmodseq']) && !isset($data['HICsearch'])) {
-            $data['HICsearch'] = array();
-        }
-
-        $this->_cache->setMetaData($mailbox, $data);
+        $status = $this->status($mailbox, Horde_Imap_Client::STATUS_UIDVALIDITY);
+        $this->_cache->setMetaData($mailbox, $status['uidvalidity'], $data);
     }
 
 }
index 8da395c..e527418 100644 (file)
@@ -266,7 +266,7 @@ class Horde_Imap_Client_Cache
      *                           the uidvalidity is still valid.
      * @param integer $uidvalid  The IMAP uidvalidity value of the mailbox.
      */
-    public function set($mailbox, $data, $uidvalid = null)
+    public function set($mailbox, $data, $uidvalid)
     {
         $save = array_keys($data);
         if (empty($save)) {
@@ -303,9 +303,10 @@ class Horde_Imap_Client_Cache
     /**
      * Get metadata information for a mailbox.
      *
-     * @param string $mailbox  An IMAP mailbox string.
-     * @param array $entries   An array of entries to return. If empty,
-     *                         returns all metadata.
+     * @param string $mailbox    An IMAP mailbox string.
+     * @param integer $uidvalid  The IMAP uidvalidity value of the mailbox.
+     * @param array $entries     An array of entries to return. If empty,
+     *                           returns all metadata.
      *
      * @return array  The requested metadata. Requested entries that do not
      *                exist will be undefined. The following entries are
@@ -314,9 +315,9 @@ class Horde_Imap_Client_Cache
      * 'uidvalid' - (integer) The UIDVALIDITY of the mailbox.
      * </pre>
      */
-    public function getMetaData($mailbox, $entries = array())
+    public function getMetaData($mailbox, $uidvalid = null, $entries = array())
     {
-        $this->_loadSliceMap($mailbox);
+        $this->_loadSliceMap($mailbox, $uidvalid);
         return empty($entries)
             ? $this->_slicemap[$mailbox]['data']
             : array_intersect_key($this->_slicemap[$mailbox]['data'], array_flip($entries));
@@ -325,17 +326,18 @@ class Horde_Imap_Client_Cache
     /**
      * Set metadata information for a mailbox.
      *
-     * @param string $mailbox  An IMAP mailbox string.
-     * @param array $data      The list of data to save. The keys are the
-     *                         metadata IDs, the values are the associated
-     *                         data. The following labels are reserved:
-     *                         'uidvalid'.
+     * @param string $mailbox    An IMAP mailbox string.
+     * @param integer $uidvalid  The IMAP uidvalidity value of the mailbox.
+     * @param array $data        The list of data to save. The keys are the
+     *                           metadata IDs, the values are the associated
+     *                           data. The following labels are reserved:
+     *                           'uidvalid'.
      */
-    public function setMetaData($mailbox, $data = array())
+    public function setMetaData($mailbox, $uidvalid, $data = array())
     {
         if (!empty($data)) {
             unset($data['uidvalid']);
-            $this->_loadSliceMap($mailbox);
+            $this->_loadSliceMap($mailbox, $uidvalid);
             $this->_slicemap[$mailbox]['data'] = array_merge($this->_slicemap[$mailbox]['data'], $data);
             if (!isset($this->_save[$mailbox])) {
                 $this->_save[$mailbox] = array();
@@ -591,22 +593,24 @@ class Horde_Imap_Client_Cache
             $ptr = &$this->_slicemap[$mailbox]['data']['uidvalid'];
             if (is_null($ptr)) {
                 $ptr = $uidvalid;
+                return;
             } elseif (!is_null($uidvalid) && ($ptr != $uidvalid)) {
                 $this->deleteMailbox($mailbox);
-                throw new Horde_Imap_Client_Exception('UIDs have been invalidated', Horde_Imap_Client_Exception::CACHEUIDINVALID);
+            } else {
+                return;
             }
-        } else {
-            $this->_slicemap[$mailbox] = array(
-                // Tracking count for purposes of determining slices
-                'count' => 0,
-                // Metadata storage
-                // By default includes UIDVALIDITY of mailbox.
-                'data' => array('uidvalid' => $uidvalid),
-                // UIDs to delete
-                'delete' => array(),
-                // The slice list.
-                'slice' => array()
-            );
         }
+
+        $this->_slicemap[$mailbox] = array(
+            // Tracking count for purposes of determining slices
+            'count' => 0,
+            // Metadata storage
+            // By default includes UIDVALIDITY of mailbox.
+            'data' => array('uidvalid' => $uidvalid),
+            // UIDs to delete
+            'delete' => array(),
+            // The slice list.
+            'slice' => array()
+        );
     }
 }
index 07230f0..f2ae3b4 100644 (file)
@@ -50,11 +50,8 @@ class Horde_Imap_Client_Exception extends Exception
     // mod-sequences.
     const MBOXNOMODSEQ = 10;
 
-    // Thrown if the cache has become invalid.
-    const CACHEUIDINVALID = 11;
-
     // Thrown if server denies the network connection.
-    const SERVER_CONNECT = 12;
+    const SERVER_CONNECT = 11;
 
     /**
      * Define a callback function used to log the exception. Will be passed
index ad5b7ba..c8e6ce5 100644 (file)
@@ -34,9 +34,9 @@
  *   RFC 5267 - ESORT
  *
  *   [NO RFC] - XIMAPPROXY
- *              + Requires imapproxy v1.2.7-rc1 or later
- *              + See http://lists.andrew.cmu.edu/pipermail/imapproxy-info/2008-October/000771.html and
- *                http://lists.andrew.cmu.edu/pipermail/imapproxy-info/2008-October/000772.html
+ *       + Requires imapproxy v1.2.7-rc1 or later
+ *       + See http://lists.andrew.cmu.edu/pipermail/imapproxy-info/2008-October/000771.html and
+ *         http://lists.andrew.cmu.edu/pipermail/imapproxy-info/2008-October/000772.html
  *
  * TODO (or not necessary?):
  *   RFC 2177 - IDLE (probably not necessary due to the limited connection
@@ -567,15 +567,14 @@ class Horde_Imap_Client_Socket extends Horde_Imap_Client_Base
         /* Only active QRESYNC/CONDSTORE if caching is enabled. */
         if ($this->_initCache()) {
             if ($this->queryCapability('QRESYNC')) {
-                /* QRESYNC REQUIRES ENABLE, so we just need to send one ENABLE
+                /* QRESYNC requires ENABLE, so we just need to send one ENABLE
                  * QRESYNC call to enable both QRESYNC && CONDSTORE. */
                 $this->_enable(array('QRESYNC'));
                 $this->_init['enabled']['CONDSTORE'] = true;
-            } elseif ($this->queryCapability('CONDSTORE')) {
+            } elseif ($this->queryCapability('CONDSTORE') &&
+                      $this->queryCapability('ENABLE')) {
                 /* CONDSTORE may be available, but ENABLE may not be. */
-                if ($this->queryCapability('ENABLE')) {
-                    $this->_enable(array('CONDSTORE'));
-                }
+                $this->_enable(array('CONDSTORE'));
             }
         }
 
@@ -781,7 +780,8 @@ class Horde_Imap_Client_Socket extends Horde_Imap_Client_Base
         /* If QRESYNC is available, synchronize the mailbox. */
         if ($qresync) {
             $this->_initCache();
-            $metadata = $this->_cache->getMetaData($mailbox, array('HICmodseq', 'uidvalid'));
+            $metadata = $this->_cache->getMetaData($mailbox, null, array('HICmodseq', 'uidvalid'));
+
             if (isset($metadata['HICmodseq'])) {
                 $uids = $this->_cache->get($mailbox);
                 if (!empty($uids)) {
@@ -818,18 +818,22 @@ class Horde_Imap_Client_Socket extends Horde_Imap_Client_Base
             throw $e;
         }
 
-        if ($qresync && isset($metadata['uidvalid'])) {
-            if (is_null($this->_temp['mailbox']['highestmodseq']) ||
-                ($this->_temp['mailbox']['uidvalidity'] != $metadata['uidvalid'])) {
+        if ($condstore) {
+            $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);
-            } elseif (!isset($metadata['HICmodseq']) ||
-                      ($metadata['HICmodseq'] != $this->_temp['mailbox']['highestmodseq'])) {
-                /* We know the mailbox has been updated, so update the
-                 * highestmodseq metadata in the cache. */
-                $this->_updateMetaData($mailbox, array('HICmodseq' => $this->_temp['mailbox']['highestmodseq']));
             }
-        } elseif ($condstore) {
-            $this->_init['enabled']['CONDSTORE'] = true;
         }
     }
 
@@ -1126,11 +1130,7 @@ class Horde_Imap_Client_Socket extends Horde_Imap_Client_Base
     {
         for ($i = 0, $len = count($data); $i < $len; $i += 2) {
             $item = strtolower($data[$i]);
-            $val = $data[$i + 1];
-            if (!$val && ($item == 'highestmodseq')) {
-                $val = null;
-            }
-            $this->_temp['status'][$item] = $val;
+            $this->_temp['status'][$item] = $data[$i + 1];
         }
     }
 
@@ -1398,10 +1398,16 @@ 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']));
+                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();
+                }
             }
 
             return $list_msgs ? $expunged : null;
@@ -1470,7 +1476,7 @@ class Horde_Imap_Client_Socket extends Horde_Imap_Client_Base
              * doesn't support it will return BAD. Catch that here and thrown
              * an exception. */
             if (($val == 'CONDSTORE') &&
-                is_null($this->_temp['mailbox']['highestmodseq']) &&
+                empty($this->_temp['mailbox']['highestmodseq']) &&
                 (strpos($options['_query']['query'], 'MODSEQ ') !== false)) {
                 throw new Horde_Imap_Client_Exception('Mailbox does not support mod-sequences.', Horde_Imap_Client_Exception::MBOXNOMODSEQ);
             }
@@ -2209,14 +2215,18 @@ class Horde_Imap_Client_Socket extends Horde_Imap_Client_Base
                 break;
 
             case Horde_Imap_Client::FETCH_SEQ:
-                // Nothing we need to add to fetch criteria.
+                // Nothing we need to add to fetch criteria unless sequence
+                // is the only criteria.
+                if (count($criteria) == 1) {
+                    $fetch[] = 'UID';
+                }
                 break;
 
             case Horde_Imap_Client::FETCH_MODSEQ:
                 /* RFC 4551 [3.1] - trying to do a FETCH of MODSEQ on a
                  * mailbox that doesn't support it will return BAD. Catch that
                  * here and throw an exception. */
-                if (is_null($this->_temp['mailbox']['highestmodseq'])) {
+                if (empty($this->_temp['mailbox']['highestmodseq'])) {
                     throw new Horde_Imap_Client_Exception('Mailbox does not support mod-sequences.', Horde_Imap_Client_Exception::MBOXNOMODSEQ);
                 }
                 $fetch[] = 'MODSEQ';
@@ -2234,7 +2244,7 @@ class Horde_Imap_Client_Socket extends Horde_Imap_Client_Base
         $cmd = ($use_seq ? '' : 'UID ') . 'FETCH ' . $seq . ' (' . implode(' ', $fetch) . ')';
 
         if (!empty($options['changedsince'])) {
-            if (is_null($this->_temp['mailbox']['highestmodseq'])) {
+            if (empty($this->_temp['mailbox']['highestmodseq'])) {
                 throw new Horde_Imap_Client_Exception('Mailbox does not support mod-sequences.', Horde_Imap_Client_Exception::MBOXNOMODSEQ);
             }
             $cmd .= ' (CHANGEDSINCE ' . intval($options['changedsince']) . ')';
@@ -2305,6 +2315,12 @@ class Horde_Imap_Client_Socket extends Horde_Imap_Client_Base
 
             case 'MODSEQ':
                 $tmp['modseq'] = reset($data[++$i]);
+
+                /* Update highestmodseq, if it exists. */
+                if (!empty($this->_temp['mailbox']['highestmodseq']) &&
+                    ($tmp['modseq'] > $this->_temp['mailbox']['highestmodseq'])) {
+                    $this->_temp['mailbox']['highestmodseq'] = $tmp['modseq'];
+                }
                 break;
 
             default:
@@ -2620,27 +2636,35 @@ class Horde_Imap_Client_Socket extends Horde_Imap_Client_Base
 
         $cmd_prefix = (empty($options['sequence']) ? 'UID ' : '') .
                       'STORE ' . $seq . ' ';
-        $ucsince = !empty($options['unchangedsince']);
 
-        if ($ucsince) {
-            /* RFC 4551 [3.1] - trying to do a UNCHANGEDSINCE STORE on a
-             * mailbox that doesn't support it will return BAD. Catch that
-             * here and throw an exception. */
-            if (is_null($this->_temp['mailbox']['highestmodseq'])) {
+        $ucsince = null;
+        if (empty($this->_temp['mailbox']['highestmodseq'])) {
+            if (!empty($options['unchangedsince'])) {
+                /* RFC 4551 [3.1] - trying to do a UNCHANGEDSINCE STORE on a
+                 * mailbox that doesn't support it will return BAD. Catch that
+                 * 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'];
+        }
 
-            $cmd .= '(UNCHANGEDSINCE ' . intval($options['unchangedsince']) . ') ';
+        if ($ucsince) {
+            $cmd_prefix .= '(UNCHANGEDSINCE ' . $ucsince . ') ';
         }
 
         $this->_temp['modified'] = array();
 
         if (!empty($options['replace'])) {
-            $this->_sendLine($cmd_prefix . 'FLAGS' . ($this->_debug ? '.SILENT' : '') . ' (' . implode(' ', $options['replace']) . ')');
+            $this->_sendLine($cmd_prefix . 'FLAGS' . ($this->_debug ? '' : '.SILENT') . ' (' . implode(' ', $options['replace']) . ')');
         } else {
             foreach (array('add' => '+', 'remove' => '-') as $k => $v) {
                 if (!empty($options[$k])) {
-                    $this->_sendLine($cmd_prefix . $v . 'FLAGS' . ($this->_debug ? '.SILENT' : '') . ' (' . implode(' ', $options[$k]) . ')');
+                    $this->_sendLine($cmd_prefix . $v . 'FLAGS' . ($this->_debug ? '' : '.SILENT') . ' (' . implode(' ', $options[$k]) . ')');
                 }
             }
         }
@@ -3554,7 +3578,7 @@ class Horde_Imap_Client_Socket extends Horde_Imap_Client_Base
         case 'HIGHESTMODSEQ':
         case 'NOMODSEQ':
             // Defined by RFC 4551 [3.1.1 & 3.1.2]
-            $this->_temp['mailbox']['highestmodseq'] = ($code == 'HIGHESTMODSEQ') ? $data : null;
+            $this->_temp['mailbox']['highestmodseq'] = ($code == 'HIGHESTMODSEQ') ? $data : 0;
             break;
 
         case 'MODIFIED':