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
This commit is contained in:
Thomas Bruederli 2014-02-06 17:33:05 +01:00
parent 69b7803f25
commit ea131a84e6

View file

@ -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']);