summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoralecpl <alec@alec.pl>2010-10-20 18:42:45 +0000
committeralecpl <alec@alec.pl>2010-10-20 18:42:45 +0000
commit659cf14cdd8862a126b775392d59727a5e8a790b (patch)
tree85fa5e0b0a4cccf5e0b0226ac6d0e2ffd7122857
parent710e2748498ed97e7086b0dc3c40f11b29aeec8f (diff)
- Improve performance of messages counting using ESEARCH extension (RFC4731)
-rw-r--r--CHANGELOG1
-rw-r--r--program/include/rcube_imap.php19
-rw-r--r--program/include/rcube_imap_generic.php92
3 files changed, 92 insertions, 20 deletions
diff --git a/CHANGELOG b/CHANGELOG
index ad733e6be..ba7de4500 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -38,6 +38,7 @@ CHANGELOG Roundcube Webmail
- Fix decoding of e-mail address strings in message headers (#1487068)
- Fix handling of attachments when Content-Disposition is not inline nor attachment (#1487051)
- Improve performance of unseen messages counting (#1487058)
+- Improve performance of messages counting using ESEARCH extension (RFC4731)
RELEASE 0.4.2
-------------
diff --git a/program/include/rcube_imap.php b/program/include/rcube_imap.php
index 5d90fe41f..4bf7cac88 100644
--- a/program/include/rcube_imap.php
+++ b/program/include/rcube_imap.php
@@ -539,19 +539,26 @@ class rcube_imap
// use SEARCH for message counting
else if ($this->skip_deleted) {
$search_str = "ALL UNDELETED";
+ $keys = array('COUNT');
+ $need_uid = false;
- // get message count and store in cache
- if ($mode == 'UNSEEN')
+ if ($mode == 'UNSEEN') {
$search_str .= " UNSEEN";
- // get message count using SEARCH
+ }
+ else if ($status) {
+ $keys[] = 'MAX';
+ $need_uid = true;
+ }
+
+ // get message count using (E)SEARCH
// not very performant but more precise (using UNDELETED)
- $index = $this->conn->search($mailbox, $search_str);
+ $index = $this->conn->search($mailbox, $search_str, $need_uid, $keys);
- $count = is_array($index) ? count($index) : 0;
+ $count = is_array($index) ? $index['COUNT'] : 0;
if ($mode == 'ALL' && $status) {
$this->set_folder_stats($mailbox, 'cnt', $count);
- $this->set_folder_stats($mailbox, 'maxuid', $index ? $this->_id2uid(max($index), $mailbox) : 0);
+ $this->set_folder_stats($mailbox, 'maxuid', is_array($index) ? $index['MAX'] : 0);
}
}
else {
diff --git a/program/include/rcube_imap_generic.php b/program/include/rcube_imap_generic.php
index 4c082b9e4..a8bbb89d4 100644
--- a/program/include/rcube_imap_generic.php
+++ b/program/include/rcube_imap_generic.php
@@ -813,10 +813,9 @@ class rcube_imap_generic
}
// Invoke SEARCH as a fallback
- // @TODO: ESEARCH support
- $index = $this->search($mailbox, 'ALL UNSEEN');
+ $index = $this->search($mailbox, 'ALL UNSEEN', false, array('COUNT'));
if (is_array($index)) {
- return count($index);
+ return (int) $index['COUNT'];
}
return false;
@@ -983,17 +982,17 @@ class rcube_imap_generic
return $result;
}
- private function compressMessageSet($message_set)
+ private function compressMessageSet($message_set, $force=false)
{
// given a comma delimited list of independent mid's,
// compresses by grouping sequences together
// if less than 255 bytes long, let's not bother
- if (strlen($message_set)<255) {
+ if (!$force && strlen($message_set)<255) {
return $message_set;
}
- // see if it's already been compress
+ // see if it's already been compressed
if (strpos($message_set, ':') !== false) {
return $message_set;
}
@@ -1561,27 +1560,92 @@ class rcube_imap_generic
return false;
}
- function search($folder, $criteria, $return_uid=false)
+ /**
+ * Executes SEARCH command
+ *
+ * @param string $mailbox Mailbox name
+ * @param string $criteria Searching criteria
+ * @param bool $return_uid Enable UID in result instead of sequence ID
+ * @param array $items Return items (MIN, MAX, COUNT, ALL)
+ *
+ * @return array Message identifiers or item-value hash
+ */
+ function search($mailbox, $criteria, $return_uid=false, $items=array())
{
$old_sel = $this->selected;
- if (!$this->select($folder)) {
+ if (!$this->select($mailbox)) {
return false;
}
// return empty result when folder is empty and we're just after SELECT
- if ($old_sel != $folder && !$this->data['EXISTS']) {
- return array();
+ if ($old_sel != $mailbox && !$this->data['EXISTS']) {
+ if (!empty($items))
+ return array_combine($items, array_fill(0, count($items), 0));
+ else
+ return array();
}
+ $esearch = empty($items) ? false : $this->getCapability('ESEARCH');
+ $criteria = trim($criteria);
+ $params = '';
+
+ // RFC4731: ESEARCH
+ if (!empty($items) && $esearch) {
+ $params .= 'RETURN (' . implode(' ', $items) . ')';
+ }
+ if (!empty($criteria)) {
+ $params .= ($params ? ' ' : '') . $criteria;
+ }
+ else {
+ $params .= 'ALL';
+ }
+
list($code, $response) = $this->execute($return_uid ? 'UID SEARCH' : 'SEARCH',
- array(trim($criteria)));
+ array($params));
if ($code == self::ERROR_OK) {
// remove prefix and \r\n from raw response
- $response = str_replace("\r\n", '', substr($response, 9));
- return preg_split('/\s+/', $response, -1, PREG_SPLIT_NO_EMPTY);
- }
+ $response = substr($response, $esearch ? 10 : 9);
+ $response = str_replace("\r\n", '', $response);
+
+ if ($esearch) {
+ // Skip prefix: ... (TAG "A285") UID ...
+ $this->tokenizeResponse($response, $return_uid ? 2 : 1);
+
+ $result = array();
+ for ($i=0; $i<count($items); $i++) {
+ // If the SEARCH results in no matches, the server MUST NOT
+ // include the item result option in the ESEARCH response
+ if ($ret = $this->tokenizeResponse($response, 2)) {
+ list ($name, $value) = $ret;
+ $result[$name] = $value;
+ }
+ }
+
+ return $result;
+ }
+ else {
+ $response = preg_split('/\s+/', $response, -1, PREG_SPLIT_NO_EMPTY);
+
+ if (!empty($items)) {
+ $result = array();
+ if (in_array('COUNT', $items))
+ $result['COUNT'] = count($response);
+ if (in_array('MIN', $items))
+ $result['MIN'] = !empty($response) ? min($response) : 0;
+ if (in_array('MAX', $items))
+ $result['MAX'] = !empty($response) ? max($response) : 0;
+ if (in_array('ALL', $items))
+ $result['ALL'] = $this->compressMessageSet(implode(',', $response), true);
+
+ return $result;
+ }
+ else {
+ return $response;
+ }
+ }
+ }
return false;
}