From 25b3c3a1f86fe24b30b459194e427fa2a35cf937 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Sun, 19 Oct 2008 11:28:21 +0000 Subject: [PATCH] Improved cache handling in egw_link class, to cope with excessive multiple reads of entries from the database: Applications can call egw_link::set_cache($app,$id,$title,$file_access=null) from their search or read method, to eliminate the need to query the entries again, when the egw_link class, link widget or links stream wrapper needs title or file_access values later. This offloads the caching to the link class, and improves performance a lot, specially for infolog. The cache is stored in the session and modified or deleted items get removed, when the link class get notified about that anyway. --- phpgwapi/inc/class.egw_link.inc.php | 137 ++++++++++++++++-- .../inc/class.links_stream_wrapper.inc.php | 6 +- 2 files changed, 125 insertions(+), 18 deletions(-) diff --git a/phpgwapi/inc/class.egw_link.inc.php b/phpgwapi/inc/class.egw_link.inc.php index 9f6327df2d..d4ec1edeba 100644 --- a/phpgwapi/inc/class.egw_link.inc.php +++ b/phpgwapi/inc/class.egw_link.inc.php @@ -97,6 +97,13 @@ class egw_link extends solink */ private static $title_cache = array(); + /** + * Cache file access permissions + * + * @var array + */ + private static $file_access_cache = array(); + /** * Private constructor to forbid instanciated use * @@ -133,6 +140,11 @@ class egw_link extends solink { self::$title_cache = array(); } + if (!(self::$file_access_cache = $GLOBALS['egw']->session->appsession('link_file_access_cache','phpgwapi'))) + { + self::$file_access_cache = array(); + } + //error_log(__METHOD__.'() items in title-cache: '.count(self::$title_cache).' file-access-cache: '.count(self::$file_access_cache)); } /** @@ -141,7 +153,9 @@ class egw_link extends solink */ static function save_session_cache() { + //error_log(__METHOD__.'() items in title-cache: '.count(self::$title_cache).' file-access-cache: '.count(self::$file_access_cache)); $GLOBALS['egw']->session->appsession('link_title_cache','phpgwapi',self::$title_cache); + $GLOBALS['egw']->session->appsession('link_file_access_cache','phpgwapi',self::$file_access_cache); } /** @@ -451,7 +465,7 @@ class egw_link extends solink if (!$link_id && !$app2 && !$id2 && $app2 != '!'.self::VFS_APPNAME) { self::delete_attached($app,$id); // deleting all attachments - unset(self::$title_cache[$app.':'.$id]); + self::delete_cache($app,$id); } $deleted =& solink::unlink($link_id,$app,$id,$owner,$app2 != '!'.self::VFS_APPNAME ? $app2 : '',$id2); @@ -530,23 +544,29 @@ class egw_link extends solink { if (!$id) return ''; - if (isset(self::$title_cache[$app.':'.$id])) + $title =& self::get_cache($app,$id); + if (isset($title)) { - if (self::DEBUG) echo '

'.__METHOD__."('$app','$id')='".self::$title_cache[$app.':'.$id]."' (from cache)

\n"; - return self::$title_cache[$app.':'.$id]; + if (self::DEBUG) echo '

'.__METHOD__."('$app','$id')='$title' (from cache)

\n"; + return $title; } if ($app == self::VFS_APPNAME) { if (is_array($id) && $link) { $link = $id; - $id = $link['name']; + $title = $link['name']; + } + else + { + $title = $id; } if (is_array($link)) { - $extra = ': '.$link['type'] . ' '.egw_vfs::hsize($link['size']); + $title .= ': '.$link['type'] . ' '.egw_vfs::hsize($link['size']); } - return self::$title_cache[$app.':'.$id] = $id.$extra; + if (self::DEBUG) echo '

'.__METHOD__."('$app','$id')='$title' (file)

\n"; + return $title; } if ($app == '' || !is_array($reg = self::$app_register[$app]) || !isset($reg['title'])) { @@ -565,7 +585,7 @@ class egw_link extends solink } if (self::DEBUG) echo '

'.__METHOD__."('$app','$id')='$title' (from $method)

\n"; - return self::$title_cache[$app.':'.$id] = $title; + return $title; } /** @@ -586,7 +606,8 @@ class egw_link extends solink $titles = $ids_to_query = array(); foreach($ids as $id) { - if (!isset(self::$title_cache[$app.':'.$id])) + $title =& self::get_cache($app,$id); + if (!isset($title)) { if (isset(self::$app_register[$app]['titles'])) { @@ -594,16 +615,16 @@ class egw_link extends solink } else { - self::title($app,$id); // no titles method --> fallback to query each link separate + $title = self::title($app,$id); // no titles method --> fallback to query each link separate } } - $titles[$id] = self::$title_cache[$app.':'.$id]; + $titles[$id] = $title; } if ($ids_to_query) { foreach(ExecMethod(self::$app_register[$app]['titles'],$ids_to_query) as $id => $title) { - $titles[$id] = self::$title_cache[$app.':'.$id] = $title; + $titles[$id] = $title; } } return $titles; @@ -930,7 +951,7 @@ class egw_link extends solink { self::notify('update',$link['app'],$link['id'],$app,$id,$link_id,$data); } - unset(self::$title_cache[$app.':'.$id]); + self::delete_cache($app,$id); } /** @@ -976,5 +997,95 @@ class egw_link extends solink self::notify('unlink',$link['link_app2'],$link['link_id2'],$link['link_app1'],$link['link_id1'],$link['link_id']); } } + + /** + * Get a reference to the cached value for $app/$id for $type + * + * @param string $app + * @param string|int $id + * @param string $type='title' 'title' or 'file_access' + * @return int|string can be null, if cache not yet set + */ + private static function &get_cache($app,$id,$type = 'title') + { + switch($type) + { + case 'title': + return self::$title_cache[$app.':'.$id]; + case 'file_access': + return self::$file_access_cache[$app.':'.$id]; + default: + throw new egw_exception_wrong_parameter("Unknown type '$type'!"); + } + } + + /** + * Set title and optional file_access cache for $app,$id + * + * Allows applications to set values for title and file access, eg. in their search method, + * to not be called again. This offloads the need to cache from the app to the link class. + * If there's no caching, items get read multiple times from the database! + * + * @param string $app + * @param int|string $id + * @param string $title title string or null + * @param int $file_access=null EGW_ACL_READ, EGW_ACL_EDIT or both or'ed together + */ + public static function set_cache($app,$id,$title,$file_access=null) + { + error_log(__METHOD__."($app,$id,$title,$file_access)"); + if (!is_null($file_access)) + { + $cache =& self::get_cache($app,$id); + $cache = $title; + } + if (!is_null($file_access)) + { + $cache =& self::get_cache($app,$id,'file_access'); + $cache = $file_access; + } + } + + /** + * Delete the diverse caches for $app/$id + * + * @param string $app + * @param int|string $id + */ + private static function delete_cache($app,$id) + { + unset(self::$title_cache[$app.':'.$id]); + unset(self::$file_access_cache[$app.':'.$id]); + } + + /** + * Check the file access perms for $app/id + * + * @ToDo $rel_path is not yet implemented, as no app use it currently + * @param string $app + * @param string|int $id id of entry + * @param int $required=EGW_ACL_READ EGW_ACL_{READ|EDIT} + * @param string $rel_path + * @return boolean + */ + static function file_access($app,$id,$required=EGW_ACL_READ,$rel_path=null) + { + $cache =& self::get_cache($app,$id,'file_access'); + + if (!isset($cache) || $required == EGW_ACL_EDIT && !($cache & $required)) + { + if(($method = self::get_registry($app,'file_access'))) + { + $cache |= ExecMethod2($method,$id,$required,$rel_path) ? $required : 0; + } + else + { + $cache |= self::title($app,$id) ? EGW_ACL_READ|EGW_ACL_EDIT : 0; + } + //error_log(__METHOD__."($app,$id,$required,$rel_path) got $cache --> ".($cache & $required ? 'true' : 'false')); + } + //else error_log(__METHOD__."($app,$id,$required,$rel_path) using cached value $cache --> ".($cache & $required ? 'true' : 'false')); + return !!($cache & $required); + } } egw_link::init_static(); \ No newline at end of file diff --git a/phpgwapi/inc/class.links_stream_wrapper.inc.php b/phpgwapi/inc/class.links_stream_wrapper.inc.php index c4e9963cb9..2657ac8ed3 100644 --- a/phpgwapi/inc/class.links_stream_wrapper.inc.php +++ b/phpgwapi/inc/class.links_stream_wrapper.inc.php @@ -83,15 +83,11 @@ class links_stream_wrapper extends sqlfs_stream_wrapper $access = true; // grant read&write access to /apps/$app } // allow applications to implement their own access control to the file storage - elseif(($method = egw_link::get_registry($app,'file_access'))) - { - $access = ExecMethod2($method,$id,$check,$rel_path); - } // otherwise use the title method to check if user has (at least read access) to the entry // which gives him then read AND write access to the file store of the entry else { - $access = !!egw_link::title($app,$id); + $access = egw_link::file_access($app,$id,$check,$rel_path); } if (self::DEBUG) error_log(__METHOD__."($url,$check) ".($access?"access granted ($app:$id:$rel_path)":'no access!!!')); return $access;