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(); } }); } }