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)
This commit is contained in:
Ralf Becker 2009-10-19 08:58:49 +00:00
parent 2904c203ad
commit d1859f90e6
4 changed files with 220 additions and 54 deletions

View File

@ -1,6 +1,6 @@
<?php <?php
/** /**
* eGroupWare: GroupDAV access: addressbook handler * EGroupware: GroupDAV access: addressbook handler
* *
* @link http://www.egroupware.org * @link http://www.egroupware.org
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
@ -12,7 +12,10 @@
*/ */
/** /**
* eGroupWare: GroupDAV access: addressbook handler * EGroupware: GroupDAV access: addressbook handler
*
* Propfind now uses a groupdav_propfind_iterator with a callback to query huge addressbooks in chunk,
* without getting into problems with memory_limit.
*/ */
class addressbook_groupdav extends groupdav_handler class addressbook_groupdav extends groupdav_handler
{ {
@ -84,7 +87,6 @@ class addressbook_groupdav extends groupdav_handler
*/ */
function propfind($path,$options,&$files,$user,$id='') function propfind($path,$options,&$files,$user,$id='')
{ {
//$this->starttime = microtime(true);
$filter = array(); $filter = array();
// show addressbook of a single user? // show addressbook of a single user?
if ($user && $path != '/addressbook/') $filter['contact_owner'] = $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)); 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 // 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) foreach($options['props'] as $prop)
{ {
if ($prop['name'] == 'address-data') if ($prop['name'] == 'address-data')
{ {
$address_data = true; $filter['address_data'] = true;
break; 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(); $handler = self::_get_handler();
} }
unset($filter['address_data']);
$files = array();
// we query etag and modified, as LDAP does not have the strong sql etag // 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))) 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) foreach($contacts as $contact)
{ {
@ -131,10 +152,7 @@ class addressbook_groupdav extends groupdav_handler
////error_log("groupdav-props\n".print_r($props,true)); ////error_log("groupdav-props\n".print_r($props,true));
if ($address_data) if ($address_data)
{ {
//$sta = microtime(true);
$content = $handler->getVCard($contact,$this->charset,false); $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('getcontentlength',bytes($content));
$props[] = HTTP_WebDAV_Server::mkprop(groupdav::CARDDAV,'address-data',$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 $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), 'path' => self::get_path($contact),
'props' => $props, 'props' => $props,
); );
@ -152,10 +170,8 @@ class addressbook_groupdav extends groupdav_handler
//error_log("function propfind foreach : $end : $icount"); //error_log("function propfind foreach : $end : $icount");
} }
} }
if ($this->debug) error_log(__METHOD__.'('.array2string($filter).','.array2string($start).") took ".(microtime(true) - $starttime).' to return '.count($files).' items');
//$endtime = microtime(true) - $this->starttime; return $files;
//error_log(__FILE__ ."->". __METHOD__ ." elapsed time : $endtime");
return true;
} }
/** /**

View File

@ -600,8 +600,14 @@ class HTTP_WebDAV_Server
echo "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; echo "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
echo "<D:multistatus xmlns:D=\"DAV:\">\n"; echo "<D:multistatus xmlns:D=\"DAV:\">\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 // now we loop over all returned file entries
foreach ($files["files"] as &$file) { foreach ($files['files'] as $file) {
// collect namespaces here // collect namespaces here
$ns_hash = array(); $ns_hash = array();
@ -687,7 +693,8 @@ class HTTP_WebDAV_Server
= $this->mkprop("DAV:", = $this->mkprop("DAV:",
"lockdiscovery", "lockdiscovery",
$this->lockdiscovery($file['path'])); $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 // add empty value for this property
$file["noprops"][] = $file["noprops"][] =
$this->mkprop($reqprop["xmlns"], $reqprop["name"], ""); $this->mkprop($reqprop["xmlns"], $reqprop["name"], "");

View File

@ -1,6 +1,6 @@
<?php <?php
/** /**
* eGroupWare: CalDAV/CardDAV/GroupDAV access * EGroupware: CalDAV/CardDAV/GroupDAV access
* *
* @link http://www.egroupware.org * @link http://www.egroupware.org
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
@ -14,7 +14,7 @@
require_once('HTTP/WebDAV/Server.php'); require_once('HTTP/WebDAV/Server.php');
/** /**
* eGroupWare: GroupDAV access * EGroupware: GroupDAV access
* *
* Using a modified PEAR HTTP/WebDAV/Server class from egw-pear! * Using a modified PEAR HTTP/WebDAV/Server class from egw-pear!
* *
@ -172,7 +172,6 @@ class groupdav extends HTTP_WebDAV_Server
$files = array('files' => array()); $files = array('files' => array());
error_log(__METHOD__."($options[path],,$method) app=$app, user=$user, id=$id, user_prefix=$user_prefix");
if (!$app) // root folder containing apps if (!$app) // root folder containing apps
{ {
// self url // self url
@ -375,23 +374,20 @@ error_log(__METHOD__."($options[path],,$method) app=$app, user=$user, id=$id, us
} }
echo "</h1>\n"; echo "</h1>\n";
$collection_props = self::props2array($files['files'][0]['props']); $n = 0;
foreach($files['files'] as $file)
{
if (!isset($collection_props))
{
$collection_props = self::props2array($file['props']);
echo '<h3>'.lang('Collection listing').': '.htmlspecialchars($collection_props['DAV:displayname'])."</h3>\n"; echo '<h3>'.lang('Collection listing').': '.htmlspecialchars($collection_props['DAV:displayname'])."</h3>\n";
//_debug_array($files['files']); continue; // own entry --> displaying properies later
if (count($files['files']) <= 1)
{
echo '<p>'.lang('Collection empty.')."</p>\n";
} }
else if(!$n++)
{ {
echo "<table>\n\t<tr class='th'><th>".lang('Name')."</th><th>".lang('Size')."</th><th>".lang('Last modified')."</th><th>". echo "<table>\n\t<tr class='th'><th>#</th><th>".lang('Name')."</th><th>".lang('Size')."</th><th>".lang('Last modified')."</th><th>".
lang('ETag')."</th><th>".lang('Content type')."</th><th>".lang('Resource type')."</th></tr>\n"; lang('ETag')."</th><th>".lang('Content type')."</th><th>".lang('Resource type')."</th></tr>\n";
}
foreach($files['files'] as $n => $file)
{
if (!$n) continue; // own entry --> displaying properies later
$props = self::props2array($file['props']); $props = self::props2array($file['props']);
//echo $file['path']; _debug_array($props); //echo $file['path']; _debug_array($props);
$class = $class == 'row_on' ? 'row_off' : 'row_on'; $class = $class == 'row_on' ? 'row_off' : 'row_on';
@ -403,13 +399,19 @@ error_log(__METHOD__."($options[path],,$method) app=$app, user=$user, id=$id, us
{ {
$name = basename($file['path']); $name = basename($file['path']);
} }
echo "\t<tr class='$class'>\n\t\t<td>".html::a_href(htmlspecialchars($name),'/groupdav.php'.$file['path'])."</td>\n"; echo "\t<tr class='$class'>\n\t\t<td>$n</td>\n\t\t<td>".html::a_href(htmlspecialchars($name),'/groupdav.php'.$file['path'])."</td>\n";
echo "\t\t<td>".$props['DAV:getcontentlength']."</td>\n"; echo "\t\t<td>".$props['DAV:getcontentlength']."</td>\n";
echo "\t\t<td>".(!empty($props['DAV:getlastmodified']) ? date('Y-m-d H:i:s',$props['DAV:getlastmodified']) : '')."</td>\n"; echo "\t\t<td>".(!empty($props['DAV:getlastmodified']) ? date('Y-m-d H:i:s',$props['DAV:getlastmodified']) : '')."</td>\n";
echo "\t\t<td>".$props['DAV:getetag']."</td>\n"; echo "\t\t<td>".$props['DAV:getetag']."</td>\n";
echo "\t\t<td>".htmlspecialchars($props['DAV:getcontenttype'])."</td>\n"; echo "\t\t<td>".htmlspecialchars($props['DAV:getcontenttype'])."</td>\n";
echo "\t\t<td>".self::prop_value($props['DAV:resourcetype'])."</td>\n\t</tr>\n"; echo "\t\t<td>".self::prop_value($props['DAV:resourcetype'])."</td>\n\t</tr>\n";
} }
if (!$n)
{
echo '<p>'.lang('Collection empty.')."</p>\n";
}
else
{
echo "</table>\n"; echo "</table>\n";
} }
echo '<h3>'.lang('Properties')."</h3>\n"; echo '<h3>'.lang('Properties')."</h3>\n";

View File

@ -1,18 +1,18 @@
<?php <?php
/** /**
* eGroupWare: GroupDAV access: abstract baseclass for groupdav/caldav/carddav handlers * EGroupware: GroupDAV access: abstract baseclass for groupdav/caldav/carddav handlers
* *
* @link http://www.egroupware.org * @link http://www.egroupware.org
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package api * @package api
* @subpackage groupdav * @subpackage groupdav
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de> * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @copyright (c) 2007/8 by Ralf Becker <RalfBecker-AT-outdoor-training.de> * @copyright (c) 2007-9 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @version $Id$ * @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 abstract class groupdav_handler
{ {
@ -102,6 +102,16 @@ abstract class groupdav_handler
*/ */
abstract function propfind($path,$options,&$files,$user); 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 * 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;
}
}