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
/**
* eGroupWare: GroupDAV access: addressbook handler
* EGroupware: GroupDAV access: addressbook handler
*
* @link http://www.egroupware.org
* @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
{
@ -84,7 +87,6 @@ class addressbook_groupdav extends groupdav_handler
*/
function propfind($path,$options,&$files,$user,$id='')
{
//$this->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;
}
/**

View File

@ -600,8 +600,14 @@ class HTTP_WebDAV_Server
echo "<?xml version=\"1.0\" encoding=\"utf-8\"?>\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
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"], "");

View File

@ -1,6 +1,6 @@
<?php
/**
* eGroupWare: CalDAV/CardDAV/GroupDAV access
* EGroupware: CalDAV/CardDAV/GroupDAV access
*
* @link http://www.egroupware.org
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
@ -14,7 +14,7 @@
require_once('HTTP/WebDAV/Server.php');
/**
* eGroupWare: GroupDAV access
* EGroupware: GroupDAV access
*
* 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());
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 "</h1>\n";
$collection_props = self::props2array($files['files'][0]['props']);
echo '<h3>'.lang('Collection listing').': '.htmlspecialchars($collection_props['DAV:displayname'])."</h3>\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 '<h3>'.lang('Collection listing').': '.htmlspecialchars($collection_props['DAV:displayname'])."</h3>\n";
continue; // own entry --> displaying properies later
}
if(!$n++)
{
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";
}
$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<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>".(!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>".htmlspecialchars($props['DAV:getcontenttype'])."</td>\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\t<tr class='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";
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<tr class='$class'>\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>".(!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>".htmlspecialchars($props['DAV:getcontenttype'])."</td>\n";
echo "\t\t<td>".self::prop_value($props['DAV:resourcetype'])."</td>\n\t</tr>\n";
}
echo "</table>\n";
}
echo '<h3>'.lang('Properties')."</h3>\n";

View File

@ -1,18 +1,18 @@
<?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
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package api
* @subpackage groupdav
* @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$
*/
/**
* 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;
}
}