* @copyright (c) 2009-16 by Ralf Becker * @version $Id$ */ namespace EGroupware\Api\Cache; use EGroupware\Api; /** * Caching provider storing data in files * * The provider creates subdirs under a given path * for each values in $key */ class Files extends Base implements Provider { /** * Extension of file used to store expiration > 0 */ const EXPIRATION_EXTENSION = '.expiration'; /** * Path of base-directory for the cache, set via parameter to the constructor or defaults to temp_dir * * @var string */ protected $base_path; /** * Constructor, eg. opens the connection to the backend * * @throws Exception if connection to backend could not be established * @param array $params eg. array(host,port) or array(directory) depending on the provider */ function __construct(array $params) { if ($params) { $this->base_path = $params[0]; } else { $this->base_path = Api\Cache::get_system_config('temp_dir', false); if (isset($this->base_path)) $this->base_path .= '/egw_cache'; } if (!isset($this->base_path) || !file_exists($this->base_path) && !mkdir($this->base_path,0700,true)) { throw new Exception (__METHOD__."() can't create basepath $this->base_path!"); } } /** * Stores some data in the cache, if it does NOT already exists there * * @param array $keys eg. array($level,$app,$location) * @param mixed $data * @param int $expiration =0 * @return boolean true on success, false on error, incl. key already exists in cache */ function add(array $keys,$data,$expiration=0) { // open only if file does NOT exist if (!($ret = fopen($fname=$this->filename($keys,true), 'x'))) { // if file exists, check if it is expired if (file_exists($fname_expiration=$fname.self::EXPIRATION_EXTENSION) && ($expiration = (int)file_get_contents($fname_expiration)) && filemtime($fname) < time()-$expiration) { // open and truncate it $ret = fopen($fname, 'w'); // remove expiration unlink($fname_expiration); } } if ($ret) { flock($ret, LOCK_EX); $ok = fwrite($ret, serialize($data)); if ((int)$expiration > 0) file_put_contents($fname.self::EXPIRATION_EXTENSION,(string)$expiration); flock($ret, LOCK_UN); fclose($ret); $ret = $ok !== false; } return $ret; } /** * Stores some data in the cache * * @param array $keys eg. array($level,$app,$location) * @param mixed $data * @param int $expiration =0 * @return boolean true on success, false on error */ function set(array $keys,$data,$expiration=0) { if (($ret = @file_put_contents($fname=$this->filename($keys,true),serialize($data),LOCK_EX) > 0)) { if ((int)$expiration > 0) file_put_contents($fname.self::EXPIRATION_EXTENSION,(string)$expiration); } return $ret; } /** * Get some data from the cache * * @param array $keys eg. array($level,$app,$location) * @return mixed data stored or NULL if not found in cache */ function get(array $keys) { if (!file_exists($fname = $this->filename($keys))) { return null; } if (file_exists($fname_expiration=$fname.self::EXPIRATION_EXTENSION) && ($expiration = (int)file_get_contents($fname_expiration)) && filemtime($fname) < time()-$expiration) { unlink($fname); unlink($fname_expiration); return null; } return unserialize(file_get_contents($fname)); } /** * Delete some data from the cache * * @param array $keys eg. array($level,$app,$location) * @return boolean true on success, false on error (eg. $key not set) */ function delete(array $keys) { if (!file_exists($fname = $this->filename($keys))) { //error_log(__METHOD__.'('.array2string($keys).") file_exists('$fname') == FALSE!"); return false; } if (file_exists($fname_expiration=$fname.self::EXPIRATION_EXTENSION)) { unlink($fname_expiration); } //error_log(__METHOD__.'('.array2string($keys).") calling unlink('$fname')"); return unlink($fname); } /** * Delete all data under given keys * * @param array $keys eg. array($level,$app,$location) * @return boolean true on success, false on error (eg. $key not set) */ function flush(array $keys) { $dir = $this->filename($keys, false); return file_exists($dir) ? self::rm_recursive($dir) : true; } /** * Recursive delete a path * * @param string $path * @return boolean true on success, false otherwise */ private static function rm_recursive($path) { if (!is_dir($path)) { return unlink($path); } foreach(scandir($path) as $file) { if ($file == '.' || $file == '..') continue; $file = $path.'/'.$file; if (is_dir($file)) { if (!self::rm_recursive($file)) return false; } else { if (!unlink($file)) return false; } } return rmdir($path); } /** * Create a path from $keys and $basepath * * @param array $keys * @param boolean $mkdir =false should we create the directory * @return string */ function filename(array $keys,$mkdir=false) { $fname = $this->base_path.'/'.str_replace(array(':','*'),'-',implode('/',$keys)); if ($mkdir && !file_exists($dirname=dirname($fname))) { @mkdir($dirname,0700,true); } return $fname; } }