<?php
/**
 * -----------------------------------------------------------------------------
 *
 * SyL - Web Application Framework for PHP
 *
 * PHP version 4 (>= 4.3.x) or 5
 *
 * Copyright (C) 2006 k.watanabe
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * -----------------------------------------------------------------------------
 * @package   SyL
 * @author    k.watanabe <k.watanabe@syl.jp>
 * @copyright 2006 k.watanabe
 * @license   http://www.opensource.org/licenses/lgpl-license.php
 * @version   CVS: $Id: SyL_Cache.php,v 1.7 2007/01/03 11:17:37 seasonstream Exp $
 * @link      http://www.syl.jp/
 * -----------------------------------------------------------------------------
 */

/**
 * キャッシュクラス
 *
 * @package   SyL
 * @author    k.watanabe <k.watanabe@syl.jp>
 * @copyright 2006 k.watanabe
 * @license   http://www.opensource.org/licenses/lgpl-license.php
 * @version   CVS: $Id: SyL_Cache.php,v 1.7 2007/01/03 11:17:37 seasonstream Exp $
 * @link      http://www.syl.jp/
 */
class SyL_Cache
{
    /**
     * キャッシュのキー
     *
     * @access private
     * @var string
     */
    var $key = '';
    /**
     * キャッシュのキーのMD5
     *
     * @access private
     * @var string
     */
    var $key_md5 = '';
    /**
     * キャッシュディレクトリ
     *
     * @access private
     * @var string
     */
    var $cache_dir = '/tmp/';
    /**
     * キャッシュファイルの接頭辞
     *
     * @access private
     * @var string
     */
    var $prefix = 'syl_cache.';
    /**
     * キャッシュファイルの有効期間
     * 単位は秒[s]
     *
     * @access private
     * @var int
     */
    var $life_time = 3600; // 1時間

    /**
     * コンストラクタ
     *
     * @access public
     */
    function SyL_Cache($key)
    {
        clearstatcache();
        $this->key     = $key;
        $this->key_md5 = md5($key);
    }

    /**
     * キャッシュオブジェクトを取得する
     *
     * @static
     * @access public
     * @param string キャッシュのキー
     * @param string キャッシュタイプ名
     */
    function &getObject($key, $type='')
    {
        if ($type != '') {
            $class_name = 'SyL_Cache' . ucfirst($type);
            include_once dirname(__FILE__) . "/Cache/{$class_name}.php";
        } else {
            $class_name = 'SyL_Cache';
        }
        $object =& new $class_name($key);
        return $object;
    }

    /**
     * キャッシュディレクトリをセットする
     *
     * @access public
     * @param string キャッシュディレクトリ
     */
    function setCacheDir($cache_dir)
    {
        if (!preg_match('/(\\\\|\/)$/', $cache_dir)) {
            $cache_dir .= '/';
        }

        if (!is_dir($cache_dir)) {
            if (!mkdir($cache_dir, 0777)) {
                trigger_error("[SyL error] SyL cache dir can't create ($cache_dir)", E_USER_ERROR);
            }
        }

        if (!is_writable($cache_dir)) {
            trigger_error("[SyL error] SyL cache dir permission denied ($cache_dir)", E_USER_ERROR);
        }

        $this->cache_dir = $cache_dir;
    }

    /**
     * キャッシュファイルの接頭辞をセットする
     *
     * @access public
     * @param string キャッシュファイルの接頭辞
     */
    function setPrefix($prefix)
    {
        $this->prefix = $prefix;
    }

    /**
     * キャッシュ有効時間をセットする
     *
     * @access public
     * @param int キャッシュ有効時間（秒[s]）
     */
    function setLifeTime($life_time)
    {
        $this->life_time = $life_time;
    }

    /**
     * キャッシュファイルを読み込む
     *
     * @access public
     * @param string キャッシュキー値
     * @param bool キャッシュ有効期間を更新
     * @return mixed キャッシュデータ
     */
    function read($modify_time_update=false)
    {
        $filename = $this->getFileName();
        if (!$filename) {
            return false;
        }
        if (file_exists($filename)) {
            // キャッシュファイルOK
            if ($this->life_time !== null) {
                $mtime = filemtime($filename);
                $life_time = $mtime + $this->life_time;
            } else {
                $life_time = null;
            }

            if (($life_time === null) || ($life_time >= time())) {
                // キャッシュ有効期間OK
                if (!($fp = @fopen($filename, "rb"))) {
                    return false;
                }
                $mqr = get_magic_quotes_runtime();
                set_magic_quotes_runtime(0);
                $len = filesize($filename);

                flock($fp, LOCK_SH);
                $hash = fread($fp, 32);
                $data = ($len > 0) ? fread($fp, $len-32) : '';
                flock($fp, LOCK_UN);

                set_magic_quotes_runtime($mqr);
                fclose($fp);

                if ($hash == $this->hash($data)) {
                    if ($modify_time_update) {
                        $this->setModifyTime();
                    }
                    return unserialize($data);
                } else {
                    $this->delete($filename);
                    return false;
                }
            } else {
                // キャッシュ有効期間切れ
                $this->delete($filename);
                return false;
            }
        } else {
            // キャッシュファイル無し
            return false;
        }
    }

    /**
     * キャッシュファイルを保存する
     *
     * @access private
     * @param string キャッシュキー値
     * @param mixed キャッシュデータ
     * @param int 更新時間(UNIX Time)
     * @return bool true: 保存成功、false: 保存エラー
     */
    function write($data, $mtime=false)
    {
        // キャッシュファイル名取得
        $filename = $this->getFileName();
        if (!$filename) {
            return false;
        }
        // データのシリアル化
        $data = serialize($data);
        // データのバイト数取得
        $len = strlen($data);
        if (!($fp = @fopen($filename, 'wb'))) {
            return false;
        }
        flock($fp, LOCK_EX);
        fwrite($fp, $this->hash($data), 32);
        fwrite($fp, $data, $len);
        flock($fp, LOCK_UN);
        fclose($fp);

        if ($mtime) {
            $this->setModifyTime($mtime);
        }
        return true;
    }

    /**
     * ディレクトリ内のキャッシュファイルを整理する
     *
     * @access private
     * @return bool true: 整理成功、false: 整理エラー
     */
    function clean()
    {
        if ($this->life_time === null) {
            return true;
        }
        if (!is_dir($this->cache_dir)) {
            return false;
        }
  
        $dh = opendir($this->cache_dir);
        if (!$dh) {
            return false;
        }
        $reg = '/^(' . preg_quote($this->prefix) . ')(.+)$/';
        while (($filename = readdir($dh)) !== false) {
            if (preg_match($reg, $filename)) {
                $life_time = filemtime($this->cache_dir . $filename) + $this->life_time;
                if ($life_time < time()) {
                    $this->delete($this->cache_dir . $filename);
                }
            }
        }
        closedir($dh);
    }

    /**
     * キャッシュファイルの更新時間を更新する
     *
     * @access public
     * @param string キャッシュファイル判定キー
     * @param int 更新時間(UNIX Time)
     * @return bool true: 更新OK、false: 更新エラー
     */
    function setModifyTime($mtime='')
    {
        $filename = $this->getFileName();
        if (!$filename) {
            return false;
        }
        if (!file_exists($filename)) {
            return false;
        }
        return ($mtime) ? touch($filename, $mtime) : touch($filename);
    }

    /**
     * キャッシュファイルの更新時間を取得する
     *
     * @access public
     * @param string キャッシュファイル判定キー
     * @return int 更新時間(UNIX Time)
     */
    function getModifyTime()
    {
        $filename = $this->getFileName();
        if (!$filename) {
            return false;
        }
        if (!file_exists($filename)) {
            return false;
        }
        return filemtime($filename);
    }

    /**
     * キャッシュファイル名を取得する
     * 
     * @access public
     * @param string キャッシュファイル判定キー
     * @return string キャッシュファイル名
     */
    function getFileName()
    {
        return $this->cache_dir . $this->prefix . $this->key_md5;
    }

    /**
     * キャッシュを削除
     * 
     * @access public
     */
    function delete($file='')
    {
        if (!$file) {
            $file = $this->getFileName();
        }
        @unlink($file);
    }


    /**
     * ハッシュ値を取得する
     * 
     * @access public
     * @param string データ
     * @return string ハッシュ値
     */
    function hash($data)
    {
        return sprintf('%+032d', crc32($data));
    }
}

?>
