Rewrite object cache to not use smarty (in progress, file only)
Rewrite object cache to not use smarty (in progress, file only)

--- a/include/cache/Cache.class.php
+++ b/include/cache/Cache.class.php
@@ -10,57 +10,43 @@
 class GitPHP_Cache
 {
 	/**
-	 * Cache template
+	 * Cache strategy
+	 *
+	 * @var GitPHP_CacheStrategy_Interface
 	 */
-	const Template = 'data.tpl';
+	protected $strategy;
 
 	/**
-	 * Smarty instance
+	 * Cache lifetime in seconds
 	 *
-	 * @var Smarty
+	 * @var int
 	 */
-	protected $tpl = null;
+	protected $lifetime = 86400;
 
 	/**
-	 * Stores whether the cache is enabled
+	 * Constructor
 	 *
-	 * @var boolean
+	 * @param GitPHP_CacheStrategy_Interface $strategy cache strategy
 	 */
-	protected $enabled = false;
+	public function __construct(GitPHP_CacheStrategy_Interface $strategy)
+	{
+		if (!$strategy)
+			throw new Exception('Cache strategy is required');
 
-	/**
-	 * Stores memcache servers
-	 *
-	 * @var array[]
-	 */
-	protected $servers = null;
-
-	/**
-	 * Gets whether the cache is enabled
-	 *
-	 * @return boolean true if enabled
-	 */
-	public function GetEnabled()
-	{
-		return $this->enabled;
+		$this->SetStrategy($strategy);
 	}
 
 	/**
-	 * Sets whether the cache is enabled
+	 * Set the cache strategy
 	 *
-	 * @param boolean $enable true to enable, false to disable
+	 * @param GitPHP_CacheStrategy_Interface $strategy cache strategy
 	 */
-	public function SetEnabled($enable)
+	public function SetStrategy(GitPHP_CacheStrategy_Interface $strategy)
 	{
-		if ($enable == $this->enabled)
+		if (!$strategy)
 			return;
 
-		$this->enabled = $enable;
-
-		if ($this->enabled)
-			$this->CreateSmarty();
-		else
-			$this->DestroySmarty();
+		$this->strategy = $strategy;
 	}
 
 	/**
@@ -70,10 +56,7 @@
 	 */
 	public function GetLifetime()
 	{
-		if (!$this->enabled)
-			return false;
-
-		return $this->tpl->cache_lifetime;
+		return $this->lifetime;
 	}
 
 	/**
@@ -83,40 +66,11 @@
 	 */
 	public function SetLifetime($lifetime)
 	{
-		if (!$this->enabled)
+		if (!is_int($lifetime))
 			return;
 
-		$this->tpl->cache_lifetime = $lifetime;
+		$this->lifetime = $lifetime;
 	}
-
-	/**
-	 * Gets memcache server array
-	 *
-	 * @return array[] memcache array
-	 */
-	public function GetServers()
-	{
-		return $this->servers;
-	}
-
-	/**
-	 * Sets memcache server array
-	 *
-	 * @param array[] $servers server array
-	 */
-	public function SetServers($servers)
-	{
-		if (($this->servers === null) && ($servers === null))
-			return;
-
-		$this->servers = $servers;
-
-		if ($this->enabled) {
-			$this->DestroySmarty();
-			$this->CreateSmarty();
-		}
-	}
-	 
 
 	/**
 	 * Get an item from the cache
@@ -124,20 +78,12 @@
 	 * @param string $key cache key
 	 * @return mixed the cached object, or false
 	 */
-	public function Get($key = null)
+	public function Get($key)
 	{
 		if (empty($key))
 			return false;
 
-		if (!$this->enabled)
-			return false;
-
-		if (!$this->tpl->isCached(GitPHP_Cache::Template, $key))
-			return false;
-
-		$data = $this->tpl->fetch(GitPHP_Cache::Template, $key);
-
-		return unserialize(trim($data));
+		return $this->strategy->Get($key);
 	}
 
 	/**
@@ -147,31 +93,15 @@
 	 * @param mixed $value value
 	 * @param int $lifetime override the lifetime for this data
 	 */
-	public function Set($key = null, $value = null, $lifetime = null)
+	public function Set($key, $value, $lifetime = null)
 	{
 		if (empty($key) || empty($value))
 			return;
 
-		if (!$this->enabled)
-			return;
+		if ($lifetime === null)
+			$lifetime = $this->lifetime;
 
-		$oldLifetime = null;
-		if ($lifetime !== null) {
-			$oldLifetime = $this->tpl->cache_lifetime;
-			$this->tpl->cache_lifetime = $lifetime;
-		}
-
-		$this->Delete($key);
-		$this->tpl->clearAllAssign();
-		$this->tpl->assign('data', serialize($value));
-
-		// Force it into smarty's cache
-		$tmp = $this->tpl->fetch(GitPHP_Cache::Template, $key);
-		unset($tmp);
-
-		if ($lifetime !== null) {
-			$this->tpl->cache_lifetime = $oldLifetime;
-		}
+		$this->strategy->Set($key, $value, $lifetime);
 	}
 
 	/**
@@ -180,15 +110,12 @@
 	 * @param string $key cache key
 	 * @return boolean true if cached, false otherwise
 	 */
-	public function Exists($key = null)
+	public function Exists($key)
 	{
 		if (empty($key))
 			return false;
 
-		if (!$this->enabled)
-			return false;
-
-		return $this->tpl->isCached(GitPHP_Cache::Template, $key);
+		return $this->strategy->Exists($key);
 	}
 
 	/**
@@ -196,15 +123,12 @@
 	 *
 	 * @param string $key cache key
 	 */
-	public function Delete($key = null)
+	public function Delete($key)
 	{
 		if (empty($key))
 			return;
 
-		if (!$this->enabled)
-			return;
-
-		$this->tpl->clearCache(GitPHP_Cache::Template, $key);
+		$this->strategy->Delete($key);
 	}
 
 	/**
@@ -212,42 +136,7 @@
 	 */
 	public function Clear()
 	{
-		if (!$this->enabled)
-			return;
-
-		$this->tpl->clearAllCache();
-	}
-
-	/**
-	 * Instantiates Smarty cache
-	 */
-	private function CreateSmarty()
-	{
-		if ($this->tpl)
-			return;
-
-		require_once(GITPHP_SMARTYDIR . 'Smarty.class.php');
-		$this->tpl = new Smarty;
-		$this->tpl->addPluginsDir(GITPHP_INCLUDEDIR . 'smartyplugins');
-
-		$this->tpl->caching = Smarty::CACHING_LIFETIME_SAVED;
-
-		if (isset($this->servers) && is_array($this->servers) && (count($this->servers) > 0)) {
-			$this->tpl->registerCacheResource('memcache', new GitPHP_CacheResource_Memcache($this->servers));
-			$this->tpl->caching_type = 'memcache';
-		}
-
-	}
-
-	/**
-	 * Destroys Smarty cache
-	 */
-	private function DestroySmarty()
-	{
-		if (!$this->tpl)
-			return;
-
-		$this->tpl = null;
+		$this->strategy->Clear();
 	}
 
 }

--- /dev/null
+++ b/include/cache/CacheStrategy.interface.php
@@ -1,1 +1,49 @@
+<?php
+/**
+ * Interface for cache provider strategies
+ *
+ * @author Christopher Han <xiphux@gmail.com>
+ * @copyright Copyright (c) 2012 Christopher Han
+ * @package GitPHP
+ * @subpackage Cache
+ */
+interface GitPHP_CacheStrategy_Interface
+{
+	/**
+	 * Gets an item from the cache
+	 *
+	 * @param string $key cache key
+	 * @return mixed cached object or false if not found
+	 */
+	public function Get($key);
 
+	/**
+	 * Sets an item into the cache
+	 *
+	 * @param string $key cache key
+	 * @param mixed $value object to cache
+	 * @param int $lifetime cached object lifetime
+	 */
+	public function Set($key, $value, $lifetime);
+
+	/**
+	 * Check if an item exists
+	 *
+	 * @param string $key cache key
+	 * @return boolean true if exists
+	 */
+	public function Exists($key);
+
+	/**
+	 * Delete an item from the cache
+	 *
+	 * @param string $key cache key
+	 */
+	public function Delete($key);
+
+	/**
+	 * Clear the cache
+	 */
+	public function Clear();
+}
+

--- /dev/null
+++ b/include/cache/Cache_File.class.php
@@ -1,1 +1,171 @@
+<?php
+/**
+ * Cache strategy using files
+ *
+ * @author Christopher Han <xiphux@gmail.com>
+ * @copyright (c) 2012 Christopher Han
+ * @package GitPHP
+ * @subpackage Cache
+ */
+class GitPHP_Cache_File implements GitPHP_CacheStrategy_Interface
+{
+	/**
+	 * Cache file directory
+	 *
+	 * @var string
+	 */
+	protected $cacheDir;
 
+	/**
+	 * Constructor
+	 *
+	 * @param string $cacheDir cache dir
+	 */
+	public function __construct($cacheDir)
+	{
+		if (file_exists($cacheDir)) {
+			if (!is_dir($cacheDir)) {
+				throw new Exception($cacheDir . ' exists but is not a directory');
+			} else if (!is_writable($cacheDir)) {
+				throw new Exception($cacheDir . ' is not writable');
+			}
+		} else {
+			if (!mkdir($cacheDir, 0777))
+				throw new Exception($cacheDir . ' could not be created');
+			chmod($cacheDir, 0777);
+		}
+
+		$this->cacheDir = GitPHP_Util::AddSlash($cacheDir, true);
+	}
+
+	/**
+	 * Gets an item from the cache
+	 *
+	 * @param string $key cache key
+	 * @return mixed cached object or false if not found
+	 */
+	public function Get($key)
+	{
+		if (empty($key))
+			return false;
+
+		$data = $this->Load($key);
+		if ($data === false)
+			return false;
+
+		return unserialize($data);
+	}
+
+	/**
+	 * Sets an item into the cache
+	 *
+	 * @param string $key cache key
+	 * @param mixed $value object to cache
+	 * @param int $lifetime cached object lifetime
+	 */
+	public function Set($key, $value, $lifetime)
+	{
+		if (empty($key) || empty($value))
+			return;
+
+		$expire = '';
+		if ($lifetime >= 0)
+			$expire = time() + $lifetime;
+		file_put_contents($this->cacheDir . $this->KeyToFile($key), $expire . "\n" . serialize($value));
+	}
+
+	/**
+	 * Check if an item exists
+	 *
+	 * @param string $key cache key
+	 * @return boolean true if exists
+	 */
+	public function Exists($key)
+	{
+		return ($this->Load($key) !== false);
+	}
+
+	/**
+	 * Delete an item from the cache
+	 *
+	 * @param string $key cache key
+	 */
+	public function Delete($key)
+	{
+		if (empty($key))
+			return;
+
+		$file = $this->cacheDir . $this->KeyToFile($key);
+
+		if (file_exists($file))
+			unlink($file);
+	}
+
+	/**
+	 * Clear the cache
+	 */
+	public function Clear()
+	{
+		if ($dh = opendir($this->cacheDir)) {
+			while (($file = readdir($dh)) !== false) {
+				if (($file == '.') || ($file == '..'))
+					continue;
+				if (file_exists($this->cacheDir . $file))
+					unlink($this->cacheDir . $file);
+			}
+			closedir($dh);
+		}
+	}
+
+	/**
+	 * Load a key's serialized data
+	 *
+	 * @param string $key cache key
+	 */
+	private function Load($key)
+	{
+		if (empty($key))
+			return false;
+
+		$file = $this->cacheDir . $this->KeyToFile($key);
+		if (!is_readable($file))
+			return false;
+
+		$contents = file_get_contents($file);
+		if (empty($contents)) {
+			unlink($file);
+			return false;
+		}
+
+		$expire = strtok($contents, "\n");
+		if (!empty($expire) && ($expire < time())) {
+			unlink($file);
+			return false;
+		}
+
+		$data = substr($contents, strlen($expire) + 1);
+		if (empty($data)) {
+			unlink($file);
+			return false;
+		}
+
+		return $data;
+	}
+
+	/**
+	 * Converts a key to a filename
+	 *
+	 * @param string $key key
+	 * @return string filename
+	 */
+	private function KeyToFile($key)
+	{
+		if (empty($key))
+			return '';
+
+		$key = preg_replace('/[^\w\|]+/', '_', $key);
+		$key = preg_replace('/\|/', '^', $key);
+		return $key . '.dat';
+	}
+}
+

--- a/include/cache/Cacheable.interface.php
+++ b/include/cache/Cacheable.interface.php
@@ -5,7 +5,7 @@
  * @author Christopher Han <xiphux@gmail.com>
  * @copyright Copyright (c) 2012 Christopher Han
  * @package GitPHP
- * @subpackage Git
+ * @subpackage Cache
  */
 interface GitPHP_Cacheable_Interface
 {

--- a/include/controller/ControllerBase.class.php
+++ b/include/controller/ControllerBase.class.php
@@ -211,9 +211,7 @@
 
 		$this->projectList->SetMemoryCache(new GitPHP_MemoryCache($this->config->GetValue('objectmemory')));
 		if ($this->config->GetValue('objectcache')) {
-			$cache = new GitPHP_Cache();
-			$cache->SetServers($this->config->GetValue('memcache'));
-			$cache->SetEnabled(true);
+			$cache = new GitPHP_Cache(new GitPHP_Cache_File(GITPHP_CACHEDIR . 'objects'));
 			$cache->SetLifetime($this->config->GetValue('objectcachelifetime'));
 			$this->projectList->SetCache($cache);
 		}

--- a/test/bootstrap.php
+++ b/test/bootstrap.php
@@ -3,3 +3,6 @@
 require(dirname(__FILE__) . '/../include/AutoLoader.class.php');
 spl_autoload_register(array('GitPHP_AutoLoader', 'AutoLoad'));
 
+define('GITPHP_BASEDIR', dirname(__FILE__) . '/../');
+define('GITPHP_CACHEDIR', GITPHP_BASEDIR . 'cache/');
+

--- /dev/null
+++ b/test/cache/CacheTest.php
@@ -1,1 +1,69 @@
+<?php
+/**
+ * Cache test class
+ *
+ * @author Christopher Han <xiphux@gmail.com>
+ * @copyright Copyright (c) 2012 Christopher Han
+ * @package GitPHP
+ * @subpackage Test\Cache
+ */
+class GitPHP_CacheTest extends PHPUnit_Framework_TestCase
+{
+	public function testFile()
+	{
+		$cache = new GitPHP_Cache(new GitPHP_Cache_File(GITPHP_CACHEDIR . 'objects'));
+		$cache->Clear();
 
+		$this->assertFalse($cache->Exists('testkey1|testkey2'));
+		$cache->Set('testkey1|testkey2', 'testvalue1');
+		$this->assertTrue($cache->Exists('testkey1|testkey2'));
+		$this->assertEquals('testvalue1', $cache->Get('testkey1|testkey2'));
+
+		$this->assertFalse($cache->Get('testkey3|testkey4'));
+		$cache->Set('testkey3|testkey4', 'testvalue2');
+
+		$cache->Delete('testkey1|testkey2');
+		$this->assertFalse($cache->Exists('testkey1|testkey2'));
+
+		$this->assertTrue($cache->Exists('testkey3|testkey4'));
+
+		$cache->Clear();
+		$this->assertFalse($cache->Exists('testkey3|testkey4'));
+	}
+
+	public function testFileLifetime()
+	{
+		$cache = new GitPHP_Cache(new GitPHP_Cache_File(GITPHP_CACHEDIR . 'objects'));
+		$cache->Clear();
+
+		$cache->Set('testkey1|testkey2', 'testvalue1', 1);
+		sleep(2);
+		$this->assertFalse($cache->Exists('testkey1|testkey2'));
+
+		$cache->SetLifetime(1);
+		$cache->Set('testkey3|testkey4', 'testvalue2');
+		sleep(2);
+		$this->assertFalse($cache->Get('testkey3|testkey4'));
+	}
+
+	public function testMemcache()
+	{
+		$this->markTestIncomplete();
+	}
+
+	public function testMemcacheLifetime()
+	{
+		$this->markTestIncomplete();
+	}
+
+	public function testMemcached()
+	{
+		$this->markTestIncomplete();
+	}
+
+	public function testMemcachedLifetime()
+	{
+		$this->markTestIncomplete();
+	}
+}
+

--- a/test/cache/MemoryCacheTest.php
+++ b/test/cache/MemoryCacheTest.php
@@ -5,7 +5,7 @@
  * @author Christopher Han <xiphux@gmail.com>
  * @copyright Copyright (c) 2012 Christopher Han
  * @package GitPHP
- * @subpackage Test
+ * @subpackage Test\Cache
  */
 class GitPHP_MemoryCacheTest extends PHPUnit_Framework_TestCase
 {

comments