Ignore:
Timestamp:
Dec 7, 2011 3:44:48 AM (18 months ago)
Author:
alec
Message:
  • Fixed issues with big memory allocation of IMAP results, improved a lot of rcube_imap class
File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/roundcubemail/program/include/rcube_imap_cache.php

    r5357 r5557  
    109109 
    110110    /** 
    111      * Return (sorted) messages index. 
     111     * Return (sorted) messages index (UIDs). 
    112112     * If index doesn't exist or is invalid, will be updated. 
    113113     * 
     
    123123        if (empty($this->icache[$mailbox])) 
    124124            $this->icache[$mailbox] = array(); 
    125  
     125console('cache::get_index'); 
    126126        $sort_order = strtoupper($sort_order) == 'ASC' ? 'ASC' : 'DESC'; 
    127127 
     
    129129        if (array_key_exists('index', $this->icache[$mailbox])) { 
    130130            // The index was fetched from database already, but not validated yet 
    131             if (!array_key_exists('result', $this->icache[$mailbox]['index'])) { 
     131            if (!array_key_exists('object', $this->icache[$mailbox]['index'])) { 
    132132                $index = $this->icache[$mailbox]['index']; 
    133133            } 
    134134            // We've got a valid index 
    135             else if ($sort_field == 'ANY' || $this->icache[$mailbox]['index']['sort_field'] == $sort_field 
    136             ) { 
    137                 if ($this->icache[$mailbox]['index']['sort_order'] == $sort_order) 
    138                     return $this->icache[$mailbox]['index']['result']; 
    139                 else 
    140                     return array_reverse($this->icache[$mailbox]['index']['result'], true); 
     135            else if ($sort_field == 'ANY' || $this->icache[$mailbox]['index']['sort_field'] == $sort_field) { 
     136                $result = $this->icache[$mailbox]['index']['object']; 
     137                if ($result->getParameters('ORDER') != $sort_order) { 
     138                    $result->revert(); 
     139                } 
     140                return $result; 
    141141            } 
    142142        } 
     
    174174                $is_valid = $this->validate($mailbox, $index, $exists); 
    175175            } 
    176  
     176console("valid:".$is_valid); 
    177177            if ($is_valid) { 
    178                 // build index, assign sequence IDs to unique IDs 
    179                 $data = array_combine($index['seq'], $index['uid']); 
     178                $data = $index['object']; 
    180179                // revert the order if needed 
    181                 if ($index['sort_order'] != $sort_order) 
    182                     $data = array_reverse($data, true); 
     180                if ($data->getParameters('ORDER') != $sort_order) { 
     181                    $data->revert(); 
     182                } 
    183183            } 
    184184        } 
     
    202202 
    203203            // insert/update 
    204             $this->add_index_row($mailbox, $sort_field, $sort_order, $data, $mbox_data, 
    205                 $exists, $index['modseq']); 
     204            $this->add_index_row($mailbox, $sort_field, $data, $mbox_data, $exists, $index['modseq']); 
    206205        } 
    207206 
    208207        $this->icache[$mailbox]['index'] = array( 
    209             'result'     => $data, 
     208            'object'     => $data, 
    210209            'sort_field' => $sort_field, 
    211             'sort_order' => $sort_order, 
    212210            'modseq'     => !empty($index['modseq']) ? $index['modseq'] : $mbox_data['HIGHESTMODSEQ'] 
    213211        ); 
     
    234232        // Seek in internal cache 
    235233        if (array_key_exists('thread', $this->icache[$mailbox])) { 
    236             return array( 
    237                 $this->icache[$mailbox]['thread']['tree'], 
    238                 $this->icache[$mailbox]['thread']['depth'], 
    239                 $this->icache[$mailbox]['thread']['children'], 
    240             ); 
     234            return $this->icache[$mailbox]['thread']['object']; 
    241235        } 
    242236 
     
    251245        } 
    252246 
    253         $data = null; 
    254  
    255247        // Entry exist, check cache status 
    256248        if (!empty($index)) { 
     
    270262            if ($mbox_data['EXISTS']) { 
    271263                // get all threads (default sort order) 
    272                 list ($thread_tree, $msg_depth, $has_children) = $this->imap->fetch_threads($mailbox, true); 
    273             } 
    274  
    275             $index = array( 
    276                 'tree'     => !empty($thread_tree) ? $thread_tree : array(), 
    277                 'depth'    => !empty($msg_depth) ? $msg_depth : array(), 
    278                 'children' => !empty($has_children) ? $has_children : array(), 
    279             ); 
     264                $threads = $this->imap->fetch_threads($mailbox, true); 
     265            } 
     266            else { 
     267                $threads = new rcube_imap_result($mailbox, ''); 
     268            } 
     269 
     270            $index['object'] = $threads; 
    280271 
    281272            // insert/update 
    282             $this->add_thread_row($mailbox, $index, $mbox_data, $exists); 
     273            $this->add_thread_row($mailbox, $threads, $mbox_data, $exists); 
    283274        } 
    284275 
    285276        $this->icache[$mailbox]['thread'] = $index; 
    286277 
    287         return array($index['tree'], $index['depth'], $index['children']); 
     278        return $index['object']; 
    288279    } 
    289280 
     
    293284     * 
    294285     * @param string $mailbox  Folder name 
    295      * @param array  $msgs     Message sequence numbers 
    296      * @param bool   $is_uid   True if $msgs contains message UIDs 
     286     * @param array  $msgs     Message UIDs 
    297287     * 
    298288     * @return array The list of messages (rcube_mail_header) indexed by UID 
    299289     */ 
    300     function get_messages($mailbox, $msgs = array(), $is_uid = true) 
     290    function get_messages($mailbox, $msgs = array()) 
    301291    { 
    302292        if (empty($msgs)) { 
    303293            return array(); 
    304         } 
    305  
    306         // @TODO: it would be nice if we could work with UIDs only 
    307         // then index would be not needed. For now we need it to 
    308         // map id to uid here and to update message id for cached message 
    309  
    310         // Convert IDs to UIDs 
    311         $index = $this->get_index($mailbox, 'ANY'); 
    312         if (!$is_uid) { 
    313             foreach ($msgs as $idx => $msgid) 
    314                 if ($uid = $index[$msgid]) 
    315                     $msgs[$idx] = $uid; 
    316294        } 
    317295 
     
    335313            $result[$uid]->body = null; 
    336314 
    337             // update message ID according to index data 
    338             if (!empty($index) && ($id = array_search($uid, $index))) 
    339                 $result[$uid]->id = $id; 
    340  
    341315            if (!empty($result[$uid])) { 
    342316                unset($msgs[$uid]); 
     
    346320        // Fetch not found messages from IMAP server 
    347321        if (!empty($msgs)) { 
    348             $messages = $this->imap->fetch_headers($mailbox, array_keys($msgs), true, true); 
     322            $messages = $this->imap->fetch_headers($mailbox, array_keys($msgs), false, true); 
    349323 
    350324            // Insert to DB and add to result list 
     
    392366            $message = $this->build_message($sql_arr); 
    393367            $found   = true; 
    394  
    395             // update message ID according to index data 
    396             $index = $this->get_index($mailbox, 'ANY'); 
    397             if (!empty($index) && ($id = array_search($uid, $index))) 
    398                 $message->id = $id; 
    399368        } 
    400369 
     
    618587 
    619588    /** 
    620      * @param string $mailbox Folder name 
    621      * @param int    $id      Message (sequence) ID 
    622      * 
    623      * @return int Message UID 
    624      */ 
    625     function id2uid($mailbox, $id) 
    626     { 
    627         if (!empty($this->icache['pending_index_update'])) 
    628             return null; 
    629  
    630         // get index if it exists 
    631         $index = $this->get_index($mailbox, 'ANY', null, true); 
    632  
    633         return $index[$id]; 
    634     } 
    635  
    636  
    637     /** 
    638      * @param string $mailbox Folder name 
    639      * @param int    $uid     Message UID 
    640      * 
    641      * @return int Message (sequence) ID 
    642      */ 
    643     function uid2id($mailbox, $uid) 
    644     { 
    645         if (!empty($this->icache['pending_index_update'])) 
    646             return null; 
    647  
    648         // get index if it exists 
    649         $index = $this->get_index($mailbox, 'ANY', null, true); 
    650  
    651         return array_search($uid, (array)$index); 
    652     } 
    653  
    654     /** 
    655589     * Fetches index data from database 
    656590     */ 
    657591    private function get_index_row($mailbox) 
    658592    { 
     593console('cache::get_index_row'); 
    659594        // Get index from DB 
    660595        $sql_result = $this->db->query( 
     
    666601 
    667602        if ($sql_arr = $this->db->fetch_assoc($sql_result)) { 
    668             $data = explode('@', $sql_arr['data']); 
     603            $data  = explode('@', $sql_arr['data']); 
     604            $index = @unserialize($data[0]); 
     605            unset($data[0]); 
     606 
     607            if (empty($index)) { 
     608                $index = new rcube_result_index($mailbox); 
     609            } 
    669610 
    670611            return array( 
    671612                'valid'      => $sql_arr['valid'], 
    672                 'seq'        => explode(',', $data[0]), 
    673                 'uid'        => explode(',', $data[1]), 
    674                 'sort_field' => $data[2], 
    675                 'sort_order' => $data[3], 
    676                 'deleted'    => $data[4], 
    677                 'validity'   => $data[5], 
    678                 'uidnext'    => $data[6], 
    679                 'modseq'     => $data[7], 
     613                'object'     => $index, 
     614                'sort_field' => $data[1], 
     615                'deleted'    => $data[2], 
     616                'validity'   => $data[3], 
     617                'uidnext'    => $data[4], 
     618                'modseq'     => $data[5], 
    680619            ); 
    681620        } 
     
    699638 
    700639        if ($sql_arr = $this->db->fetch_assoc($sql_result)) { 
    701             $data = explode('@', $sql_arr['data']); 
    702  
    703             // Uncompress data, see add_thread_row() 
    704   //          $data[0] = str_replace(array('*', '^', '#'), array(';a:0:{}', 'i:', ';a:1:'), $data[0]); 
    705             $data[0] = unserialize($data[0]); 
    706  
    707             // build 'depth' and 'children' arrays 
    708             $depth = $children = array(); 
    709             $this->build_thread_data($data[0], $depth, $children); 
     640            $data   = explode('@', $sql_arr['data']); 
     641            $thread = @unserialize($data[0]); 
     642            unset($data[0]); 
     643 
     644            if (empty($thread)) { 
     645                $thread = new rcube_imap_result($mailbox); 
     646            } 
    710647 
    711648            return array( 
    712                 'tree'     => $data[0], 
    713                 'depth'    => $depth, 
    714                 'children' => $children, 
     649                'object'   => $thread, 
    715650                'deleted'  => $data[1], 
    716651                'validity' => $data[2], 
     
    726661     * Saves index data into database 
    727662     */ 
    728     private function add_index_row($mailbox, $sort_field, $sort_order, 
    729         $data = array(), $mbox_data = array(), $exists = false, $modseq = null) 
     663    private function add_index_row($mailbox, $sort_field, 
     664        $data, $mbox_data = array(), $exists = false, $modseq = null) 
    730665    { 
    731666        $data = array( 
    732             implode(',', array_keys($data)), 
    733             implode(',', array_values($data)), 
     667            serialize($data), 
    734668            $sort_field, 
    735             $sort_order, 
    736669            (int) $this->skip_deleted, 
    737670            (int) $mbox_data['UIDVALIDITY'], 
     
    760693     * Saves thread data into database 
    761694     */ 
    762     private function add_thread_row($mailbox, $data = array(), $mbox_data = array(), $exists = false) 
    763     { 
    764         $tree = serialize($data['tree']); 
    765         // This significantly reduces data length 
    766 //        $tree = str_replace(array(';a:0:{}', 'i:', ';a:1:'), array('*', '^', '#'), $tree); 
    767  
     695    private function add_thread_row($mailbox, $data, $mbox_data = array(), $exists = false) 
     696    { 
    768697        $data = array( 
    769             $tree, 
     698            serialize($data), 
    770699            (int) $this->skip_deleted, 
    771700            (int) $mbox_data['UIDVALIDITY'], 
     
    795724    private function validate($mailbox, $index, &$exists = true) 
    796725    { 
    797         $is_thread = isset($index['tree']); 
     726        $object    = $index['object']; 
     727        $is_thread = is_a($object, 'rcube_result_thread'); 
    798728 
    799729        // Get mailbox data (UIDVALIDITY, counters, etc.) for status check 
     
    815745        // Folder is empty but cache isn't 
    816746        if (empty($mbox_data['EXISTS'])) { 
    817             if (!empty($index['seq']) || !empty($index['tree'])) { 
     747            if (!$object->isEmpty()) { 
    818748                $this->clear($mailbox); 
    819749                $exists = false; 
     
    822752        } 
    823753        // Folder is not empty but cache is 
    824         else if (empty($index['seq']) && empty($index['tree'])) { 
     754        else if ($object->isEmpty()) { 
    825755            unset($this->icache[$mailbox][$is_thread ? 'thread' : 'index']); 
    826756            return false; 
     
    829759        // Validation flag 
    830760        if (!$is_thread && empty($index['valid'])) { 
    831             unset($this->icache[$mailbox][$is_thread ? 'thread' : 'index']); 
     761            unset($this->icache[$mailbox]['index']); 
    832762            return false; 
    833763        } 
     
    854784        if ($is_thread) { 
    855785            // check messages number... 
    856             if (!$this->skip_deleted && $mbox_data['EXISTS'] != @max(array_keys($index['depth']))) { 
     786            if (!$this->skip_deleted && $mbox_data['EXISTS'] != $object->countMessages()) { 
    857787                return false; 
    858788            } 
     
    863793        if (!empty($this->skip_deleted)) { 
    864794            // compare counts if available 
    865             if ($mbox_data['COUNT_UNDELETED'] != null 
    866                 && $mbox_data['COUNT_UNDELETED'] != count($index['uid'])) { 
     795            if (!empty($mbox_data['UNDELETED']) 
     796                && $mbox_data['UNDELETED']->count() != $object->count() 
     797            ) { 
    867798                return false; 
    868799            } 
    869800            // compare UID sets 
    870             if ($mbox_data['ALL_UNDELETED'] != null) { 
    871                 $uids_new = rcube_imap_generic::uncompressMessageSet($mbox_data['ALL_UNDELETED']); 
    872                 $uids_old = $index['uid']; 
     801            if (!empty($mbox_data['UNDELETED'])) { 
     802                $uids_new = $mbox_data['UNDELETED']->get(); 
     803                $uids_old = $object->get(); 
    873804 
    874805                if (count($uids_new) != count($uids_old)) { 
     
    885816                // get all undeleted messages excluding cached UIDs 
    886817                $ids = $this->imap->search_once($mailbox, 'ALL UNDELETED NOT UID '. 
    887                     rcube_imap_generic::compressMessageSet($index['uid'])); 
    888  
    889                 if (!empty($ids)) { 
     818                    rcube_imap_generic::compressMessageSet($object->get())); 
     819 
     820                if (!$ids->isEmpty()) { 
    890821                    return false; 
    891822                } 
     
    894825        else { 
    895826            // check messages number... 
    896             if ($mbox_data['EXISTS'] != max($index['seq'])) { 
     827            if ($mbox_data['EXISTS'] != $object->count()) { 
    897828                return false; 
    898829            } 
    899830            // ... and max UID 
    900             if (max($index['uid']) != $this->imap->id2uid($mbox_data['EXISTS'], $mailbox, true)) { 
     831            if ($object->max() != $this->imap->id2uid($mbox_data['EXISTS'], $mailbox, true)) { 
    901832                return false; 
    902833            } 
     
    1061992 
    1062993        $sort_field = $index['sort_field']; 
    1063         $sort_order = $index['sort_order']; 
     994        $sort_order = $index['object']->getParameters('ORDER'); 
    1064995        $exists     = true; 
    1065996 
     
    10701001        } 
    10711002        else { 
    1072             $data = array_combine($index['seq'], $index['uid']); 
     1003            $data = $index['object']; 
    10731004        } 
    10741005 
    10751006        // update index and/or HIGHESTMODSEQ value 
    1076         $this->add_index_row($mailbox, $sort_field, $sort_order, $data, $mbox_data, $exists); 
     1007        $this->add_index_row($mailbox, $sort_field, $data, $mbox_data, $exists); 
    10771008 
    10781009        // update internal cache for get_index() 
    1079         $this->icache[$mailbox]['index']['result'] = $data; 
     1010        $this->icache[$mailbox]['index']['object'] = $data; 
    10801011    } 
    10811012 
     
    11041035 
    11051036    /** 
    1106      * Creates 'depth' and 'children' arrays from stored thread 'tree' data. 
    1107      */ 
    1108     private function build_thread_data($data, &$depth, &$children, $level = 0) 
    1109     { 
    1110         foreach ((array)$data as $key => $val) { 
    1111             $children[$key] = !empty($val); 
    1112             $depth[$key] = $level; 
    1113             if (!empty($val)) 
    1114                 $this->build_thread_data($val, $depth, $children, $level + 1); 
    1115         } 
    1116     } 
    1117  
    1118  
    1119     /** 
    11201037     * Saves message stored in internal cache 
    11211038     */ 
     
    11711088    private function get_index_data($mailbox, $sort_field, $sort_order, $mbox_data = array()) 
    11721089    { 
    1173         $data = array(); 
    1174  
    11751090        if (empty($mbox_data)) { 
    11761091            $mbox_data = $this->imap->mailbox_data($mailbox); 
    11771092        } 
    11781093 
    1179         // Prevent infinite loop. 
    1180         // It happens when rcube_imap::message_index_direct() is called. 
    1181         // There id2uid() is called which will again call get_index() and so on. 
    1182         if (!$sort_field && !$this->skip_deleted) 
    1183             $this->icache['pending_index_update'] = true; 
    1184  
    11851094        if ($mbox_data['EXISTS']) { 
    11861095            // fetch sorted sequence numbers 
    1187             $data_seq = $this->imap->message_index_direct($mailbox, $sort_field, $sort_order); 
    1188             // fetch UIDs 
    1189             if (!empty($data_seq)) { 
    1190                 // Seek in internal cache 
    1191                 if (array_key_exists('index', (array)$this->icache[$mailbox]) 
    1192                     && array_key_exists('result', (array)$this->icache[$mailbox]['index']) 
    1193                 ) 
    1194                     $data_uid = $this->icache[$mailbox]['index']['result']; 
    1195                 else 
    1196                     $data_uid = $this->imap->conn->fetchUIDs($mailbox, $data_seq); 
    1197  
    1198                 // build index 
    1199                 if (!empty($data_uid)) { 
    1200                     foreach ($data_seq as $seq) 
    1201                         if ($uid = $data_uid[$seq]) 
    1202                             $data[$seq] = $uid; 
    1203                 } 
    1204             } 
    1205         } 
    1206  
    1207         // Reset internal flags 
    1208         $this->icache['pending_index_update'] = false; 
    1209  
    1210         return $data; 
     1096            $index = $this->imap->message_index_direct($mailbox, $sort_field, $sort_order); 
     1097        } 
     1098        else { 
     1099            $index = new rcube_result_index($mailbox, '* SORT'); 
     1100        } 
     1101 
     1102        return $index; 
    12111103    } 
    12121104} 
Note: See TracChangeset for help on using the changeset viewer.