From ea131a84e65481048a3e865aa8f0756f3f055b42 Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Thu, 6 Feb 2014 17:33:05 +0100 Subject: [PATCH] Refactor access to storage backend to avoid memory limit errors (#2828): 1. query backend and read contact names for sorting 2. sort index according to UI settings and fetched names 3. select the subset for the current page 4. fetch contacts for current page --- .../lib/rcube_kolab_contacts.php | 116 +++++++----------- 1 file changed, 47 insertions(+), 69 deletions(-) diff --git a/plugins/kolab_addressbook/lib/rcube_kolab_contacts.php b/plugins/kolab_addressbook/lib/rcube_kolab_contacts.php index 3fc85275..858ad17e 100644 --- a/plugins/kolab_addressbook/lib/rcube_kolab_contacts.php +++ b/plugins/kolab_addressbook/lib/rcube_kolab_contacts.php @@ -89,6 +89,8 @@ class rcube_kolab_contacts extends rcube_addressbook private $gid; private $storagefolder; + private $dataset; + private $sortindex; private $contacts; private $distlists; private $groupmembers; @@ -266,10 +268,11 @@ class rcube_kolab_contacts extends rcube_addressbook * * @param array List of cols to show * @param int Only return this number of records, use negative values for tail + * @param boolean True to skip the count query (select only) * * @return array Indexed list of contact records, each a hash array */ - public function list_records($cols = null, $subset = 0) + public function list_records($cols = null, $subset = 0, $nocount = false) { $this->result = new rcube_result_set(0, ($this->list_page-1) * $this->page_size); @@ -277,8 +280,10 @@ class rcube_kolab_contacts extends rcube_addressbook if ($this->gid) { $this->_fetch_groups(); - $this->contacts = array(); - $uids = array(); + $this->sortindex = array(); + $this->contacts = array(); + $local_sortindex = array(); + $uids = array(); // get members with email specified foreach ((array)$this->distlists[$this->gid]['member'] as $member) { @@ -287,23 +292,20 @@ class rcube_kolab_contacts extends rcube_addressbook continue; } - if (!empty($member['email'])) { - $this->contacts[$member['ID']] = $member; - } if (!empty($member['uid'])) { $uids[] = $member['uid']; } + else if (!empty($member['email'])) { + $this->contacts[$member['ID']] = $member; + $local_sortindex[$member['ID']] = $this->_sort_string($member); + } } // get members by UID if (!empty($uids)) { - foreach ((array)$this->storagefolder->select(array(array('uid', '=', $uids))) as $record) { - $member = $this->_to_rcube_contact($record); - $this->contacts[$member['ID']] = $member; - } + $this->_fetch_contacts(array(array('uid', '=', $uids))); + $this->sortindex = array_merge($this->sortindex, $local_sortindex); } - - $ids = array_keys($this->contacts); } else if (is_array($this->filter['ids'])) { $ids = $this->filter['ids']; @@ -311,34 +313,25 @@ class rcube_kolab_contacts extends rcube_addressbook $uids = array_map(array($this, 'id2uid'), $this->filter['ids']); $this->_fetch_contacts(array(array('uid', '=', $uids))); } - else { - $this->contacts = array(); - } } else { $this->_fetch_contacts(); - $ids = array_keys($this->contacts); } - // sort data arrays according to desired list sorting - if ($count = count($ids)) { - uasort($this->contacts, array($this, '_sort_contacts_comp')); - // get sorted IDs - if ($count != count($this->contacts)) - $ids = array_values(array_intersect(array_keys($this->contacts), $ids)); - else - $ids = array_keys($this->contacts); - - $this->result->count = count($ids); - } + // sort results (index only) + asort($this->sortindex, SORT_LOCALE_STRING); + $ids = array_keys($this->sortindex); // fill contact data into the current result set + $this->result->count = count($ids); $start_row = $subset < 0 ? $this->result->first + $this->page_size + $subset : $this->result->first; - $last_row = min($subset != 0 ? $start_row + abs($subset) : $this->result->first + $this->page_size, $count); + $last_row = min($subset != 0 ? $start_row + abs($subset) : $this->result->first + $this->page_size, $this->result->count); for ($i = $start_row; $i < $last_row; $i++) { - if ($id = $ids[$i]) - $this->result->add($this->contacts[$id]); + if (array_key_exists($i, $ids)) { + $idx = $ids[$i]; + $this->result->add($this->contacts[$idx] ?: $this->_to_rcube_contact($this->dataset[$idx])); + } } return $this->result; @@ -411,8 +404,11 @@ class rcube_kolab_contacts extends rcube_addressbook // save searching conditions $this->filter = array('fields' => $fields, 'value' => $value, 'mode' => $mode, 'ids' => array()); - // search be iterating over all records in memory - foreach ($this->contacts as $id => $contact) { + // search by iterating over all records in dataset + foreach ($this->dataset as $i => $record) { + $contact = $this->_to_rcube_contact($record); + $id = $contact['ID']; + // check if current contact has required values, otherwise skip it if ($required) { foreach ($required as $f) { @@ -593,10 +589,7 @@ class rcube_kolab_contacts extends rcube_addressbook true, false); } else { - $contact = $this->_to_rcube_contact($object); - $id = $contact['ID']; - $this->contacts[$id] = $contact; - $insert_id = $id; + $insert_id = $this->uid2id($object['uid']); } } @@ -627,7 +620,6 @@ class rcube_kolab_contacts extends rcube_addressbook true, false); } else { - $this->contacts[$id] = $this->_to_rcube_contact($object); $updated = true; // TODO: update data in groups this contact is member of @@ -674,7 +666,7 @@ class rcube_kolab_contacts extends rcube_addressbook } // clear internal cache - unset($this->contacts[$id], $this->groupmembers[$id]); + unset($this->groupmembers[$id]); $count++; } } @@ -723,6 +715,8 @@ class rcube_kolab_contacts extends rcube_addressbook { if ($this->storagefolder->delete_all()) { $this->contacts = array(); + $this->sortindex = array(); + $this->dataset = null; $this->result = null; } } @@ -979,57 +973,41 @@ class rcube_kolab_contacts extends rcube_addressbook */ private function _fetch_contacts($query = array()) { - if (!isset($this->contacts)) { - $this->contacts = array(); - foreach ((array)$this->storagefolder->select($query) as $record) { + if (!isset($this->dataset) || !empty($query)) { + $this->sortindex = array(); + $this->dataset = $this->storagefolder->select($query); + foreach ($this->dataset as $idx => $record) { $contact = $this->_to_rcube_contact($record); - $id = $contact['ID']; - $this->contacts[$id] = $contact; + $this->sortindex[$idx] = $this->_sort_string($contact); } } } /** - * Callback function for sorting contacts + * Extract a string for sorting from the given contact record */ - private function _sort_contacts_comp($a, $b) + private function _sort_string($rec) { - $a_value = $b_value = ''; + $str = ''; switch ($this->sort_col) { case 'name': - $a_value = $a['name'] . $a['prefix']; - $b_value = $b['name'] . $b['prefix']; + $str = $rec['name'] . $rec['prefix']; case 'firstname': - $a_value .= $a['firstname'] . $a['middlename'] . $a['surname']; - $b_value .= $b['firstname'] . $b['middlename'] . $b['surname']; + $str .= $rec['firstname'] . $rec['middlename'] . $rec['surname']; break; case 'surname': - $a_value = $a['surname'] . $a['firstname'] . $a['middlename']; - $b_value = $b['surname'] . $b['firstname'] . $b['middlename']; + $str = $rec['surname'] . $rec['firstname'] . $rec['middlename']; break; default: - $a_value = $a[$this->sort_col]; - $b_value = $b[$this->sort_col]; + $str = $rec[$this->sort_col]; break; } - $a_value .= is_array($a['email']) ? $a['email'][0] : $a['email']; - $b_value .= is_array($b['email']) ? $b['email'][0] : $b['email']; - - $a_value = mb_strtolower($a_value); - $b_value = mb_strtolower($b_value); - - // return strcasecmp($a_value, $b_value); - // make sorting unicode-safe and locale-dependent - if ($a_value == $b_value) - return 0; - - $arr = array($a_value, $b_value); - sort($arr, SORT_LOCALE_STRING); - return $a_value == $arr[0] ? -1 : 1; + $str .= is_array($rec['email']) ? $rec['email'][0] : $rec['email']; + return mb_strtolower($str); } /** @@ -1039,7 +1017,7 @@ class rcube_kolab_contacts extends rcube_addressbook { if (!isset($this->distlists)) { $this->distlists = $this->groupmembers = array(); - foreach ((array)$this->storagefolder->get_objects('distribution-list') as $record) { + foreach ($this->storagefolder->get_objects('distribution-list') as $record) { $record['ID'] = $this->uid2id($record['uid']); foreach ((array)$record['member'] as $i => $member) { $mid = $this->uid2id($member['uid'] ? $member['uid'] : 'mailto:' . $member['email']);