diff --git a/src/Core/Cache.php b/src/Core/Cache.php index cc77d9bfd0..ea7807031f 100644 --- a/src/Core/Cache.php +++ b/src/Core/Cache.php @@ -23,13 +23,15 @@ class Cache extends \Friendica\BaseObject /** * @var Cache\ICacheDriver */ - private static $driver = null; + private static $driver = null; + public static $driver_class = null; + public static $driver_name = null; public static function init() { - $driver_name = Config::get('system', 'cache_driver', 'database'); - - self::$driver = CacheDriverFactory::create($driver_name); + self::$driver_name = Config::get('system', 'cache_driver', 'database'); + self::$driver = CacheDriverFactory::create(self::$driver_name); + self::$driver_class = get_class(self::$driver); } /** @@ -46,6 +48,29 @@ class Cache extends \Friendica\BaseObject return self::$driver; } + /** + * @brief Returns all the cache keys sorted alphabetically + * + * @return array|null Null if the driver doesn't support this feature + */ + public static function getAllKeys() + { + $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); + + self::getApp()->save_timestamp($time, 'cache'); + + return $return; + } + /** * @brief Fetch cached data according to the key * @@ -107,12 +132,12 @@ class Cache extends \Friendica\BaseObject /** * @brief Remove outdated data from the cache * - * @param integer $max_level The maximum cache level that is to be cleared + * @param boolean $outdated just remove outdated values * * @return void */ - public static function clear() + public static function clear($outdated = true) { - return self::getDriver()->clear(); + return self::getDriver()->clear($outdated); } } diff --git a/src/Core/Cache/ArrayCache.php b/src/Core/Cache/ArrayCache.php index d1302c1d6e..47c9c16680 100644 --- a/src/Core/Cache/ArrayCache.php +++ b/src/Core/Cache/ArrayCache.php @@ -19,6 +19,14 @@ class ArrayCache extends AbstractCacheDriver implements IMemoryCacheDriver /** @var array Array with the cached data */ protected $cachedData = array(); + /** + * (@inheritdoc) + */ + public function getAllKeys() + { + return array_keys($this->cachedData); + } + /** * (@inheritdoc) */ diff --git a/src/Core/Cache/DatabaseCacheDriver.php b/src/Core/Cache/DatabaseCacheDriver.php index ca4842a468..74dfe3991e 100644 --- a/src/Core/Cache/DatabaseCacheDriver.php +++ b/src/Core/Cache/DatabaseCacheDriver.php @@ -13,6 +13,19 @@ use Friendica\Util\DateTimeFormat; */ class DatabaseCacheDriver extends AbstractCacheDriver implements ICacheDriver { + /** + * (@inheritdoc) + */ + public function getAllKeys() + { + $stmt = DBA::select('cache', ['k'], ['`expires` >= ?', DateTimeFormat::utcNow()]); + + return DBA::toArray($stmt); + } + + /** + * (@inheritdoc) + */ public function get($key) { $cache = DBA::selectFirst('cache', ['v'], ['`k` = ? AND `expires` >= ?', $key, DateTimeFormat::utcNow()]); @@ -32,6 +45,9 @@ class DatabaseCacheDriver extends AbstractCacheDriver implements ICacheDriver return null; } + /** + * (@inheritdoc) + */ public function set($key, $value, $ttl = Cache::FIVE_MINUTES) { $fields = [ @@ -43,11 +59,17 @@ class DatabaseCacheDriver extends AbstractCacheDriver implements ICacheDriver return DBA::update('cache', $fields, ['k' => $key], true); } + /** + * (@inheritdoc) + */ public function delete($key) { return DBA::delete('cache', ['k' => $key]); } + /** + * (@inheritdoc) + */ public function clear($outdated = true) { if ($outdated) { diff --git a/src/Core/Cache/ICacheDriver.php b/src/Core/Cache/ICacheDriver.php index 9ddcf5ad1f..b77aa03c16 100644 --- a/src/Core/Cache/ICacheDriver.php +++ b/src/Core/Cache/ICacheDriver.php @@ -11,6 +11,13 @@ use Friendica\Core\Cache; */ interface ICacheDriver { + /** + * Lists all cache keys + * + * @return array|null Null if it isn't supported by the cache driver + */ + public function getAllKeys(); + /** * Fetches cached data according to the key * diff --git a/src/Core/Cache/MemcacheCacheDriver.php b/src/Core/Cache/MemcacheCacheDriver.php index 4ca5aa38db..207225b1a2 100644 --- a/src/Core/Cache/MemcacheCacheDriver.php +++ b/src/Core/Cache/MemcacheCacheDriver.php @@ -22,6 +22,11 @@ class MemcacheCacheDriver extends AbstractCacheDriver implements IMemoryCacheDri */ private $memcache; + /** + * @param string $memcache_host + * @param int $memcache_port + * @throws Exception + */ public function __construct($memcache_host, $memcache_port) { if (!class_exists('Memcache', false)) { @@ -35,6 +40,28 @@ class MemcacheCacheDriver extends AbstractCacheDriver implements IMemoryCacheDri } } + /** + * (@inheritdoc) + */ + public function getAllKeys() + { + $list = []; + $allSlabs = $this->memcache->getExtendedStats('slabs'); + foreach ($allSlabs as $slabs) { + foreach (array_keys($slabs) as $slabId) { + $cachedump = $this->memcache->getExtendedStats('cachedump', (int)$slabId); + foreach ($cachedump as $keys => $arrVal) { + if (!is_array($arrVal)) { + continue; + } + $list = array_merge($list, array_keys($arrVal)); + } + } + } + + return $list; + } + /** * (@inheritdoc) */ diff --git a/src/Core/Cache/MemcachedCacheDriver.php b/src/Core/Cache/MemcachedCacheDriver.php index 9e9c00f0b8..c1d08f3321 100644 --- a/src/Core/Cache/MemcachedCacheDriver.php +++ b/src/Core/Cache/MemcachedCacheDriver.php @@ -53,6 +53,17 @@ class MemcachedCacheDriver extends AbstractCacheDriver implements IMemoryCacheDr } } + /** + * (@inheritdoc) + */ + public function getAllKeys() + { + return $this->memcached->getAllKeys(); + } + + /** + * (@inheritdoc) + */ public function get($key) { $return = null; @@ -68,6 +79,9 @@ class MemcachedCacheDriver extends AbstractCacheDriver implements IMemoryCacheDr return $return; } + /** + * (@inheritdoc) + */ public function set($key, $value, $ttl = Cache::FIVE_MINUTES) { $cachekey = $this->getCacheKey($key); @@ -88,12 +102,18 @@ class MemcachedCacheDriver extends AbstractCacheDriver implements IMemoryCacheDr } + /** + * (@inheritdoc) + */ public function delete($key) { $cachekey = $this->getCacheKey($key); return $this->memcached->delete($cachekey); } + /** + * (@inheritdoc) + */ public function clear($outdated = true) { if ($outdated) { @@ -104,12 +124,7 @@ class MemcachedCacheDriver extends AbstractCacheDriver implements IMemoryCacheDr } /** - * @brief Sets a value if it's not already stored - * - * @param string $key The cache key - * @param mixed $value The old value we know from the cache - * @param int $ttl The cache lifespan, must be one of the Cache constants - * @return bool + * (@inheritdoc) */ public function add($key, $value, $ttl = Cache::FIVE_MINUTES) { diff --git a/src/Core/Cache/RedisCacheDriver.php b/src/Core/Cache/RedisCacheDriver.php index 2c2a3e5d7c..f9d00fde21 100644 --- a/src/Core/Cache/RedisCacheDriver.php +++ b/src/Core/Cache/RedisCacheDriver.php @@ -20,6 +20,11 @@ class RedisCacheDriver extends AbstractCacheDriver implements IMemoryCacheDriver */ private $redis; + /** + * @param string $redis_host + * @param int $redis_port + * @throws Exception + */ public function __construct($redis_host, $redis_port) { if (!class_exists('Redis', false)) { @@ -33,6 +38,17 @@ class RedisCacheDriver extends AbstractCacheDriver implements IMemoryCacheDriver } } + /** + * (@inheritdoc) + */ + public function getAllKeys() + { + return null; + } + + /** + * (@inheritdoc) + */ public function get($key) { $return = null; @@ -55,6 +71,9 @@ class RedisCacheDriver extends AbstractCacheDriver implements IMemoryCacheDriver return $return; } + /** + * (@inheritdoc) + */ public function set($key, $value, $ttl = Cache::FIVE_MINUTES) { $cachekey = $this->getCacheKey($key); @@ -75,12 +94,18 @@ class RedisCacheDriver extends AbstractCacheDriver implements IMemoryCacheDriver } } + /** + * (@inheritdoc) + */ public function delete($key) { $cachekey = $this->getCacheKey($key); return ($this->redis->delete($cachekey) > 0); } + /** + * (@inheritdoc) + */ public function clear($outdated = true) { if ($outdated) { @@ -127,6 +152,7 @@ class RedisCacheDriver extends AbstractCacheDriver implements IMemoryCacheDriver $this->redis->unwatch(); return false; } + /** * (@inheritdoc) */ diff --git a/src/Core/Console.php b/src/Core/Console.php index 29ccd795b8..9edc12b64c 100644 --- a/src/Core/Console.php +++ b/src/Core/Console.php @@ -14,6 +14,7 @@ class Console extends \Asika\SimpleConsole\Console protected $customHelpOptions = ['h', 'help', '?']; protected $subConsoles = [ + 'cache' => __NAMESPACE__ . '\Console\Cache', 'config' => __NAMESPACE__ . '\Console\Config', 'createdoxygen' => __NAMESPACE__ . '\Console\CreateDoxygen', 'docbloxerrorchecker' => __NAMESPACE__ . '\Console\DocBloxErrorChecker', @@ -37,6 +38,7 @@ class Console extends \Asika\SimpleConsole\Console Usage: bin/console [--version] [-h|--help|-?] [] [-v] Commands: + cache Manage node cache config Edit site config createdoxygen Generate Doxygen headers dbstructure Do database updates diff --git a/src/Core/Console/Cache.php b/src/Core/Console/Cache.php new file mode 100644 index 0000000000..d0bc427dee --- /dev/null +++ b/src/Core/Console/Cache.php @@ -0,0 +1,180 @@ + + */ +class Cache extends \Asika\SimpleConsole\Console +{ + protected $helpOptions = ['h', 'help', '?']; + + protected function getHelp() + { + $help = << [-h|--help|-?] [-v] + bin/console cache set [-h|--help|-?] [-v] + bin/console cache flush [-h|--help|-?] [-v] + bin/console cache clear [-h|--help|-?] [-v] + +Description + bin/console cache list [] + List all cache keys, optionally filtered by a prefix + + bin/console cache get + Shows the value of the provided cache key + + bin/console cache set [] + Sets the value of the provided cache key, optionally with the provided TTL (time to live) with a default of five minutes. + + bin/console cache flush + Clears expired cache keys + + bin/console cache clear + Clears all cache keys + +Options + -h|--help|-? Show help information + -v Show more debug information. +HELP; + return $help; + } + + protected function doExecute() + { + $a = \Friendica\BaseObject::getApp(); + + if ($this->getOption('v')) { + $this->out('Executable: ' . $this->executable); + $this->out('Class: ' . __CLASS__); + $this->out('Arguments: ' . var_export($this->args, true)); + $this->out('Options: ' . var_export($this->options, true)); + } + + if (!($a->mode & App::MODE_DBCONFIGAVAILABLE)) { + $this->out('Database isn\'t ready or populated yet, database cache won\'t be available'); + } + + Core\Cache::init(); + + if ($this->getOption('v')) { + $this->out('Cache Driver Name: ' . Core\Cache::$driver_name); + $this->out('Cache Driver Class: ' . Core\Cache::$driver_class); + } + + switch ($this->getArgument(0)) { + case 'list': + $this->executeList(); + break; + case 'get': + $this->executeGet(); + break; + case 'set': + $this->executeSet(); + break; + case 'flush': + $this->executeFlush(); + break; + case 'clear': + $this->executeClear(); + break; + } + + if (count($this->args) == 0) { + $this->out($this->getHelp()); + return 0; + } + + return 0; + } + + private function executeList() + { + $prefix = $this->getArgument(1); + $keys = Core\Cache::getAllKeys(); + + if (empty($prefix)) { + $this->out('Listing all cache keys:'); + } else { + $this->out('Listing all cache keys starting with "' . $prefix . '":'); + } + + $count = 0; + foreach ($keys as $key) { + if (empty($prefix) || strpos($key, $prefix) === 0) { + $this->out($key); + $count++; + } + } + + $this->out($count . ' keys found'); + } + + private function executeGet() + { + if (count($this->args) >= 2) { + $key = $this->getArgument(1); + $value = Core\Cache::get($key); + + $this->out("{$key} => " . var_export($value, true)); + } else { + throw new CommandArgsException('Too few arguments for get'); + } + } + + private function executeSet() + { + if (count($this->args) >= 3) { + $key = $this->getArgument(1); + $value = $this->getArgument(2); + $duration = intval($this->getArgument(3, Core\Cache::FIVE_MINUTES)); + + if (is_array(Core\Cache::get($key))) { + throw new RuntimeException("$key is an array and can't be set using this command."); + } + + $result = Core\Cache::set($key, $value, $duration); + if ($result) { + $this->out("{$key} <= " . Core\Cache::get($key)); + } else { + $this->out("Unable to set {$key}"); + } + } else { + throw new CommandArgsException('Too few arguments for set'); + } + } + + private function executeFlush() + { + $result = Core\Cache::clear(); + if ($result) { + $this->out('Cache successfully flushed'); + } else { + $this->out('Unable to flush the cache'); + } + } + + private function executeClear() + { + $result = Core\Cache::clear(false); + if ($result) { + $this->out('Cache successfully cleared'); + } else { + $this->out('Unable to flush the cache'); + } + } +} diff --git a/src/Core/Console/Config.php b/src/Core/Console/Config.php index da2d20fd13..db436b8394 100644 --- a/src/Core/Console/Config.php +++ b/src/Core/Console/Config.php @@ -1,11 +1,5 @@ getOption('v')) { $this->out('Executable: ' . $this->executable);