Add caching of collection reads

Also a special header is added for telling DAViCal to flush the
cache during regression testing etc.

Signed-off-by: Andrew McMillan <andrew@morphoss.com>
This commit is contained in:
Andrew McMillan 2011-01-01 21:25:46 +13:00
parent fe095d009d
commit 4178ab4254
10 changed files with 111 additions and 30 deletions

View File

@ -14,6 +14,7 @@
* @license http://gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once("AwlCache.php");
require_once("XMLDocument.php");
require_once("CalDAVPrincipal.php");
include("DAVTicket.php");
@ -343,6 +344,11 @@ EOSQL;
$qry->Exec('caldav',__LINE__,__FILE__);
dbg_error_log( 'caldav', 'Created new collection as "%s".', trim($params[':boxname']) );
// Uncache anything to do with the collection
$cache = getCacheInstance();
$cache->delete( 'collection-'.$params[':dav_name'], null );
$cache->delete( 'principal-'.$params[':parent_container'], null );
$qry = new AwlQuery( "SELECT * FROM collection WHERE dav_name = :dav_name", array( ':dav_name' => $matches[1] ) );
if ( $qry->Exec('caldav',__LINE__,__FILE__) && $qry->rows() == 1 && ($row = $qry->Fetch()) ) {
$this->collection_id = $row->collection_id;

View File

@ -9,6 +9,7 @@
* @license http://gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once('AwlCache.php');
require_once('AwlQuery.php');
require_once('DAVTicket.php');
@ -295,23 +296,8 @@ class DAVResource
}
/**
* Find the collection associated with this resource.
*/
function FetchCollection() {
global $c, $session, $request;
/**
* RFC4918, 8.3: Identifiers for collections SHOULD end in '/'
* - also discussed at more length in 5.2
*
* So we look for a collection which matches one of the following URLs:
* - The exact request.
* - If the exact request, doesn't end in '/', then the request URL with a '/' appended
* - The request URL truncated to the last '/'
* The collection URL for this request is therefore the longest row in the result, so we
* can "... ORDER BY LENGTH(dav_name) DESC LIMIT 1"
*/
dbg_error_log( 'DAVResource', ':FetchCollection: Looking for collection for "%s".', $this->dav_name );
private function ReadCollectionFromDatabase() {
global $c, $session;
$this->collection = (object) array(
'collection_id' => -1,
@ -385,7 +371,9 @@ EOSQL;
|| preg_match( '#^((/principals/[^/]+/)[^/]+)/?$#', $this->dav_name, $matches) ) {
$this->_is_principal = true;
$this->FetchPrincipal();
}
$this->collection->is_principal = true;
$this->collection->type = 'principal';
}
else if ( $this->dav_name == '/' ) {
$this->collection->dav_name = '/';
$this->collection->type = 'root';
@ -418,16 +406,10 @@ EOSQL;
if ( $qry->Exec('DAVResource',__LINE__,__FILE__) && $qry->rows() == 1 && ($row = $qry->Fetch()) ) {
$this->collection = $row;
$this->collection->exists = true;
$this->_is_binding = true;
$this->collection->parent_set = $row->parent_container;
$this->collection->parent_container = $row->bind_parent_container;
$this->bound_from = str_replace( $row->bound_to, $row->dav_name, $this->dav_name);
$this->collection->bound_from = $row->dav_name;
$this->collection->dav_name = $row->bound_to;
if ( isset($row->access_ticket_id) ) {
if ( !isset($this->tickets) ) $this->tickets = array();
$this->tickets[] = new DAVTicket($row->access_ticket_id);
}
if ( $row->is_calendar == 't' )
$this->collection->type = 'calendar';
else if ( $row->is_addressbook == 't' )
@ -436,6 +418,13 @@ EOSQL;
$this->collection->type = 'schedule-'. $matches[3]. 'box';
else
$this->collection->type = 'collection';
$this->_is_binding = true;
$this->bound_from = str_replace( $row->bound_to, $row->dav_name, $this->dav_name);
if ( isset($row->access_ticket_id) ) {
if ( !isset($this->tickets) ) $this->tickets = array();
$this->tickets[] = new DAVTicket($row->access_ticket_id);
}
}
else {
dbg_error_log( 'DAVResource', 'No collection for path "%s".', $this->dav_name );
@ -443,9 +432,63 @@ EOSQL;
$this->collection->dav_name = preg_replace('{/[^/]*$}', '/', $this->dav_name);
}
}
}
/**
* Find the collection associated with this resource.
*/
function FetchCollection() {
global $session;
/**
* RFC4918, 8.3: Identifiers for collections SHOULD end in '/'
* - also discussed at more length in 5.2
*
* So we look for a collection which matches one of the following URLs:
* - The exact request.
* - If the exact request, doesn't end in '/', then the request URL with a '/' appended
* - The request URL truncated to the last '/'
* The collection URL for this request is therefore the longest row in the result, so we
* can "... ORDER BY LENGTH(dav_name) DESC LIMIT 1"
*/
dbg_error_log( 'DAVResource', ':FetchCollection: Looking for collection for "%s".', $this->dav_name );
@dbg_error_log( 'DAVResource', ':FetchCollection: Found collection named "%s" of type "%s".', $this->collection->dav_name, $this->collection->type );
// Try and pull the answer out of a hat
$cache = getCacheInstance();
$cache_ns = 'collection-'.preg_replace( '{/[^/]*$}', '/', $this->dav_name);
$cache_key = 'dav_resource'.$session->user_no;
$this->collection = $cache->get( $cache_ns, $cache_key );
if ( $this->collection === false ) {
$this->ReadCollectionFromDatabase();
if ( $this->collection->type != 'principal' ) {
$cache_ns = 'collection-'.$this->collection->dav_name;
@dbg_error_log( 'Cache', ':FetchCollection: Setting cache ns "%s" key "%s". Type: %s', $cache_ns, $cache_key, $this->collection->type );
$cache->set( $cache_ns, $cache_key, $this->collection );
}
@dbg_error_log( 'DAVResource', ':FetchCollection: Found collection named "%s" of type "%s".', $this->collection->dav_name, $this->collection->type );
}
else {
@dbg_error_log( 'Cache', ':FetchCollection: Got cache ns "%s" key "%s". Type: %s', $cache_ns, $cache_key, $this->collection->type );
if ( preg_match( '#^(/[^/]+)/?$#', $this->dav_name, $matches)
|| preg_match( '#^((/principals/[^/]+/)[^/]+)/?$#', $this->dav_name, $matches) ) {
$this->_is_principal = true;
$this->FetchPrincipal();
$this->collection->is_principal = true;
$this->collection->type = 'principal';
}
@dbg_error_log( 'DAVResource', ':FetchCollection: Read cached collection named "%s" of type "%s".', $this->collection->dav_name, $this->collection->type );
}
if ( isset($this->collection->bound_from) ) {
$this->_is_binding = true;
$this->bound_from = str_replace( $this->collection->bound_to, $this->collection->bound_from, $this->dav_name);
if ( isset($this->collection->access_ticket_id) ) {
if ( !isset($this->tickets) ) $this->tickets = array();
$this->tickets[] = new DAVTicket($this->collection->access_ticket_id);
}
}
$this->_is_collection = ( $this->_is_principal || $this->collection->dav_name == $this->dav_name || $this->collection->dav_name == $this->dav_name.'/' );
if ( $this->_is_collection ) {
$this->dav_name = $this->collection->dav_name;

View File

@ -78,6 +78,12 @@ $params = array(
$qry = new AwlQuery( $sql, $params );
if ( $qry->Exec('BIND',__LINE__,__FILE__) ) {
header('Location: '. ConstructURL($destination_path) );
// Uncache anything to do with the target
$cache = getCacheInstance();
$cache_ns = 'collection-'.$destination_path;
$cache->delete( $cache_ns, null );
$request->DoResponse(201);
}
else {

View File

@ -59,6 +59,9 @@ if ( $dav_resource->IsBinding() ) {
}
else if ( $dav_resource->IsCollection() ) {
if ( delete_collection( $dav_resource->resource_id() ) && $qry->Commit() ) {
// Uncache anything to do with the collection
$cache = getCacheInstance();
$cache->delete( 'collection-'.$dav_resource->dav_name(), null );
$request->DoResponse( 204 );
}
}
@ -78,6 +81,8 @@ else {
if ( function_exists('log_caldav_action') ) {
log_caldav_action( 'DELETE', $dav_resource->GetProperty('uid'), $dav_resource->GetProperty('user_no'), $dav_resource->GetProperty('collection_id'), $request->path );
}
$cache = getCacheInstance();
$cache->delete( 'collection-'.$dav_resource->parent_path(), null );
$request->DoResponse( 204 );
}
}

View File

@ -108,8 +108,13 @@ $dst_collection = $dest->GetProperty('collection_id');
$src_user_no = $src->GetProperty('user_no');
$dst_user_no = $dest->GetProperty('user_no');
$cache = getCacheInstance();
$cachekeys = array();
if ( $src->IsCollection() ) {
$cachekeys[] = ($src->ContainerType() == 'principal' ? 'principal' : 'collection').'-'.$src->parent_path();
$cachekeys[] = ($src->IsPrincipal() == 'principal' ? 'principal' : 'collection').'-'.$src->dav_name();
$cachekeys[] = ($src->IsPrincipal() ? 'principal' : 'collection').'-'.$dest->dav_name();
if ( $dest->Exists() ) {
$qry = new AwlQuery( 'DELETE FROM collection WHERE dav_name = :dst_name', array( ':dst_name' => $dst_name ) );
if ( !$qry->Exec('move') ) rollback(500);
@ -121,6 +126,11 @@ if ( $src->IsCollection() ) {
$sql .= ', user_no = :dst_user_no ';
$params[':dst_user_no'] = $dst_user_no;
}
if ( $src->parent_path() != $dest->parent_path() ) {
$sql .= ', parent_container=:parent ';
$params[':parent'] = $dest->parent_path();
$cachekeys[] = ($dest->ContainerType() == 'principal' ? 'principal' : 'collection').'-'.$dest->parent_path();
}
$sql .= 'WHERE collection_id = :src_collection';
$params[':src_collection'] = $src_collection;
$qry = new AwlQuery( $sql, $params );
@ -131,6 +141,8 @@ else {
$qry = new AwlQuery( 'DELETE FROM caldav_data WHERE dav_name = :dst_name', array( ':dst_name' => $dst_name) );
if ( !$qry->Exec('move') ) rollback(500);
}
$cachekeys[] = ($src->ContainerType() == 'principal' ? 'principal' : 'collection').'-'.$src->parent_path();
$sql = 'UPDATE caldav_data SET dav_name = :dst_name';
$params = array( ':dst_name' => $dst_name );
if ( $src_user_no != $dst_user_no ) {
@ -140,6 +152,7 @@ else {
if ( $src_collection != $dst_collection ) {
$sql .= ', collection_id = :dst_collection';
$params[':dst_collection'] = $dst_collection;
$cachekeys[] = ($dest->ContainerType() == 'principal' ? 'principal' : 'collection').'-'.$dest->parent_path();
}
$sql .=' WHERE dav_name = :src_name';
$params[':src_name'] = $src_name;
@ -170,4 +183,7 @@ else {
$qry = new AwlQuery('COMMIT');
if ( !$qry->Exec('move') ) rollback(500);
// We need to delete from the cache *after* we commit the transaction :-)
foreach( $cachekeys AS $cache_ns ) $cache->delete( $cache_ns, null );
$request->DoResponse( 200 );

View File

@ -10,7 +10,6 @@
*/
dbg_error_log("PROPPATCH", "method handler");
require_once('AWLCache.php');
require_once('iCalendar.php');
require_once('DAVResource.php');

View File

@ -15,7 +15,7 @@
* return true if it's a whole calendar
*/
require_once('AWLCache.php');
require_once('AwlCache.php');
require_once('iCalendar.php');
require_once('WritableCollection.php');
@ -121,7 +121,7 @@ VALUES( :user_no, :parent_container, :dav_name, :dav_etag, :dav_displayname, TRU
);
$qry->QDo( $sql, $params );
}
else if ( isset($public) ) {
else if ( isset($public) && $collection->is_public == ($public?'t':'f') ) {
$collection = $qry->Fetch();
$sql = 'UPDATE collection SET publicly_readable = :is_public::boolean WHERE collection_id = :collection_id';
$params = array( ':is_public' => ($public?'t':'f'), ':collection_id' => $collection->collection_id );
@ -129,6 +129,7 @@ VALUES( :user_no, :parent_container, :dav_name, :dav_etag, :dav_displayname, TRU
rollback_on_error( $caldav_context, $user_no, $path );
}
}
}
}
@ -631,7 +632,7 @@ EOSQL;
// Uncache anything to do with the collection
$cache = getCacheInstance();
$cache_ns = 'collection-'.preg_replace( '{/.*$}', '/', $path);
$cache_ns = 'collection-'.preg_replace( '{/[^/]*$}', '/', $path);
$cache->delete( $cache_ns, null );
}
@ -995,7 +996,7 @@ EOSQL;
// Uncache anything to do with the collection
$cache = getCacheInstance();
$cache_ns = 'collection-'.preg_replace( '{/.*$}', '/', $path);
$cache_ns = 'collection-'.preg_replace( '{/[^/]*$}', '/', $path);
$cache->delete( $cache_ns, null );
dbg_error_log( 'PUT', 'User: %d, ETag: %s, Path: %s', $author, $etag, $path);

View File

@ -12,6 +12,7 @@ SELECT ticket_id FROM access_ticket
WHERE target_collection_id = 11 AND target_resource_id is null;
ENDSQL
HEADER=X-DAViCal-Flush-Cache: true
HEADER=User-Agent: Ticket Spec Tests
HEADER=Content-Type: text/xml; charset="UTF-8"
HEADER=Ticket: ##ticket##

View File

@ -5,6 +5,8 @@ TYPE=MKCOL
URL=http://mycaldav/caldav.php/user1/addressbook/
HEADER=User-Agent: DAViCalTester/public
HEADER=Content-Type: text/xml
HEADER=X-DAViCal-Flush-Cache: true
HEAD

View File

@ -3,6 +3,8 @@
#
TYPE=POST
URL=http://mycaldav/caldav.php/user1/.out/
HEADER=X-DAViCal-Flush-Cache: true
HEADER=User-Agent: DAVKit/4.0.3 (732); CalendarStore/4.0.4 (997); iCal/4.0.4 (1395); Mac OS X/10.6.5 (10H574)
HEADER=Recipient: mailto:user2@example.net
HEADER=Content-Type: text/calendar