From d1859f90e66716f8fc13bad78b0966ff14f73236 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Mon, 19 Oct 2009 08:58:49 +0000 Subject: [PATCH] Backport of r28140-2, r28156 to EPL-9.1: - using iterator to query AB in chunks of 500 entries - Brief: t header (no 404 for missing props) --- .../inc/class.addressbook_groupdav.inc.php | 46 ++++-- egw-pear/HTTP/WebDAV/Server.php | 11 +- phpgwapi/inc/class.groupdav.inc.php | 68 ++++---- phpgwapi/inc/class.groupdav_handler.inc.php | 149 +++++++++++++++++- 4 files changed, 220 insertions(+), 54 deletions(-) diff --git a/addressbook/inc/class.addressbook_groupdav.inc.php b/addressbook/inc/class.addressbook_groupdav.inc.php index 4958f52a25..23e2d3e30c 100644 --- a/addressbook/inc/class.addressbook_groupdav.inc.php +++ b/addressbook/inc/class.addressbook_groupdav.inc.php @@ -1,6 +1,6 @@ starttime = microtime(true); $filter = array(); // show addressbook of a single user? if ($user && $path != '/addressbook/') $filter['contact_owner'] = $user; @@ -99,25 +101,44 @@ class addressbook_groupdav extends groupdav_handler if ($this->debug) error_log(__METHOD__."($path,".array2string($options).",,$user,$id) filter=".array2string($filter)); // check if we have to return the full calendar data or just the etag's - if (!($address_data = $options['props'] == 'all' && $options['root']['ns'] == groupdav::CARDDAV) && is_array($options['props'])) + if (!($filter['address_data'] = $options['props'] == 'all' && $options['root']['ns'] == groupdav::CARDDAV) && is_array($options['props'])) { foreach($options['props'] as $prop) { if ($prop['name'] == 'address-data') { - $address_data = true; + $filter['address_data'] = true; break; } } } - if ($address_data) + // return iterator, calling ourself to return result in chunks + $files['files'] = new groupdav_propfind_iterator($this,$filter,$files['files']); + + return true; + } + + /** + * Callback for profind interator + * + * @param array $filter + * @param array|boolean $start=false false=return all or array(start,num) + * @return array with "files" array with values for keys path and props + */ + function &propfind_callback(array $filter,$start=false) + { + $starttime = microtime(true); + + if (($address_data = $filter['address_data'])) { $handler = self::_get_handler(); } + unset($filter['address_data']); + + $files = array(); // we query etag and modified, as LDAP does not have the strong sql etag if (($contacts =& $this->bo->search(array(),$address_data ? false : array('id','uid','etag','modified'),'contact_id','','',False,'AND',false,$filter))) { - //$icount= 0; foreach($contacts as $contact) { @@ -131,10 +152,7 @@ class addressbook_groupdav extends groupdav_handler ////error_log("groupdav-props\n".print_r($props,true)); if ($address_data) { - //$sta = microtime(true); $content = $handler->getVCard($contact,$this->charset,false); - //$en = microtime(true) - $sta; - //error_log("getVCard took : $en"); $props[] = HTTP_WebDAV_Server::mkprop('getcontentlength',bytes($content)); $props[] = HTTP_WebDAV_Server::mkprop(groupdav::CARDDAV,'address-data',$content); } @@ -142,7 +160,7 @@ class addressbook_groupdav extends groupdav_handler { $props[] = HTTP_WebDAV_Server::mkprop('getcontentlength', ''); // expensive to calculate and no CalDAV client uses it } - $files['files'][] = array( + $files[] = array( 'path' => self::get_path($contact), 'props' => $props, ); @@ -152,10 +170,8 @@ class addressbook_groupdav extends groupdav_handler //error_log("function propfind foreach : $end : $icount"); } } - - //$endtime = microtime(true) - $this->starttime; - //error_log(__FILE__ ."->". __METHOD__ ." elapsed time : $endtime"); - return true; + if ($this->debug) error_log(__METHOD__.'('.array2string($filter).','.array2string($start).") took ".(microtime(true) - $starttime).' to return '.count($files).' items'); + return $files; } /** diff --git a/egw-pear/HTTP/WebDAV/Server.php b/egw-pear/HTTP/WebDAV/Server.php index 6c74d033df..1bf7cc1bae 100644 --- a/egw-pear/HTTP/WebDAV/Server.php +++ b/egw-pear/HTTP/WebDAV/Server.php @@ -600,8 +600,14 @@ class HTTP_WebDAV_Server echo "\n"; echo "\n"; + // using an ArrayIterator to prevent foreach from copying the array, + // as we cant loop by reference, when an iterator is given in $files['files'] + if (is_array($files['files'])) + { + $files['files'] = new ArrayIterator($files['files']); + } // now we loop over all returned file entries - foreach ($files["files"] as &$file) { + foreach ($files['files'] as $file) { // collect namespaces here $ns_hash = array(); @@ -687,7 +693,8 @@ class HTTP_WebDAV_Server = $this->mkprop("DAV:", "lockdiscovery", $this->lockdiscovery($file['path'])); - } else { + // only collect $file['noprops'] if we have NO Brief: t HTTP Header + } elseif (!isset($this->_SERVER['HTTP_BRIEF']) || $this->_SERVER['HTTP_BRIEF'] != 't') { // add empty value for this property $file["noprops"][] = $this->mkprop($reqprop["xmlns"], $reqprop["name"], ""); diff --git a/phpgwapi/inc/class.groupdav.inc.php b/phpgwapi/inc/class.groupdav.inc.php index 69769fa7f7..b10d050de4 100644 --- a/phpgwapi/inc/class.groupdav.inc.php +++ b/phpgwapi/inc/class.groupdav.inc.php @@ -1,6 +1,6 @@ array()); -error_log(__METHOD__."($options[path],,$method) app=$app, user=$user, id=$id, user_prefix=$user_prefix"); if (!$app) // root folder containing apps { // self url @@ -375,41 +374,44 @@ error_log(__METHOD__."($options[path],,$method) app=$app, user=$user, id=$id, us } echo "\n"; - $collection_props = self::props2array($files['files'][0]['props']); - echo '

'.lang('Collection listing').': '.htmlspecialchars($collection_props['DAV:displayname'])."

\n"; - //_debug_array($files['files']); - - if (count($files['files']) <= 1) + $n = 0; + foreach($files['files'] as $file) + { + if (!isset($collection_props)) + { + $collection_props = self::props2array($file['props']); + echo '

'.lang('Collection listing').': '.htmlspecialchars($collection_props['DAV:displayname'])."

\n"; + continue; // own entry --> displaying properies later + } + if(!$n++) + { + echo "\n\t\n"; + } + $props = self::props2array($file['props']); + //echo $file['path']; _debug_array($props); + $class = $class == 'row_on' ? 'row_off' : 'row_on'; + if (substr($file['path'],-1) == '/') + { + $name = basename(substr($file['path'],0,-1)).'/'; + } + else + { + $name = basename($file['path']); + } + echo "\t\n\t\t\n\t\t\n"; + echo "\t\t\n"; + echo "\t\t\n"; + echo "\t\t\n"; + echo "\t\t\n"; + echo "\t\t\n\t\n"; + } + if (!$n) { echo '

'.lang('Collection empty.')."

\n"; } else { - echo "
#".lang('Name')."".lang('Size')."".lang('Last modified')."". + lang('ETag')."".lang('Content type')."".lang('Resource type')."
$n".html::a_href(htmlspecialchars($name),'/groupdav.php'.$file['path'])."".$props['DAV:getcontentlength']."".(!empty($props['DAV:getlastmodified']) ? date('Y-m-d H:i:s',$props['DAV:getlastmodified']) : '')."".$props['DAV:getetag']."".htmlspecialchars($props['DAV:getcontenttype'])."".self::prop_value($props['DAV:resourcetype'])."
\n\t\n"; - - foreach($files['files'] as $n => $file) - { - if (!$n) continue; // own entry --> displaying properies later - - $props = self::props2array($file['props']); - //echo $file['path']; _debug_array($props); - $class = $class == 'row_on' ? 'row_off' : 'row_on'; - if (substr($file['path'],-1) == '/') - { - $name = basename(substr($file['path'],0,-1)).'/'; - } - else - { - $name = basename($file['path']); - } - echo "\t\n\t\t\n"; - echo "\t\t\n"; - echo "\t\t\n"; - echo "\t\t\n"; - echo "\t\t\n"; - echo "\t\t\n\t\n"; - } echo "
".lang('Name')."".lang('Size')."".lang('Last modified')."". - lang('ETag')."".lang('Content type')."".lang('Resource type')."
".html::a_href(htmlspecialchars($name),'/groupdav.php'.$file['path'])."".$props['DAV:getcontentlength']."".(!empty($props['DAV:getlastmodified']) ? date('Y-m-d H:i:s',$props['DAV:getlastmodified']) : '')."".$props['DAV:getetag']."".htmlspecialchars($props['DAV:getcontenttype'])."".self::prop_value($props['DAV:resourcetype'])."
\n"; } echo '

'.lang('Properties')."

\n"; diff --git a/phpgwapi/inc/class.groupdav_handler.inc.php b/phpgwapi/inc/class.groupdav_handler.inc.php index c55c5e28d5..1bbadffd79 100644 --- a/phpgwapi/inc/class.groupdav_handler.inc.php +++ b/phpgwapi/inc/class.groupdav_handler.inc.php @@ -1,18 +1,18 @@ - * @copyright (c) 2007/8 by Ralf Becker + * @copyright (c) 2007-9 by Ralf Becker * @version $Id$ */ /** - * eGroupWare: GroupDAV access: abstract baseclass for groupdav/caldav/carddav handlers + * EGroupware: GroupDAV access: abstract baseclass for groupdav/caldav/carddav handlers */ abstract class groupdav_handler { @@ -86,7 +86,7 @@ abstract class groupdav_handler #if (!is_null($debug)) $this->debug = $debug = 3; $this->base_uri = is_null($base_uri) ? $base_uri : $_SERVER['SCRIPT_NAME']; $this->agent = self::get_agent(); - + $this->translation =& $GLOBALS['egw']->translation; $this->egw_charset = $this->translation->charset(); } @@ -102,6 +102,16 @@ abstract class groupdav_handler */ abstract function propfind($path,$options,&$files,$user); + /** + * Propfind callback, if interator is used + * + * @param array $filter + * @param array|boolean $start false=return all or array(start,num) + * @param int &$total + * @return array with "files" array with values for keys path and props + */ + function &propfind_callback(array $filter,$start,&$total) { } + /** * Handle get request for an applications entry * @@ -306,3 +316,134 @@ abstract class groupdav_handler } } +/** + * Iterator for propfinds using propfind callback of a groupdav_handler to query results in chunks + * + * The propfind method just computes a filter and then returns an instance of this iterator instead of the files: + * + * function propfind($path,$options,&$files,$user,$id='') + * { + * $filter = array(); + * // compute filter from path, options, ... + * + * $files['files'] = new groupdav_propfind_iterator($this,$filter,$files['files']); + * + * return true; + * } + */ +class groupdav_propfind_iterator implements Iterator +{ + /** + * Handler to call for entries + * + * @var groupdav_handler + */ + protected $handler; + + /** + * Filter of propfind call + * + * @var array + */ + protected $filter; + + /** + * Extra responses to return too + * + * @var array + */ + protected $files; + + /** + * Start value for callback + * + * @var int + */ + protected $start=0; + + /** + * Number of entries queried from callback in one call + * + */ + const CHUNK_SIZE = 500; + + /** + * Constructor + * + * @param groupdav_handler $handler + * @param array $filter filter for propfind call + * @param array $files=null extra files/responses to return too + */ + public function __construct(groupdav_handler $handler,array $filter,array &$files=null) + { + $this->handler = $handler; + $this->filter = $filter; + $this->files = $files; + reset($this->files); + } + + /** + * Return the current element + * + * @return array + */ + public function current() + { + return current($this->files); + } + + /** + * Return the key of the current element + * + * @return int|string + */ + public function key() + { + $current = $this->current(); + + return $current['path']; // we return path as key + } + + /** + * Move forward to next element (called after each foreach loop) + */ + public function next() + { + if (next($this->files) !== false) + { + return true; + } + if (!$this->handler) + { + return false; // no further entries + } + // try query further files via propfind callback of handler and store result in $this->files + $this->files = $this->handler->propfind_callback($this->filter,array($this->start,self::CHUNK_SIZE)); + $this->start += self::CHUNK_SIZE; + reset($this->files); + + if (count($this->files) < self::CHUNK_SIZE) // less entries then asked --> no further available + { + unset($this->handler); + } + return current($this->files) !== false; + } + + /** + * Rewind the Iterator to the first element (called at beginning of foreach loop) + */ + public function rewind() + { + + } + + /** + * Checks if current position is valid + * + * @return boolean + */ + public function valid () + { + return current($this->files) !== false; + } +}