Cache.php

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