<?php
/**
* Web Application Component Toolkit
*
* @link http://www.phpwact.org/
*
* @author Wact Development Team
* @link http://www.phpwact.org/team
*
* @copyright Copyright 2006, Jeff Moore
* @license http://opensource.org/licenses/mit-license.php MIT
*/

/**
* A file based cache.
*/
class Wact_Cache_Store_File implements Wact_Cache_Store {

    /**
    * The directory used to hold the cached information.
    * @var string
    */
    protected $location;

    /**
    * object constructor
    * @param string location Directory used to store the cached data.
    */
    public function __construct($location = '/tmp/wact') {
        $this->location = $location;
    }

    /**
    * Transform a cache key into a filename
    * @param string id cache id
    * @return string file name
    */
    public function getFilenameFromKey($key) {

        // We must be careful not to transform different keys to identical
        // Filenames
        
        // We also want to transform illegal or annoying characters
        
        // What do we do about illegal file name lengths?  Exception?
        // (Unix = 256 char, OS X = 151)
        
        // Windows characters are irrelevant here because this class
        // requires unix file system semantics that windows does not support.
        
        $illegal = array(
            '%' => '%%', // distinguish real % chars from our escape codes
            chr(0) => '%0', // illegal for unix
            ':' => '%1', // illegal on Mac OS X 
        );
        
        $filename = $this->location . DIRECTORY_SEPARATOR . strtr( $key, $illegal );
        
        return $filename;
    }
    
    /**
    * Returns the directory where the cached data is stored.
    * @return string Directory
    */
    public function getLocation() {
        return $this->location;
    }

    /**
    * Remove all files and subdirectories in the specified directory.
    * The top level directory is not itself deleted.
    * @param string dirname directory
    */
    protected function removeDirectoryContents($dirname) {

        if (is_dir($dirname)) {
            if ($dh = opendir($dirname)) {
                while ($file = readdir($dh)) {
                    if ($file=='.' || $file=='..') {
                        continue;
                    }
                    
                    $item = $dirname . '/' . $file;
                    if (is_dir($item)) {
                        $this->removeDirectoryContents($item);
                        rmdir($item);
                    }
                    if (is_file($item)) {
                        unlink($item);
                    }
                }
                closedir($dh);
            }
        }
    }

    /**
    * @see Wact_Cache_Store.store
    */
    public function store($key, $data, $lifetime = NULL) {

        if ($lifetime !== NULL && $lifetime !== 0) {
            throw new Wact_Cache_Exception(
                'Wact_Cache_Store_File does not support expiring');
        }
        
        settype($key, 'string');
        
        $filename = $this->getFilenameFromKey($key);
        $dir = dirname($filename);

        // This technique provides atomic read and write operations under unix
        // without locking

        if ((!is_dir($dir) && !@mkdir($dir, 0777, TRUE)) ||
            !($tmpfile = @tempnam($dir, '.tmp')) ||
            !@file_put_contents($tmpfile, $data) ||
            !@rename($tmpfile, $filename)) {
                return FALSE;
        }
        return TRUE;
    }

    /**
    * @see Wact_Cache_Store.storeValue
    */
    public function storeValue($key, $data, $lifetime = NULL) {
        return $this->store($key, serialize($data), $lifetime);
    }
    
    /**
    * @see Wact_Cache_Store.fetch
    */
    public function fetch($key, $default = NULL) {
        settype($key, 'string');
        $filename = $this->getFilenameFromKey($key);
        $result = @file_get_contents($filename);
        if ($result === FALSE) {
            return $default;
        }
        return $result;
    }

    /**
    * @see Wact_Cache_Store.fetchValue
    */
    public function fetchValue($key, $default = NULL) {
        $result = $this->fetch($key, FALSE);
        if ($result === FALSE) {
            return $default;
        }
        return unserialize($result);
    }
    
    /**
    * @see Wact_Cache_Store.includeCode
    */
    public function includeCode($key) {
        settype($key, 'string');
        $filename = $this->getFilenameFromKey($key);
        $errorlevel = error_reporting();
        error_reporting($errorlevel & ~E_WARNING);
        $result = include_once($filename);
        error_reporting($errorlevel);
        return $result;
    }
    
    /**
    * @see Wact_Cache_Store.remove
    */
    public function remove($key) {
        settype($key, 'string');

        $filename = $this->getFilenameFromKey($key);
        if (file_exists($filename) && !@unlink($filename)) {
            return FALSE;
        }
        return TRUE;
    }
    
    /**
    * @see Wact_Cache_Store.clear
    */
    public function clear() {
        $this->removeDirectoryContents($this->location);
        return TRUE;
    }
    
}

?>