From 6ffc729cc7c17db3971da70287462420bc75eada Mon Sep 17 00:00:00 2001 From: Christopher Vagnetoft Date: Sun, 11 Oct 2020 03:09:54 +0200 Subject: [PATCH] Initial commit --- .gitignore | 2 + composer.json | 25 +++++++ src/LocalCache.php | 176 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 203 insertions(+) create mode 100644 .gitignore create mode 100644 composer.json create mode 100644 src/LocalCache.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ff72e2d --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/composer.lock +/vendor diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..1a0d752 --- /dev/null +++ b/composer.json @@ -0,0 +1,25 @@ +{ + "name": "noccylabs/local-cache", + "description": "A local system-wide always available cache", + "type": "library", + "license": "GPL-3.0-or-later", + "authors": [ + { + "name": "Christopher Vagnetoft", + "email": "cvagnetoft@gmail.com" + } + ], + "require": { + "psr/log": "^1.1", + "psr/cache": "^1.0", + "psr/simple-cache": "^1.0" + }, + "autoload": { + "psr-4": { + "NoccyLabs\\LocalCache\\": "src/" + } + }, + "require-dev": { + "phpunit/phpunit": "^9.4" + } +} diff --git a/src/LocalCache.php b/src/LocalCache.php new file mode 100644 index 0000000..94498b7 --- /dev/null +++ b/src/LocalCache.php @@ -0,0 +1,176 @@ +poolName = preg_replace("/[^a-zA-Z0-9_\\-]/", "_", $poolName); + $this->cacheDir = $this->findCachePath(); + } + + protected function findCachePath(): string + { + $candidates = [ + '/var/cache', + getenv("HOME").'/.cache', + ]; + while ($candidate = array_shift($candidates)) { + if (!is_writable($candidate)) { + continue; + } + $path = $candidate . DIRECTORY_SEPARATOR . 'php-localcache'; + /* + if (!is_dir($path)) { + @mkdir($path); + if (!is_dir($path)) { + continue; + } + } + */ + return $path; + } + throw new CacheException("Couldn't find a usable cache directory"); + } + + protected function hashKey(string $key) + { + $hash = hash("sha256", $key); + return $hash; + } + + protected function getKeyPath(string $key) + { + $hash = $this->hashKey($key); + // $path = substr($hash, 0, 2) . DIRECTORY_SEPARATOR . substr($hash, 2, 2) . DIRECTORY_SEPARATOR . $hash; + $full = $this->cacheDir . DIRECTORY_SEPARATOR . $this->poolName . DIRECTORY_SEPARATOR . $hash; // $path; + return $full; + } + + /** + * {@inheritDoc} + */ + public function get($key, $default = null) + { + if (!$this->has($key)) { + return $default; + } + $path = $this->getKeyPath($key); + return file_get_contents($path); + } + + /** + * {@inheritDoc} + */ + public function set($key, $value, $ttl = null) + { + $info = [ + 'key' => $key, + 'ttl' => $ttl ?? $this->defaultTtl + ]; + $info['expires'] = time() + $info['ttl']; + $path = $this->getKeyPath($key); + if (!is_dir(dirname($path))) { + mkdir(dirname($path), 0777, true); + } + file_put_contents($path, $value); + file_put_contents($path.".info", serialize($info)); + } + + /** + * {@inheritDoc} + */ + public function delete($key) + { + if (!$this->has($key)) { + return; + } + $path = $this->getKeyPath($key); + @unlink($path); + @unlink($path.".info"); + } + + /** + * {@inheritDoc} + */ + public function has($key) + { + $path = $this->getKeyPath($key); + if (!file_exists($path)) { + return false; + } + + $info = unserialize(file_get_contents($path.".info")); + if ($info['expires'] < time()) { + return false; + } + + return true; + } + + /** + * {@inheritDoc} + */ + public function clear() + { + $items = glob($this->cacheDir.DIRECTORY_SEPARATOR.$this->poolName.DIRECTORY_SEPARATOR."*.info"); + foreach ($items as $item) { + unlink(substr($item, 0, -5)); + unlink($item); + } + @rmdir($this->cacheDir . DIRECTORY_SEPARATOR . $this->poolName); + } + + /** + * {@inheritDoc} + */ + public function getMultiple($keys, $default = null) + { + if (!is_iterable($keys)) { + throw new InvalidArgumentException("Argument not iterable"); + } + $ret = []; + foreach ($keys as $key) { + $ret[$key] = $this->get($key, $default); + } + return $ret; + } + + /** + * {@inheritDoc} + */ + public function setMultiple($values, $ttl = null) + { + if (!is_iterable($values)) { + throw new InvalidArgumentException("Argument not iterable"); + } + foreach ($values as $key=>$value) { + $this->set($key, $value, $ttl); + } + } + + /** + * {@inheritDoc} + */ + public function deleteMultiple($keys) + { + if (!is_iterable($keys)) { + throw new InvalidArgumentException("Argument not iterable"); + } + foreach ($keys as $key) { + $this->delete($key); + } + } +} \ No newline at end of file