<?php if (!defined('VB_ENTRY')) die('Access denied.');
/*======================================================================*\
|| #################################################################### ||
|| # vBulletin 5.0.0 Beta 10
|| # ---------------------------------------------------------------- # ||
|| # Copyright �2000-2012 vBulletin Solutions Inc. All Rights Reserved. ||
|| # This file may not be redistributed in whole or significant part. # ||
|| # ---------------- VBULLETIN IS NOT FREE SOFTWARE ---------------- # ||
|| # http://www.vbulletin.com | http://www.vbulletin.com/license.html # ||
|| #################################################################### ||
\*======================================================================*/

/**
 * Memcached.
 * Handler that caches and retrieves data from the memcache.
 * @see vB_Cache
 *
 * @package vBulletin
 * @author vBulletin Development Team
 * @version $Revision: 29424 $
 * @since $Date: 2009-02-02 14:07:13 +0000 (Mon, 02 Feb 2009) $
 * @copyright vBulletin Solutions Inc.
 */
class vB_Cache_Memcached extends vB_Cache
{
	const LOCK_PREFIX = 'lock_';

	/*Properties====================================================================*/

	/**
	 * A reference to the singleton instance
	 *
	 * @var vB_Cache_Memcached
	 */
	protected static $instance;

	/**
	 *
	 * @var vB_dB_Assertor
	 */
	protected $assertor;

	/**
	* The Memcached wrapper
	*
	* @var	vB_Memcache
	*/
	protected $memcached = null;

	/**
	 * Memcached expiration in seconds
	 * @var int
	 */
	protected $expiration;

	/*Construction==================================================================*/

	/**
	 * Constructor protected to enforce singleton use.
	 * @see instance()
	 */
	protected function __construct()
	{
		parent::__construct();

		$this->memcached = vB_Memcache::instance();
		$this->memcached->connect();
		$this->expiration = 48*60*60; // two days
		$this->assertor = vB::getDbAssertor();
	}


	/**
	 * Returns singleton instance of self.
	 * @todo This can be inherited once late static binding is available.  For now
	 * it has to be redefined in the child classes
	 *
	 * @return vB_Cache_Db						- Reference to singleton instance of cache handler
	 */
	public static function instance($type = NULL)
	{
		if (!isset(self::$instance))
		{
			$class = __CLASS__;
			self::$instance = new $class();
		}

		return self::$instance;
	}

	/**
	 * Returns the UNIX timestamp of the last ocurrence of $event, and FALSE if there isn't one
	 *
	 * @param string $event
	 * @param bool $use_db
	 * @return mixed
	 */
	protected function getEventTime($event, $read_db = FALSE, $refresh_memcached = FALSE)
	{
		if ($time = $this->memcached->get($this->prefix . $event))
		{
			return $time;
		}
		else if ($read_db)
		{
			$event_info = $this->assertor->getRow('cacheevent_log', array('event'=>$event));

			if ($event_info)
			{
				if ($refresh_memcached)
				{
					$this->setEventTime($event, $event_info['eventtime'], FALSE);
				}

				return $event_info['eventtime'];
			}
		}

		return FALSE;
	}

	/**
	 * Store event in memcache and optionally in the database
	 *
	 * @param string $event - Event identifier
	 * @param int $time - If 0, the event won't overwrite any memcache entry and won't be stored in db
	 * @param bool $write_db - If true, event will be stored in db
	 */
	protected function setEventTime($event, $time, $write_db = FALSE)
	{
		// if $time = 0, this is a dummy event which should not overwrite real events in memcache
		$method = ($time == 0) ? 'add' : 'set';
		$this->memcached->$method($this->prefix . $event, $time, $this->expiration);

		if ($time != 0 AND $write_db)
		{
			$this->assertor->delete('cacheevent_log', array(
				array(
					'field'	=> 'event',
					'value'	=> $event
				),
				array(
					'field'		=> 'eventtime',
					'value'		=> $time,
					'operator'	=> vB_dB_Query::OPERATOR_LT
				)
			));
			$this->assertor->insertIgnore('cacheevent_log', array('event'=>$event, 'eventtime'=>$time));
		}
	}

	// disable the use values_read and no_values
	public function saveCacheInfo($cacheid)
	{
		return true;
	}
	public function restoreCacheInfo($cacheid)
	{
		return true;
	}
	protected function setLoadedValue(vB_CacheObject $cache) {}
	protected function unsetLoadedValue($key) {}
	protected function expireLoadedValue($key) {}
	protected function getLoadedValue($key)
	{
		return false;
	}
	protected function setLoadedValueEmpty($key) {}
	protected function unsetLoadedValueEmpty($key) {}
	protected function isLoadedValueEmpty($key){
		return false;
	}

	/*Initialisation================================================================*/

	/**
	 * Writes the cache data to storage.
	 *
	 * @param vB_CacheObject $cache
	 */
	protected function writeCache(vB_CacheObject $cache)
	{
		$ptitle = $this->prefix . $cache->getKey();
		
		$this->memcached->set($ptitle, $cache, $cache->getExpiry());

		if ($events = $cache->getEvents())
		{
			foreach ($events AS $event)
			{
				if ($time = $this->getEventTime($event, TRUE, TRUE))
				{
					// there's already an event which might not have been processed yet
					// don't do anything
				}
				else
				{
					// no events in memcached or db, set event time to 0
					$this->memcached->set($this->prefix . $event, 0, $this->expiration);
				}
			}
		}
	}


	/**
	 * Reads the cache object from storage.
	 *
	 * @param string $key						- Id of the cache entry to read
	 * @return vB_CacheObject
	 */
	protected function readCache($key)
	{
		$ptitle = $this->prefix . $key;
		$entry = $this->memcached->get($ptitle);

		if (!$entry)
		{
			return false;
		}

		// check if it is still valid
		if ($events = $entry->getEvents())
		{
			foreach ($events AS $event)
			{
				if ($time = $this->getEventTime($event, TRUE, TRUE))
				{
					if ($time > $entry->getCreated())
					{
						// we need to expire this object and stop checking
						$this->expireCache($key);

						return false;
					}
				}
				else
				{
					// event not logged in memcached nor in db
					$this->setEventTime($event, 0, FALSE);
				}
			}
		}

		return $entry;
	}

	/**
	 * Removes a cache object from storage.
	 *
	 * @param int $key							- Key of the cache entry to purge
	 * @return bool								- Whether anything was purged
	 */
	protected function purgeCache($key)
	{
		$ptitle = $this->prefix . $title;
		$this->memcached->delete($ptitle);

		return true;
	}


	/**
	 * Sets a cache entry as expired in storage.
	 *
	 * @param string $key						- Key of the cache entry to expire
	 */
	protected function expireCache($key)
	{
		$ptitle = $this->prefix . $key;
		if ($entry = $this->memcached->get($ptitle))
		{
			$entry->setExpiry(vB::getRequest()->getTimeNow()-1);
			$this->memcached->set($ptitle, $entry, $this->expiration);

			return TRUE;
		}
		else
		{
			return FALSE;
		}
	}

	/**
	 * Expires cache objects based on a triggered event.
	 *
	 * An event handling vB_CacheObserver must be attached to handle cache events.
	 * Generally the CacheObservers would respond by calling vB_Cache::expire() with
	 * the cache_id's of the objects to expire.
	 *
	 * @param string | array $event				- The name of the event
	 */
	public function event($events)
	{
		// set to an array of strings
		$events = (array)$events;
		foreach ($events AS $key=>$event)
		{
			$strEvent = strval($event);
			$events[$key] = $strEvent;
			$this->setEventTime($strEvent, time(), TRUE);
		}

		// Notify observers of event but don't expect them to callback
		$this->notifyEvent($events);

		return $this;
	}

	/**
	 * Locks a cache entry.
	 *
	 * @param string $key						- Key of the cache entry to lock
	 */
	public function lock($key)
	{
		// For some weird reason, storing a simple timestamp does not work, so use prefix.
		$lock_expiration = max(array(ini_get('max_execution_time'),30));
		return ($this->memcached->add($this->prefix . self::LOCK_PREFIX . $key,self::LOCK_PREFIX . vB::getRequest()->getTimeNow(), $lock_expiration));
	}

	public function unlock($key)
	{
		return ($this->memcached->delete($this->prefix . self::LOCK_PREFIX . $key));
	}

	/*Clean=========================================================================*/

	/**
	 * Cleans cache.
	 *
	 * @param bool $only_expired				- Only clean expired entries
	 */
	public function clean($only_expired = true)
	{
		if (!$only_expired)
		{
			$this->memcached->flush();
			$this->cleanNow();
		}
	}



	/*Shutdown======================================================================*\

	/**
	 * Perform any finalisation on shutdown.
	 */
	public function shutdown()
	{
		parent::shutdown();
	}
}

/*======================================================================*\
|| ####################################################################
|| # 
|| # SVN: $Revision: 29401 $
|| ####################################################################
\*======================================================================*/
