<?php
namespace Lia\Compo;
/**
*
* @todo add way to get cache file path even if there is not a file or the file is out of date
*/
class Cache extends \Lia\Compo {
public function onReady(){
$lia = $this->lia;
$lia->addApi('lia:cache.deprecated',[$this,'getCacheFile']);
$lia->addApiMethod('lia:cache.deprecated', 'getCacheFile');
$lia->addApi('lia:cache.get',[$this,'getCachedFilePath']);
$lia->addApiMethod('lia:cache.get', 'getCachedFilePath');
$lia->addApi('lia:cache.set',[$this,'cacheFile']);
$lia->addApiMethod('lia:cache.set', 'cacheFile');
}
public function onPackageReady(){
parent::onPackageReady();
if ($this->lia->hasApi('lia:config.default')){
$this->lia->api('lia:config.default', 'lia:cache.dir', $this->package->dir('cache'));
// delete stuff every 5 days
$this->lia->api('lia:config.default', 'lia:cache.clearAfterXMinutes', 60*24*5);
}
}
public function onEmitResponseSent(){
if ($this->isStaleCacheCheckRequired()){
$this->deleteStaleCacheFiles();
}
}
public function getCacheFile($key){
$path = $this->lia->getCachedFilePath($key);
return $path;
}
/**
*
* @param string $key
* @return boolean|string, false when cache file is not found or is stale, else absolute path to the cache file
*/
public function getCachedFilePath(string $key){
$dir = $this->lia->api('lia:config.get', 'lia:cache.dir');
$file = $dir.'/file-'.$key;
$metaFile = $dir.'/meta-'.$key;
if (file_exists($file)
&&file_exists($metaFile)){
$meta = json_decode(file_get_contents($metaFile),true);
if (time()>$meta['expiry']){
//@TODO Auto-delete stale cache files when requested
return false;
}
return $file;
} else {
return false;
}
}
/**
* Store the given $value in a file identified by $key
* Returns the path to the file
*/
public function cacheFile($key, $value, $maxAge=60*60*24*5){
$dir = $this->lia->api('lia:config.get', 'lia:cache.dir');
$file = $dir.'/file-'.$key;
$metaFile = $dir.'/meta-'.$key;
$parent = $dir;
$hasDir = true;
while (!is_dir($parent)){
$hasDir = false;
$parent = dirname($parent);
}
$perms = fileperms($parent);
if (!$hasDir)mkdir($dir, $perms, true);
file_put_contents($file,$value);
$expiry = time() + $maxAge;
$meta = [
'expiry'=>$expiry,
];
file_put_contents($metaFile,json_encode($meta));
return $file;
}
protected function isStaleCacheCheckRequired(){
// get cache-clearing frequency (lia config)
$frequencyInMinutes = $this->lia->api('lia:config.get', 'lia:cache.clearAfterXMinutes');
$seconds = $frequencyInMinutes*60;
// check when last cache-clear was performed
$path = $this->lia->getCachedFilePath('lia.Cache.LastCleanupCheck');
if ($path==null||!is_file($path)){
$lastClear = 0;
} else if (is_file($path)){
$content = file_get_contents($path);
$content = trim($content);
$lastClear = (int)$content;
} else {
throw new \Lia\Exception\Base("Could not load cache frequency file.");
}
// if enough time has elapsed, clear the cache
if ($lastClear + $seconds > time())return false;
return true;
}
protected function deleteStaleCacheFiles(){
$this->lia->cacheFile('lia.Cache.LastCleanupCheck', time(), 60*60*24*180);
$dir = $this->lia->api('lia:config.get', 'lia:cache.dir');
if (!is_dir($dir))throw new \Lia\Exception\Base("The cache directory '{$dir}' does not exist, thus cannot be checked for stale files.");
$dh = opendir($dir);
$openLen = strlen('{"expiry":');
$endLen = -1 * strlen('}');
$now = time();
while (($f = readdir($dh)) !== false){
if (substr($f,0,5)!='meta-')continue;
$content = trim(file_get_contents($dir.'/'.$f));
$expiresAt = substr($content, $openLen, $endLen);
if ($expiresAt>$now)continue;
$actualFile = $dir.'/file-'.substr($f, 5);
if (is_file($actualFile))unlink($actualFile);
//delete the meta file
unlink($dir.'/'.$f);
}
closedir($dh);
}
}