<?php
/*
 * Copyright (c) 2003-2022 Willem Dijkstra
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *    - Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *    - Redistributions in binary form must reproduce the above
 *      copyright notice, this list of conditions and the following
 *      disclaimer in the documentation and/or other materials provided
 *      with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 */

/*
 * This class provides a filesystem cache. Cache entries can be requested and
 * are dated at the time of the request.
 *
 * The current implementation gets the date once at class creation. All files
 * in the cache are evaluated on class creation and are deleted if eligable.
 *
 * $symon['cache_keepstale'] : set to disable file deletion.
 *
 * Typical use:
 *
 * $key = $cache->insert(content, <content_identifiers>+);
 * $filename = $cache->getfilename($key);
 * $cache->expire($key);
 *
 * Use obtain_file_cache(key) to get a direct filename to a cache entry with
 * key <key>.
 */
require_once(__DIR__ . "/setup.inc");
require_once(__DIR__ . "/tools.inc");

class Cache {
    public $db;
    public $timestamp;

    function __construct() {
        global $symon;

        if (!isset($symon["cache_duration"])) {
            config_error("cache_duration", "is not set");
            return 0;
        }
        if (!isset($symon["cache_dir"])) {
            config_error("cache_dir", "is not set");
            return 0;
        }
        if (!is_dir($symon["cache_dir"])) {
            config_error("cache_dir", "does not point to a directory");
            return 0;
        }

        return $this->_load();
    }

    function _load() {
        global $symon;

        $ts = time();
        $this->timestamp = $ts - ($ts % $symon["cache_duration"]);

        $dir = dir($symon["cache_dir"]);
        while ($file = $dir->read()) {
            if ($file == '.' || $file == '..') {
                continue;
            }
            $this->_cache($file);
        }
    }

    function _cache($filename) {
        global $symon;

        if (preg_match("/^(\\d+)_([0-9a-f]+)(\\.[a-z]+)?\$/i",
                       (string) $filename, $match)) {
            $this->db[$match[2]] = $filename;
            if (($this->timestamp > $match[1]) &&
                (!isset($symon['cache_keepstale']))) {
                $this->expire_key($match[2]);
            }
        } else {
            runtime_error("'".$filename."' is not a cache file");
            return 0;
        }
    }

    function _iscached($filename) {
        if (preg_match("/^(\\d+)_([0-9a-f]+)(\\.[a-z]+)?\$/i",
                       (string) $filename, $match)) {
            return isset($this->db[$match[2]]);
        } elseif (preg_match("/^([0-9a-f]+)(\.[a-z]+)?$/i",
                             (string) $filename, $match)) {
            return isset($this->db[$match[1]]);
        } else {
            return isset($this->db[$filename]);
	    }
    }

    function _get($filename) {
        if (preg_match("/^(\\d+)_([0-9a-f]+)(\\.[a-z]+)?\$/i",
                       (string) $filename, $match)) {
            if (isset($this->db[$match[2]])) {
                return $this->db[$match[2]];
            }
        } elseif (preg_match("/^([0-9a-f]+)(\.[a-z]+)?$/i",
                             (string) $filename, $match)) {
            if (isset($this->db[$match[1]])) {
                return $this->db[$match[1]];
            }
        } elseif (isset($this->db[$filename])) {
            return $this->db[$filename];
        }

        return \UNDEF;
    }

    function insert(&$content, $extension = '') { /* variadric */
        $args = '';
        $maxargs = func_num_args();
        $arg_list = func_get_args();
        for ($i = 0; $i < $maxargs; $i++) {
            if (is_array($arg_list[$i])) {
                $args .= implode($arg_list[$i]);
            } elseif (is_scalar($arg_list[$i])) {
                $args .= $arg_list[$i];
            } else {
                runtime_error('cache: wrong type of argument '.gettype($arg_list[$i]) .' in "insert"');
            }
        }

        $key = md5($args);

        if (!$this->_iscached($key)) {
            $this->_cache($this->timestamp . '_' . $key . $extension);
            $filename = $this->getfilename($key);
            if (strlen((string) $content) > 0) {
                save($filename, $content);
            }
        }

        return $key;
    }

    function obtain_filecache($key) {
        if (!$this->_iscached($key)) {
            $this->_cache($this->timestamp . '_' . $key);
        }

        return $this->getfilename($key);
    }

    function getfilename($key) {
        global $symon;

        if ($this->_iscached($key)) {
            $filename = $this->_get($key);
            return $symon["cache_dir"].'/'.$filename;
        } else {
            return '';
        }
    }

    function expire_key($key) {
        global $runtime_args;

        $filename = $this->getfilename($key);
        if (is_file($filename) && !unlink($filename)) {
            runtime_error("'".$filename."' cannot be deleted");
        }

        unset($this->db[$key]);
    }

    function _expire_all() {
        if (is_array($this->db)) {
            foreach(array_keys($this->db) as $key) {
                $this->expire_key($key);
            }
        }
    }

    function _display() {
        if (is_array($this->db)) {
            foreach($this->db as $key => $value) {
                print "\xa ".$key.'='.$value;
            }
        }
    }

    function _test() {
        $r1 = 'this is recipe 1';
        $r2 = 'this is recipe 2';

        print "\xa display current cache";
        $this->_display();

        print "\xa flushing cache";
        $this->_expire_all();

        print "\xa insert tests 1 2 3";
        $c1 = $this->insert($r1);
        $c2 = $this->insert($r1, '.txt');
        $c3 = $this->insert($r2);

        print "\xa display current cache\xa";
        $this->_display();
        print "\xa flushing cache";
        $this->_expire_all();
    }
}

if (!isset($cache)) {
    $cache = new Cache();
}
?>
