*
* 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.
*
!$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'];
}
}
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);
}
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']);
}
}
}
/* 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) {
}
$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();
$highestmodseq[] = $v['modseq'];
}
$tmp['HICflags'] = $val;
- $is_flags = true;
}
break;
}
}
- 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));
}
}
}
{
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])) {
return array('data' => unserialize($metadata['HICsearch'][$cache]));
}
+ $metadata['HICsearch']['cacheid'] = $cacheid;
+
return array(
'id' => $cache,
'mailbox' => $mailbox,
protected function _setSearchCache($data, $cache)
{
$cache['metadata']['HICsearch'][$cache['id']] = serialize($data);
+
$this->_updateMetaData($cache['mailbox'], $cache['metadata']);
if ($this->_debug) {
}
/**
- * 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);
}
}
* 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)) {
/**
* 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
* '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));
/**
* 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();
$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()
+ );
}
}
// 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
* 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
/* 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'));
}
}
/* 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)) {
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;
}
}
{
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];
}
}
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;
* 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);
}
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';
$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']) . ')';
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:
$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]) . ')');
}
}
}
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':