From 363e9667ececca66a223934ded569425abd34cdf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= <jfd@butonic.de>
Date: Wed, 10 Dec 2014 12:24:20 +0100
Subject: [PATCH] Add Redis cache implementation, prefer over memcached, tests
 & config sample

---
 config/config.sample.php         | 14 ++++-
 lib/private/memcache/factory.php |  4 +-
 lib/private/memcache/redis.php   | 94 ++++++++++++++++++++++++++++++++
 tests/lib/memcache/redis.php     | 29 ++++++++++
 4 files changed, 139 insertions(+), 2 deletions(-)
 create mode 100644 lib/private/memcache/redis.php
 create mode 100644 tests/lib/memcache/redis.php

diff --git a/config/config.sample.php b/config/config.sample.php
index 791ffa3df90..35e3f6ce5f1 100644
--- a/config/config.sample.php
+++ b/config/config.sample.php
@@ -749,9 +749,21 @@ $CONFIG = array(
  */
 'cipher' => 'AES-256-CFB',
 
+
+/**
+ * Connection details for redis to use for memory caching.
+ * Redis is only used if other memory cache options (xcache, apc, apcu) are
+ * not available.
+ */
+'redis' => array(
+	'host' => 'localhost', // can also be a unix domain socket: '/tmp/redis.sock'
+	'port' => 6379,
+	'timeout' => 0.0
+),
+
 /**
  * Server details for one or more memcached servers to use for memory caching.
- * Memcache is only used if other memory cache options (xcache, apc, apcu) are
+ * Memcache is only used if other memory cache options (xcache, apc, apcu, redis) are
  * not available.
  */
 'memcached_servers' => array(
diff --git a/lib/private/memcache/factory.php b/lib/private/memcache/factory.php
index 8e47a8899fc..dba9e8a0e00 100644
--- a/lib/private/memcache/factory.php
+++ b/lib/private/memcache/factory.php
@@ -37,6 +37,8 @@ class Factory implements ICacheFactory {
 			return new APCu($prefix);
 		} elseif (APC::isAvailable()) {
 			return new APC($prefix);
+		} elseif (Redis::isAvailable()) {
+			return new Redis($prefix);
 		} elseif (Memcached::isAvailable()) {
 			return new Memcached($prefix);
 		} else {
@@ -50,7 +52,7 @@ class Factory implements ICacheFactory {
 	 * @return bool
 	 */
 	public function isAvailable() {
-		return XCache::isAvailable() || APCu::isAvailable() || APC::isAvailable() || Memcached::isAvailable();
+		return XCache::isAvailable() || APCu::isAvailable() || APC::isAvailable() || Redis::isAvailable() || Memcached::isAvailable();
 	}
 
 	/**
diff --git a/lib/private/memcache/redis.php b/lib/private/memcache/redis.php
new file mode 100644
index 00000000000..f21619887d0
--- /dev/null
+++ b/lib/private/memcache/redis.php
@@ -0,0 +1,94 @@
+<?php
+/**
+ * Copyright (c) 2014 Jörn Friedrich Dreyer <jfd@butonic.de>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+namespace OC\Memcache;
+
+class Redis extends Cache {
+
+	/**
+	 * @var \Redis $cache
+	 */
+	private static $cache = null;
+
+	public function __construct($prefix = '') {
+		parent::__construct($prefix);
+		if (is_null(self::$cache)) {
+			// TODO allow configuring a RedisArray, see https://github.com/nicolasff/phpredis/blob/master/arrays.markdown#redis-arrays
+			self::$cache = new \Redis();
+			$config = \OC::$server->getSystemConfig()->getValue('redis', array());
+			if (isset($config['host'])) {
+				$host = $config['host'];
+			} else {
+				$host = '127.0.0.1';
+			}
+			if (isset($config['port'])) {
+				$port = $config['port'];
+			} else {
+				$port = 6379;
+			}
+			if (isset($config['timeout'])) {
+				$timeout = $config['timeout'];
+			} else {
+				$timeout = 0.0; // unlimited
+			}
+			self::$cache->connect( $host, $port, $timeout );
+		}
+	}
+
+	/**
+	 * entries in redis get namespaced to prevent collisions between ownCloud instances and users
+	 */
+	protected function getNameSpace() {
+		return $this->prefix;
+	}
+
+	public function get($key) {
+		$result = self::$cache->get($this->getNamespace() . $key);
+		if ($result === false and ! self::$cache->exists($this->getNamespace() . $key)) {
+			return null;
+		} else {
+			return $result;
+		}
+	}
+
+	public function set($key, $value, $ttl = 0) {
+		if ($ttl > 0) {
+			return self::$cache->setex($this->getNamespace() . $key, $ttl, $value);
+		} else {
+			return self::$cache->set($this->getNamespace() . $key, $value);
+		}
+	}
+
+	public function hasKey($key) {
+		return self::$cache->exists($this->getNamespace() . $key);
+	}
+
+	public function remove($key) {
+		if (self::$cache->delete($this->getNamespace() . $key)) {
+			return true;
+		} else {
+			return false;
+		}
+
+	}
+
+	public function clear($prefix = '') {
+		$prefix = $this->getNamespace() . $prefix.'*';
+		$it = null;
+		self::$cache->setOption(\Redis::OPT_SCAN, \Redis::SCAN_RETRY);
+		while($keys = self::$cache->scan($it, $prefix)) {
+			self::$cache->delete($keys);
+		}
+		return true;
+	}
+
+	static public function isAvailable() {
+		return extension_loaded('redis');
+	}
+}
+
diff --git a/tests/lib/memcache/redis.php b/tests/lib/memcache/redis.php
new file mode 100644
index 00000000000..c0bd18b46f9
--- /dev/null
+++ b/tests/lib/memcache/redis.php
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+namespace Test\Memcache;
+
+class Redis extends Cache {
+	static public function setUpBeforeClass() {
+		parent::setUpBeforeClass();
+
+		if (!\OC\Memcache\Redis::isAvailable()) {
+			self::markTestSkipped('The redis extension is not available.');
+		}
+		$instance = new \OC\Memcache\Redis(self::getUniqueID());
+		if ($instance->set(self::getUniqueID(), self::getUniqueID()) === false) {
+			self::markTestSkipped('redis server seems to be down.');
+		}
+	}
+
+	protected function setUp() {
+		parent::setUp();
+		$this->instance = new \OC\Memcache\Redis($this->getUniqueID());
+	}
+}
-- 
GitLab