From 3f0f3b6ae60ac514b8ac890db996e40e65795b0b Mon Sep 17 00:00:00 2001
From: Philipp Holzer <admin@philipp.info>
Date: Sun, 7 Oct 2018 00:27:54 +0200
Subject: [PATCH] friendica-5847 Console Cache List command doesn't work

- Added $prefix to all CacheDriver
- Moved hostname magic to CacheDriver
- Added test for getAllKeys()
---
 src/Core/Cache.php                            | 13 ++---
 src/Core/Cache/AbstractCacheDriver.php        | 49 ++++++++++++++++++-
 src/Core/Cache/ArrayCache.php                 |  4 +-
 src/Core/Cache/DatabaseCacheDriver.php        | 18 +++++--
 src/Core/Cache/ICacheDriver.php               |  4 +-
 src/Core/Cache/MemcacheCacheDriver.php        |  6 ++-
 src/Core/Cache/MemcachedCacheDriver.php       | 14 +++++-
 src/Core/Cache/RedisCacheDriver.php           | 12 ++++-
 src/Core/Console/Cache.php                    |  2 +-
 tests/src/Core/Cache/ArrayCacheDriverTest.php |  5 --
 tests/src/Core/Cache/CacheTest.php            | 32 ++++++++++++
 .../Core/Cache/DatabaseCacheDriverTest.php    |  5 --
 .../Core/Cache/MemcacheCacheDriverTest.php    |  5 --
 .../Core/Cache/MemcachedCacheDriverTest.php   |  5 --
 tests/src/Core/Cache/RedisCacheDriverTest.php |  5 --
 15 files changed, 131 insertions(+), 48 deletions(-)

diff --git a/src/Core/Cache.php b/src/Core/Cache.php
index ea7807031f..7b9f6edd2c 100644
--- a/src/Core/Cache.php
+++ b/src/Core/Cache.php
@@ -51,20 +51,15 @@ class Cache extends \Friendica\BaseObject
 	/**
 	 * @brief Returns all the cache keys sorted alphabetically
 	 *
+	 * @param string $prefix Prefix of the keys (optional)
+	 *
 	 * @return array|null Null if the driver doesn't support this feature
 	 */
-	public static function getAllKeys()
+	public static function getAllKeys($prefix = null)
 	{
 		$time = microtime(true);
 
-		$return = self::getDriver()->getAllKeys();
-
-		// Keys are prefixed with the node hostname, let's remove it
-		array_walk($return, function (&$value) {
-			$value = preg_replace('/^' . self::getApp()->get_hostname() . ':/', '', $value);
-		});
-
-		sort($return);
+		$return = self::getDriver()->getAllKeys($prefix);
 
 		self::getApp()->save_timestamp($time, 'cache');
 
diff --git a/src/Core/Cache/AbstractCacheDriver.php b/src/Core/Cache/AbstractCacheDriver.php
index 15b822dc3b..e694b9c314 100644
--- a/src/Core/Cache/AbstractCacheDriver.php
+++ b/src/Core/Cache/AbstractCacheDriver.php
@@ -17,8 +17,55 @@ abstract class AbstractCacheDriver extends BaseObject
 	 * @param string $key	The original key
 	 * @return string		The cache key used for the cache
 	 */
-	protected function getCacheKey($key) {
+	protected function getCacheKey($key)
+	{
 		// We fetch with the hostname as key to avoid problems with other applications
 		return self::getApp()->get_hostname() . ":" . $key;
 	}
+
+	/**
+	 * @param array $keys   A list of cached keys
+	 * @return array        A list of original keys
+	 */
+	protected function getOriginalKeys($keys)
+	{
+		if (empty($keys)) {
+			return [];
+		} else {
+			// Keys are prefixed with the node hostname, let's remove it
+			array_walk($keys, function (&$value) {
+				$value = preg_replace('/^' . self::getApp()->get_hostname() . ':/', '', $value);
+			});
+
+			sort($keys);
+
+			return $keys;
+		}
+	}
+
+	/**
+	 * Filters a list for a given prefix
+	 *
+	 * @param array $list the list
+	 * @param string|null $prefix the prefix
+	 *
+	 * @return array the filtered list
+	 */
+	protected function filterPrefix($list, $prefix = null)
+	{
+		if (empty($prefix)) {
+			return array_keys($list);
+		} else {
+			$result = [];
+
+			foreach (array_keys($list) as $key) {
+				if (strpos($key, $prefix) === 0) {
+					array_push($result, $key);
+				}
+			}
+
+			return $result;
+		}
+
+	}
 }
diff --git a/src/Core/Cache/ArrayCache.php b/src/Core/Cache/ArrayCache.php
index 47c9c16680..0d314fadb7 100644
--- a/src/Core/Cache/ArrayCache.php
+++ b/src/Core/Cache/ArrayCache.php
@@ -22,9 +22,9 @@ class ArrayCache extends AbstractCacheDriver implements IMemoryCacheDriver
 	/**
 	 * (@inheritdoc)
 	 */
-	public function getAllKeys()
+	public function getAllKeys($prefix = null)
 	{
-		return array_keys($this->cachedData);
+		return $this->filterPrefix($this->cachedData, $prefix);
 	}
 
 	/**
diff --git a/src/Core/Cache/DatabaseCacheDriver.php b/src/Core/Cache/DatabaseCacheDriver.php
index 74dfe3991e..9966457467 100644
--- a/src/Core/Cache/DatabaseCacheDriver.php
+++ b/src/Core/Cache/DatabaseCacheDriver.php
@@ -16,11 +16,23 @@ class DatabaseCacheDriver extends AbstractCacheDriver implements ICacheDriver
 	/**
 	 * (@inheritdoc)
 	 */
-	public function getAllKeys()
+	public function getAllKeys($prefix = null)
 	{
-		$stmt = DBA::select('cache', ['k'], ['`expires` >= ?', DateTimeFormat::utcNow()]);
+		if (empty($prefix)) {
+			$where = ['`expires` >= ?', DateTimeFormat::utcNow()];
+		} else {
+			$where = ['`expires` >= ? AND k LIKE CONCAT(?, \'%\')', DateTimeFormat::utcNow(), $prefix];
+		}
+
+		$stmt = DBA::select('cache', ['k'], $where);
+
+		$list = [];
+		while ($key = DBA::fetch($stmt)) {
+			array_push($list, $key['k']);
+		}
+		DBA::close($stmt);
 
-		return DBA::toArray($stmt);
+		return $list;
 	}
 
 	/**
diff --git a/src/Core/Cache/ICacheDriver.php b/src/Core/Cache/ICacheDriver.php
index b77aa03c16..0a206559cc 100644
--- a/src/Core/Cache/ICacheDriver.php
+++ b/src/Core/Cache/ICacheDriver.php
@@ -14,9 +14,11 @@ interface ICacheDriver
 	/**
 	 * Lists all cache keys
 	 *
+	 * @param string prefix optional a prefix to search
+	 *
 	 * @return array|null Null if it isn't supported by the cache driver
 	 */
-	public function getAllKeys();
+	public function getAllKeys($prefix = null);
 
 	/**
 	 * Fetches cached data according to the key
diff --git a/src/Core/Cache/MemcacheCacheDriver.php b/src/Core/Cache/MemcacheCacheDriver.php
index 207225b1a2..f31e21d14e 100644
--- a/src/Core/Cache/MemcacheCacheDriver.php
+++ b/src/Core/Cache/MemcacheCacheDriver.php
@@ -43,7 +43,7 @@ class MemcacheCacheDriver extends AbstractCacheDriver implements IMemoryCacheDri
 	/**
 	 * (@inheritdoc)
 	 */
-	public function getAllKeys()
+	public function getAllKeys($prefix = null)
 	{
 		$list = [];
 		$allSlabs = $this->memcache->getExtendedStats('slabs');
@@ -59,7 +59,9 @@ class MemcacheCacheDriver extends AbstractCacheDriver implements IMemoryCacheDri
 			}
 		}
 
-		return $list;
+		$list = $this->getOriginalKeys($list);
+
+		return $this->filterPrefix($list, $prefix);
 	}
 
 	/**
diff --git a/src/Core/Cache/MemcachedCacheDriver.php b/src/Core/Cache/MemcachedCacheDriver.php
index c1d08f3321..82df98f13b 100644
--- a/src/Core/Cache/MemcachedCacheDriver.php
+++ b/src/Core/Cache/MemcachedCacheDriver.php
@@ -5,6 +5,7 @@ namespace Friendica\Core\Cache;
 use Friendica\Core\Cache;
 
 use Exception;
+use Friendica\Network\HTTPException\InternalServerErrorException;
 use Memcached;
 
 /**
@@ -40,6 +41,9 @@ class MemcachedCacheDriver extends AbstractCacheDriver implements IMemoryCacheDr
 
 		$this->memcached = new Memcached();
 
+
+		$this->memcached->setOption(Memcached::OPT_BINARY_PROTOCOL, false);
+
 		array_walk($memcached_hosts, function (&$value) {
 			if (is_string($value)) {
 				$value = array_map('trim', explode(',', $value));
@@ -56,9 +60,15 @@ class MemcachedCacheDriver extends AbstractCacheDriver implements IMemoryCacheDr
 	/**
 	 * (@inheritdoc)
 	 */
-	public function getAllKeys()
+	public function getAllKeys($prefix = null)
 	{
-		return $this->memcached->getAllKeys();
+		// Doesn't work because of https://github.com/php-memcached-dev/php-memcached/issues/367
+		// returns everytime an empty array
+		throw new InternalServerErrorException('getAllKeys for Memcached not supported yet');
+
+		$list = $this->getOriginalKeys($this->memcached->getAllKeys());
+
+		return $this->filterPrefix($list, $prefix);
 	}
 
 	/**
diff --git a/src/Core/Cache/RedisCacheDriver.php b/src/Core/Cache/RedisCacheDriver.php
index f9d00fde21..fcbfab548a 100644
--- a/src/Core/Cache/RedisCacheDriver.php
+++ b/src/Core/Cache/RedisCacheDriver.php
@@ -41,9 +41,17 @@ class RedisCacheDriver extends AbstractCacheDriver implements IMemoryCacheDriver
 	/**
 	 * (@inheritdoc)
 	 */
-	public function getAllKeys()
+	public function getAllKeys($prefix = null)
 	{
-		return null;
+		if (empty($prefix)) {
+			$search = '*';
+		} else {
+			$search = $prefix . '*';
+		}
+
+		$list = $this->redis->keys($this->getCacheKey($search));
+
+		return $this->getOriginalKeys($list);
 	}
 
 	/**
diff --git a/src/Core/Console/Cache.php b/src/Core/Console/Cache.php
index 2d3508894a..0dfef43f35 100644
--- a/src/Core/Console/Cache.php
+++ b/src/Core/Console/Cache.php
@@ -105,7 +105,7 @@ HELP;
 	private function executeList()
 	{
 		$prefix = $this->getArgument(1);
-		$keys = Core\Cache::getAllKeys();
+		$keys = Core\Cache::getAllKeys($prefix);
 
 		if (empty($prefix)) {
 			$this->out('Listing all cache keys:');
diff --git a/tests/src/Core/Cache/ArrayCacheDriverTest.php b/tests/src/Core/Cache/ArrayCacheDriverTest.php
index 0cad6e9c7f..6863d149ff 100644
--- a/tests/src/Core/Cache/ArrayCacheDriverTest.php
+++ b/tests/src/Core/Cache/ArrayCacheDriverTest.php
@@ -7,11 +7,6 @@ use Friendica\Core\Cache\ArrayCache;
 
 class ArrayCacheDriverTest extends MemoryCacheTest
 {
-	/**
-	 * @var \Friendica\Core\Cache\IMemoryCacheDriver
-	 */
-	private $cache;
-
 	protected function getInstance()
 	{
 		$this->cache = new ArrayCache();
diff --git a/tests/src/Core/Cache/CacheTest.php b/tests/src/Core/Cache/CacheTest.php
index 5c56c2072f..86bf5e7f01 100644
--- a/tests/src/Core/Cache/CacheTest.php
+++ b/tests/src/Core/Cache/CacheTest.php
@@ -2,6 +2,7 @@
 
 namespace Friendica\Test\src\Core\Cache;
 
+use Friendica\Core\Cache\MemcachedCacheDriver;
 use Friendica\Core\Config;
 use Friendica\Test\DatabaseTest;
 use Friendica\Util\DateTimeFormat;
@@ -13,6 +14,12 @@ abstract class CacheTest extends DatabaseTest
 	 */
 	protected $instance;
 
+	/**
+	 * @var \Friendica\Core\Cache\IMemoryCacheDriver
+	 */
+	protected $cache;
+
+
 	abstract protected function getInstance();
 
 	protected function setUp()
@@ -29,6 +36,8 @@ abstract class CacheTest extends DatabaseTest
 		Config::set('system', 'throttle_limit_week', 100);
 		Config::set('system', 'throttle_limit_month', 100);
 		Config::set('system', 'theme', 'system_theme');
+
+		$this->instance->clear(false);
 	}
 
 	/**
@@ -177,4 +186,27 @@ abstract class CacheTest extends DatabaseTest
 		$received = $this->instance->get('objVal');
 		$this->assertEquals($value, $received, 'Value type changed from ' . gettype($value) . ' to ' . gettype($received));
 	}
+
+	/**
+	 * @small
+	 */
+	public function testGetAllKeys() {
+		if ($this->cache instanceof MemcachedCacheDriver) {
+			$this->markTestSkipped('Memcached doesn\'t support getAllKeys anymore');
+		}
+
+		$this->assertTrue($this->instance->set('value1', 'test'));
+		$this->assertTrue($this->instance->set('value2', 'test'));
+		$this->assertTrue($this->instance->set('test_value3', 'test'));
+
+		$list = $this->instance->getAllKeys();
+
+		$this->assertContains('value1', $list);
+		$this->assertContains('value2', $list);
+		$this->assertContains('test_value3', $list);
+
+		$list = $this->instance->getAllKeys('test');
+
+		$this->assertContains('test_value3', $list);
+	}
 }
diff --git a/tests/src/Core/Cache/DatabaseCacheDriverTest.php b/tests/src/Core/Cache/DatabaseCacheDriverTest.php
index 5df00fc913..3cc4a72ed1 100644
--- a/tests/src/Core/Cache/DatabaseCacheDriverTest.php
+++ b/tests/src/Core/Cache/DatabaseCacheDriverTest.php
@@ -6,11 +6,6 @@ use Friendica\Core\Cache\CacheDriverFactory;
 
 class DatabaseCacheDriverTest extends CacheTest
 {
-	/**
-	 * @var \Friendica\Core\Cache\IMemoryCacheDriver
-	 */
-	private $cache;
-
 	protected function getInstance()
 	{
 		$this->cache = CacheDriverFactory::create('database');
diff --git a/tests/src/Core/Cache/MemcacheCacheDriverTest.php b/tests/src/Core/Cache/MemcacheCacheDriverTest.php
index 62f7440033..db85723af1 100644
--- a/tests/src/Core/Cache/MemcacheCacheDriverTest.php
+++ b/tests/src/Core/Cache/MemcacheCacheDriverTest.php
@@ -11,11 +11,6 @@ use Friendica\Core\Cache\CacheDriverFactory;
  */
 class MemcacheCacheDriverTest extends MemoryCacheTest
 {
-	/**
-	 * @var \Friendica\Core\Cache\IMemoryCacheDriver
-	 */
-	private $cache;
-
 	protected function getInstance()
 	{
 		$this->cache = CacheDriverFactory::create('memcache');
diff --git a/tests/src/Core/Cache/MemcachedCacheDriverTest.php b/tests/src/Core/Cache/MemcachedCacheDriverTest.php
index 5a07814a38..fba5c4a958 100644
--- a/tests/src/Core/Cache/MemcachedCacheDriverTest.php
+++ b/tests/src/Core/Cache/MemcachedCacheDriverTest.php
@@ -11,11 +11,6 @@ use Friendica\Core\Cache\CacheDriverFactory;
  */
 class MemcachedCacheDriverTest extends MemoryCacheTest
 {
-	/**
-	 * @var \Friendica\Core\Cache\IMemoryCacheDriver
-	 */
-	private $cache;
-
 	protected function getInstance()
 	{
 		$this->cache = CacheDriverFactory::create('memcached');
diff --git a/tests/src/Core/Cache/RedisCacheDriverTest.php b/tests/src/Core/Cache/RedisCacheDriverTest.php
index 460c1184ba..0ee73945b9 100644
--- a/tests/src/Core/Cache/RedisCacheDriverTest.php
+++ b/tests/src/Core/Cache/RedisCacheDriverTest.php
@@ -11,11 +11,6 @@ use Friendica\Core\Cache\CacheDriverFactory;
  */
 class RedisCacheDriverTest extends MemoryCacheTest
 {
-	/**
-	 * @var \Friendica\Core\Cache\IMemoryCacheDriver
-	 */
-	private $cache;
-
 	protected function getInstance()
 	{
 		$this->cache = CacheDriverFactory::create('redis');
-- 
2.39.5