Initial Commit
This commit is contained in:
147
dependencies/amphp/cache/lib/FileCache.php
vendored
Normal file
147
dependencies/amphp/cache/lib/FileCache.php
vendored
Normal file
@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
/** @noinspection PhpUndefinedFunctionInspection */
|
||||
namespace WP_Ultimo\Dependencies\Amp\Cache;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\File;
|
||||
use WP_Ultimo\Dependencies\Amp\File\Driver;
|
||||
use WP_Ultimo\Dependencies\Amp\Loop;
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
use WP_Ultimo\Dependencies\Amp\Sync\KeyedMutex;
|
||||
use WP_Ultimo\Dependencies\Amp\Sync\Lock;
|
||||
use function WP_Ultimo\Dependencies\Amp\call;
|
||||
final class FileCache implements Cache
|
||||
{
|
||||
private static function getFilename(string $key) : string
|
||||
{
|
||||
return \hash('sha256', $key) . '.cache';
|
||||
}
|
||||
/** @var string */
|
||||
private $directory;
|
||||
/** @var KeyedMutex */
|
||||
private $mutex;
|
||||
/** @var string */
|
||||
private $gcWatcher;
|
||||
/** @var bool */
|
||||
private $ampFileVersion2;
|
||||
public function __construct(string $directory, KeyedMutex $mutex)
|
||||
{
|
||||
$this->directory = $directory = \rtrim($directory, "/\\");
|
||||
$this->mutex = $mutex;
|
||||
if (!\interface_exists(Driver::class)) {
|
||||
throw new \Error(__CLASS__ . ' requires amphp/file to be installed');
|
||||
}
|
||||
$this->ampFileVersion2 = $ampFileVersion2 = \function_exists('WP_Ultimo\\Dependencies\\Amp\\File\\listFiles');
|
||||
$gcWatcher = static function () use($directory, $mutex, $ampFileVersion2) : \Generator {
|
||||
try {
|
||||
/** @psalm-suppress UndefinedFunction */
|
||||
$files = (yield $ampFileVersion2 ? File\listFiles($directory) : File\scandir($directory));
|
||||
foreach ($files as $file) {
|
||||
if (\strlen($file) !== 70 || \substr($file, -\strlen('.cache')) !== '.cache') {
|
||||
continue;
|
||||
}
|
||||
/** @var Lock $lock */
|
||||
$lock = (yield $mutex->acquire($file));
|
||||
try {
|
||||
/** @var File\File $handle */
|
||||
/** @psalm-suppress UndefinedFunction */
|
||||
$handle = (yield $ampFileVersion2 ? File\openFile($directory . '/' . $file, 'r') : File\open($directory . '/' . $file, 'r'));
|
||||
$ttl = (yield $handle->read(4));
|
||||
if ($ttl === null || \strlen($ttl) !== 4) {
|
||||
(yield $handle->close());
|
||||
continue;
|
||||
}
|
||||
$ttl = \unpack('Nttl', $ttl)['ttl'];
|
||||
if ($ttl < \time()) {
|
||||
/** @psalm-suppress UndefinedFunction */
|
||||
(yield $ampFileVersion2 ? File\deleteFile($directory . '/' . $file) : File\unlink($directory . '/' . $file));
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// ignore
|
||||
} finally {
|
||||
$lock->release();
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// ignore
|
||||
}
|
||||
};
|
||||
// trigger once, so short running scripts also GC and don't grow forever
|
||||
Loop::defer($gcWatcher);
|
||||
$this->gcWatcher = Loop::repeat(300000, $gcWatcher);
|
||||
}
|
||||
public function __destruct()
|
||||
{
|
||||
if ($this->gcWatcher !== null) {
|
||||
Loop::cancel($this->gcWatcher);
|
||||
}
|
||||
}
|
||||
/** @inheritdoc */
|
||||
public function get(string $key) : Promise
|
||||
{
|
||||
return call(function () use($key) {
|
||||
$filename = $this->getFilename($key);
|
||||
/** @var Lock $lock */
|
||||
$lock = (yield $this->mutex->acquire($filename));
|
||||
try {
|
||||
/** @psalm-suppress UndefinedFunction */
|
||||
$cacheContent = (yield $this->ampFileVersion2 ? File\read($this->directory . '/' . $filename) : File\get($this->directory . '/' . $filename));
|
||||
if (\strlen($cacheContent) < 4) {
|
||||
return null;
|
||||
}
|
||||
$ttl = \unpack('Nttl', \substr($cacheContent, 0, 4))['ttl'];
|
||||
if ($ttl < \time()) {
|
||||
/** @psalm-suppress UndefinedFunction */
|
||||
(yield $this->ampFileVersion2 ? File\deleteFile($this->directory . '/' . $filename) : File\unlink($this->directory . '/' . $filename));
|
||||
return null;
|
||||
}
|
||||
$value = \substr($cacheContent, 4);
|
||||
\assert(\is_string($value));
|
||||
return $value;
|
||||
} catch (\Throwable $e) {
|
||||
return null;
|
||||
} finally {
|
||||
$lock->release();
|
||||
}
|
||||
});
|
||||
}
|
||||
/** @inheritdoc */
|
||||
public function set(string $key, string $value, int $ttl = null) : Promise
|
||||
{
|
||||
if ($ttl < 0) {
|
||||
throw new \Error("Invalid cache TTL ({$ttl}); integer >= 0 or null required");
|
||||
}
|
||||
return call(function () use($key, $value, $ttl) {
|
||||
$filename = $this->getFilename($key);
|
||||
/** @var Lock $lock */
|
||||
$lock = (yield $this->mutex->acquire($filename));
|
||||
if ($ttl === null) {
|
||||
$ttl = \PHP_INT_MAX;
|
||||
} else {
|
||||
$ttl = \time() + $ttl;
|
||||
}
|
||||
$encodedTtl = \pack('N', $ttl);
|
||||
try {
|
||||
/** @psalm-suppress UndefinedFunction */
|
||||
(yield $this->ampFileVersion2 ? File\write($this->directory . '/' . $filename, $encodedTtl . $value) : File\put($this->directory . '/' . $filename, $encodedTtl . $value));
|
||||
} finally {
|
||||
$lock->release();
|
||||
}
|
||||
});
|
||||
}
|
||||
/** @inheritdoc */
|
||||
public function delete(string $key) : Promise
|
||||
{
|
||||
return call(function () use($key) {
|
||||
$filename = $this->getFilename($key);
|
||||
/** @var Lock $lock */
|
||||
$lock = (yield $this->mutex->acquire($filename));
|
||||
try {
|
||||
/** @psalm-suppress UndefinedFunction */
|
||||
return (yield $this->ampFileVersion2 ? File\deleteFile($this->directory . '/' . $filename) : File\unlink($this->directory . '/' . $filename));
|
||||
} finally {
|
||||
$lock->release();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user