alecpl
2010-10-20 659cf14cdd8862a126b775392d59727a5e8a790b
- Improve performance of messages counting using ESEARCH extension (RFC4731)


3 files modified
114 ■■■■ changed files
CHANGELOG 1 ●●●● patch | view | raw | blame | history
program/include/rcube_imap.php 21 ●●●●● patch | view | raw | blame | history
program/include/rcube_imap_generic.php 92 ●●●● patch | view | raw | blame | history
CHANGELOG
@@ -38,6 +38,7 @@
- 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
-------------
program/include/rcube_imap.php
@@ -539,19 +539,26 @@
        // 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
            // not very performant but more precise (using UNDELETED)
            $index = $this->conn->search($mailbox, $search_str);
            }
            else if ($status) {
                $keys[]   = 'MAX';
                $need_uid = true;
            }
            $count = is_array($index) ? count($index) : 0;
            // get message count using (E)SEARCH
            // not very performant but more precise (using UNDELETED)
            $index = $this->conn->search($mailbox, $search_str, $need_uid, $keys);
            $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 {
program/include/rcube_imap_generic.php
@@ -813,10 +813,9 @@
        }
        // 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 @@
        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 @@
        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;
    }