<?php
/*
* @package LabCMS
* @copyright (c) 2006 Alexander Levin aka Loki (loki_angel@mail.ru)
* @support http://labcms.ru
* @license http://opensource.org/licenses/gpl-license.php GNU Public License
*/

include_once dirname(__FILE__)."/Lite.php";

class Cache_Lab extends Cache_Lite implements Cache_Interface
{
	/**
	* Disable / Tune the automatic cleaning process
	*
	* The automatic cleaning process destroy too old (for the given life time)
	* cache files when a new cache file is written.
	* 0			   => no automatic cache cleaning
	* 1			   => systematic cache cleaning
	* x (integer) > 1 => automatic cleaning randomly 1 times on x cache write
	*
	* @var int $_automaticCleaning
	*/
	var $_automaticCleaningFactor = 1000;
	var $host_id=null;
	
	private function __clone()
	{
	}
	
	function __construct()
	{
		if (!is_dir(LAB_PATH_DATA.'/cache/files')) {
			mkdir(LAB_PATH_DATA.'/cache/files', 0777, true);
		}
		parent::Cache_Lite(array('cacheDir' => LAB_PATH_DATA.'/cache/files/', 'automaticSerialization' => true));
		$this->host_id=config::get('core.host_id');
	}
	
	protected function _getKey($key)
	{
		if (empty($this->host_id)) return $key;
		else return $this->host_id.'_'.$key;
	}
	
	public function get($id, $group = 'default', $doNotTestCacheValidity = false)
	{
		if (!config::get('core.cache')) return;
		if (is_array($id)) {
			$result=array();
			foreach ($id as $item) {
				if ($data=$this->_get($item, $group, $doNotTestCacheValidity)) {
					$result[$item]=$data;
				}
			}
			return $result;
		} else {
			return $this->_get($id, $group, $doNotTestCacheValidity);
		}
	}
	
	/**
	* Test if a cache is available and (if yes) return it
	*
	* @param string $id cache id
	* @param string $group name of the cache group
	* @param boolean $doNotTestCacheValidity if set to true, the cache validity won't be tested
	* @return string data of the cache (else : false)
	* @access public
	*/
	protected function _get($id, $group = 'default', $doNotTestCacheValidity = false)
	{
		$this->_id = $this->_getKey($id);
		$data = false;
		if ($this->_caching) {
			$this->_setRefreshTime();
			$this->_setFileName($this->_getKey($id), $group);
			clearstatcache();
			if ($this->_memoryCaching) {
				if (isset($this->_memoryCachingArray[$this->_file])) {
					if ($this->_automaticSerialization) {
						return unserialize($this->_memoryCachingArray[$this->_file]);
					}
					return $this->_memoryCachingArray[$this->_file];
				}
				if ($this->_onlyMemoryCaching) {
					return false;
				}				
			}
			if (($doNotTestCacheValidity) || (is_null($this->_refreshTime))) {
				if (file_exists($this->_file)) {
					$data = $this->_read();
				}
			} else {
				//проверяем не утратил ли файл актуальность (он был сохранен будущим числом)
				if ((file_exists($this->_file))
					&& (@filemtime($this->_file) > time())) {
					$data = $this->_read();
				}
			}
			if (($data) and ($this->_memoryCaching)) {
				$this->_memoryCacheAdd($data);
			}
			if (($this->_automaticSerialization) and (is_string($data))) {
				$data = unserialize($data);
			}
			return $data;
		}
		return false;
	}
	
	/**
	* Save some data in a cache file
	*
	* @param string $data data to put in cache (can be another type than strings if automaticSerialization is on)
	* @param string $id cache id
	* @param string $group name of the cache group
	* @return boolean true if no problem (else : false or a PEAR_Error object)
	* @access public
	*/
	public function set($id, $data, $ttl=NULL, $group = 'default')
	{
		if (!config::get('core.cache')) return;
		if ($ttl==-1) $ttl=60*60*24*365; //вместо "вечности" кэшируем на год
		if ($this->_caching) {
			if ($this->_automaticSerialization) {
				$data = serialize($data);
			}
			if (isset($id)) {
				$this->_setFileName($this->_getKey($id), $group);
			}
			if ($this->_memoryCaching) {
				$this->_memoryCacheAdd($data);
				if ($this->_onlyMemoryCaching) {
					return true;
				}
			}
			if ($this->_automaticCleaningFactor>0
				&& ($this->_automaticCleaningFactor==1
					|| mt_rand(1, $this->_automaticCleaningFactor)==1)) {
				$this->clean(false, 'old');			
			}
			if ($this->_writeControl) {
				$res = $this->_writeAndControl($data);
				if (is_bool($res)) {
					if ($res) {
						//ставим файлу дату, когда он должен утратить актуальность
						@touch($this->_file, time()+$ttl);
						return true;  
					}
					// if $res if false, we need to invalidate the cache
					@touch($this->_file, time() - 2*abs($this->_lifeTime));
					return false;
				}			
			} else {
				$res = $this->_write($data);
				@touch($this->_file, time()+$ttl);
			}
			if (is_object($res)) {
				// $res is a PEAR_Error object 
				if (!($this->_errorHandlingAPIBreak)) {   
					return false; // we return false (old API)
				}
			}
			return $res;
		}
		return false;
	}
	
	public function add($id, $data, $ttl=NULL, $group = 'default')
	{
		if (!config::get('core.cache')) return;
		if (isset($id)) {
			$this->_setFileName($this->_getKey($id), $group);
			if (!$filemtime=@filemtime($this->_file) || $filemtime<time()) return false;
		}
		return $this->set($id, $data, $ttl, $group);
	}
	
	
	
	/**
	* Remove a cache file
	*
	* @param string $id cache id
	* @param string $group name of the cache group
	* @param boolean $checkbeforeunlink check if file exists before removing it
	* @return boolean true if no problem
	* @access public
	*/
	function remove($id, $group = 'default', $checkbeforeunlink = false)
	{
		if (!is_array($id)) return parent::remove($this->_getKey($id), $group, $checkbeforeunlink);
		$result=true;
		foreach ($id as $key) {
			if (!parent::remove($this->_getKey($key), $group, $checkbeforeunlink)) $result=false;
		}
		return $result;
	}
	
	/**
	* Recursive function for cleaning cache file in the given directory
	*
	* @param string $dir directory complete path (with a trailing slash)
	* @param string $group name of the cache group
	* @param string $mode flush cache mode : 'old', 'ingroup', 'notingroup',
											 'callback_myFunction'
	* @return boolean true if no problem
	* @access private
	*/
	function _cleanDir($dir, $group = 'default', $mode = 'ingroup')	 
	{
		if ($this->_fileNameProtection) {
			$motif = ($group) ? 'cache_'.md5($group).'_' : 'cache_';
		} else {
			$motif = ($group) ? 'cache_'.$group.'_' : 'cache_';
		}
		if ($this->_memoryCaching) {
			foreach ($this->_memoryCachingArray as $key => $v) {
				if (strpos($key, $motif) !== false) {
					unset($this->_memoryCachingArray[$key]);
					$this->_memoryCachingCounter = $this->_memoryCachingCounter - 1;
				}
			}
			if ($this->_onlyMemoryCaching) {
				return true;
			}
		}
		if (!($dh = opendir($dir))) {
			return $this->raiseError('Cache_Lite : Unable to open cache directory !', -4);
		}
		$result = true;
		while ($file = readdir($dh)) {
			if (($file != '.') && ($file != '..')) {
				if (substr($file, 0, 6)=='cache_') {
					$file2 = $dir . $file;
					if (is_file($file2)) {
						switch (substr($mode, 0, 9)) {
							case 'old':
								// files older than lifeTime get deleted from cache
								if (!is_null($this->_lifeTime)) {
									if (time() > @filemtime($file2)) {
										$result = ($result and ($this->_unlink($file2)));
									}
								}
								break;
							case 'notingrou':
								if (strpos($file2, $motif) === false) {
									$result = ($result and ($this->_unlink($file2)));
								}
								break;
							case 'callback_':
								$func = substr($mode, 9, strlen($mode) - 9);
								if ($func($file2, $group)) {
									$result = ($result and ($this->_unlink($file2)));
								}
								break;
							case 'ingroup':
							default:
								if (strpos($file2, $motif) !== false) {
									$result = ($result and ($this->_unlink($file2)));
								}
								break;
						}
					}
					if ((is_dir($file2)) and ($this->_hashedDirectoryLevel>0)) {
						$result = ($result and ($this->_cleanDir($file2 . '/', $group, $mode)));
					}
				}
			}
		}
		return $result;
	}
	
	function _unlink($file)
	{
		return @unlink($file);
	}
} 

