<?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 # ||
|| #################################################################### ||
\*======================================================================*/

/**
 * DB Cache.
 * Handler that caches and retrieves data from the database.
 * @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_Db extends vB_Cache
{
	/*Properties====================================================================*/

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

	/**
	 *
	 * @var requestStart
	 */
	protected $requestStart;


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

	/**
	 * Constructor public to allow for separate automated unit testing. Actual code should use
	 * vB_Cache::instance();
	 * @see vB_Cache::instance()
	 */
	public function __construct()
	{
		$this->attachObserver(new vB_Cache_Observer_Db($this));
		$this->assertor = vB::getDbAssertor();
		$this->requestStart = vB::getRequest()->getTimeNow();
	}



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

	/**
	 * Writes the cache data to storage.
	 *
	 * @param vB_CacheObject $cache
	 */
	protected function writeCache(vB_CacheObject $cache)
	{
		$data = $cache->getData();

		if (is_array($data) OR is_object($data))
		{
			$serialized = '1';
			$data = serialize($data);
		}
		else
		{
			$serialized = '0';
		}

		$this->assertor->delete('cache', array('cacheid'=>$cache->getKey()));
		try
		{
			$this->assertor->insert('cache', array(
				'cacheid' => $cache->getKey(),
				'expires' => intval($cache->getExpiry()),
				'created' => $this->requestStart,
				'locktime' => 0,
				'data' => $data,
				'serialized' => $serialized
			));
		}
		catch(Exception $e)
		{
			$this->assertor->update('cache', array(
				'expires' => intval($cache->getExpiry()),
				'created' => $this->requestStart,
				'locktime' => 0,
				'data' => $data,
				'serialized' => $serialized
			), array('cacheid' => $cache->getKey()));
		}

	}


	/**
	 * Reads the cache object from storage.
	 *
	 * @param string $key						- Id of the cache entry to read
	 * @return vB_CacheObject
	 */
	protected function readCache($key)
	{
		$entry = $this->assertor->getRow('cache', array('cacheid' => $key));

		if (!$entry)
		{
			return false;
		}

		if (intval($entry['serialized']))
		{
			$entry['data'] = unserialize($entry['data']);
		}

		$cache_obj = new vB_CacheObject($key, $entry['data'], $entry['expires'], $entry['locktime']);
		$cache_obj->setCreated($entry['created']);
		return $cache_obj;
	}


	/**
	 * Reads an array of cache objects from storage.
	 *
	 * @param string $keys						- Ids of the cache entry to read
	 * @return array of vB_CacheObjects
	 */
	protected function readCacheArray($keys)
	{
		$rst = $this->assertor->getRows('cache', array(
			array(
				'field' => 'cacheid',
				'value'	=> $keys,
				'operator' => vB_dB_Query::OPERATOR_EQ
			),
			array(
				'field' => 'expires',
				'value' => $this->requestStart,
				'operator' => vB_dB_Query::OPERATOR_GT
			)
		));

		if (!$rst)
		{
			return false;
		}

		$found = array();
		foreach($rst AS $record)
		{
			try
			{

				if (intval($record['serialized']))
				{
					$record['data'] = unserialize($record['data']);
				}

				if ($record['data'])
				{
					$obj = new vB_CacheObject($record['cacheid'], $record['data'], $record['expires'], $record['locktime']);
					$obj->setCreated($record['created']);
					//only return good values
					if (!$obj->isExpired())
					{
						$found[$record['cacheid']] = $obj;
					}
					unset($obj);
				}
			}
			catch (exception $e)
			{
				//If we got here, something was improperly serialized
				//There's not much we can do, but we don't want to return bad data.

			}
		}
		return $found;

	}


	/**
	 * 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)
	{
		$this->assertor->delete('cache', array('cacheid'=>$key));

		return true;
	}


	/**
	 * Sets a cache entry as expired in storage.
	 *
	 * @param string $key						- Key of the cache entry to expire
	 */
	protected function expireCache($key)
	{
		$this->assertor->update('cache', array('expires'=>$this->requestStart-1), array('cacheid'=>$key));

		return true;
	}


	/**
	 * Locks a cache entry.
	 *
	 * @param string $key						- Key of the cache entry to lock
	 * @return bool - TRUE iff the lock was obtained
	 */
	public function lock($key)
	{
		// locks depend on real time, so don't use TIMENOW
		$currentTime = time();
		$cache_obj = $this->readCache($key);

		if ($cache_obj)
		{
			// If the cache is expired, there's no reason to lock it
			if ($cache_obj->isExpired())
			{
				return false;
			}

			// Set loaded value so that we won't query the cache again in load(). See VBV-1862
			$this->setLoadedValue($cache_obj);

			// there's an entry, check if it is locked...
			if (($currentTime - $cache_obj->getLockTime()) < $cache_obj->getLockDuration())
			{
				// object is already locked
				return false;
			}
			else
			{
				$res = $this->assertor->update('cache', array('locktime'=>$currentTime-1), array(
					array(
						'field'	=> 'cacheid',
						'value'	=> $key,
						'operator' => vB_dB_Query::OPERATOR_EQ
					),
					array(
						'field' => 'locktime',
						'value' => $currentTime - $cache_obj->getLockDuration(),
						'operator'=> vB_dB_Query::OPERATOR_LT
					)
				));
				return (bool)$res;
			}
		}
		else
		{
			// there's no entry, try to insert a dummy one with the lock
			// we are not using an autoincrement field, so the response is not useful
			$this->assertor->insert('cache',
				array(
					'cacheid' => $key,
					'locktime' => $currentTime,
					'expires' => ($this->requestStart - 1)
				)
			);
			return true;
		}
	}



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

	/**
	 * Cleans cache.
	 *
	 * @param bool $only_expired				- Only clean expired entries
	 */
	public function clean($only_expired = true)
	{
		$queryData = array(
			vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_STORED,
			'timenow' => $this->requestStart,
		);

		$toBePurged = $this->assertor->getRows('cacheExpireSelect', $queryData);

		$entryIds = array();
		foreach ($toBePurged AS $entry)
		{
			$entryIds[] = $entry['cacheid'];
			$this->unsetLoadedValue($entry['cacheid']);
		}

		$this->assertor->assertQuery('cacheExpireDelete', $queryData);

		foreach ($entryIds AS $entryId)
		{
			$this->notifyPurged($entryId);
		}
	}
}

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