<?php

/**
 * Simple wrapper around the Depcache.  Safe when there is no depcache.
 * Prefixes all keys with the db connection information, so that multiple sites can use
 * the same depcache deamons.
 * 
 * @author     Guilherme Lopes <gui@mediamatic.nl> 
 * @author     Marc Worrell <marc@mediamatic.nl> 
 * @copyright  2007 Mediamatic
 * @version    CVS: $Id: Cache.php 29387 2007-07-18 09:41:30Z marc $
 */
 
require_once dirname(__FILE__) . '/../core/interfaces/iSingleton.class.php';


class Anymeta_Cache implements iSingleton
{
	/**
	 * iSingleton
	 */
	static private $instance 	= false;
	
	private	$depcached	= null;
	private $memo		= array();
	private $requests	= 0;
	private $msec		= 0.0;
	private $stats		= null;
	
	public static function instance() 
	{
	    if (!Anymeta_Cache::$instance) 
	    {
	    	Anymeta_Cache::$instance = new Anymeta_Cache();
	    }
		else
		{
	    	if (is_null(Anymeta_Cache::$instance->stats) && function_exists('any_config_read'))
			{
				// Prevent recursion by first assigning the stats var
				Anymeta_Cache::$instance->stats = false;
				Anymeta_Cache::$instance->stats = any_config_read('debugging.controller.pagestats', false);
			}
		}
	    return Anymeta_Cache::$instance;	
	}
	
	
	/**
	 * Constructor, tries to read the depcached_config.inc.php file from the directory above
	 * the anymeta directory.  When depcache extension is loaded and the config file is
	 * present, then the depcache is initialised.
	 * When there is no dc_config. then a connection with the localhost will be tried.
	 * 
	 * @param array options
	 * @return void
	 */
	public function __construct ( )
	{
		$this->connect();
	}


	/**
	 * Get cache status, also useful for /status controller
	 * 
	 * return array()
	 */
	public function getStatus ()
	{
		return array(
				'enabled'	=> !empty($this->depcached),
				'requests'	=> $this->requests,
				'msec'		=> $this->msec
			);
	}

	/**
	 * Perform a (re-)connect, whilst setting options for the connection.
	 * 
	 * @param array options
	 * @return void
	 */
	function connect ( $options = array() )
	{
		if (extension_loaded('depcached'))
		{
			global $dc_config;

			@include_once dirname(__FILE__) . '/../../depcached_config.inc.php';

			// Discard old depcached object
			if ($this->depcached)
			{
				unset($this->depcached);
			}
			$this->depcached = new Depcached();
			$persistent		 = true;
			$retry_interval	 = isset($options['retry_interval']) ? $options['retry_interval'] : 600;
			$timeout		 = isset($options['timeout'])        ? $options['timeout']        : 1;
						
			if (isset($dc_config) && is_array($dc_config))
			{			
				foreach ($dc_config as $cfg)
				{
					if (is_array($cfg))
					{
						$server = $cfg[0];
						$port	= empty($cfg[1]) ? 31307 : $cfg[1];
						$weight	= empty($cfg[2]) ? 1     : $cfg[2];
					}
					else
					{
						$server = $cfg;
						$port	= 31307;
						$weight = 1;
					}
					$this->depcached->addServer($server, $port, $persistent, $weight, $timeout, $retry_interval);
				}
			}
			else
			{
				$this->depcached->addServer('localhost', 31307, $persistent, 1, $timeout, $retry_interval);
			}
		}
		else
		{
			$this->depcached = null;
		}
	}
	
	
	/**
	 * Flush the memo cache
	 * 
	 * @return void
	 */
	public function forget ()
	{
		$this->memo = array();
	}


	/**
	 * Set a value in the cache
	 *
	 * @param string key		key with optional path in front
	 * @param mixed value		value to store
	 * @param int	expire		relative expiration time in seconds, or absolute unix time_t
	 * @param mixed deps		string or array with dependency keys (or space seperated string with multiple keys)
	 * @param bool	locked		set to 1 to prevent garbage collection in depcached
	 * @return bool				false when key could not be set
	 */
	public function set ( $key, $value, $expire = 3600, $deps = null, $locked = 0 )
	{
		if ($this->depcached)
		{
			$this->memo[$key] = $value;

			if (is_array($deps))
			{
				$ds = "";
				foreach ($deps as $d)
				{
					$ds .= " ".$this->key($d);
				}
			}
			else if (!empty($deps))
			{
				$ds = $this->key($deps);
			}
			else
			{
				$ds = "";
			}

			if ($this->stats)
			{
				$this->requests++;
				$msec = microtime(true);
			}
			$set = $this->depcached->set($this->key($key), $value, $locked, $expire, $ds);
			if ($this->stats)
			{
				$this->msec += microtime(true) - $msec;
			}
		}
		else
		{
			$set = false;
		}
		return $set;
	}


	/**
	 * Get a value from the cache
	 *
	 * @param mixed key		key or array with keys with optional path in front
	 * @return mixed		false when key was not found or expired, set value otherwise
	 */
	public function get ( $key )
	{
		if ($this->depcached)
		{
			if (any_is_array($key))
			{
				if (count($key) == 1)
				{
					$k = reset($key);
					if (isset($this->memo[$k]))
					{
						$ret = array($k => $this->memo[$k]);
					}
					else
					{
						if ($this->stats)
						{
							$this->requests++;
							$msec = microtime(true);
						}
						$v              = $this->depcached->get($this->key($k));
						if ($this->stats)
						{
							$this->msec += microtime(true) - $msec;
						}
						$ret            = array($k => $v);
						$this->memo[$k] = $v;
					}
				}
				else
				{
					$ks  = array();
					$ret = array();

					foreach ($key as $k)
					{
						if (isset($this->memo[$k]))
						{
							$ret[$k] = $this->memo[$k];
						}
						else
						{
							$ks[$this->key($k)] = $k; 
						}
					}
					
					if (!empty($ks))
					{
						if ($this->stats)
						{
							$this->requests++;
							$msec = microtime(true);
						}
						$vs = $this->depcached->get(array_keys($ks));
						if ($this->stats)
						{
							$this->msec += microtime(true) - $msec;
						}
						if (is_array($vs))
						{
							foreach ($vs as $k => $v)
							{
								if (isset($ks[$k]))
								{
									$this->memo[$ks[$k]] = $v;
									$ret[$ks[$k]]        = $v;
								}
								else
								{
									$this->memo[$ks[$k]] = false;
									$ret[$ks[$k]]        = false;
								}
							}
						}
					}
				}
			}
			else
			{
				if (isset($this->memo[$key]))
				{
					$ret             = $this->memo[$key];
				}
				else
				{
					if ($this->stats)
					{
						$this->requests++;
						$msec = microtime(true);
					}
					$ret 			  = $this->depcached->get($this->key($key));
					if ($this->stats)
					{
						$this->msec += microtime(true) - $msec;
					}
					$this->memo[$key] = $ret;
				}
			}
		}
		else
		{
			if (any_is_array($key))
			{
				$ret = array();
			}
			else
			{
				$ret = false;
			}
		}
		return $ret;
	}


	/**
	 * Delete a value from the cache
	 *
	 * @param string key		key with optional path in front
	 * @return bool				true when deleted
	 */
	public function delete ( $key )
	{
		if ($this->depcached)
		{
			if ($this->stats)
			{
				$this->requests++;
				$msec = microtime(true);
			}
			$ret = $this->depcached->delete($this->key($key));
			if ($this->stats)
			{
				$this->msec += microtime(true) - $msec;
			}
		}
		else
		{
			$ret = false;
		}
		unset($this->memo[$key]);
		return $ret;
	}


	/**
	 * Flush all keys from the cache (for this db connection...)
	 *
	 * @return bool		true when flushed
	 */
	public function flush_all ( )
	{
		if ($this->depcached)
		{
			if ($this->stats)
			{
				$this->requests++;
				$msec = microtime(true);
			}
			$ret = $this->depcached->flush($this->key(""));
		}
		else
		{
			$ret = false;
		}
		$this->forget();
		return $ret;
	}


	/**
	 * Flush a dependency key
	 *
	 * @param string			dependency key to be flushed
	 * @return bool				true when flushed
	 */
	public function flush ( $key )
	{
		if ($this->depcached)
		{
			if ($this->stats)
			{
				$this->requests++;
				$msec = microtime(true);
			}
			$ret = $this->depcached->flush($this->key($key));
			if ($this->stats)
			{
				$this->msec += microtime(true) - $msec;
			}
		}
		else
		{
			$ret = false;
		}
		return $ret;
	}


	/**
	 * Prefix a key with the database connection to make it unique across sites, also remove spaces from the key.
	 * 
	 * @param string key	key unique within site
	 * @return string		key unique across sites
	 */
	private function key ( $key )
	{
		global $mysql_host;
		global $mysql_db;

		if (empty($mysql_db))
		{
			return 'unknown/' . mb_strtolower(str_replace(' ', '_', $key));;
		}
		else
		{
			return $mysql_host . '/' . $mysql_db . '/' . mb_strtolower(str_replace(' ', '_', $key));;
		}
	}


	// Prevent users to clone the instance
	final private  function __clone() 
	{
		throw new Exception('Cannot clone the Anymeta_Cache object.');
	}
}

/* vi:set ts=4 sts=4 sw=4 binary noeol: */

?>